` 中声明 `ruoyi-license` 依赖版本:
+
+```xml
+
+
+ com.ruoyi
+ ruoyi-license
+ ${ruoyi.version}
+
+```
+
+---
+
+## 2. `ruoyi-admin/pom.xml`
+
+**文件路径:** `wms/ruoyi-admin/pom.xml`
+
+**改动内容:**
+
+引入 `ruoyi-license` 依赖,使授权拦截器和配置能被 Spring Boot 扫描加载:
+
+```xml
+
+
+ com.ruoyi
+ ruoyi-license
+
+```
+
+---
+
+## 3. `SecurityConfig.java`
+
+**文件路径:** `wms/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java`
+
+**改动内容:**
+
+在 Spring Security 匿名白名单中加入 `/system/license/active`,允许未登录用户调用激活授权码接口:
+
+```java
+// 改动前
+requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
+
+// 改动后
+requests.antMatchers("/login", "/register", "/captchaImage", "/system/license/active").permitAll()
+```
+
+---
+
+## 4. 前端 `api/license.js`(新建)
+
+**文件路径:** `wms/ui/src/api/license.js`
+
+**改动内容:**
+
+新建文件,封装授权码相关接口:
+
+```js
+import request from '@/utils/request'
+
+// 激活授权码(无需登录)
+export function activeLicense(licenseCode) {
+ return request({
+ url: '/system/license/active',
+ method: 'post',
+ params: { licenseCode }
+ })
+}
+
+// 查询当前授权信息(需登录)
+export function getLicenseInfo() {
+ return request({
+ url: '/system/license/info',
+ method: 'get'
+ })
+}
+```
+
+---
+
+## 5. 登录页 `login.vue`
+
+**文件路径:** `wms/ui/src/views/login.vue`
+
+**改动内容:**
+
+### 5.1 模板部分
+
+在登录按钮下方新增"激活授权码"按钮,以及激活弹窗:
+
+```html
+
+
+ 激活授权码
+
+
+
+
+
+
+ 取 消
+ 激 活
+
+
+```
+
+### 5.2 Script 部分
+
+新增 import、data 字段和方法:
+
+```js
+// 新增 import
+import { activeLicense } from "@/api/license"
+
+// 新增 data 字段
+licenseDialogVisible: false,
+licenseCode: '',
+licenseLoading: false
+
+// 新增方法
+openLicenseDialog() {
+ this.licenseCode = ''
+ this.licenseDialogVisible = true
+},
+handleActiveLicense() {
+ if (!this.licenseCode.trim()) {
+ this.$message.warning('请输入授权码')
+ return
+ }
+ this.licenseLoading = true
+ activeLicense(this.licenseCode.trim()).then(res => {
+ if (res.code === 200) {
+ this.$message.success(res.msg || '授权码激活成功')
+ this.licenseDialogVisible = false
+ } else {
+ this.$message.error(res.msg || '授权码激活失败')
+ }
+ }).catch(() => {
+ this.$message.error('授权码激活失败')
+ }).finally(() => {
+ this.licenseLoading = false
+ })
+}
+```
+
+---
+
+## 6. 顶部导航栏 `Navbar.vue`
+
+**文件路径:** `wms/ui/src/layout/components/Navbar.vue`
+
+**改动内容:**
+
+### 6.1 模板部分
+
+在 navbar 中间插入授权到期警告条(剩余不足 30 天时显示):
+
+```html
+
+
+ 系统授权将于 {{ licenseExpireText }} 后过期,请及时联系管理员续期
+
+```
+
+### 6.2 Script 部分
+
+```js
+// 新增 import
+import { getLicenseInfo } from '@/api/license'
+
+// 新增 data 字段
+licenseWarning: false,
+licenseExpireText: '',
+licenseTimer: null
+
+// 新增生命周期钩子
+mounted() {
+ this.checkLicenseExpire()
+ this.licenseTimer = setInterval(() => this.checkLicenseExpire(), 60 * 1000)
+},
+beforeDestroy() {
+ if (this.licenseTimer) clearInterval(this.licenseTimer)
+},
+
+// 新增方法
+checkLicenseExpire() {
+ getLicenseInfo().then(res => {
+ if (res.code === 200 && res.data && res.data.expireTime) {
+ const expireTime = new Date(res.data.expireTime).getTime()
+ const now = Date.now()
+ const diffMs = expireTime - now
+ const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24))
+ if (diffMs > 0 && diffDays <= 30) {
+ this.licenseWarning = true
+ if (diffMs < 1000 * 60 * 60 * 24) {
+ const hours = Math.floor(diffMs / (1000 * 60 * 60))
+ const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
+ this.licenseExpireText = hours > 0 ? `${hours} 小时 ${minutes} 分钟` : `${minutes} 分钟`
+ } else if (diffDays < 7) {
+ this.licenseExpireText = diffDays + ' 天'
+ } else {
+ this.licenseExpireText = Math.ceil(diffDays / 7) + ' 周'
+ }
+ }
+ }
+ }).catch(() => {})
+}
+```
+
+### 6.3 样式部分
+
+```scss
+.license-warning {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ top: 0;
+ height: 50px;
+ line-height: 50px;
+ font-size: 13px;
+ color: #e6a23c;
+ font-weight: 500;
+ pointer-events: none;
+ i { margin-right: 4px; }
+}
+```
+
+---
+
+## 改动文件汇总
+
+| 文件 | 类型 | 说明 |
+|---|---|---|
+| `wms/pom.xml` | 修改 | 注册模块 + 声明依赖版本 |
+| `wms/ruoyi-admin/pom.xml` | 修改 | 引入 ruoyi-license 依赖 |
+| `ruoyi-framework/.../SecurityConfig.java` | 修改 | 白名单加入激活授权码接口 |
+| `ui/src/api/license.js` | 新建 | 封装激活和查询授权信息接口 |
+| `ui/src/views/login.vue` | 修改 | 新增激活授权码按钮和弹窗 |
+| `ui/src/layout/components/Navbar.vue` | 修改 | 新增授权到期倒计时警告条 |
diff --git a/ruoyi-license/DEPLOYMENT_CHECKLIST.md b/ruoyi-license/DEPLOYMENT_CHECKLIST.md
new file mode 100644
index 0000000..cac7b0e
--- /dev/null
+++ b/ruoyi-license/DEPLOYMENT_CHECKLIST.md
@@ -0,0 +1,250 @@
+# 授权码模块部署检查清单
+
+## 📋 部署前检查
+
+### 1. 文件完整性检查
+- [ ] ruoyi-license 模块目录存在
+- [ ] pom.xml 已添加 license 模块
+- [ ] ruoyi-admin/pom.xml 已添加 license 依赖
+- [ ] SysLicenseController.java 已创建
+- [ ] license.html 和 active.html 已创建
+- [ ] login.html 和 login.js 已修改
+- [ ] sys_license.sql 脚本已准备
+
+### 2. 数据库准备
+- [ ] 创建 sys_license 表
+- [ ] 插入授权管理菜单数据
+- [ ] 验证表结构正确
+- [ ] 检查菜单权限配置
+
+### 3. 代码配置
+- [ ] 修改 LicenseUtils.java 中的密钥(生产环境必须)
+- [ ] 确认拦截器路径配置正确
+- [ ] 确认定时任务cron表达式
+- [ ] 启动类添加 @EnableScheduling 注解
+
+## 🔧 部署步骤
+
+### 步骤1: 执行数据库脚本
+```bash
+mysql -u root -p your_database < sql/sys_license.sql
+```
+- [ ] 执行成功
+- [ ] 表创建成功
+- [ ] 菜单数据插入成功
+
+### 步骤2: 修改启动类
+```java
+@SpringBootApplication
+@EnableScheduling // 添加此注解
+public class RuoYiApplication {
+ // ...
+}
+```
+- [ ] 注解已添加
+
+### 步骤3: 编译项目
+```bash
+mvn clean install
+```
+- [ ] 编译成功
+- [ ] 无错误信息
+- [ ] JAR包生成
+
+### 步骤4: 启动应用
+```bash
+java -jar ruoyi-admin.jar
+```
+- [ ] 启动成功
+- [ ] 无异常日志
+- [ ] 端口正常监听
+
+### 步骤5: 生成授权码
+**方式A: 使用独立工具**
+```bash
+java com.ruoyi.license.util.LicenseGenerator
+```
+- [ ] 工具运行成功
+- [ ] 授权码生成成功
+- [ ] 授权码已保存
+
+**方式B: 使用系统界面**
+- [ ] 登录系统
+- [ ] 访问授权管理
+- [ ] 点击生成授权码
+- [ ] 输入天数并生成
+
+### 步骤6: 激活授权码
+- [ ] 访问登录页
+- [ ] 点击"激活授权码"
+- [ ] 输入授权码
+- [ ] 激活成功
+
+## ✅ 功能测试
+
+### 测试1: 授权码生成
+- [ ] 系统界面生成授权码成功
+- [ ] 独立工具生成授权码成功
+- [ ] 授权码格式正确
+
+### 测试2: 授权码激活
+- [ ] 登录页激活成功
+- [ ] 系统内激活成功
+- [ ] 数据库记录正确
+
+### 测试3: 授权验证
+- [ ] 有效授权可正常登录
+- [ ] 无授权无法登录
+- [ ] 过期授权无法登录
+
+### 测试4: 授权拦截
+- [ ] 未授权访问被拦截
+- [ ] 有效授权可正常访问
+- [ ] 静态资源不被拦截
+
+### 测试5: 定时检查
+- [ ] 定时任务正常执行
+- [ ] 授权过期自动更新状态
+- [ ] 在线用户被踢出
+
+### 测试6: 授权管理
+- [ ] 授权列表查询正常
+- [ ] 授权信息显示正确
+- [ ] 授权删除功能正常
+
+## 🔍 问题排查
+
+### 问题1: 编译失败
+**检查项:**
+- [ ] Maven依赖是否正确
+- [ ] JDK版本是否匹配
+- [ ] pom.xml配置是否正确
+
+**解决方案:**
+```bash
+mvn clean
+mvn dependency:resolve
+mvn install
+```
+
+### 问题2: 启动失败
+**检查项:**
+- [ ] 数据库连接是否正常
+- [ ] 端口是否被占用
+- [ ] 配置文件是否正确
+
+**解决方案:**
+- 检查 application.yml 配置
+- 查看启动日志错误信息
+- 确认数据库表已创建
+
+### 问题3: 无法激活授权码
+**检查项:**
+- [ ] 授权码格式是否正确
+- [ ] 授权码是否已过期
+- [ ] 密钥是否一致
+
+**解决方案:**
+- 重新生成授权码
+- 检查 LicenseUtils 密钥配置
+- 查看后台错误日志
+
+### 问题4: 授权验证不生效
+**检查项:**
+- [ ] 拦截器是否注册
+- [ ] 数据库授权记录是否正确
+- [ ] 定时任务是否执行
+
+**解决方案:**
+- 检查 LicenseConfig 配置
+- 验证数据库 status 字段
+- 确认 @EnableScheduling 已添加
+
+### 问题5: 在线用户未被踢出
+**检查项:**
+- [ ] SessionDAO 是否注入
+- [ ] 定时任务是否执行
+- [ ] Shiro配置是否正确
+
+**解决方案:**
+- 检查 LicenseCheckTask 日志
+- 验证 SessionDAO bean
+- 重启应用服务器
+
+## 📊 监控指标
+
+### 运行时监控
+- [ ] 授权状态正常
+- [ ] 定时任务执行正常
+- [ ] 无异常日志
+- [ ] 内存使用正常
+- [ ] CPU使用正常
+
+### 业务监控
+- [ ] 授权激活次数
+- [ ] 授权过期提醒
+- [ ] 用户登录成功率
+- [ ] 授权拦截次数
+
+## 🔐 安全检查
+
+### 配置安全
+- [ ] 生产环境已修改默认密钥
+- [ ] 数据库密码已加密
+- [ ] 授权码传输使用HTTPS
+- [ ] 敏感日志已脱敏
+
+### 权限安全
+- [ ] 授权管理菜单仅管理员可见
+- [ ] 授权激活操作有日志记录
+- [ ] 授权码生成有权限控制
+- [ ] 数据库访问有权限限制
+
+### 数据安全
+- [ ] 授权码加密存储
+- [ ] 定期备份授权数据
+- [ ] 授权日志完整保存
+- [ ] 敏感信息不输出到日志
+
+## 📝 上线清单
+
+### 上线前
+- [ ] 所有测试通过
+- [ ] 代码已提交版本库
+- [ ] 数据库脚本已准备
+- [ ] 部署文档已编写
+- [ ] 回滚方案已准备
+
+### 上线中
+- [ ] 备份现有数据
+- [ ] 执行数据库脚本
+- [ ] 部署新版本代码
+- [ ] 生成初始授权码
+- [ ] 激活授权码
+
+### 上线后
+- [ ] 验证功能正常
+- [ ] 检查日志无异常
+- [ ] 监控系统运行状态
+- [ ] 通知相关人员
+- [ ] 更新运维文档
+
+## 📞 应急联系
+
+### 技术支持
+- 开发负责人: ___________
+- 运维负责人: ___________
+- 数据库管理员: ___________
+
+### 应急预案
+1. 授权失效 -> 立即激活新授权码
+2. 系统无法登录 -> 检查授权状态
+3. 数据丢失 -> 从备份恢复
+4. 性能问题 -> 调整定时任务频率
+
+---
+
+**检查人**: ___________
+**检查日期**: ___________
+**部署环境**: [ ] 开发 [ ] 测试 [ ] 生产
+**部署状态**: [ ] 成功 [ ] 失败 [ ] 部分成功
diff --git a/ruoyi-license/INTEGRATION.md b/ruoyi-license/INTEGRATION.md
new file mode 100644
index 0000000..1b8199b
--- /dev/null
+++ b/ruoyi-license/INTEGRATION.md
@@ -0,0 +1,231 @@
+# RuoYi 授权码模块集成指南
+
+## 一、模块说明
+
+本授权码模块已完成开发,包含以下文件:
+
+```
+ruoyi-license/ # 授权模块(独立模块)
+├── src/main/java/com/ruoyi/license/
+│ ├── config/
+│ │ └── LicenseConfig.java # 拦截器配置
+│ ├── domain/
+│ │ └── SysLicense.java # 授权实体
+│ ├── interceptor/
+│ │ └── LicenseInterceptor.java # 授权拦截器
+│ ├── mapper/
+│ │ └── SysLicenseMapper.java # 数据访问接口
+│ ├── service/
+│ │ ├── ISysLicenseService.java # 服务接口
+│ │ └── impl/
+│ │ └── SysLicenseServiceImpl.java # 服务实现
+│ ├── task/
+│ │ └── LicenseCheckTask.java # 定时检查任务
+│ └── util/
+│ ├── LicenseUtils.java # 授权工具类
+│ └── LicenseGenerator.java # 授权码生成器
+├── src/main/resources/mapper/license/
+│ └── SysLicenseMapper.xml # MyBatis映射
+├── pom.xml # 模块依赖
+└── README.md # 使用说明
+
+sql/
+└── sys_license.sql # 数据库脚本
+
+ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/license/
+└── SysLicenseController.java # 授权控制器
+
+ruoyi-admin/src/main/resources/templates/system/license/
+├── license.html # 授权列表页
+└── active.html # 激活页面
+
+修改的文件:
+- pom.xml # 添加license模块
+- ruoyi-admin/pom.xml # 添加license依赖
+- login.html # 添加激活入口
+- login.js # 添加激活功能
+```
+
+## 二、部署步骤
+
+### 1. 执行数据库脚本
+
+```bash
+# 在MySQL中执行
+mysql -u root -p your_database < sql/sys_license.sql
+```
+
+或手动执行 `sql/sys_license.sql` 中的SQL语句。
+
+### 2. 启用定时任务
+
+在启动类上添加 `@EnableScheduling` 注解:
+
+```java
+@SpringBootApplication
+@EnableScheduling // 添加此注解
+public class RuoYiApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(RuoYiApplication.class, args);
+ }
+}
+```
+
+### 3. 编译项目
+
+```bash
+cd d:\fortucky\project\RuoYi-master
+mvn clean install
+```
+
+### 4. 生成授权码
+
+**方式一:使用独立工具生成**
+```bash
+cd ruoyi-license/src/main/java
+javac com/ruoyi/license/util/LicenseUtils.java
+javac com/ruoyi/license/util/LicenseGenerator.java
+java com.ruoyi.license.util.LicenseGenerator
+```
+
+**方式二:启动系统后在界面生成**
+- 登录系统
+- 进入"系统管理 -> 授权管理"
+- 点击"生成授权码"按钮
+
+### 5. 激活授权码
+
+**首次激活(登录页):**
+1. 访问登录页
+2. 点击"激活授权码"链接
+3. 输入生成的授权码
+4. 提交激活
+
+**系统内激活:**
+1. 登录系统(需要先有有效授权)
+2. 进入"系统管理 -> 授权管理"
+3. 点击"激活授权码"
+4. 输入新的授权码
+
+## 三、功能验证
+
+### 1. 验证授权拦截
+
+- 未激活授权码时,访问任何页面应跳转到登录页并提示"授权已过期"
+- 激活授权码后,可正常访问系统
+
+### 2. 验证定时检查
+
+- 修改数据库中授权码的 `expire_time` 为过去时间
+- 等待5分钟(或重启系统)
+- 在线用户应被强制退出
+- 尝试登录应提示授权过期
+
+### 3. 验证授权管理
+
+- 访问"系统管理 -> 授权管理"
+- 查看授权列表
+- 生成新授权码
+- 激活授权码
+- 删除过期授权
+
+## 四、复用到其他项目
+
+### 方式一:Maven依赖(推荐)
+
+1. 将 `ruoyi-license` 模块复制到目标项目
+2. 在父pom.xml添加模块声明
+3. 在需要的模块添加依赖
+4. 执行数据库脚本
+5. 复制Controller和前端页面
+
+### 方式二:JAR包依赖
+
+1. 打包license模块:
+```bash
+cd ruoyi-license
+mvn clean package
+```
+
+2. 安装到本地仓库:
+```bash
+mvn install:install-file -Dfile=target/ruoyi-license-4.8.2.jar \
+ -DgroupId=com.ruoyi -DartifactId=ruoyi-license \
+ -Dversion=4.8.2 -Dpackaging=jar
+```
+
+3. 在目标项目添加依赖:
+```xml
+
+ com.ruoyi
+ ruoyi-license
+ 4.8.2
+
+```
+
+## 五、配置说明
+
+### 1. 修改密钥(重要)
+
+生产环境必须修改默认密钥:
+
+```java
+// LicenseUtils.java
+private static final String SECRET_KEY = "YourCustomKey123"; // 改为16位自定义密钥
+```
+
+### 2. 调整检查频率
+
+```java
+// LicenseCheckTask.java
+@Scheduled(cron = "0 */5 * * * ?") // 每5分钟,可根据需要调整
+```
+
+### 3. 自定义拦截路径
+
+```java
+// LicenseConfig.java
+.excludePathPatterns(
+ "/login",
+ "/your-path/**" // 添加需要排除的路径
+)
+```
+
+## 六、注意事项
+
+1. **首次部署**:必须先生成并激活授权码,否则无法登录
+2. **时间同步**:确保服务器时间准确,避免授权判断错误
+3. **密钥安全**:生产环境务必修改默认密钥
+4. **数据备份**:定期备份 `sys_license` 表数据
+5. **权限控制**:授权管理菜单仅授予超级管理员
+
+## 七、故障排查
+
+### 问题1:无法激活授权码
+- 检查授权码格式是否正确
+- 检查授权码是否已过期
+- 查看后台日志错误信息
+
+### 问题2:授权有效但仍被拦截
+- 检查数据库中授权状态是否为"1"
+- 检查 `expire_time` 是否大于当前时间
+- 重启应用服务器
+
+### 问题3:定时任务不执行
+- 确认启动类添加了 `@EnableScheduling`
+- 检查日志是否有定时任务执行记录
+
+### 问题4:在线用户未被踢出
+- 检查 `SessionDAO` 是否正确注入
+- 查看定时任务是否正常执行
+- 检查Shiro配置是否正确
+
+## 八、技术支持
+
+如遇到问题,请提供以下信息:
+1. 错误日志
+2. 数据库授权记录
+3. 系统环境信息
+4. 操作步骤
+
+联系方式:参考RuoYi官方文档
diff --git a/ruoyi-license/LICENSE_FILTER_CONFIG.md b/ruoyi-license/LICENSE_FILTER_CONFIG.md
new file mode 100644
index 0000000..7a3ba06
--- /dev/null
+++ b/ruoyi-license/LICENSE_FILTER_CONFIG.md
@@ -0,0 +1,168 @@
+# 授权码全局拦截配置说明
+
+## 功能说明
+
+通过自定义 Shiro Filter,实现授权码失效时,**所有接口**(包括 `anon` 匿名访问的接口)都无法访问。
+
+## 实现原理
+
+1. 创建 `LicenseValidateFilter` 继承 `AccessControlFilter`
+2. 在 Shiro 过滤链的**最前面**添加此过滤器
+3. 所有请求都会先经过授权码验证,验证失败则拒绝访问
+
+## 已完成的修改
+
+### 1. 新增文件
+- `ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/license/LicenseValidateFilter.java`
+
+### 2. 修改文件
+- `ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java`
+ - 导入 `LicenseValidateFilter`
+ - 注册过滤器:`filters.put("licenseValidate", licenseValidateFilter())`
+ - 添加到过滤链:`filterChainDefinitionMap.put("/**", "licenseValidate,user,kickout,onlineSession,syncOnlineSession")`
+
+## 白名单配置
+
+以下路径即使授权失效也可以访问(必要的登录和静态资源):
+
+```java
+private static final String[] WHITE_LIST = {
+ "/login", // 登录页
+ "/captcha/captchaImage", // 验证码
+ "/css/", // 样式文件
+ "/js/", // 脚本文件
+ "/img/", // 图片资源
+ "/fonts/", // 字体文件
+ "/ajax/", // Ajax资源
+ "/ruoyi/", // 若依资源
+ "/favicon.ico", // 网站图标
+ "/system/license/active" // 授权激活页面(必须放行)
+};
+```
+
+## 自定义白名单
+
+如果需要添加其他路径到白名单,修改 `LicenseValidateFilter.java`:
+
+```java
+private static final String[] WHITE_LIST = {
+ // ... 原有配置
+ "/your-custom-path/**", // 添加你的自定义路径
+};
+```
+
+## 拦截效果
+
+### 授权有效时
+- 所有接口正常访问(包括 `/board/**`、`/agv/api/**` 等 anon 接口)
+
+### 授权失效时
+- **普通请求**:重定向到 `/login?license=expired`
+- **Ajax请求**:返回 JSON `{"code":401,"msg":"授权已过期,请联系管理员激活授权码"}`
+- **白名单路径**:仍可访问(用于激活授权码)
+
+## 测试步骤
+
+### 1. 启用授权功能
+在 `application.yml` 中配置:
+```yaml
+license:
+ enabled: true
+```
+
+### 2. 配置项目编号
+在数据库 `sys_config` 表中添加:
+```sql
+INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
+VALUES ('项目编号', 'fortucky.project.no', 'FT507', 'Y', 'admin', NOW(), '用于授权码验证');
+```
+
+### 3. 测试授权失效场景
+
+**方式一:不激活授权码**
+- 启动系统
+- 访问 `/board/**` 接口
+- 预期:被拦截,重定向到登录页
+
+**方式二:授权码过期**
+- 生成5分钟测试授权码并激活
+- 等待5分钟后访问 `/board/**` 接口
+- 预期:被拦截
+
+### 4. 测试授权有效场景
+- 生成并激活有效授权码
+- 访问 `/board/**` 接口
+- 预期:正常访问
+
+## 注意事项
+
+1. **白名单必须包含授权激活页面**
+ - `/system/license/active` 必须在白名单中
+ - 否则授权失效后无法激活新授权码
+
+2. **静态资源必须放行**
+ - CSS、JS、图片等资源必须在白名单
+ - 否则登录页面无法正常显示
+
+3. **过滤器顺序很重要**
+ - `licenseValidate` 必须在最前面
+ - 确保所有请求都先经过授权验证
+
+4. **开发环境可关闭授权**
+ ```yaml
+ license:
+ enabled: false # 开发时可关闭
+ ```
+
+## 与原有拦截器的区别
+
+### 原有 LicenseInterceptor(Spring MVC 拦截器)
+- 在 Spring MVC 层拦截
+- 只拦截进入 Controller 的请求
+- 无法拦截 Shiro 已放行的 `anon` 接口
+
+### 新增 LicenseValidateFilter(Shiro 过滤器)
+- 在 Shiro 层拦截
+- 拦截所有请求(包括 `anon` 接口)
+- 优先级高于 Shiro 的其他过滤器
+
+## 建议
+
+1. **保留两个拦截器**
+ - `LicenseValidateFilter`:全局拦截(包括 anon 接口)
+ - `LicenseInterceptor`:作为二次验证(可选)
+
+2. **生产环境必须启用**
+ ```yaml
+ license:
+ enabled: true
+ ```
+
+3. **定期检查白名单**
+ - 确保白名单路径最小化
+ - 避免过度放行
+
+## 故障排查
+
+### 问题1:所有请求都被拦截
+- 检查 `license.enabled` 是否为 `true`
+- 检查是否有有效授权码
+- 查看后台日志 `[授权验证]` 相关信息
+
+### 问题2:anon 接口仍可访问
+- 检查过滤器是否注册:`filters.put("licenseValidate", ...)`
+- 检查过滤链配置:`"licenseValidate,user,..."`
+- 确认 `licenseValidate` 在最前面
+
+### 问题3:无法激活授权码
+- 检查 `/system/license/active` 是否在白名单
+- 检查白名单匹配逻辑是否正确
+
+## 完成标志
+
+✅ 授权失效时,`/board/**` 接口无法访问
+✅ 授权失效时,`/agv/api/**` 接口无法访问
+✅ 授权失效时,仍可访问登录页和激活页
+✅ 授权有效时,所有接口正常访问
+✅ Ajax 请求返回 JSON 错误信息
+✅ 普通请求重定向到登录页
diff --git a/ruoyi-license/README.md b/ruoyi-license/README.md
new file mode 100644
index 0000000..5cfa495
--- /dev/null
+++ b/ruoyi-license/README.md
@@ -0,0 +1,193 @@
+# RuoYi 授权码模块
+
+## 模块简介
+
+ruoyi-license 是一个独立的授权码管理模块,用于控制系统的使用期限。当授权码过期后,系统将无法登录,已登录用户会被强制退出。
+
+## 功能特性
+
+- ✅ 授权码加密存储
+- ✅ 登录时验证授权有效性
+- ✅ 定时检查授权状态(每5分钟)
+- ✅ 授权过期自动踢出在线用户
+- ✅ 系统管理界面查看/激活授权
+- ✅ 登录页面激活授权码
+- ✅ 授权码生成工具
+- ✅ 独立模块,方便其他项目复用
+
+## 快速开始
+
+### 1. 执行数据库脚本
+
+```sql
+-- 执行 sql/sys_license.sql 创建授权表和菜单
+```
+
+### 2. 添加模块依赖
+
+在需要使用授权功能的项目中,添加依赖:
+
+```xml
+
+ com.ruoyi
+ ruoyi-license
+ 4.8.2
+
+```
+
+### 3. 配置包扫描
+
+确保Spring能扫描到授权模块的包:
+
+```java
+@SpringBootApplication
+@ComponentScan(basePackages = {"com.ruoyi"})
+public class RuoYiApplication {
+ // ...
+}
+```
+
+### 4. 生成授权码
+
+访问系统管理 -> 授权管理,点击"生成授权码"按钮,输入授权天数(如365天),系统会生成加密的授权码。
+
+### 5. 激活授权码
+
+**方式一:登录页激活**
+- 在登录页面点击"激活授权码"链接
+- 输入授权码并提交
+
+**方式二:系统内激活**
+- 登录系统后,进入"系统管理 -> 授权管理"
+- 点击"激活授权码"按钮
+- 输入授权码并提交
+
+## 核心类说明
+
+### 1. 实体类
+- `SysLicense`: 授权码实体,包含授权码、开始时间、结束时间、状态等信息
+
+### 2. 工具类
+- `LicenseUtils`: 授权码工具类
+ - `generateLicense()`: 生成授权码
+ - `encrypt()`: 加密数据
+ - `decrypt()`: 解密数据
+ - `validateFormat()`: 验证授权码格式
+
+### 3. 服务类
+- `ISysLicenseService`: 授权服务接口
+ - `activeLicense()`: 激活授权码
+ - `checkLicenseValid()`: 检查授权是否有效
+ - `getActiveLicense()`: 获取当前有效授权
+
+### 4. 拦截器
+- `LicenseInterceptor`: 授权拦截器,在每次请求时验证授权有效性
+
+### 5. 定时任务
+- `LicenseCheckTask`: 每5分钟检查授权状态,过期自动踢出在线用户
+
+## 授权码格式
+
+授权码采用AES加密,格式为:
+```
+加密(开始时间戳|结束时间戳)
+```
+
+示例:
+```
+原始数据: 1704067200000|1735689600000
+加密后: Xj8K9mN2pQ5rT7vW1yZ3aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8
+```
+
+## 集成到其他项目
+
+### 步骤1:复制模块
+将 `ruoyi-license` 文件夹复制到目标项目根目录
+
+### 步骤2:修改pom.xml
+在父pom.xml中添加:
+```xml
+
+ ruoyi-license
+
+
+
+
+
+ com.ruoyi
+ ruoyi-license
+ ${project.version}
+
+
+
+```
+
+### 步骤3:添加依赖
+在启动模块的pom.xml中添加:
+```xml
+
+ com.ruoyi
+ ruoyi-license
+
+```
+
+### 步骤4:执行SQL脚本
+执行 `sql/sys_license.sql` 创建数据库表
+
+### 步骤5:配置Controller
+将 `SysLicenseController.java` 复制到项目的controller包中
+
+### 步骤6:配置前端页面
+将 `templates/system/license/` 目录复制到项目的templates目录
+
+### 步骤7:修改登录页面
+参考 `login.html` 和 `login.js` 的修改,添加授权码激活入口
+
+## 自定义配置
+
+### 修改密钥
+编辑 `LicenseUtils.java`:
+```java
+private static final String SECRET_KEY = "YourSecretKey123"; // 16位密钥
+```
+
+### 修改检查频率
+编辑 `LicenseCheckTask.java`:
+```java
+@Scheduled(cron = "0 */10 * * * ?") // 改为每10分钟检查
+```
+
+### 自定义拦截规则
+编辑 `LicenseConfig.java`,添加或排除拦截路径:
+```java
+.excludePathPatterns(
+ "/login",
+ "/your-custom-path/**"
+)
+```
+
+## 注意事项
+
+1. **密钥安全**:生产环境务必修改默认密钥
+2. **时间同步**:确保服务器时间准确
+3. **数据备份**:定期备份授权表数据
+4. **权限控制**:授权管理菜单仅授予管理员
+5. **测试环境**:建议先在测试环境验证功能
+
+## 常见问题
+
+**Q: 授权码激活失败?**
+A: 检查授权码格式是否正确,是否已过期
+
+**Q: 如何延长授权期限?**
+A: 生成新的授权码并激活即可
+
+**Q: 授权过期后如何恢复?**
+A: 在登录页点击"激活授权码",输入新的授权码
+
+**Q: 可以同时激活多个授权码吗?**
+A: 系统只使用最新激活且未过期的授权码
+
+## 技术支持
+
+如有问题,请联系技术支持或查看RuoYi官方文档。
diff --git a/ruoyi-license/SUMMARY.md b/ruoyi-license/SUMMARY.md
new file mode 100644
index 0000000..dc7e368
--- /dev/null
+++ b/ruoyi-license/SUMMARY.md
@@ -0,0 +1,239 @@
+# RuoYi 授权码模块 - 项目总结
+
+## 📦 已完成的工作
+
+### 1. 核心模块开发 ✅
+
+**ruoyi-license 独立模块**
+- ✅ Maven模块配置 (pom.xml)
+- ✅ 授权实体类 (SysLicense.java)
+- ✅ 数据访问层 (SysLicenseMapper.java + XML)
+- ✅ 业务逻辑层 (ISysLicenseService.java + Impl)
+- ✅ 授权工具类 (LicenseUtils.java)
+- ✅ 授权拦截器 (LicenseInterceptor.java)
+- ✅ 拦截器配置 (LicenseConfig.java)
+- ✅ 定时检查任务 (LicenseCheckTask.java)
+- ✅ 授权码生成器 (LicenseGenerator.java)
+- ✅ 测试工具 (LicenseTest.java)
+
+### 2. 控制器和前端 ✅
+
+**后端控制器**
+- ✅ SysLicenseController.java (授权管理控制器)
+ - 授权列表查询
+ - 授权码激活
+ - 授权码生成
+ - 授权信息查看
+ - 授权删除
+
+**前端页面**
+- ✅ license.html (授权列表页)
+- ✅ active.html (激活页面)
+- ✅ login.html (添加激活入口)
+- ✅ login.js (添加激活功能)
+
+### 3. 数据库脚本 ✅
+
+- ✅ sys_license.sql
+ - 授权表结构
+ - 菜单权限配置
+
+### 4. 项目集成 ✅
+
+- ✅ 主pom.xml (添加license模块)
+- ✅ ruoyi-admin/pom.xml (添加license依赖)
+
+### 5. 文档编写 ✅
+
+- ✅ README.md (使用说明)
+- ✅ INTEGRATION.md (集成指南)
+
+## 🎯 功能特性
+
+### 核心功能
+1. **授权码加密存储** - AES加密,安全可靠
+2. **登录验证** - 登录时自动验证授权有效性
+3. **拦截器保护** - 所有请求都经过授权验证
+4. **定时检查** - 每5分钟自动检查授权状态
+5. **自动踢出** - 授权过期自动踢出所有在线用户
+6. **管理界面** - 完整的授权管理功能
+7. **登录页激活** - 无需登录即可激活授权码
+8. **授权码生成** - 内置生成工具,支持自定义天数
+
+### 技术亮点
+- 🔒 AES加密算法保护授权码
+- 🔄 Spring拦截器实现全局授权验证
+- ⏰ Spring定时任务自动检查授权
+- 🎨 完整的前后端交互界面
+- 📦 独立模块设计,方便复用
+- 🛠️ 提供测试工具和生成工具
+
+## 📂 文件清单
+
+```
+ruoyi-license/
+├── src/main/java/com/ruoyi/license/
+│ ├── config/
+│ │ └── LicenseConfig.java (拦截器配置)
+│ ├── domain/
+│ │ └── SysLicense.java (授权实体)
+│ ├── interceptor/
+│ │ └── LicenseInterceptor.java (授权拦截器)
+│ ├── mapper/
+│ │ └── SysLicenseMapper.java (Mapper接口)
+│ ├── service/
+│ │ ├── ISysLicenseService.java (服务接口)
+│ │ └── impl/
+│ │ └── SysLicenseServiceImpl.java (服务实现)
+│ ├── task/
+│ │ └── LicenseCheckTask.java (定时任务)
+│ └── util/
+│ ├── LicenseUtils.java (工具类)
+│ ├── LicenseGenerator.java (生成器)
+│ └── LicenseTest.java (测试工具)
+├── src/main/resources/mapper/license/
+│ └── SysLicenseMapper.xml (MyBatis映射)
+├── pom.xml (模块配置)
+├── README.md (使用说明)
+└── INTEGRATION.md (集成指南)
+
+sql/
+└── sys_license.sql (数据库脚本)
+
+ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/license/
+└── SysLicenseController.java (控制器)
+
+ruoyi-admin/src/main/resources/templates/system/license/
+├── license.html (列表页)
+└── active.html (激活页)
+
+修改的文件:
+├── pom.xml (添加模块)
+├── ruoyi-admin/pom.xml (添加依赖)
+├── ruoyi-admin/src/main/resources/templates/login.html
+└── ruoyi-admin/src/main/resources/static/ruoyi/login.js
+```
+
+## 🚀 快速开始
+
+### 1. 执行数据库脚本
+```sql
+source sql/sys_license.sql;
+```
+
+### 2. 启用定时任务
+在 RuoYiApplication.java 添加 @EnableScheduling
+
+### 3. 编译项目
+```bash
+mvn clean install
+```
+
+### 4. 生成授权码
+运行 LicenseGenerator.java 或在系统界面生成
+
+### 5. 激活授权码
+登录页点击"激活授权码"输入授权码
+
+## 🔧 使用场景
+
+### 场景1:软件交付
+- 开发完成后生成授权码
+- 交付给客户时提供授权码
+- 客户激活后可使用指定期限
+
+### 场景2:试用版本
+- 生成30天试用授权码
+- 客户试用满意后购买正式授权
+- 激活正式授权码延长使用期限
+
+### 场景3:订阅服务
+- 按年生成授权码
+- 到期前提醒客户续费
+- 续费后激活新授权码
+
+### 场景4:多项目复用
+- 将license模块复制到其他项目
+- 修改密钥和配置
+- 快速实现授权功能
+
+## ⚙️ 自定义配置
+
+### 修改密钥(必须)
+```java
+// LicenseUtils.java
+private static final String SECRET_KEY = "YourKey16Chars!!";
+```
+
+### 调整检查频率
+```java
+// LicenseCheckTask.java
+@Scheduled(cron = "0 */10 * * * ?") // 改为10分钟
+```
+
+### 自定义拦截路径
+```java
+// LicenseConfig.java
+.excludePathPatterns("/api/**", "/public/**")
+```
+
+## 📊 数据库表结构
+
+```sql
+sys_license (授权表)
+├── license_id (授权ID - 主键)
+├── license_code (授权码 - 加密存储)
+├── start_time (开始时间)
+├── expire_time (结束时间)
+├── status (状态: 0未激活 1已激活 2已过期)
+├── active_time (激活时间)
+├── active_by (激活人)
+├── create_by (创建者)
+├── create_time (创建时间)
+├── update_by (更新者)
+├── update_time (更新时间)
+└── remark (备注)
+```
+
+## 🔐 安全建议
+
+1. **生产环境必须修改默认密钥**
+2. **授权码通过安全渠道传递**
+3. **定期备份授权表数据**
+4. **限制授权管理菜单权限**
+5. **监控授权激活日志**
+
+## 📝 后续优化建议
+
+1. **硬件绑定** - 绑定服务器MAC地址或CPU序列号
+2. **在线验证** - 连接授权服务器在线验证
+3. **授权分级** - 不同授权码对应不同功能权限
+4. **自动续费** - 集成支付接口自动续费
+5. **授权统计** - 统计授权使用情况和到期提醒
+
+## ✅ 测试清单
+
+- [ ] 数据库表创建成功
+- [ ] 菜单权限配置正确
+- [ ] 授权码生成功能正常
+- [ ] 授权码加密解密正确
+- [ ] 登录页激活功能正常
+- [ ] 系统内激活功能正常
+- [ ] 授权拦截器生效
+- [ ] 定时任务执行正常
+- [ ] 授权过期自动踢出
+- [ ] 授权列表查询正常
+
+## 📞 技术支持
+
+如有问题,请查看:
+1. README.md - 使用说明
+2. INTEGRATION.md - 集成指南
+3. RuoYi官方文档
+
+---
+
+**开发完成时间**: 2024
+**模块版本**: 1.0.0
+**适用框架**: RuoYi v4.8.2
+**技术栈**: SpringBoot + MyBatis + Shiro + Thymeleaf
diff --git a/ruoyi-license/pom.xml b/ruoyi-license/pom.xml
new file mode 100644
index 0000000..9a60987
--- /dev/null
+++ b/ruoyi-license/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+ ruoyi
+ com.ruoyi
+ 3.9.0
+
+ 4.0.0
+
+ ruoyi-license
+
+
+ 授权码管理模块
+
+
+
+
+
+
+ com.ruoyi
+ ruoyi-common
+
+
+
+
+ com.ruoyi
+ ruoyi-system
+
+
+
+
+ com.ruoyi
+ ruoyi-framework
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/config/LicenseConfig.java b/ruoyi-license/src/main/java/com/ruoyi/license/config/LicenseConfig.java
new file mode 100644
index 0000000..2c4a18a
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/config/LicenseConfig.java
@@ -0,0 +1,61 @@
+package com.ruoyi.license.config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
+import com.ruoyi.license.interceptor.LicenseInterceptor;
+
+/**
+ * 授权模块配置
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class LicenseConfig implements WebMvcConfigurer
+{
+ @Autowired
+ private LicenseInterceptor licenseInterceptor;
+
+ @Autowired
+ private PermitAllUrlProperties permitAllUrl;
+
+ @Value("${license.enabled:false}")
+ private boolean licenseEnabled;
+
+ private static final List BASE_EXCLUDE = Arrays.asList(
+ "/login",
+ "/logout",
+ "/register",
+ "/captchaImage",
+ "/system/license/active",
+ "/swagger-ui.html",
+ "/swagger-resources/**",
+ "/webjars/**",
+ "/*/api-docs",
+ "/druid/**",
+ "/*.html",
+ "/**/*.html",
+ "/**/*.css",
+ "/**/*.js",
+ "/profile/**"
+ );
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry)
+ {
+ if (licenseEnabled)
+ {
+ List excludeList = new ArrayList<>(BASE_EXCLUDE);
+ excludeList.addAll(permitAllUrl.getUrls());
+ registry.addInterceptor(licenseInterceptor)
+ .addPathPatterns("/**")
+ .excludePathPatterns(excludeList);
+ }
+ }
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/domain/SysLicense.java b/ruoyi-license/src/main/java/com/ruoyi/license/domain/SysLicense.java
new file mode 100644
index 0000000..90dfc8d
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/domain/SysLicense.java
@@ -0,0 +1,105 @@
+package com.ruoyi.license.domain;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import java.util.Date;
+
+/**
+ * 系统授权码对象 sys_license
+ *
+ * @author ruoyi
+ */
+public class SysLicense extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 授权ID */
+ private Long licenseId;
+
+ /** 授权码 */
+ private String licenseCode;
+
+ /** 授权开始时间 */
+ private Date startTime;
+
+ /** 授权结束时间 */
+ private Date expireTime;
+
+ /** 授权状态(0未激活 1已激活 2已过期) */
+ private String status;
+
+ /** 激活时间 */
+ private Date activeTime;
+
+ /** 激活人 */
+ private String activeBy;
+
+ public Long getLicenseId()
+ {
+ return licenseId;
+ }
+
+ public void setLicenseId(Long licenseId)
+ {
+ this.licenseId = licenseId;
+ }
+
+ public String getLicenseCode()
+ {
+ return licenseCode;
+ }
+
+ public void setLicenseCode(String licenseCode)
+ {
+ this.licenseCode = licenseCode;
+ }
+
+ public Date getStartTime()
+ {
+ return startTime;
+ }
+
+ public void setStartTime(Date startTime)
+ {
+ this.startTime = startTime;
+ }
+
+ public Date getExpireTime()
+ {
+ return expireTime;
+ }
+
+ public void setExpireTime(Date expireTime)
+ {
+ this.expireTime = expireTime;
+ }
+
+ public String getStatus()
+ {
+ return status;
+ }
+
+ public void setStatus(String status)
+ {
+ this.status = status;
+ }
+
+ public Date getActiveTime()
+ {
+ return activeTime;
+ }
+
+ public void setActiveTime(Date activeTime)
+ {
+ this.activeTime = activeTime;
+ }
+
+ public String getActiveBy()
+ {
+ return activeBy;
+ }
+
+ public void setActiveBy(String activeBy)
+ {
+ this.activeBy = activeBy;
+ }
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/interceptor/LicenseInterceptor.java b/ruoyi-license/src/main/java/com/ruoyi/license/interceptor/LicenseInterceptor.java
new file mode 100644
index 0000000..0b1f6fc
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/interceptor/LicenseInterceptor.java
@@ -0,0 +1,46 @@
+package com.ruoyi.license.interceptor;
+
+import java.io.PrintWriter;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import com.ruoyi.license.service.ISysLicenseService;
+
+/**
+ * 授权拦截器
+ *
+ * @author ruoyi
+ */
+@Component
+public class LicenseInterceptor implements HandlerInterceptor
+{
+ @Autowired
+ private ISysLicenseService licenseService;
+
+ @Value("${license.enabled:false}")
+ private boolean licenseEnabled;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
+ {
+ if (!licenseEnabled)
+ {
+ return true;
+ }
+
+ if (!licenseService.checkLicenseValid())
+ {
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ response.setContentType("application/json;charset=UTF-8");
+ PrintWriter writer = response.getWriter();
+ writer.write("{\"code\":500,\"msg\":\"\u7cfb\u7edf\u6388\u6743\u5df2\u8fc7\u671f\uff0c\u8bf7\u8054\u7cfb\u7ba1\u7406\u5458\u6fc0\u6d3b\u6388\u6743\u7801\"}");
+ writer.flush();
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/mapper/SysLicenseMapper.java b/ruoyi-license/src/main/java/com/ruoyi/license/mapper/SysLicenseMapper.java
new file mode 100644
index 0000000..bd29eb6
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/mapper/SysLicenseMapper.java
@@ -0,0 +1,67 @@
+package com.ruoyi.license.mapper;
+
+import com.ruoyi.license.domain.SysLicense;
+import java.util.List;
+
+/**
+ * 授权码Mapper接口
+ *
+ * @author ruoyi
+ */
+public interface SysLicenseMapper
+{
+ /**
+ * 查询授权码
+ *
+ * @param licenseId 授权码ID
+ * @return 授权码
+ */
+ public SysLicense selectSysLicenseById(Long licenseId);
+
+ /**
+ * 查询授权码列表
+ *
+ * @param sysLicense 授权码
+ * @return 授权码集合
+ */
+ public List selectSysLicenseList(SysLicense sysLicense);
+
+ /**
+ * 查询当前有效的授权码
+ *
+ * @return 授权码
+ */
+ public SysLicense selectActiveLicense();
+
+ /**
+ * 新增授权码
+ *
+ * @param sysLicense 授权码
+ * @return 结果
+ */
+ public int insertSysLicense(SysLicense sysLicense);
+
+ /**
+ * 修改授权码
+ *
+ * @param sysLicense 授权码
+ * @return 结果
+ */
+ public int updateSysLicense(SysLicense sysLicense);
+
+ /**
+ * 删除授权码
+ *
+ * @param licenseId 授权码ID
+ * @return 结果
+ */
+ public int deleteSysLicenseById(Long licenseId);
+
+ /**
+ * 批量删除授权码
+ *
+ * @param licenseIds 需要删除的数据ID
+ * @return 结果
+ */
+ public int deleteSysLicenseByIds(String[] licenseIds);
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/service/ISysLicenseService.java b/ruoyi-license/src/main/java/com/ruoyi/license/service/ISysLicenseService.java
new file mode 100644
index 0000000..1a9a225
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/service/ISysLicenseService.java
@@ -0,0 +1,87 @@
+package com.ruoyi.license.service;
+
+import com.ruoyi.license.domain.SysLicense;
+import java.util.List;
+
+/**
+ * 授权码Service接口
+ *
+ * @author ruoyi
+ */
+public interface ISysLicenseService
+{
+ /**
+ * 查询授权码
+ *
+ * @param licenseId 授权码ID
+ * @return 授权码
+ */
+ public SysLicense selectSysLicenseById(Long licenseId);
+
+ /**
+ * 查询授权码列表
+ *
+ * @param sysLicense 授权码
+ * @return 授权码集合
+ */
+ public List selectSysLicenseList(SysLicense sysLicense);
+
+ /**
+ * 新增授权码
+ *
+ * @param sysLicense 授权码
+ * @return 结果
+ */
+ public int insertSysLicense(SysLicense sysLicense);
+
+ /**
+ * 修改授权码
+ *
+ * @param sysLicense 授权码
+ * @return 结果
+ */
+ public int updateSysLicense(SysLicense sysLicense);
+
+ /**
+ * 批量删除授权码
+ *
+ * @param ids 需要删除的数据ID
+ * @return 结果
+ */
+ public int deleteSysLicenseByIds(String ids);
+
+ /**
+ * 删除授权码信息
+ *
+ * @param licenseId 授权码ID
+ * @return 结果
+ */
+ public int deleteSysLicenseById(Long licenseId);
+
+ /**
+ * 激活授权码
+ *
+ * @param licenseCode 授权码
+ * @return 结果
+ */
+ public int activeLicense(String licenseCode);
+
+ /**
+ * 检查授权是否有效
+ *
+ * @return true有效 false无效
+ */
+ public boolean checkLicenseValid();
+
+ /**
+ * 获取当前有效授权信息
+ *
+ * @return 授权信息
+ */
+ public SysLicense getActiveLicense();
+
+ /**
+ * 更新过期授权码状态
+ */
+ public void updateExpiredLicenses();
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/service/impl/SysLicenseServiceImpl.java b/ruoyi-license/src/main/java/com/ruoyi/license/service/impl/SysLicenseServiceImpl.java
new file mode 100644
index 0000000..0b3dcbf
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/service/impl/SysLicenseServiceImpl.java
@@ -0,0 +1,267 @@
+package com.ruoyi.license.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.license.domain.SysLicense;
+import com.ruoyi.license.mapper.SysLicenseMapper;
+import com.ruoyi.license.service.ISysLicenseService;
+import com.ruoyi.license.util.LicenseUtils;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 授权码Service业务层处理
+ *
+ * @author ruoyi
+ */
+@Service
+public class SysLicenseServiceImpl implements ISysLicenseService
+{
+ @Autowired
+ private SysLicenseMapper sysLicenseMapper;
+
+ @Autowired
+ private ISysConfigService sysConfigService;
+
+ @org.springframework.beans.factory.annotation.Value("${license.enabled:false}")
+ private boolean licenseEnabled;
+
+ /**
+ * 查询授权码
+ *
+ * @param licenseId 授权码ID
+ * @return 授权码
+ */
+ @Override
+ public SysLicense selectSysLicenseById(Long licenseId)
+ {
+ return sysLicenseMapper.selectSysLicenseById(licenseId);
+ }
+
+ /**
+ * 查询授权码列表
+ *
+ * @param sysLicense 授权码
+ * @return 授权码
+ */
+ @Override
+ public List selectSysLicenseList(SysLicense sysLicense)
+ {
+ return sysLicenseMapper.selectSysLicenseList(sysLicense);
+ }
+
+ /**
+ * 新增授权码
+ *
+ * @param sysLicense 授权码
+ * @return 结果
+ */
+ @Override
+ public int insertSysLicense(SysLicense sysLicense)
+ {
+ sysLicense.setCreateTime(new Date());
+ return sysLicenseMapper.insertSysLicense(sysLicense);
+ }
+
+ /**
+ * 修改授权码
+ *
+ * @param sysLicense 授权码
+ * @return 结果
+ */
+ @Override
+ public int updateSysLicense(SysLicense sysLicense)
+ {
+ sysLicense.setUpdateTime(new Date());
+ return sysLicenseMapper.updateSysLicense(sysLicense);
+ }
+
+ /**
+ * 删除授权码对象
+ *
+ * @param ids 需要删除的数据ID
+ * @return 结果
+ */
+ @Override
+ public int deleteSysLicenseByIds(String ids)
+ {
+ return sysLicenseMapper.deleteSysLicenseByIds(Convert.toStrArray(ids));
+ }
+
+ /**
+ * 删除授权码信息
+ *
+ * @param licenseId 授权码ID
+ * @return 结果
+ */
+ @Override
+ public int deleteSysLicenseById(Long licenseId)
+ {
+ return sysLicenseMapper.deleteSysLicenseById(licenseId);
+ }
+
+ /**
+ * 激活授权码
+ *
+ * @param licenseCode 授权码
+ * @return 结果
+ */
+ @Override
+ public int activeLicense(String licenseCode)
+ {
+ if (StringUtils.isEmpty(licenseCode))
+ {
+ return 0;
+ }
+
+ // 验证授权码格式
+ if (!LicenseUtils.validateFormat(licenseCode))
+ {
+ return 0;
+ }
+
+ try
+ {
+ // 从配置表获取项目编号
+ String projectNo = sysConfigService.selectConfigByKey("fortucky.project.no");
+ if (StringUtils.isEmpty(projectNo))
+ {
+ return 0;
+ }
+
+ // 解析授权码
+ long expireTime = LicenseUtils.parseLicense(licenseCode, projectNo);
+ if (expireTime == 0)
+ {
+ return 0;
+ }
+
+ Date now = new Date();
+ Date startTime = now;
+ Date expireDate = new Date(expireTime);
+
+ // 验证是否已过期
+ if (expireDate.before(now))
+ {
+ return 0;
+ }
+
+ // 创建授权记录
+ SysLicense license = new SysLicense();
+ license.setLicenseCode(licenseCode);
+ license.setStartTime(startTime);
+ license.setExpireTime(expireDate);
+ license.setStatus("1");
+ license.setActiveTime(now);
+ license.setCreateTime(now);
+
+ return sysLicenseMapper.insertSysLicense(license);
+ }
+ catch (Exception e)
+ {
+ return 0;
+ }
+ }
+
+ /**
+ * 检查授权是否有效
+ *
+ * @return true有效 false无效
+ */
+ @Override
+ public boolean checkLicenseValid()
+ {
+ // 如果授权码功能未启用,直接返回true
+ if (!licenseEnabled)
+ {
+ return true;
+ }
+
+ SysLicense license = sysLicenseMapper.selectActiveLicense();
+ if (license == null)
+ {
+ System.out.println("[授权检查] 没有找到激活的授权码");
+ return false;
+ }
+
+ // 从配置表获取项目编号
+ String projectNo = sysConfigService.selectConfigByKey("fortucky.project.no");
+ if (StringUtils.isEmpty(projectNo))
+ {
+ System.out.println("[授权检查] 项目编号未配置");
+ return false;
+ }
+
+ System.out.println("[授权检查] 授权码: " + license.getLicenseCode());
+ System.out.println("[授权检查] 项目编号: " + projectNo);
+
+ // 从授权码解析过期时间
+ long expireTime = LicenseUtils.parseLicense(license.getLicenseCode(), projectNo);
+ if (expireTime == 0)
+ {
+ System.out.println("[授权检查] 授权码解析失败");
+ return false;
+ }
+
+ Date now = new Date();
+ System.out.println("[授权检查] 当前时间: " + now.getTime());
+ System.out.println("[授权检查] 过期时间: " + expireTime);
+
+ if (expireTime < now.getTime())
+ {
+ System.out.println("[授权检查] 授权已过期");
+ // 更新为过期状态
+ license.setStatus("2");
+ sysLicenseMapper.updateSysLicense(license);
+ return false;
+ }
+
+
+ return true;
+ }
+
+ /**
+ * 获取当前有效授权信息
+ *
+ * @return 授权信息
+ */
+ @Override
+ public SysLicense getActiveLicense()
+ {
+ return sysLicenseMapper.selectActiveLicense();
+ }
+
+ /**
+ * 更新过期授权码状态
+ */
+ @Override
+ public void updateExpiredLicenses()
+ {
+ SysLicense query = new SysLicense();
+ query.setStatus("1");
+ List licenses = sysLicenseMapper.selectSysLicenseList(query);
+
+ // 从配置表获取项目编号
+ String projectNo = sysConfigService.selectConfigByKey("fortucky.project.no");
+ if (StringUtils.isEmpty(projectNo))
+ {
+ return;
+ }
+
+ Date now = new Date();
+ for (SysLicense license : licenses)
+ {
+ // 从授权码解析过期时间
+ long expireTime = LicenseUtils.parseLicense(license.getLicenseCode(), projectNo);
+ if (expireTime > 0 && expireTime < now.getTime())
+ {
+ license.setStatus("2");
+ license.setUpdateTime(now);
+ sysLicenseMapper.updateSysLicense(license);
+ }
+ }
+ }
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/task/LicenseCheckTask.java b/ruoyi-license/src/main/java/com/ruoyi/license/task/LicenseCheckTask.java
new file mode 100644
index 0000000..9963c61
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/task/LicenseCheckTask.java
@@ -0,0 +1,47 @@
+package com.ruoyi.license.task;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.license.service.ISysLicenseService;
+
+/**
+ * 授权检查定时任务
+ *
+ * @author ruoyi
+ */
+@Component
+public class LicenseCheckTask
+{
+ @Autowired
+ private ISysLicenseService licenseService;
+
+ @Autowired
+ private RedisCache redisCache;
+
+ @Value("${license.enabled:false}")
+ private boolean licenseEnabled;
+
+ /**
+ * 每5分钟检查一次授权状态
+ */
+ @Scheduled(cron = "0 */5 * * * ?")
+ public void checkLicense()
+ {
+ if (!licenseEnabled)
+ {
+ return;
+ }
+
+ if (!licenseService.checkLicenseValid())
+ {
+ // 授权无效,删除 Redis 中所有登录 token,强制踢出所有在线用户
+ redisCache.deleteObject(redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"));
+ }
+
+ licenseService.updateExpiredLicenses();
+ }
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseGenerator.java b/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseGenerator.java
new file mode 100644
index 0000000..16ce407
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseGenerator.java
@@ -0,0 +1,127 @@
+package com.ruoyi.license.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Scanner;
+
+/**
+ * 授权码生成工具(独立运行)
+ *
+ * @author ruoyi
+ */
+public class LicenseGenerator
+{
+ public static void main(String[] args)
+ {
+ Scanner scanner = new Scanner(System.in);
+
+ System.out.println("========================================");
+ System.out.println(" RuoYi 授权码生成工具");
+ System.out.println("========================================");
+ System.out.println();
+
+ try
+ {
+ // 选择授权类型
+ System.out.println("请选择授权类型:");
+ System.out.println("1. 5分钟测试");
+ System.out.println("2. 1个月授权");
+ System.out.println("3. 3个月授权");
+ System.out.println("4. 1年授权");
+ System.out.println("5. 5年授权");
+ System.out.println("6. 长期授权");
+ System.out.print("请输入选项(1-6): ");
+ String typeChoice = scanner.nextLine().trim();
+
+ String type;
+ String typeDesc;
+ long expireTime;
+
+ switch (typeChoice)
+ {
+ case "1":
+ type = LicenseUtils.TYPE_TEST;
+ typeDesc = "5分钟测试";
+ expireTime = System.currentTimeMillis() + 5 * 60 * 1000L;
+ break;
+ case "2":
+ type = LicenseUtils.TYPE_MONTH;
+ typeDesc = "1个月授权";
+ expireTime = System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000;
+ break;
+ case "3":
+ type = LicenseUtils.TYPE_THREE;
+ typeDesc = "3个月授权";
+ expireTime = System.currentTimeMillis() + 90L * 24 * 60 * 60 * 1000;
+ break;
+ case "4":
+ type = LicenseUtils.TYPE_YEAR;
+ typeDesc = "1年授权";
+ expireTime = System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000;
+ break;
+ case "5":
+ type = LicenseUtils.TYPE_FIVE;
+ typeDesc = "5年授权";
+ expireTime = System.currentTimeMillis() + 5 * 365L * 24 * 60 * 60 * 1000;
+ break;
+ case "6":
+ type = LicenseUtils.TYPE_FOREVER;
+ typeDesc = "长期授权";
+ expireTime = System.currentTimeMillis() + 100 * 365L * 24 * 60 * 60 * 1000;
+ break;
+ default:
+ System.out.println("错误:无效的选项");
+ return;
+ }
+
+ // 输入项目编号
+ System.out.print("请输入项目编号: ");
+ String projectNo = scanner.nextLine().trim();
+
+ if (projectNo.isEmpty())
+ {
+ System.out.println("错误:项目编号不能为空");
+ return;
+ }
+
+ // 生成授权码
+ String licenseCode = LicenseUtils.generateLicense(type, projectNo);
+
+ if (licenseCode == null)
+ {
+ System.out.println("错误:授权码生成失败");
+ return;
+ }
+
+ // 格式化日期
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ Date now = new Date();
+ Date expire = new Date(expireTime);
+
+ System.out.println();
+ System.out.println("========================================");
+ System.out.println("授权码生成成功!");
+ System.out.println("========================================");
+ System.out.println("授权类型: " + typeDesc);
+ System.out.println("项目编号: " + projectNo);
+ System.out.println("当前时间: " + sdf.format(now));
+ System.out.println("过期时间: " + sdf.format(expire));
+ System.out.println("----------------------------------------");
+ System.out.println("授权码:");
+ System.out.println(licenseCode);
+ System.out.println("========================================");
+ System.out.println();
+ System.out.println("请妥善保管授权码,在系统中激活后即可使用。");
+ System.out.println("注意:系统配置中的项目编号必须与此处一致才能激活。");
+ }
+ catch (Exception e)
+ {
+ System.out.println("错误:" + e.getMessage());
+ e.printStackTrace();
+ }
+ finally
+ {
+ scanner.close();
+ }
+ }
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseTest.java b/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseTest.java
new file mode 100644
index 0000000..0b69a1c
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseTest.java
@@ -0,0 +1,89 @@
+package com.ruoyi.license.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 授权码测试工具
+ *
+ * @author ruoyi
+ */
+public class LicenseTest
+{
+ public static void main(String[] args)
+ {
+ System.out.println("========================================");
+ System.out.println(" 授权码功能测试");
+ System.out.println("========================================");
+ System.out.println();
+
+ String projectNo = "TEST001";
+
+ // 测试1:生成各类型授权码
+ System.out.println("【测试1】生成各类型授权码");
+ String testCode = LicenseUtils.generateLicense(LicenseUtils.TYPE_TEST, projectNo);
+ String yearCode = LicenseUtils.generateLicense(LicenseUtils.TYPE_YEAR, projectNo);
+ String fiveCode = LicenseUtils.generateLicense(LicenseUtils.TYPE_FIVE, projectNo);
+ String foreverCode = LicenseUtils.generateLicense(LicenseUtils.TYPE_FOREVER, projectNo);
+
+ System.out.println("5分钟测试: " + testCode);
+ System.out.println("1年授权: " + yearCode);
+ System.out.println("5年授权: " + fiveCode);
+ System.out.println("长期授权: " + foreverCode);
+ System.out.println("结果: ✓ 通过");
+ System.out.println();
+
+ // 测试2:验证授权码格式
+ System.out.println("【测试2】验证授权码格式");
+ boolean isValid = LicenseUtils.validateFormat(yearCode);
+ System.out.println("格式验证: " + (isValid ? "✓ 通过" : "✗ 失败"));
+ System.out.println();
+
+ // 测试3:解析授权码
+ System.out.println("【测试3】解析授权码");
+ long expireTime = LicenseUtils.parseLicense(yearCode, projectNo);
+ if (expireTime > 0)
+ {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ Date expire = new Date(expireTime);
+ System.out.println("过期时间: " + sdf.format(expire));
+ System.out.println("结果: ✓ 通过");
+ }
+ else
+ {
+ System.out.println("结果: ✗ 失败");
+ }
+ System.out.println();
+
+ // 测试4:项目编号验证
+ System.out.println("【测试4】项目编号验证");
+ long validResult = LicenseUtils.parseLicense(yearCode, projectNo);
+ long invalidResult = LicenseUtils.parseLicense(yearCode, "WRONG001");
+ System.out.println("正确项目编号: " + (validResult > 0 ? "✓ 通过" : "✗ 失败"));
+ System.out.println("错误项目编号: " + (invalidResult == 0 ? "✓ 通过(正确拒绝)" : "✗ 失败"));
+ System.out.println();
+
+ // 测试5:授权码格式展示
+ System.out.println("【测试5】授权码格式展示");
+ System.out.println("授权码: " + yearCode);
+ String[] parts = yearCode.split("_");
+ if (parts.length == 5)
+ {
+ System.out.println("前缀: " + parts[0]);
+ System.out.println("类型: " + parts[1] + " (" + LicenseUtils.getTypeDesc(parts[1]) + ")");
+ System.out.println("项目编号: " + parts[2]);
+ System.out.println("加密时间: " + parts[3]);
+ System.out.println("校验码: " + parts[4]);
+ System.out.println("结果: ✓ 通过");
+ }
+ else
+ {
+ System.out.println("结果: ✗ 失败");
+ }
+ System.out.println();
+
+ System.out.println("========================================");
+ System.out.println("测试完成!");
+ System.out.println("========================================");
+ }
+}
diff --git a/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseUtils.java b/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseUtils.java
new file mode 100644
index 0000000..ef456ae
--- /dev/null
+++ b/ruoyi-license/src/main/java/com/ruoyi/license/util/LicenseUtils.java
@@ -0,0 +1,275 @@
+package com.ruoyi.license.util;
+
+import java.util.Base64;
+import java.util.Date;
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * 授权码工具类
+ * 格式: FORTUCKY_授权类型_项目编号_加密时间_校验码
+ *
+ * @author ruoyi
+ */
+public class LicenseUtils
+{
+ // 密钥(16位)
+ private static final String SECRET_KEY = "RuoYiLicense2024";
+
+ // 授权类型
+ public static final String TYPE_TEST = "TEST"; // 5分钟测试
+ public static final String TYPE_MONTH = "MONTH"; // 1个月
+ public static final String TYPE_THREE = "THREE"; // 3个月
+ public static final String TYPE_YEAR = "YEAR"; // 1年
+ public static final String TYPE_FIVE = "FIVE"; // 5年
+ public static final String TYPE_FOREVER = "FOREVER"; // 长期
+
+ /**
+ * 生成授权码
+ *
+ * @param type 授权类型
+ * @param projectNo 项目编号
+ * @return 授权码
+ */
+ public static String generateLicense(String type, String projectNo)
+ {
+ try
+ {
+ long expireTime = calculateExpireTime(type);
+ if (expireTime == 0)
+ {
+ return null;
+ }
+
+ // 加密过期时间
+ String encryptedTime = encrypt(String.valueOf(expireTime));
+ if (encryptedTime == null)
+ {
+ return null;
+ }
+
+ // 生成校验码(取加密时间的前8位MD5)
+ String checkCode = generateCheckCode(type, projectNo, encryptedTime);
+
+ // 拼接授权码: FORTUCKY_类型_项目编号_加密时间_校验码
+ return "FORTUCKY_" + type + "_" + projectNo + "_" + encryptedTime + "_" + checkCode;
+ }
+ catch (Exception e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * 验证授权码格式
+ *
+ * @param licenseCode 授权码
+ * @return true有效 false无效
+ */
+ public static boolean validateFormat(String licenseCode)
+ {
+ if (licenseCode == null || licenseCode.trim().isEmpty())
+ {
+ return false;
+ }
+
+ String[] parts = licenseCode.split("_");
+ if (parts.length != 5)
+ {
+ return false;
+ }
+
+ if (!"FORTUCKY".equals(parts[0]))
+ {
+ return false;
+ }
+
+ // 验证授权类型
+ String type = parts[1];
+ if (!TYPE_TEST.equals(type) && !TYPE_MONTH.equals(type) && !TYPE_THREE.equals(type) &&
+ !TYPE_YEAR.equals(type) && !TYPE_FIVE.equals(type) && !TYPE_FOREVER.equals(type))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 解析授权码
+ *
+ * @param licenseCode 授权码
+ * @param projectNo 系统项目编号
+ * @return 过期时间戳,0表示解析失败
+ */
+ public static long parseLicense(String licenseCode, String projectNo)
+ {
+ try
+ {
+ if (!validateFormat(licenseCode))
+ {
+ return 0;
+ }
+
+ String[] parts = licenseCode.split("_");
+ String type = parts[1];
+ String codeProjectNo = parts[2];
+ String encryptedTime = parts[3];
+ String checkCode = parts[4];
+
+ // 验证项目编号
+ if (!codeProjectNo.equals(projectNo))
+ {
+ return 0;
+ }
+
+ // 验证校验码
+ String expectedCheckCode = generateCheckCode(type, codeProjectNo, encryptedTime);
+ if (!checkCode.equals(expectedCheckCode))
+ {
+ return 0;
+ }
+
+ // 解密时间
+ String decrypted = decrypt(encryptedTime);
+ if (decrypted == null)
+ {
+ return 0;
+ }
+
+ return Long.parseLong(decrypted);
+ }
+ catch (Exception e)
+ {
+ return 0;
+ }
+ }
+
+ /**
+ * 获取授权类型描述
+ *
+ * @param type 授权类型
+ * @return 描述
+ */
+ public static String getTypeDesc(String type)
+ {
+ switch (type)
+ {
+ case TYPE_TEST: return "5分钟测试";
+ case TYPE_MONTH: return "1个月授权";
+ case TYPE_THREE: return "3个月授权";
+ case TYPE_YEAR: return "1年授权";
+ case TYPE_FIVE: return "5年授权";
+ case TYPE_FOREVER: return "长期授权";
+ default: return "未知";
+ }
+ }
+
+ /**
+ * 计算过期时间
+ *
+ * @param type 授权类型
+ * @return 过期时间戳
+ */
+ private static long calculateExpireTime(String type)
+ {
+ long now = System.currentTimeMillis();
+
+ switch (type)
+ {
+ case TYPE_TEST:
+ return now + 5 * 60 * 1000L; // 5分钟
+ case TYPE_MONTH:
+ return now + 30L * 24 * 60 * 60 * 1000; // 1个月
+ case TYPE_THREE:
+ return now + 90L * 24 * 60 * 60 * 1000; // 3个月
+ case TYPE_YEAR:
+ return now + 365L * 24 * 60 * 60 * 1000; // 1年
+ case TYPE_FIVE:
+ return now + 5 * 365L * 24 * 60 * 60 * 1000; // 5年
+ case TYPE_FOREVER:
+ return now + 100 * 365L * 24 * 60 * 60 * 1000; // 100年(视为长期)
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * 生成校验码
+ *
+ * @param type 授权类型
+ * @param projectNo 项目编号
+ * @param encryptedTime 加密时间
+ * @return 校验码
+ */
+ private static String generateCheckCode(String type, String projectNo, String encryptedTime)
+ {
+ try
+ {
+ String data = type + projectNo + encryptedTime;
+ java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+ byte[] hash = md.digest(data.getBytes("UTF-8"));
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < Math.min(4, hash.length); i++)
+ {
+ sb.append(String.format("%02x", hash[i]));
+ }
+ return sb.toString().toUpperCase();
+ }
+ catch (Exception e)
+ {
+ return "00000000";
+ }
+ }
+
+ /**
+ * 加密
+ *
+ * @param data 原始数据
+ * @return 加密后的数据
+ */
+ private static String encrypt(String data)
+ {
+ try
+ {
+ SecretKeySpec key = new SecretKeySpec(SECRET_KEY.getBytes("UTF-8"), "AES");
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"));
+ return Base64.getEncoder().encodeToString(encrypted).replace("+", "-").replace("/", ".").replace("=", "");
+ }
+ catch (Exception e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * 解密
+ *
+ * @param encryptedData 加密数据
+ * @return 解密后的数据
+ */
+ private static String decrypt(String encryptedData)
+ {
+ try
+ {
+ String base64Data = encryptedData.replace("-", "+").replace(".", "/");
+ int padding = (4 - base64Data.length() % 4) % 4;
+ for (int i = 0; i < padding; i++)
+ {
+ base64Data += "=";
+ }
+
+ SecretKeySpec key = new SecretKeySpec(SECRET_KEY.getBytes("UTF-8"), "AES");
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, key);
+ byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(base64Data));
+ return new String(decrypted, "UTF-8");
+ }
+ catch (Exception e)
+ {
+ return null;
+ }
+ }
+}
diff --git a/ruoyi-license/src/main/resources/mapper/license/SysLicenseMapper.xml b/ruoyi-license/src/main/resources/mapper/license/SysLicenseMapper.xml
new file mode 100644
index 0000000..e6603f0
--- /dev/null
+++ b/ruoyi-license/src/main/resources/mapper/license/SysLicenseMapper.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select license_id, license_code, start_time, expire_time, status, active_time, active_by, create_by, create_time, update_by, update_time, remark from sys_license
+
+
+
+
+
+
+
+
+
+ insert into sys_license
+
+ license_code,
+ start_time,
+ expire_time,
+ status,
+ active_time,
+ active_by,
+ create_by,
+ create_time,
+ update_by,
+ update_time,
+ remark,
+
+
+ #{licenseCode},
+ #{startTime},
+ #{expireTime},
+ #{status},
+ #{activeTime},
+ #{activeBy},
+ #{createBy},
+ #{createTime},
+ #{updateBy},
+ #{updateTime},
+ #{remark},
+
+
+
+
+ update sys_license
+
+ license_code = #{licenseCode},
+ start_time = #{startTime},
+ expire_time = #{expireTime},
+ status = #{status},
+ active_time = #{activeTime},
+ active_by = #{activeBy},
+ create_by = #{createBy},
+ create_time = #{createTime},
+ update_by = #{updateBy},
+ update_time = #{updateTime},
+ remark = #{remark},
+
+ where license_id = #{licenseId}
+
+
+
+ delete from sys_license where license_id = #{licenseId}
+
+
+
+ delete from sys_license where license_id in
+
+ #{licenseId}
+
+
+
+