增加许可证认证
This commit is contained in:
parent
023ea78d18
commit
f577cbcbe5
|
|
@ -13,3 +13,19 @@ INSERT INTO `excel-handle`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `ord
|
||||||
INSERT INTO `excel-handle`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2012, '客户列表数据导出', 2001, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerExport:export', '#', 'admin', '2026-02-07 15:39:03', '', NULL, '');
|
INSERT INTO `excel-handle`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2012, '客户列表数据导出', 2001, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerExport:export', '#', 'admin', '2026-02-07 15:39:03', '', NULL, '');
|
||||||
|
|
||||||
INSERT INTO `excel-handle`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3000, '企业信息', 2000, 4, 'corpInfo', 'wecom/corpinfo/index', NULL, '', 1, 0, 'C', '0', '0', 'wecom:corpInfo:list', 'download', 'admin', '2026-02-07 15:39:03', '', NULL, '企业信息');
|
INSERT INTO `excel-handle`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3000, '企业信息', 2000, 4, 'corpInfo', 'wecom/corpinfo/index', NULL, '', 1, 0, 'C', '0', '0', 'wecom:corpInfo:list', 'download', 'admin', '2026-02-07 15:39:03', '', NULL, '企业信息');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO `excel-handle`.`sys_menu` ( `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES ('企业信息查看', 3000, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:corp:add', '#', 'admin', '2026-02-07 15:39:03', '', NULL, '');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO `excel-handle`.`sys_menu` ( `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES ('企业信息查看', 3000, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:corp:query', '#', 'admin', '2026-02-07 15:39:03', '', NULL, '');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO `excel-handle`.`sys_menu` ( `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES ('企业信息编辑', 3000, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:corp:edit', '#', 'admin', '2026-02-07 15:39:03', '', NULL, '');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO `excel-handle`.`sys_menu` ( `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES ('企业信息删除', 3000, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:corp:remove', '#', 'admin', '2026-02-07 15:39:03', '', NULL, '');
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
# 数据源配置
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
|
druid:
|
||||||
|
# 主库数据源
|
||||||
|
master:
|
||||||
|
url: jdbc:mysql://host.docker.internal:3316/excel-handle?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
|
username: root
|
||||||
|
password: jiong1114
|
||||||
|
# 从库数据源
|
||||||
|
slave:
|
||||||
|
# 从数据源开关/默认关闭
|
||||||
|
enabled: false
|
||||||
|
url:
|
||||||
|
username:
|
||||||
|
password:
|
||||||
|
# 初始连接数
|
||||||
|
initialSize: 5
|
||||||
|
# 最小连接池数量
|
||||||
|
minIdle: 10
|
||||||
|
# 最大连接池数量
|
||||||
|
maxActive: 20
|
||||||
|
# 配置获取连接等待超时的时间
|
||||||
|
maxWait: 60000
|
||||||
|
# 配置连接超时时间
|
||||||
|
connectTimeout: 30000
|
||||||
|
# 配置网络超时时间
|
||||||
|
socketTimeout: 60000
|
||||||
|
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||||
|
timeBetweenEvictionRunsMillis: 60000
|
||||||
|
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||||
|
minEvictableIdleTimeMillis: 300000
|
||||||
|
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||||
|
maxEvictableIdleTimeMillis: 900000
|
||||||
|
# 配置检测连接是否有效
|
||||||
|
validationQuery: SELECT 1 FROM DUAL
|
||||||
|
testWhileIdle: true
|
||||||
|
testOnBorrow: false
|
||||||
|
testOnReturn: false
|
||||||
|
webStatFilter:
|
||||||
|
enabled: true
|
||||||
|
statViewServlet:
|
||||||
|
enabled: true
|
||||||
|
# 设置白名单,不填则允许所有访问
|
||||||
|
allow:
|
||||||
|
url-pattern: /druid/*
|
||||||
|
# 控制台管理用户名和密码
|
||||||
|
login-username: ruoyi
|
||||||
|
login-password: 123456
|
||||||
|
filter:
|
||||||
|
stat:
|
||||||
|
enabled: true
|
||||||
|
# 慢SQL记录
|
||||||
|
log-slow-sql: true
|
||||||
|
slow-sql-millis: 1000
|
||||||
|
merge-sql: true
|
||||||
|
wall:
|
||||||
|
config:
|
||||||
|
multi-statement-allow: true
|
||||||
|
|
||||||
|
redis:
|
||||||
|
# 地址
|
||||||
|
host: host.docker.internal
|
||||||
|
# 端口,默认为6379
|
||||||
|
port: 6379
|
||||||
|
# 数据库索引
|
||||||
|
database: 0
|
||||||
|
# 密码
|
||||||
|
password:
|
||||||
|
# 连接超时时间
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
# 连接池中的最小空闲连接
|
||||||
|
min-idle: 0
|
||||||
|
# 连接池中的最大空闲连接
|
||||||
|
max-idle: 8
|
||||||
|
# 连接池的最大数据库连接数
|
||||||
|
max-active: 8
|
||||||
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
max-wait: -1ms
|
||||||
|
|
@ -58,4 +58,25 @@ spring:
|
||||||
merge-sql: true
|
merge-sql: true
|
||||||
wall:
|
wall:
|
||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
|
redis:
|
||||||
|
# 地址
|
||||||
|
host: localhost
|
||||||
|
# 端口,默认为6379
|
||||||
|
port: 6379
|
||||||
|
# 数据库索引
|
||||||
|
database: 0
|
||||||
|
# 密码
|
||||||
|
password:
|
||||||
|
# 连接超时时间
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
# 连接池中的最小空闲连接
|
||||||
|
min-idle: 0
|
||||||
|
# 连接池中的最大空闲连接
|
||||||
|
max-idle: 8
|
||||||
|
# 连接池的最大数据库连接数
|
||||||
|
max-active: 8
|
||||||
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
max-wait: -1ms
|
||||||
|
|
|
||||||
|
|
@ -58,4 +58,25 @@ spring:
|
||||||
merge-sql: true
|
merge-sql: true
|
||||||
wall:
|
wall:
|
||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
|
redis:
|
||||||
|
# 地址
|
||||||
|
host: redis
|
||||||
|
# 端口,默认为6379
|
||||||
|
port: 6379
|
||||||
|
# 数据库索引
|
||||||
|
database: 0
|
||||||
|
# 密码
|
||||||
|
password: ash@szmp
|
||||||
|
# 连接超时时间
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
# 连接池中的最小空闲连接
|
||||||
|
min-idle: 0
|
||||||
|
# 连接池中的最大空闲连接
|
||||||
|
max-idle: 8
|
||||||
|
# 连接池的最大数据库连接数
|
||||||
|
max-active: 8
|
||||||
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
max-wait: -1ms
|
||||||
|
|
@ -66,28 +66,6 @@ spring:
|
||||||
restart:
|
restart:
|
||||||
# 热部署开关
|
# 热部署开关
|
||||||
enabled: true
|
enabled: true
|
||||||
# redis 配置
|
|
||||||
redis:
|
|
||||||
# 地址
|
|
||||||
host: redis
|
|
||||||
# 端口,默认为6379
|
|
||||||
port: 6379
|
|
||||||
# 数据库索引
|
|
||||||
database: 0
|
|
||||||
# 密码
|
|
||||||
password: ash@szmp
|
|
||||||
# 连接超时时间
|
|
||||||
timeout: 10s
|
|
||||||
lettuce:
|
|
||||||
pool:
|
|
||||||
# 连接池中的最小空闲连接
|
|
||||||
min-idle: 0
|
|
||||||
# 连接池中的最大空闲连接
|
|
||||||
max-idle: 8
|
|
||||||
# 连接池的最大数据库连接数
|
|
||||||
max-active: 8
|
|
||||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
|
||||||
max-wait: -1ms
|
|
||||||
|
|
||||||
# token配置
|
# token配置
|
||||||
token:
|
token:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.ruoyi.common.core.domain.entity;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统许可证对象 sys_license(加密版)
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
public class SysLicense
|
||||||
|
{
|
||||||
|
/** 加密的许可证密钥(包含激活时间、到期时间) */
|
||||||
|
@Excel(name = "许可证密钥")
|
||||||
|
private String licenseKey;
|
||||||
|
|
||||||
|
|
||||||
|
public String getLicenseKey() {
|
||||||
|
return licenseKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLicenseKey(String licenseKey) {
|
||||||
|
this.licenseKey = licenseKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
package com.ruoyi.common.utils;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证加密工具类
|
||||||
|
* 使用AES加密算法生成和验证许可证密钥
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
public class LicenseEncryptUtil {
|
||||||
|
// AES加密密钥(生产环境应该使用更复杂的密钥,并妥善保管)
|
||||||
|
private static final String SECRET_KEY = "RuoYi@2026#License$Key!Secure*9527";
|
||||||
|
|
||||||
|
// 加密算法
|
||||||
|
private static final String ALGORITHM = "AES";
|
||||||
|
|
||||||
|
// 字段分隔符
|
||||||
|
private static final String SEPARATOR = "|";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成许可证密钥
|
||||||
|
*
|
||||||
|
* @param activationTime 激活时间(时间戳)
|
||||||
|
* @param expiryTime 到期时间(时间戳)
|
||||||
|
* @param trialDays 试用天数
|
||||||
|
* @return 加密后的许可证密钥
|
||||||
|
*/
|
||||||
|
public static String generateLicenseKey(long activationTime, long expiryTime, int trialDays) {
|
||||||
|
try {
|
||||||
|
// 构建原始数据:激活时间|到期时间|机器码|试用天数|校验码
|
||||||
|
String rawData = activationTime + SEPARATOR +
|
||||||
|
expiryTime + SEPARATOR +
|
||||||
|
trialDays + SEPARATOR +
|
||||||
|
generateChecksum(activationTime, expiryTime, trialDays);
|
||||||
|
|
||||||
|
// AES加密
|
||||||
|
byte[] encrypted = encrypt(rawData);
|
||||||
|
|
||||||
|
// Base64编码
|
||||||
|
return Base64.getEncoder().encodeToString(encrypted);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("生成许可证密钥失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析许可证密钥
|
||||||
|
*
|
||||||
|
* @param licenseKey 加密的许可证密钥
|
||||||
|
* @return 包含许可证信息的Map,如果解析失败返回null
|
||||||
|
*/
|
||||||
|
public static Map<String, Object> parseLicenseKey(String licenseKey) {
|
||||||
|
try {
|
||||||
|
// Base64解码
|
||||||
|
byte[] decoded = Base64.getDecoder().decode(licenseKey);
|
||||||
|
|
||||||
|
// AES解密
|
||||||
|
String rawData = decrypt(decoded);
|
||||||
|
|
||||||
|
// 分割数据
|
||||||
|
String[] parts = rawData.split("\\" + SEPARATOR);
|
||||||
|
if (parts.length != 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
long activationTime = Long.parseLong(parts[0]);
|
||||||
|
long expiryTime = Long.parseLong(parts[1]);
|
||||||
|
int trialDays = Integer.parseInt(parts[2]);
|
||||||
|
String checksum = parts[3];
|
||||||
|
|
||||||
|
// 验证校验码
|
||||||
|
String expectedChecksum = generateChecksum(activationTime, expiryTime, trialDays);
|
||||||
|
if (!checksum.equals(expectedChecksum)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回解析结果
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("activationTime", activationTime);
|
||||||
|
result.put("expiryTime", expiryTime);
|
||||||
|
result.put("trialDays", trialDays);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证许可证密钥是否有效
|
||||||
|
*
|
||||||
|
* @param licenseKey 许可证密钥
|
||||||
|
* @return true-有效,false-无效
|
||||||
|
*/
|
||||||
|
public static boolean validateLicenseKey(String licenseKey) {
|
||||||
|
if (licenseKey == null || licenseKey.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> licenseInfo = parseLicenseKey(licenseKey);
|
||||||
|
if (licenseInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否过期
|
||||||
|
long expiryTime = (Long) licenseInfo.get("expiryTime");
|
||||||
|
return System.currentTimeMillis() <= expiryTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取许可证剩余天数
|
||||||
|
*
|
||||||
|
* @param licenseKey 许可证密钥
|
||||||
|
* @return 剩余天数,如果已过期返回0,解析失败返回-1
|
||||||
|
*/
|
||||||
|
public static int getRemainingDays(String licenseKey) {
|
||||||
|
Map<String, Object> licenseInfo = parseLicenseKey(licenseKey);
|
||||||
|
if (licenseInfo == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long expiryTime = (Long) licenseInfo.get("expiryTime");
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (currentTime >= expiryTime) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long remainingMillis = expiryTime - currentTime;
|
||||||
|
return (int) (remainingMillis / (1000 * 60 * 60 * 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES加密
|
||||||
|
*/
|
||||||
|
private static byte[] encrypt(String data) throws Exception {
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(getAESKey(), ALGORITHM);
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
|
||||||
|
return cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES解密(从Base64字符串)
|
||||||
|
*
|
||||||
|
* @param encryptedKey Base64编码的加密字符串
|
||||||
|
* @return 解密后的原始数据,失败返回null
|
||||||
|
*/
|
||||||
|
public static String decrypt(String encryptedKey) {
|
||||||
|
try {
|
||||||
|
// Base64解码
|
||||||
|
byte[] decoded = Base64.getDecoder().decode(encryptedKey);
|
||||||
|
// AES解密
|
||||||
|
return decrypt(decoded);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES解密(从字节数组)
|
||||||
|
*/
|
||||||
|
private static String decrypt(byte[] data) throws Exception {
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(getAESKey(), ALGORITHM);
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, keySpec);
|
||||||
|
byte[] decrypted = cipher.doFinal(data);
|
||||||
|
return new String(decrypted, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AES密钥(16字节)
|
||||||
|
*/
|
||||||
|
private static byte[] getAESKey() {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] hash = md.digest(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return hash; // MD5产生16字节
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("生成AES密钥失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成校验码(防止数据被篡改)- 公共方法
|
||||||
|
*
|
||||||
|
* @param activationTime 激活时间
|
||||||
|
* @param expiryTime 到期时间
|
||||||
|
* @param trialDays 试用天数
|
||||||
|
* @return 校验码
|
||||||
|
*/
|
||||||
|
public static String generateChecksum(long activationTime, long expiryTime, int trialDays) {
|
||||||
|
try {
|
||||||
|
String data = activationTime + "" + expiryTime + trialDays + SECRET_KEY;
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] hash = md.digest(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return Base64.getEncoder().encodeToString(hash).substring(0, 16);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("生成校验码失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
package com.ruoyi.common.utils;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化版许可证密钥生成工具
|
||||||
|
* 用于生成指定到期时间的许可证密钥
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
public class LicenseKeyGeneratorSimple {
|
||||||
|
|
||||||
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner scanner = new Scanner(System.in);
|
||||||
|
|
||||||
|
System.out.println("=== 许可证密钥生成工具(简化版)===\n");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
System.out.println("请选择操作:");
|
||||||
|
System.out.println("1. 生成指定天数的许可证");
|
||||||
|
System.out.println("2. 生成指定到期日期的许可证");
|
||||||
|
System.out.println("3. 验证许可证密钥");
|
||||||
|
System.out.println("0. 退出");
|
||||||
|
System.out.print("\n请输入选项: ");
|
||||||
|
|
||||||
|
String choice = scanner.nextLine().trim();
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case "1":
|
||||||
|
generateByDays(scanner);
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
generateByDate(scanner);
|
||||||
|
break;
|
||||||
|
case "3":
|
||||||
|
validateKey(scanner);
|
||||||
|
break;
|
||||||
|
case "0":
|
||||||
|
System.out.println("退出程序");
|
||||||
|
scanner.close();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
System.out.println("无效选项,请重新输入\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指定天数的许可证
|
||||||
|
*/
|
||||||
|
private static void generateByDays(Scanner scanner) {
|
||||||
|
System.out.print("\n请输入许可证有效天数(例如:30、90、365): ");
|
||||||
|
String input = scanner.nextLine().trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int days = Integer.parseInt(input);
|
||||||
|
|
||||||
|
// 当前时间作为激活时间
|
||||||
|
Date now = new Date();
|
||||||
|
long activationTime = now.getTime();
|
||||||
|
|
||||||
|
// 计算到期时间
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.setTime(now);
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, days);
|
||||||
|
Date expiryDate = calendar.getTime();
|
||||||
|
long expiryTime = expiryDate.getTime();
|
||||||
|
|
||||||
|
// 生成许可证密钥
|
||||||
|
String encryptedKey = LicenseEncryptUtil.generateLicenseKey(activationTime, expiryTime, days);
|
||||||
|
|
||||||
|
// 输出结果
|
||||||
|
System.out.println("\n========================================");
|
||||||
|
System.out.println("许可证密钥生成成功!");
|
||||||
|
System.out.println("========================================");
|
||||||
|
System.out.println("激活时间: " + DATE_FORMAT.format(now));
|
||||||
|
System.out.println("到期时间: " + DATE_FORMAT.format(expiryDate));
|
||||||
|
System.out.println("有效天数: " + days + " 天");
|
||||||
|
System.out.println("\n许可证密钥(请复制以下内容):");
|
||||||
|
System.out.println("----------------------------------------");
|
||||||
|
System.out.println(encryptedKey);
|
||||||
|
System.out.println("----------------------------------------\n");
|
||||||
|
|
||||||
|
// 解密并验证许可证
|
||||||
|
String decryptedData = LicenseEncryptUtil.decrypt(encryptedKey);
|
||||||
|
|
||||||
|
// 解析许可证数据:格式为 "激活时间|到期时间|试用天数|校验码"
|
||||||
|
String[] parts = decryptedData.split("\\|");
|
||||||
|
|
||||||
|
try {
|
||||||
|
long activationTimeMs = Long.parseLong(parts[0]);
|
||||||
|
long expiryTimeMs = Long.parseLong(parts[1]);
|
||||||
|
int trialDays = Integer.parseInt(parts[2]);
|
||||||
|
String checksum = parts[3];
|
||||||
|
|
||||||
|
// 验证校验码
|
||||||
|
String expectedChecksum = LicenseEncryptUtil.generateChecksum(
|
||||||
|
activationTimeMs, expiryTimeMs, trialDays);
|
||||||
|
if (!checksum.equals(expectedChecksum)) {
|
||||||
|
System.out.println("许可证校验码验证失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否过期
|
||||||
|
Date expiryTime2 = new Date(expiryTimeMs);
|
||||||
|
|
||||||
|
if (now.after(expiryTime2)) {
|
||||||
|
System.out.println("许可证已过期,到期时间: {}" + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiryTime2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("许可证验证通过,到期时间: {}" + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiryTime2));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println("许可证数据解析失败" + e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证生成的密钥
|
||||||
|
System.out.println("密钥验证:✓ 有效");
|
||||||
|
System.out.println("========================================\n");
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println("输入格式错误,请输入数字\n");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("生成失败: " + e.getMessage() + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指定到期日期的许可证
|
||||||
|
*/
|
||||||
|
private static void generateByDate(Scanner scanner) {
|
||||||
|
System.out.print("\n请输入到期日期(格式:yyyy-MM-dd,例如:2027-12-31): ");
|
||||||
|
String dateInput = scanner.nextLine().trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析日期(设置为当天的23:59:59)
|
||||||
|
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
Date expiryDate = inputFormat.parse(dateInput);
|
||||||
|
|
||||||
|
// 设置为当天的23:59:59
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.setTime(expiryDate);
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, 23);
|
||||||
|
calendar.set(Calendar.MINUTE, 59);
|
||||||
|
calendar.set(Calendar.SECOND, 59);
|
||||||
|
expiryDate = calendar.getTime();
|
||||||
|
long expiryTime = expiryDate.getTime();
|
||||||
|
|
||||||
|
// 当前时间作为激活时间
|
||||||
|
Date now = new Date();
|
||||||
|
long activationTime = now.getTime();
|
||||||
|
|
||||||
|
// 计算天数
|
||||||
|
long diffMillis = expiryTime - activationTime;
|
||||||
|
int days = (int) (diffMillis / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (days <= 0) {
|
||||||
|
System.out.println("到期日期必须在当前日期之后\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成许可证密钥
|
||||||
|
String licenseKey = LicenseEncryptUtil.generateLicenseKey(activationTime, expiryTime, days);
|
||||||
|
|
||||||
|
// 输出结果
|
||||||
|
System.out.println("\n========================================");
|
||||||
|
System.out.println("许可证密钥生成成功!");
|
||||||
|
System.out.println("========================================");
|
||||||
|
System.out.println("激活时间: " + DATE_FORMAT.format(now));
|
||||||
|
System.out.println("到期时间: " + DATE_FORMAT.format(expiryDate));
|
||||||
|
System.out.println("有效天数: " + days + " 天");
|
||||||
|
System.out.println("\n许可证密钥(请复制以下内容):");
|
||||||
|
System.out.println("----------------------------------------");
|
||||||
|
System.out.println(licenseKey);
|
||||||
|
System.out.println("----------------------------------------\n");
|
||||||
|
|
||||||
|
// 验证生成的密钥
|
||||||
|
boolean isValid = LicenseEncryptUtil.validateLicenseKey(licenseKey);
|
||||||
|
System.out.println("密钥验证: " + (isValid ? "✓ 有效" : "✗ 无效"));
|
||||||
|
System.out.println("========================================\n");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("日期格式错误或生成失败: " + e.getMessage() + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证许可证密钥
|
||||||
|
*/
|
||||||
|
private static void validateKey(Scanner scanner) {
|
||||||
|
System.out.print("\n请输入要验证的许可证密钥: ");
|
||||||
|
String licenseKey = scanner.nextLine().trim();
|
||||||
|
|
||||||
|
if (licenseKey.isEmpty()) {
|
||||||
|
System.out.println("许可证密钥不能为空\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析许可证
|
||||||
|
Map<String, Object> licenseInfo = LicenseEncryptUtil.parseLicenseKey(licenseKey);
|
||||||
|
|
||||||
|
if (licenseInfo == null) {
|
||||||
|
System.out.println("\n========================================");
|
||||||
|
System.out.println("✗ 许可证密钥无效(解析失败或校验码错误)");
|
||||||
|
System.out.println("========================================\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取信息
|
||||||
|
long activationTime = (Long) licenseInfo.get("activationTime");
|
||||||
|
long expiryTime = (Long) licenseInfo.get("expiryTime");
|
||||||
|
int trialDays = (Integer) licenseInfo.get("trialDays");
|
||||||
|
|
||||||
|
Date activationDate = new Date(activationTime);
|
||||||
|
Date expiryDate = new Date(expiryTime);
|
||||||
|
Date now = new Date();
|
||||||
|
|
||||||
|
boolean isValid = LicenseEncryptUtil.validateLicenseKey(licenseKey);
|
||||||
|
int remainingDays = LicenseEncryptUtil.getRemainingDays(licenseKey);
|
||||||
|
|
||||||
|
// 输出结果
|
||||||
|
System.out.println("\n========================================");
|
||||||
|
System.out.println("许可证信息");
|
||||||
|
System.out.println("========================================");
|
||||||
|
System.out.println("激活时间: " + DATE_FORMAT.format(activationDate));
|
||||||
|
System.out.println("到期时间: " + DATE_FORMAT.format(expiryDate));
|
||||||
|
System.out.println("有效天数: " + trialDays + " 天");
|
||||||
|
System.out.println("当前时间: " + DATE_FORMAT.format(now));
|
||||||
|
System.out.println("\n状态: " + (isValid ? "✓ 有效" : "✗ 已过期"));
|
||||||
|
if (isValid) {
|
||||||
|
System.out.println("剩余天数: " + remainingDays + " 天");
|
||||||
|
}
|
||||||
|
System.out.println("========================================\n");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("\n验证失败: " + e.getMessage() + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.ruoyi.framework.config;
|
||||||
import com.ruoyi.common.config.RuoYiConfig;
|
import com.ruoyi.common.config.RuoYiConfig;
|
||||||
import com.ruoyi.common.constant.Constants;
|
import com.ruoyi.common.constant.Constants;
|
||||||
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
|
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
|
||||||
|
import com.ruoyi.framework.interceptor.LicenseInterceptor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
@ -27,6 +28,9 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||||
@Autowired
|
@Autowired
|
||||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LicenseInterceptor licenseInterceptor;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
||||||
|
|
@ -47,6 +51,8 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry)
|
public void addInterceptors(InterceptorRegistry registry)
|
||||||
{
|
{
|
||||||
|
// 许可证验证拦截器
|
||||||
|
registry.addInterceptor(licenseInterceptor).addPathPatterns("/**");
|
||||||
// 防重复提交拦截器
|
// 防重复提交拦截器
|
||||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.ruoyi.framework.interceptor;
|
||||||
|
|
||||||
|
import com.ruoyi.system.service.ISysLicenseService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证验证拦截器
|
||||||
|
* 在用户登录后验证系统许可证是否有效
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class LicenseInterceptor implements HandlerInterceptor
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(LicenseInterceptor.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISysLicenseService sysLicenseService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
|
||||||
|
{
|
||||||
|
// 获取请求URI
|
||||||
|
String uri = request.getRequestURI();
|
||||||
|
|
||||||
|
// 排除登录、登出、静态资源等请求
|
||||||
|
if (uri.contains("/login") || uri.contains("/logout") ||
|
||||||
|
uri.contains("/captchaImage") || uri.contains("/static") ||
|
||||||
|
uri.contains("/css") || uri.contains("/js") || uri.contains("/img") ||
|
||||||
|
uri.contains("/fonts") || uri.contains("/ajax"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证许可证
|
||||||
|
boolean isValid = sysLicenseService.validateLicense();
|
||||||
|
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
log.warn("许可证验证失败,拒绝访问: {}", uri);
|
||||||
|
|
||||||
|
// 设置响应状态码和错误信息
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
|
||||||
|
String errorMessage = "{\"code\":601,\"msg\":\"系统许可证已过期或无效。\"}";
|
||||||
|
|
||||||
|
response.getWriter().write(errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.ruoyi.system.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysLicense;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统许可证Mapper接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
public interface SysLicenseMapper
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询系统许可证(获取第一条记录)
|
||||||
|
*
|
||||||
|
* @return 系统许可证
|
||||||
|
*/
|
||||||
|
public SysLicense selectSysLicense();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.ruoyi.system.service;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysLicense;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统许可证Service接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
public interface ISysLicenseService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 查询系统许可证(获取第一条记录)
|
||||||
|
*
|
||||||
|
* @return 系统许可证
|
||||||
|
*/
|
||||||
|
public SysLicense selectSysLicense();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证许可证是否有效
|
||||||
|
*
|
||||||
|
* @return true=有效 false=无效
|
||||||
|
*/
|
||||||
|
public boolean validateLicense();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取许可证状态信息
|
||||||
|
*
|
||||||
|
* @return 许可证状态信息
|
||||||
|
*/
|
||||||
|
public String getLicenseStatusInfo();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
package com.ruoyi.system.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysLicense;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.ruoyi.common.utils.LicenseEncryptUtil;
|
||||||
|
import com.ruoyi.system.mapper.SysLicenseMapper;
|
||||||
|
import com.ruoyi.system.service.ISysLicenseService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统许可证Service业务层处理(加密版)
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SysLicenseServiceImpl implements ISysLicenseService
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SysLicenseServiceImpl.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysLicenseMapper sysLicenseMapper;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询系统许可证(获取第一条记录)
|
||||||
|
*
|
||||||
|
* @return 系统许可证
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SysLicense selectSysLicense()
|
||||||
|
{
|
||||||
|
return sysLicenseMapper.selectSysLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证许可证是否有效(解密验证)
|
||||||
|
*
|
||||||
|
* @return true=有效 false=无效
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean validateLicense()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SysLicense license = selectSysLicense();
|
||||||
|
if (license == null)
|
||||||
|
{
|
||||||
|
log.error("许可证不存在");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String encryptedKey = license.getLicenseKey();
|
||||||
|
if (encryptedKey == null || encryptedKey.trim().isEmpty())
|
||||||
|
{
|
||||||
|
log.error("许可证密钥为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密并验证许可证
|
||||||
|
String decryptedData = LicenseEncryptUtil.decrypt(encryptedKey);
|
||||||
|
if (decryptedData == null)
|
||||||
|
{
|
||||||
|
log.error("许可证密钥解密失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析许可证数据:格式为 "激活时间|到期时间|试用天数|校验码"
|
||||||
|
String[] parts = decryptedData.split("\\|");
|
||||||
|
if (parts.length != 4)
|
||||||
|
{
|
||||||
|
log.error("许可证数据格式错误");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long activationTimeMs = Long.parseLong(parts[0]);
|
||||||
|
long expiryTimeMs = Long.parseLong(parts[1]);
|
||||||
|
int trialDays = Integer.parseInt(parts[2]);
|
||||||
|
String checksum = parts[3];
|
||||||
|
|
||||||
|
// 验证校验码
|
||||||
|
String expectedChecksum = LicenseEncryptUtil.generateChecksum(
|
||||||
|
activationTimeMs, expiryTimeMs, trialDays);
|
||||||
|
if (!checksum.equals(expectedChecksum))
|
||||||
|
{
|
||||||
|
log.error("许可证校验码验证失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否过期
|
||||||
|
Date now = new Date();
|
||||||
|
Date expiryTime = new Date(expiryTimeMs);
|
||||||
|
|
||||||
|
if (now.after(expiryTime))
|
||||||
|
{
|
||||||
|
log.warn("许可证已过期,到期时间: {}", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiryTime));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("许可证验证通过,到期时间: {}", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiryTime));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
log.error("许可证数据解析失败", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.error("验证许可证时发生异常", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取许可证状态信息
|
||||||
|
*
|
||||||
|
* @return 许可证状态信息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getLicenseStatusInfo()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SysLicense license = selectSysLicense();
|
||||||
|
if (license == null)
|
||||||
|
{
|
||||||
|
return "许可证不存在";
|
||||||
|
}
|
||||||
|
|
||||||
|
String encryptedKey = license.getLicenseKey();
|
||||||
|
if (encryptedKey == null || encryptedKey.trim().isEmpty())
|
||||||
|
{
|
||||||
|
return "许可证密钥为空";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密许可证
|
||||||
|
String decryptedData = LicenseEncryptUtil.decrypt(encryptedKey);
|
||||||
|
if (decryptedData == null)
|
||||||
|
{
|
||||||
|
return "许可证密钥无效";
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = decryptedData.split("\\|");
|
||||||
|
if (parts.length != 5)
|
||||||
|
{
|
||||||
|
return "许可证数据格式错误";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long expiryTimeMs = Long.parseLong(parts[1]);
|
||||||
|
Date expiryTime = new Date(expiryTimeMs);
|
||||||
|
Date now = new Date();
|
||||||
|
|
||||||
|
long diffInMillies = expiryTime.getTime() - now.getTime();
|
||||||
|
long diffInDays = diffInMillies / (1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
|
if (diffInDays < 0)
|
||||||
|
{
|
||||||
|
return "试用期已过期,请联系管理员续费";
|
||||||
|
}
|
||||||
|
else if (diffInDays == 0)
|
||||||
|
{
|
||||||
|
return "试用期今天到期,请尽快续费";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return String.format("试用期剩余 %d 天(到期时间: %s)",
|
||||||
|
diffInDays,
|
||||||
|
DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiryTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
log.error("解析许可证时间失败", e);
|
||||||
|
return "许可证数据解析失败";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.error("获取许可证状态信息时发生异常", e);
|
||||||
|
return "获取许可证状态失败";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.system.mapper.SysLicenseMapper">
|
||||||
|
|
||||||
|
<resultMap type="SysLicense" id="SysLicenseResult">
|
||||||
|
<result property="licenseKey" column="license_key" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectSysLicenseVo">
|
||||||
|
select license_key
|
||||||
|
from sys_license
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectSysLicense" resultMap="SysLicenseResult">
|
||||||
|
<include refid="selectSysLicenseVo"/>
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vue-cli-service serve",
|
"dev": "vue-cli-service serve",
|
||||||
"build:prod": "vue-cli-service build",
|
"build:prod": "vue-cli-service build",
|
||||||
|
"build:dev": "vue-cli-service build --mode development",
|
||||||
"build:stage": "vue-cli-service build --mode staging",
|
"build:stage": "vue-cli-service build --mode staging",
|
||||||
"preview": "node build/index.js --preview"
|
"preview": "node build/index.js --preview"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ service.interceptors.response.use(res => {
|
||||||
return Promise.reject(new Error(msg))
|
return Promise.reject(new Error(msg))
|
||||||
} else if (code === 601) {
|
} else if (code === 601) {
|
||||||
Message({ message: msg, type: 'warning' })
|
Message({ message: msg, type: 'warning' })
|
||||||
return Promise.reject('error')
|
return Promise.reject(new Error(msg))
|
||||||
} else if (code !== 200) {
|
} else if (code !== 200) {
|
||||||
Notification.error({ title: msg })
|
Notification.error({ title: msg })
|
||||||
return Promise.reject('error')
|
return Promise.reject('error')
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ module.exports = {
|
||||||
// 部署生产环境和开发环境下的URL。
|
// 部署生产环境和开发环境下的URL。
|
||||||
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
|
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
|
||||||
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
|
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
|
||||||
publicPath: process.env.NODE_ENV === "production" ? "" : "/",
|
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
|
||||||
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
|
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
|
||||||
outputDir: 'dist',
|
outputDir: 'dist',
|
||||||
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
|
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
|
||||||
|
drop table if exists sys_license;
|
||||||
|
create table sys_license (
|
||||||
|
licenseKey varchar(200) comment '许可认证'
|
||||||
|
) engine=innodb auto_increment=200 comment = '许可认证表';
|
||||||
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- 1、部门表
|
-- 1、部门表
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
-- ----------------------------
|
||||||
|
-- 系统许可证表(加密版本)
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_license`;
|
||||||
|
CREATE TABLE `sys_license` (
|
||||||
|
`license_key` TEXT NOT NULL COMMENT '加密的许可证密钥(包含激活时间、到期时间等信息)',
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='系统许可证表(加密版)';
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- 注意:首次启动时,系统会自动生成加密的许可证密钥
|
||||||
|
-- 不需要手动插入数据
|
||||||
|
-- 许可证密钥是加密字符串,包含:激活时间、到期时间、机器码、试用天数、校验码
|
||||||
|
-- 即使修改数据库中的密钥,也无法通过后端的解密验证
|
||||||
|
-- ----------------------------
|
||||||
Loading…
Reference in New Issue