1 Commits
dev ... v3.7.0

Author SHA1 Message Date
118e307f3e release: v3.7.0
Closes #ICECEY
2025-06-13 23:54:18 +08:00
461 changed files with 2301 additions and 8752 deletions

View File

@@ -15,14 +15,14 @@ body:
options:
- label: 重启项目和 IDE 后,仍然能够复现此问题
required: true
- label: 查阅过 [使用指南](https://continew.top/admin/backend/structure.html) 和 [常见问题](https://continew.top/admin/faq.html) ,仍无解决方法
required: true
- label: 查看过控制台是否有报错,如果有报错,下拉控制台到最下查找过 Caused 提示(如果有 Caused 关键字,一般其后都有直观提示,请自行翻译英文),并百度或 Google 后,仍无法解决
required: true
- label: 查阅过 [使用指南](https://continew.top/docs/admin/guide/introduction.html) 和 [常见问题](https://continew.top/docs/admin/faq.html) ,仍无解决方法
required: true
- label: 搜索了 [吐槽广场](https://continew.top/docs/admin/issue-hub.html),没有其他人提交过类似的 Bug如果对应 Bug 尚未解决,您可以先订阅关注该 Issue为了方便后来者查找问题解决方法请避免创建重复的 Issue
required: true
- label: 尝试了最新 dev 分支代码(演示环境),仍有相同问题
required: true
- label: 搜索了项目 Issues没有其他人提交过类似的 Bug如果对应 Bug 尚未解决,您可以先订阅关注该 Issue为了方便后来者查找问题解决方法请避免创建重复的 Issue
required: true
- label: 【后端】确认不是依赖组件相关的问题例如sa-token、mybatis-plus、snail-job、crane4j、cosid等如有此类组件相关的问题请提交至对应组件仓库
required: true
- label: 【前端】确认不是 gi-demo 前端模板相关的组件问题例如GiTable、GiForm、基础布局和配置等如有此类组件相关的问题请提交至 [gi-demo](https://gitee.com/lin0716/gi-demo) 或对应组件仓库)

View File

@@ -15,9 +15,11 @@ body:
options:
- label: 尝试了最新 dev 分支代码(演示环境),仍没有该功能
required: true
- label: 查阅过 [使用指南](https://continew.top/docs/admin/guide/introduction.html) 和 [常见问题](https://continew.top/docs/admin/faq.html) ,仍然认为很有必要
- label: 查阅过 [使用指南](https://continew.top/admin/backend/structure.html) 和 [常见问题](https://continew.top/admin/faq.html) ,仍然认为很有必要
required: true
- label: 搜索了 [吐槽广场](https://continew.top/docs/admin/issue-hub.html),没有其他人提交过类似的 Feature如果对应 Feature 尚未实现,您可以先订阅关注该 Issue为了方便后来者查找问题解决方法请避免创建重复的 Issue
- label: 查阅过 [需求墙](https://continew.top/admin/other/feature.html)没有该功能计划
required: true
- label: 搜索了项目 Issues没有其他人提交过类似的 Feature如果对应 Feature 尚未实现,您可以先订阅关注该 Issue为了方便后来者查找问题解决方法请避免创建重复的 Issue
required: true
- label: 【后端】确认不是依赖组件相关的需求例如sa-token、mybatis-plus、snail-job、crane4j、cosid等如有此类组件相关的需求请提交至对应组件仓库
required: true

View File

@@ -35,8 +35,8 @@ jobs:
port: ${{ secrets.SERVER_PORT }}
username: ${{ secrets.SERVER_USERNAME }}
password: ${{ secrets.SERVER_PASSWORD }}
source: ./continew-server/target/app/*
target: ${{ secrets.SERVER_PATH }}/continew-admin
source: ./continew-webapi/target/app/*
target: /docker/continew-admin
strip_components: 3
# 5、启动
- name: Start
@@ -47,6 +47,6 @@ jobs:
username: ${{ secrets.SERVER_USERNAME }}
password: ${{ secrets.SERVER_PASSWORD }}
script: |
cd ${{ secrets.SERVER_PATH }}
docker-compose up --force-recreate --build -d continew-server
docker images | grep none | awk '{print $3}' | xargs -r docker rmi
cd /docker
docker-compose up --force-recreate --build -d continew-admin-server
docker images | grep none | awk '{print $3}' | xargs docker rmi

View File

@@ -1,63 +1,3 @@
## [v4.0.0](https://github.com/continew-org/continew-admin/compare/v3.7.0...v4.0.0) (2025-07-27)
### ✨ 新特性
- 新增多租户插件模块 (GitHub#175@xtanyu) ([ed6dd65](https://github.com/continew-org/continew-admin/commit/ed6dd65a51a1c26af2c9d76407463b7f67c71fd5)) ([dec5d61](https://github.com/continew-org/continew-admin/commit/dec5d611bef76bbd145d076f62fe5b8deced75ae)) ([af1079d](https://github.com/continew-org/continew-admin/commit/af1079da6d009fa9cdeb7c1caf7a131a0449229f)) ([6e7d371](https://github.com/continew-org/continew-admin/commit/6e7d371565e75272f027a547442adf3071dbe152)) ([7e9a950](https://github.com/continew-org/continew-admin/commit/7e9a950694ce78f11b0f2ffb8fe69617502c361e)) ([9eff846](https://github.com/continew-org/continew-admin/commit/9eff8467119d20aa7bd710d78f89fd194edd4a9f)) ([84b2c39](https://github.com/continew-org/continew-admin/commit/84b2c39a303a3423f4114c7bb59fe1de62414cab)) ([e6edb57](https://github.com/continew-org/continew-admin/commit/e6edb57a8cf991be9408df2dfaf50271d3e0b638)) ([819be06](https://github.com/continew-org/continew-admin/commit/819be0688d3138fbbe02f02a09e481852625819d)) ([19bbbd9](https://github.com/continew-org/continew-admin/commit/19bbbd93ad9e06ad6037c1eef4d7171d4e107733)) ([382c87f](https://github.com/continew-org/continew-admin/commit/382c87f8bd5cf9549617ca78c170b2638a37c486)) ([b8c44c9](https://github.com/continew-org/continew-admin/commit/b8c44c9fe286cdb911806687387cf4c21dd4f55b)) ([ada6f3e](https://github.com/continew-org/continew-admin/commit/ada6f3ef5c8f17028a360765295fe8ab8a31406a)) ([f350ee1](https://github.com/continew-org/continew-admin/commit/f350ee1567ba192b1ea2912d7ae401322b51413e))
- 新增 database-id 配置项适配多数据库SQL语法差异提升MyBatis XML兼容性 (GitCode#5@onekr-billy) ([151a0fa](https://github.com/continew-org/continew-admin/commit/151a0faeb0578e98e5d72f67f83623cc846a3172)) ([7ff5166](https://github.com/continew-org/continew-admin/commit/7ff516694ee5b9ccc260ed5940538900149a7552))
- 重构 CRUD API 权限控制,新增 CrudApiPermissionPrefixCache 缓存权限前缀 ([83514b9](https://github.com/continew-org/continew-admin/commit/83514b92518ee412398490b4a71f9657ba79266b))
- SecureUtils 新增 decryptPasswordByRsaPrivateKey 方法统一处理密码解密 ([1ec154f](https://github.com/continew-org/continew-admin/commit/1ec154f0116020d9282bab922762e820e467c2e1))
- 新增查询角色权限树列表接口(替换角色分配权限的菜单树列表接口) ([950942a](https://github.com/continew-org/continew-admin/commit/950942a742f991e524218eef2c09da0c2486fd65))
### 💎 功能优化
- 优化模块命名 continew-webapi => continew-servercontinew-module-system => continew-system ([71fee0f](https://github.com/continew-org/continew-admin/commit/71fee0f58d01266469c1307284464fde737e5f58))
- 拆分接口文档分组配置及 controller 到各自模块 ([93bd70d](https://github.com/continew-org/continew-admin/commit/93bd70dc5ce0b64dc90b04bda9547f28114da8be))
- 使用 Snail Job Open API(SDK API) 替换修改状态接口 ([31cdf86](https://github.com/continew-org/continew-admin/commit/31cdf86eb643767339fbc8aae99104984038cb23))
- 优化角色和用户相关查询数据填充 ([b7a5a41](https://github.com/continew-org/continew-admin/commit/b7a5a41eac09c3a04969ab551aba10278ea2fe6b))
- 为枚举类型的字段添加自定义 Excel 转换器 ([930b1d4](https://github.com/continew-org/continew-admin/commit/930b1d461ba83caa7e9c0fc9ce796b0d4fcf9ca3))
- 为 Mapper 接口增加 Mapper 注解,以消除 IDEA 警告标志(减少小白咨询) ([efb65c2](https://github.com/continew-org/continew-admin/commit/efb65c21a15f8493318ef6a7bc37093b85e3bc39))
- 使用 SpEL Validator 优化部分校验场景 ([0d3c1bb](https://github.com/continew-org/continew-admin/commit/0d3c1bb2b125d9886fae069d29ac91c118ecd8ff))
- 优化行为验证码 CaptchaService 服务获取方式,关闭行为验证码不会导致启动报错 ([dcc28bc](https://github.com/continew-org/continew-admin/commit/dcc28bcf34c8d69f53b9924bf93563bcd172e270))
- CommonController 迁移至 system 模块、OnlineUserController 迁移至 system/auth 模块 ([08f45b5](https://github.com/continew-org/continew-admin/commit/08f45b5f4da37c1d17f98f704c684a2438239486))
- 使用 CollUtils 替代部分 Stream 操作,提高代码的可读性,减少代码行数(缺点:方法写起来不如流式代码舒爽) ([33d8943](https://github.com/continew-org/continew-admin/commit/33d89431cf18a01fc3d09ae0d2de8e1c3745e254))
- 重构系统模块的唯一性校验逻辑 ([c813f2e](https://github.com/continew-org/continew-admin/commit/c813f2ebbdac8edad2f101d29e2c873fc9225409))
- 添加 SQL 解析本地缓存 ([aadf879](https://github.com/continew-org/continew-admin/commit/aadf879be07d28f1afb584b1a337409f5e88fc62))
- 增加测试用户数据 ([5fded43](https://github.com/continew-org/continew-admin/commit/5fded43b7a4fee7c10732e268f4dfc9c66ddfb4c))
- 优化 Lombok 配置,全局禁用 Log4j、Log4j2 注解,请使用 Slf4j ([24f233e](https://github.com/continew-org/continew-admin/commit/24f233e2b5584eae8e7840a994c1241017a8c33d))
- CRUD Api 忽略排除(放行)路径的权限校验 ([3af43ef](https://github.com/continew-org/continew-admin/commit/3af43ef6c7fa3f4f2d4d390d47fae9c5f518e60d))
- 重构内部 API 依赖模式(降低耦合,公众号投票结论),在 common 模块新增 api 包,在对应 biz 模块增加实现 ([7f00599](https://github.com/continew-org/continew-admin/commit/7f0059984deae80a109a457f41ef8732d52228ff))
- 梳理用户和角色体系,内置角色:超级管理员、租户管理员(系统管理员),且内置用户和角色不允许变更及分配 ([93bf749](https://github.com/continew-org/continew-admin/commit/93bf749ce3f140e9bb6fc5a825210ad42971388c))
- 重构用户邮箱和手机号唯一性校验逻辑 (Gitee#71@lishuyanla) ([b42902e](https://github.com/continew-org/continew-admin/commit/b42902e1b974f3339a197a8ff4c2d23c08d884e4))
- 优化创建用户代码 ([54ba099](https://github.com/continew-org/continew-admin/commit/54ba0999e4fcc1a90c2ff366790fa93decb421a0))
- 暂时下线查询日志详情功能Snail Job 日志详情 API 变更为 WebSocket 模式) ([600f447](https://github.com/continew-org/continew-admin/commit/600f4477c8d3dafdaa6da41940a7b89dad9a9484))
### 🐛 问题修复
- 修复全部已读无效 ([271e2d8](https://github.com/continew-org/continew-admin/commit/271e2d8681e213dca0acb4573e11d9949101796c))
- 修复查询未读公告和消息数据错误 ([13c18f1](https://github.com/continew-org/continew-admin/commit/13c18f1861e14d32dd791bfbc7864f5722e340e4))
- 补充 captcha 和 dashboard 接口的文档分组路径 (Gitee#65@dom-w) ([ca320c7](https://github.com/continew-org/continew-admin/commit/ca320c7a172a0fc78202a68c7a49f060e96d23f4))
- 代码生成详情页模板增加对字典字段的翻译处理 ([e0a7cfd](https://github.com/continew-org/continew-admin/commit/e0a7cfd44814384c3802b48f0dd0fbacc8867fe6))
- 修复对象存储域名配置 ([fcc3cb9](https://github.com/continew-org/continew-admin/commit/fcc3cb909ab03c17705b0f2d0b8a25eeb44898c8))
- 修复查询角色关联用户时,角色信息映射错误问题 ([b514c9e](https://github.com/continew-org/continew-admin/commit/b514c9eeba2639b6108432dd49aaedf350b39f85))
- 修复 SaToken 全局异常处理中的错误信息 ([d4df425](https://github.com/continew-org/continew-admin/commit/d4df4254fca36264a882d1d4de715dc4e288ec8d))
- 修复了在过滤无效 token 时没有增加对StpUtil.getLoginIdByToken 返回 null情况处理导致 执行 groupingBy 报错 (GitCode#3@onekr-billy) ([53fc674](https://github.com/continew-org/continew-admin/commit/53fc674f4adb8cdcf768b83e70d09d91d447bfe6))
- 修复数据权限重写 deleteById 方法导致 Parameter 'id' 未映射异常 (GitCode#7@QAQ_Z) ([4c14feb](https://github.com/continew-org/continew-admin/commit/4c14feb15f9d52be14345f0b10db64bed3d4a111))
- 将"代码生成"菜单项重命名为"开发工具"显示一级菜单时出现的key重复问题 ([1076b4a](https://github.com/continew-org/continew-admin/commit/1076b4a19b7c86d7d51633895f2ebd45aafdb76e))
- 修复非管理员用户查询个人已读公告时出现重复数据的问题 ([72493f8](https://github.com/continew-org/continew-admin/commit/72493f8161582a0afe329f8d54de347aad76959b))
- SpelFuture => Future 以修复定时发布时间校验错误 ([317a937](https://github.com/continew-org/continew-admin/commit/317a9372dac89e512581061844097e104dc61e78))
- 修复菜单删除功能不支持级联删除子菜单的问题 ([15cd05b](https://github.com/continew-org/continew-admin/commit/15cd05bf77acb7c0110f6c071229195079d59e1c))
- 修复 PostgreSQL JDBC URL 配置及部分 SQL 语法错误 (GitHub#178@BruceMaa) ([d95bb15](https://github.com/continew-org/continew-admin/commit/d95bb15beba13476351820c8deba5a076c0ae5db))
### 📦 依赖升级
- continew-starter 2.12.2 => 2.13.0 ([2138bee](https://github.com/continew-org/continew-admin/commit/2138bee42c7105363f4413a847d4e6e4daca48d1))
- continew-starter 2.13.0 => 2.13.1 ([6136797](https://github.com/continew-org/continew-admin/commit/61367975887ffa7c673f5a656e69be151432f60d))
- continew-starter 2.13.1 => 2.13.2-SNAPSHOT ([2f445d9](https://github.com/continew-org/continew-admin/commit/2f445d9150ed98ed0dbc150a930ee8f4ddecefa8))
- continew-starter 2.13.2-SNAPSHOT => 2.13.2 ([bc44de4](https://github.com/continew-org/continew-admin/commit/bc44de4bdd96ee92d90b64974c62128c0a1edbd2))
- continew-starter 2.13.2 => 2.13.3 ([57b1868](https://github.com/continew-org/continew-admin/commit/57b186835d72f5410cf85a1324ad65e1ace3230c))
- continew-starter 2.13.3 => 2.13.4 ([e6169bd](https://github.com/continew-org/continew-admin/commit/e6169bdb6c2d6c41986e81e2df12ceaf472aaf7d))
- 升级环境版本 mysql 8.0.33 => 8.0.42redis 7.2.3 => 7.2.8nginx 1.25.3 => 1.27.0 ([f1a87b4](https://github.com/continew-org/continew-admin/commit/f1a87b4c236a635b5a3330537b88c4ff002d0924))
## [v3.7.0](https://github.com/continew-org/continew-admin/compare/v3.6.0...v3.7.0) (2025-06-13)
### ✨ 新特性
@@ -100,7 +40,7 @@
### 📦 依赖升级
- 🔥ContiNew Starter 2.11.0 => 2.12.2 (更多特性及依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/continew-org/continew-starter/blob/dev/CHANGELOG.md))
- 🔥ContiNew Starter 2.11.0 => 2.12.1 (更多特性及依赖升级详情,请查看 ContiNew Starter [更新日志](https://github.com/continew-org/continew-starter/blob/dev/CHANGELOG.md))
## [v3.6.0](https://github.com/continew-org/continew-admin/compare/v3.5.0...v3.6.0) (2025-04-13)

327
README.md
View File

@@ -1,13 +1,13 @@
# ContiNew Admin 多租户中后台管理框架
# ContiNew Admin 中后台管理框架
<a href="https://github.com/continew-org/continew-admin" title="Release" target="_blank">
<img src="https://img.shields.io/badge/SNAPSHOT-v4.1.0-%23ff3f59.svg" alt="Release" />
<img src="https://img.shields.io/badge/RELEASE-v3.7.0-%23ff3f59.svg" alt="Release" />
</a>
<a href="https://github.com/continew-org/continew-starter" title="ContiNew Starter" target="_blank">
<img src="https://img.shields.io/badge/ContiNew Starter-2.13.4-%236CB52D.svg" alt="ContiNew Starter" />
<img src="https://img.shields.io/badge/ContiNew Starter-2.12.2-%236CB52D.svg" alt="ContiNew Starter" />
</a>
<a href="https://spring.io/projects/spring-boot" title="Spring Boot" target="_blank">
<img src="https://img.shields.io/badge/Spring Boot-3.3.12-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
<img src="https://img.shields.io/badge/Spring Boot-3.3.11-%236CB52D.svg?logo=Spring-Boot" alt="Spring Boot" />
</a>
<a href="https://github.com/continew-org/continew-admin" title="Open JDK" target="_blank">
<img src="https://img.shields.io/badge/Open JDK-17-%236CB52D.svg?logo=OpenJDK&logoColor=FFF" alt="Open JDK" />
@@ -38,19 +38,13 @@
<img src="https://gitcode.com/continew/continew-admin/star/badge.svg" alt="GitCode Stars" />
</a>
📚 [在线文档](https://continew.top) | 🚀 [演示地址](https://continew.top/docs/admin/guide/demo.html)
📚 [在线文档](https://continew.top) | 🚀 [演示地址](https://continew.top/admin/guide/demo.html)
## 简介
**AI 编程纪元已经开启,基于 ContiNew 项目开发,让 AI 助手“学习”更优雅的代码规范,“写出”更优质的代码。**
ContiNew AdminContinue New Admin持续迭代优化的前后端分离中后台管理系统框架。开箱即用重视每一处代码规范重视每一种解决方案细节持续提供舒适的前、后端开发体验。
ContiNew AdminContinue New Admin页面现代美观且专注设计与代码细节的 **高质量多租户中后台** 管理系统框架。开箱即用,持续迭代优化,持续提供舒适的开发体验
当前采用的技术栈Spring Boot3Java17、Vue3 & Arco Design & TS & Vite、Sa-Token、MyBatis Plus、Redisson、FastExcel、CosId、JetCache、JustAuth、Crane4j、Spring Doc、Hutool 等。
我们始终坚信好的产品必然是反复打磨出来的,而在工作中我们受限于客户需求、开发周期等因素,无法深度打磨、重构我们的代码,这也是架构腐烂的根源。所以,我们希望能在业余时间,通过开源社区的力量来打磨出一个好的产品,一个好的实践,一个好的生态。
我们的愿景在于,当你将 ContiNew 系列项目应用到工作场景时,不仅仅是得到效率的提高,更可以得到舒适的开发体验,让更多开发者的编程工作多一点“甜”。
当前采用的技术栈Spring Boot3Java17、Vue3 & Arco Design & TS & Vite、Sa-Token、MyBatis Plus、Redisson、JetCache、JustAuth、Crane4j、EasyExcel、Liquibase、Hutool 等
## 项目源码
@@ -80,62 +74,49 @@ ContiNew AdminContinue New Admin页面现代美观且专注设计与
## 为什么选我们?
> [!TIP]
> 更为完整的图文描述请查阅[《在线文档》](https://continew.top/docs/admin/guide/why-choose-us.html)。
> 更为完整的图文描述请查阅[《在线文档》](https://continew.top/admin/guide/why-choose-us.html)。
**AI 编程纪元已经开启,基于 ContiNew 项目开发,让 AI 助手“学习”更优雅的代码规范,“写出”更优质的代码。**
1.**甄选技术栈:** ContiNewContinue New 项目致力于持续迭代优化,让技术不掉队。在技术选型时,进行深度广泛地调研,从流行度、成熟度和发展潜力等多方面甄选技术栈。
**1.长期稳定:** 自 2022 年 12 月 8 日创建2023 年 3 月 26 日发布 v1.0.0截至今日ContiNew Admin 已累计发布 25 个版本ContiNew Starter 已累计发布 43 个版本。
2.**Starter 组件:** 从 v2.1.0 版本开始,抽取并封装后端基础组件及各框架集成配置到 ContiNew Starter 项目,且 **[已发布至 Maven 中央仓库](https://central.sonatype.com/search?q=continew-starter&namespace=top.continew)**,可在你的任意项目中直接引入所需依赖使用。即使你不用脚手架项目,难道能让你搭项目框架更快、更爽、更省力的 Starter 也要 Say No 吗?
**2.甄选技术栈:** ContiNewContinue New 项目致力于持续迭代优化,确保技术栈紧跟时代。在技术选型时,我们进行了深度广泛的调研,从流行度、成熟度和发展潜力等多维度精心挑选技术栈
**3.Starter 组件:** 从 v2.1.0 版本开始,我们将后端基础组件及各框架集成配置抽取并封装到 ContiNew Starter 项目中,极大降低上手和升级难度。且 **[已发布至 Maven 中央仓库](https://central.sonatype.com/search?q=continew-starter&namespace=top.continew)**,你可以在任意项目中直接引入所需依赖使用。即使你不使用完整的中后台框架,这些能让你搭项目框架更快、更爽、更省力的 Starter 组件,难道不香吗?
**4.CRUD 套件:** 封装通用增删改查套件,适配后端各分层架构,几分钟即可提供一套完整的 CRUD API包括新增、修改、批量删除、查询详情、分页列表、全部列表、树型列表、Excel 导出,甚至是字典列表(用于下拉选项场景)。所有 API 均可根据实际需求灵活开放或扩展。
3.**CRUD 套件:** 封装通用增删改查套件,适配后端各分层,几分钟即可提供一套 CRUD API包括新增、修改、批量删除、查询详情、分页列表查询、全部列表查询、树型列表查询、导出到 Excel且 API 支持按实际所需开放或扩展
```java
@Tag(name = "部门管理 API")
@RestController
@CrudRequestMapping(value = "/system/dept", api = {Api.TREE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT, Api.DICT_TREE})
@CrudRequestMapping(value = "/system/dept", api = {Api.TREE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT})
public class DeptController extends BaseController<DeptService, DeptResp, DeptDetailResp, DeptQuery, DeptReq> {}
```
**5.代码生成器:** 同步提供代码生成器,配套前后端代码生成模板数据表设计完后,简单配置即可生成前后端 80% 的代码,包 CRUD API、权限控制、参数校验、接口文档等内容。业务不复杂,甚至能覆盖 95% 的代码
4.**代码生成器:** 提供代码生成器,配套前后端代码生成模板数据表设计完后,简单配置一下即可生成前后端 80% 的代码,包 CRUD API、权限控制、参数校验、接口文档等内容。如果业务不复杂,也可能就是 95% 的代码。
**6.提升开发体验:** 持续优化适配各类能提升开发体验的组件。
5.**改善开发体验:** 持续优化适配能改善开发体验的组件。
- ContiNew Starter 组件集合:针对 Spring 基础配置、通用解决方案及流行框架进行深度封装,改善你开发每个 Spring Boot Web 项目的体验(包含时间日期及枚举参数自动转换、默认线程池、跨域、加密、脱敏、限流、幂等、License、日志、异常及响应通用解决方案等)
- Crane4j 数据填充组件减少因单个字段(如用户名而产生的联表查询
- SpEL Validator基于 SpEL 表达式的参数校验,强化复杂场景下的参数验证(如:当某字段为特定值时,另一字段不能为空)
- P6Spy SQL 性能分析:开发期间可方便地监控 SQL 执行性能
- TLog 链路追踪:在繁杂的日志中快速定位某次请求的完整日志
- JetCache 缓存框架:通过注解即可实现方法级缓存,支持灵活的二级缓存配置和分布式自动刷新;
- 适配 ContiNew Starter 组件针对 Spring 基础配置、通用解决方案及流行框架进行深度封装的 starter 集合,改善你开发每个 Spring Boot Web 项目的体验。(枚举参数处理、默认线程池、跨域、加密、脱敏、限流、日志、异常及响应通用解决方案等等,更多细节可查看 Starter 源码
- 适配 Crane4j 数据填充组件减少因为一个用户名而产生的联表回填
- 适配 P6Spy SQL 性能分析组件,开发期间方便监控 SQL 执行
- 适配 TLog 链路追踪组件,方便在杂乱的日志文件中追踪你某次请求的日志记录
- 适配 JetCache 缓存框架(比 Spring Cache 更强大易用),通过注解声明即可快速实现方法级缓存,极大改善编码式缓存体验,且支持灵活的二级缓存配置、分布式自动刷新等能力
- 前端适配 Vue DevtoolsVue 官方提供的调试浏览器插件),极大提高 Vue 开发及调试效率
**7.Almost 最佳后端规范:** 后端严格遵循阿里巴巴 Java 编码规范,注释覆盖率 > 45%,接口参数示例 100%代码分层清晰,变量方法命名统一规范,前端代码同样采用严格的 ESLint、StyleLint 等检查。优秀的设计带来极高的代码复用率!开发时,你会有一种无需多写,理应如此”的流畅感
6.**Almost最佳后端规范** 后端严格遵循阿里巴巴 Java 编码规范,注释覆盖率 > 45%,接口参数示例 100%代码分层使用体验佳,变量方法命名清晰统一,前端代码也使用严格的 ESLint、StyleLint 等检查。良好的设计,代码复用率极高!写代码时,让你有一种无需多写,理应如此的感觉。我是代码洁癖,我实际写的时候很清楚这到底是不是乱吹
**8.卓越工程化实践** 后端采用模块化工程结构,集成了统一版本管理、编译自动代码格式化等插件提供自定义打包部署配置(配置文件、三方依赖主程序分离),以及全套环境应用的 Docker Compose 部署脚本。
7.**卓越工程:** 后端采用模块化工程结构,并适配了统一项目版本号、编译项目自动代码格式化、代码混淆等插件提供自定义打包部署结构配置(配置文件、三方依赖主程序分离),提供全套环境应用的 Docker Compose 部署脚本。为了减少您开发新项目时的改造耗时,项目品牌配置持续进行深度聚合,简单的配置和结构修改即可快速开始独属于你的新项目。
为减少新项目开发的改造成本,我们持续深度聚合项目品牌配置,通过简单的配置和结构修改,即可快速启动你的专属项目
8.**业务脚手架:** 有颜有料,不止是说说而已,持续打磨 UI 设计与色彩主题。提供基于 RBAC 的权限控制、通用数据权限,包含丰富的通用业务功能:第三方登录,邮箱、短信(生产级炸弹漏洞处理方案),个人中心、用户管理、角色管理、部门管理、系统配置(基础站点配置、邮件配置、安全配置)、系统日志、消息中心、通知公告等,设计用心,逻辑合理闭环
我们还进行了全局 Lombok 配置,继承场景默认自动应用 `@EqualsAndHashCode(callSuper = true)``@ToString(callSuper = true)`,无需手动添加。同时主动禁用了部分 Lombok 注解(如 `@Val``@Log4j` 等),避免“又菜又爱玩”的 partner 滥用
> 一个好的脚手架项目,不仅仅是提供一系列组件集成与配置,也不仅仅是封装一堆好用的工具,还更应该提供一系列通用基础业务解决方案及设计,为初创团队项目减负
**9.全能业务脚手架:** 支持 **SaaS 租户架构**,基于 RBAC 的权限控制与通用数据权限管理。精心设计的 UI 界面与色彩主题,兼具美观与实用性。内置丰富的通用业务解决方案:第三方登录、邮箱/短信服务(含生产级漏洞处理方案)、个人中心、用户管理、角色管理、组织管理、系统配置、系统日志、消息中心、通知公告等,逻辑闭环,开箱即用
9.**质量与安全:** CI 已集成 Sonar、CodacyPush 即扫描代码质量,定期扫描 CVE 漏洞及时解决潜在问题。封装数据库字段加密、JSON 脱敏、XSS 过滤等工具,提供诸多安全解决方案
> 优秀的中后台框架不仅提供组件集成与配置,封装好用的工具,更应提供通用基础业务设计及解决方案,为初创团队减负
**10.质量与安全并重:** 我们高度重视项目质量与安全CI 已集成 Sonar、Codacy代码提交即自动扫描质量问题。定期扫描 CVE 漏洞及时解决潜在风险。封装了数据库字段加密、JSON 脱敏、XSS 过滤等工具,提供全方位的安全解决方案。
许多项目在开发或交付过程中需满足 Sonarqube 等质量指标,使用 ContiNew Admin 框架,让你从一开始就站在高质量的起点。
---
由于篇幅有限,且项目正处于高速发展期,更多功能正在持续开发中,敬请关注仓库或加入交流群了解最新动态。至于统一异常处理、错误处理、基础线程池配置(默认线程参数、线程上下文支持异步传递)等基础特性,这里不再赘述,更多细节优化欢迎克隆代码体验。
由于篇幅有限,且项目正处于高速发展期,更多功能正在陆续上线(敬请关注仓库或群内动态)。另外像最基本的统一异常、错误处理,基础线程池等配置就不在此赘述,细节优化详情请 clone 代码查看
> Talk is cheap, show the code.
## 系统功能
> [!TIP]
> 更多功能和优化正在赶来💦,最新项目计划、进展请进群或查看 [吐槽广场](https://continew.top/docs/admin/issue-hub.html) 和 [更新日志](https://continew.top/docs/admin/changelog/)。
> 功能不会用?请查看 [功能手册](https://continew.top/docs/admin/function/tenant/management.html)。
> 更多功能和优化正在赶来💦,最新项目计划、进展请进群或关注 [需求墙](https://continew.top/admin/other/feature.html) 和 [更新日志](https://continew.top/admin/other/changelog.html)。
- 仪表盘:提供工作台、分析页,工作台提供功能快捷导航入口、最新公告、动态;分析页提供全面数据可视化能力
- 个人中心:支持基础信息修改、密码修改、邮箱绑定、手机号绑定(并提供行为验证码、短信限流等安全处理)、第三方账号绑定/解绑、头像裁剪上传
@@ -157,12 +138,10 @@ public class DeptController extends BaseController<DeptService, DeptResp, DeptDe
- 客户端配置多端PC端、小程序端等认证管理可设置不同的 token 有效期
- 在线用户:管理当前登录用户,可一键踢除下线
- 日志管理:管理系统登录日志、操作日志,支持查看日志详情,包含请求头、响应头等报文信息
- 短信日志:管理系统短信发送日志,支持删除、导出
- 应用管理:管理第三方系统应用 AK、SK包含新增、修改、删除、查看密钥、重置密钥等功能支持设置密钥有效期
- 租户管理:管理租户信息,包含新增、修改、删除、分配角色等功能
- 租户套餐:管理租户套餐信息,包含新增、修改、删除、查看等功能
- 短信日志:管理系统短信发送日志,支持删除、导出
- 任务管理:管理系统定时任务,包含新增、修改、删除、执行功能,支持 Cron可配置式生成 Cron 表达式) 和固定频率
- 任务日志:管理定时任务执行日志,包含停止、重试指定批次等功能
- 任务日志:管理定时任务执行日志,包含停止、重试指定批次,查询集群各节点的详细输出日志等功能
- 应用管理:管理第三方系统应用 AK、SK包含新增、修改、删除、查看密钥、重置密钥等功能支持设置密钥有效期
- 代码生成:提供根据数据库表自动生成相应的前后端 CRUD 代码的功能,支持同步最新表结构及代码生成预览
## 系统截图
@@ -240,40 +219,39 @@ public class DeptController extends BaseController<DeptService, DeptResp, DeptDe
| <a href="https://arco.design/vue/docs/start" target="_blank">Arco Design</a> | 2.57.0 | 字节跳动推出的前端 UI 框架,年轻化的色彩和组件设计。 |
| <a href="https://www.typescriptlang.org/zh/" target="_blank">TypeScript</a> | 5.0.4 | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 |
| <a href="https://vite.dev/" target="_blank">Vite</a> | 5.1.5 | 下一代的前端工具链,为开发提供极速响应。 |
| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.13.4 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken可轻松集成到应用中为开发人员减少手动引入依赖及配置的麻烦为 Spring Boot Web 项目的灵活快速构建提供支持。 |
| <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a> | 3.3.12 | 简化 Spring 应用的初始搭建和开发过程基于“约定优于配置”的理念使开发人员不再需要定义样板化的配置。Spring Boot 3.0 开始,要求 Java 17 作为最低版本) |
| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.12.2 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken可轻松集成到应用中为开发人员减少手动引入依赖及配置的麻烦为 Spring Boot Web 项目的灵活快速构建提供支持。 |
| <a href="https://spring.io/projects/spring-boot" target="_blank">Spring Boot</a> | 3.3.11 | 简化 Spring 应用的初始搭建和开发过程基于“约定优于配置”的理念使开发人员不再需要定义样板化的配置。Spring Boot 3.0 开始,要求 Java 17 作为最低版本) |
| <a href="https://undertow.io/" target="_blank">Undertow</a> | 2.3.18.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 |
| <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token + JWT</a> | 1.44.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
| <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a> | 3.5.12 | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 |
| <a href="https://sa-token.dev33.cn/" target="_blank">Sa-Token + JWT</a> | 1.42.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
| <a href="https://baomidou.com/" target="_blank">MyBatis Plus</a> | 3.5.8 | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 |
| <a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611" target="_blank">dynamic-datasource-spring-boot-starter</a> | 4.3.1 | 基于 Spring Boot 的快速集成多数据源的启动器。 |
| Hikari | 5.1.0 | JDBC 连接池,号称 “史上最快连接池”SpringBoot 在 2.0 之后,采用的默认数据库连接池就是 Hikari。 |
| <a href="https://dev.mysql.com/downloads/mysql/" target="_blank">MySQL</a> | 8.0.42 | 体积小、速度快、总体拥有成本低,是最流行的关系型数据库管理系统之一。 |
| <a href="https://dev.mysql.com/downloads/mysql/" target="_blank">MySQL</a> | 8.0.33 | 体积小、速度快、总体拥有成本低,是最流行的关系型数据库管理系统之一。 |
| <a href="https://dev.mysql.com/doc/connector-j/8.0/en/" target="_blank">mysql-connector-j</a> | 8.3.0 | MySQL Java 驱动。 |
| <a href="https://github.com/p6spy/p6spy" target="_blank">P6Spy</a> | 3.9.1 | SQL 性能分析组件。 |
| <a href="https://github.com/liquibase/liquibase" target="_blank">Liquibase</a> | 4.27.0 | 用于管理数据库版本,跟踪、管理和应用数据库变化。 |
| [JetCache](https://github.com/alibaba/jetcache/blob/master/docs/CN/Readme.md) | 2.7.8 | 一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新还提供了 Cache 接口用于手工缓存操作。 |
| <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a> | 3.49.0 | 不仅仅是一个 Redis Java 客户端Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等。 |
| <a href="https://redis.io/" target="_blank">Redis</a> | 7.2.8 | 高性能的 key-value 数据库。 |
| [Snail Job](https://snailjob.opensnail.com/) | 1.5.0 | 灵活,可靠和快速的分布式任务重试和分布式任务调度平台。 |
| <a href="https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D" target="_blank">Redisson</a> | 3.46.0 | 不仅仅是一个 Redis Java 客户端Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,为使用者提供了一系列具有分布式特性的常用工具:分布式锁、限流器等。 |
| <a href="https://redis.io/" target="_blank">Redis</a> | 7.2.3 | 高性能的 key-value 数据库。 |
| [Snail Job](https://snailjob.opensnail.com/) | 1.4.0 | 灵活,可靠和快速的分布式任务重试和分布式任务调度平台。 |
| [X File Storage](https://x-file-storage.xuyanwu.cn/#/) | 2.2.1 | 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS...等其它兼容 S3 协议的存储平台。 |
| <a href="https://sms4j.com/" target="_blank">SMS4J</a> | 3.3.4 | 短信聚合框架,轻松集成多家短信服务,解决接入多个短信 SDK 的繁琐流程。 |
| <a href="https://justauth.cn/" target="_blank">Just Auth</a> | 1.16.7 | 开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK让登录变得 So easy |
| <a href="https://github.com/fast-excel/fastexcel" target="_blank">Fast Excel</a> | 1.2.0 | (由原 EasyExcel 作者创建的新项目)一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。 |
| <a href="https://easyexcel.opensource.alibaba.com/" target="_blank">Easy Excel</a> | 3.3.4 | 一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。 |
| [AJ-Captcha](https://ajcaptcha.beliefteam.cn/captcha-doc/) | 1.3.0 | Java 行为验证码包含滑动拼图、文字点选两种方式UI支持弹出和嵌入两种方式。 |
| Easy Captcha | 1.6.2 | Java 图形验证码,支持 gif、中文、算术等类型可用于 Java Web、JavaSE 等项目。 |
| [Crane4j](https://createsequence.gitee.io/crane4j-doc/#/) | 2.9.0 | 一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B再把 B 的属性映射到 A” 这类需求的字段填充框架。 |
| [SpEL Validator](https://spel-validator.sticki.cn/) | 0.5.2-beta | 基于 SpEL 的 jakarta.validation-api 扩展增强包。 |
| [CosID](https://cosid.ahoo.me/guide/getting-started.html) | 2.13.0 | 旨在提供通用、灵活、高性能的分布式 ID 生成器。 |
| [CosID](https://cosid.ahoo.me/guide/getting-started.html) | 2.12.3 | 旨在提供通用、灵活、高性能的分布式 ID 生成器。 |
| [Graceful Response](https://doc.feiniaojin.com/graceful-response/home.html) | 5.0.4-boot3 | 一个Spring Boot技术栈下的优雅响应处理组件可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程提高开发效率提高代码质量。 |
| <a href="https://doc.xiaominfo.com/" target="_blank">Knife4j</a> | 4.5.0 | 前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案。 |
| [OpenFeign](https://springdoc.cn/spring-cloud-openfeign/) | 13.5 | Spring Cloud OpenFeign 是一种基于 Spring Cloud 的声明式 REST 客户端,它简化了与 HTTP 服务交互的过程。 |
| <a href="https://www.hutool.cn/" target="_blank">Hutool</a> | 5.8.38 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 |
| <a href="https://www.hutool.cn/" target="_blank">Hutool</a> | 5.8.37 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 |
| <a href="https://projectlombok.org/" target="_blank">Lombok</a> | 1.18.36 | 在 Java 开发过程中用注解的方式,简化了 JavaBean 的编写,避免了冗余和样板式代码,让编写的类更加简洁。 |
## 快速开始
> [!TIP]
> 更详细的流程,请查看在线文档[《快速开始》](https://continew.top/docs/admin/guide/quick-start.html)。
> 更详细的流程,请查看在线文档[《快速开始》](https://continew.top/admin/guide/quick-start.html)。
```bash
# 1.克隆本项目
@@ -285,26 +263,37 @@ git clone https://github.com/continew-org/continew-admin.git
# [3.也可以在 IntelliJ IDEA 中直接配置程序启动环境变量DB_HOST、DB_PORT、DB_USER、DB_PWD、DB_NAMEREDIS_HOST、REDIS_PORT、REDIS_PWD、REDIS_DB]
# 4.启动程序
# 启动成功,在控制台末尾会输出 ContiNew Admin service started successfully.
# 并输出 API 地址及 API 接口文档地址
# 4.1 启动成功:访问 http://localhost:8000/页面输出Xxx started successfully.
# 4.2 接口文档http://localhost:8000/doc.html
# 5.部署
# 5.1 Docker 部署
# 5.1.1 服务器安装好 docker 及 docker-compose参考https://blog.charles7c.top/categories/fragments/2022/10/31/CentOS%E5%AE%89%E8%A3%85Docker
# 5.1.2 执行 mvn package 进行项目打包,将 target/app 目录下的所有内容放到 /docker/continew-admin 目录下
# 5.1.3 将 docker 目录上传到服务器 / 目录下并授权chmod -R 777 /docker
# 5.1.4 修改 docker-compose.yml 中的 MySQL 配置、Redis 配置、continew-admin-server 配置、Nginx 配置
# 5.1.5 执行 docker-compose up -d 创建并后台运行所有容器
# 5.2 其他方式部署
```
## 项目结构
> [!TIP]
> 后端采用按功能拆分模块的开发方式,下方项目目录结构是按照模块的层次顺序进行介绍的,实际 IDE 中 `continew-common` 模块会因为字母排序原因排在上方。
> 后端采用按功能拆分模块的开发方式,下方项目目录结构是按照模块的层次顺序进行介绍的,实际 IDE 中 `continew-admin-common` 模块会因为字母排序原因排在上方。
```
continew-admin
├─ continew-server打包部署模块)
├─ continew-webapiAPI 及打包部署模块)
│ ├─ src
│ │ ├─ main
│ │ │ ├─ java/top/continew/admin
│ │ │ │ ├─ config (配置)
│ │ │ │ │ ├─ log操作日志配置
│ │ │ │ │ satokenSaToken 认证配置
│ │ │ │ ├─ controller(通用 API
│ │ │ │ ├─ job (定时任务
│ │ │ │ ├─ controller
│ │ │ │ │ auth系统认证相关 API
│ │ │ │ ├─ common(通用相关 API
│ │ │ │ │ ├─ monitor系统监控相关 API
│ │ │ │ │ ├─ system系统管理相关 API
│ │ │ │ │ └─ tool系统工具相关 API
│ │ │ │ └─ ContiNewAdminApplication.javaContiNew Admin 启动程序)
│ │ │ └─ resources
│ │ │ ├─ config核心配置目录
@@ -320,140 +309,89 @@ continew-admin
│ │ │ └─ logback-spring.xml日志配置文件
│ │ └─ test测试相关代码目录
│ └─ pom.xml包含打包相关配置
├─ continew-system系统管理模块存放系统管理相关业务功能例如部门管理、角色管理、用户管理等
├─ continew-module-system系统管理模块存放系统管理相关业务功能例如部门管理、角色管理、用户管理等
│ ├─ src
│ │ ├─ main
│ │ │ ├─ java/top/continew/admin
│ │ │ │ ├─ auth系统认证相关业务
│ │ │ │ │ ├─ controller系统认证相关 API
│ │ │ │ │ ├─ service系统认证相关业务接口及实现类
│ │ │ │ │ ├─ model系统认证相关模型
│ │ │ │ │ │ ├─ query系统认证相关查询条件
│ │ │ │ │ │ ├─ req系统认证相关请求参数Request
│ │ │ │ │ │ └─ resp系统认证相关响应参数Response
│ │ │ │ │ enums系统认证相关枚举
│ │ │ │ │ ├─ constant系统认证相关常量
│ │ │ │ │ ├─ handler系统认证相关处理器
│ │ │ │ │ └─ config系统认证相关配置
│ │ │ │ │ │ ├─ req系统认证相关请求对象Request
│ │ │ │ │ │ └─ resp系统认证相关响应对象Response
│ │ │ │ │ service系统认证相关业务接口及实现类
│ │ │ │ └─ system系统管理相关业务
│ │ │ │ ├─ api(系统管理相关公共业务 API 实现
│ │ │ │ ├─ controller(系统管理相关 API
│ │ │ │ ├─ service系统管理相关业务接口及实现类
│ │ │ │ ├─ config(系统管理相关配置
│ │ │ │ ├─ enums(系统管理相关枚举
│ │ │ │ ├─ mapper系统管理相关 Mapper
│ │ │ │ ├─ model系统管理相关模型
│ │ │ │ │ ├─ entity系统管理相关实体
│ │ │ │ │ ├─ entity系统管理相关实体对象
│ │ │ │ │ ├─ query系统管理相关查询条件
│ │ │ │ │ ├─ req系统管理相关请求参数Request
│ │ │ │ │ └─ resp系统管理相关响应参数Response
│ │ │ │ ├─ enums系统管理相关枚举
│ │ │ │ constant(系统管理相关常量
│ │ │ │ ├─ util系统管理相关工具类
│ │ │ │ ├─ validation系统管理相关参数校验工具类
│ │ │ │ ├─ container系统管理相关 Crane4j 数据填充容器配置)
│ │ │ │ └─ config系统管理相关配置
│ │ │ │ │ ├─ req系统管理相关请求对象Request
│ │ │ │ │ └─ resp系统管理相关响应对象Response
│ │ │ │ ├─ service系统管理相关业务接口及实现类
│ │ │ │ util(系统管理相关工具类
│ │ │ └─ resources
│ │ │ └─ mapper系统管理相关 Mapper XML 文件目录)
│ │ └─ test测试相关代码目录
│ └─ pom.xml
├─ continew-plugin插件模块存放能力开放、租户等扩展模块,后续会进行插件化改造)
│ ├─ continew-plugin-open能力开放插件模块
│ │ ├─ src
│ │ │ ├─ main/java/top/continew/admin/open
│ │ │ │ ├─ controller能力开放相关 API
│ │ │ │ ├─ service能力开放相关业务接口及实现类
│ │ │ │ ├─ mapper能力开放相关 Mapper
│ │ │ │ ├─ model能力开放相关模型
│ │ │ │ │ ├─ entity能力开放相关实体
│ │ │ │ │ ├─ query能力开放相关查询条件
│ │ │ │ │ ├─ req能力开放相关请求参数Request
│ │ │ │ │ └─ resp能力开放相关响应参数Response
│ │ │ │ ├─ util能力开放相关工具类
│ │ │ │ ├─ handler能力开放相关处理器
│ │ │ │ ├─ sign能力开放相关 API 参数签名算法)
│ │ │ │ └─ config能力开放相关配置
│ │ │ └─ test测试相关代码目录
│ │ └─ pom.xml
│ ├─ continew-plugin-tenant租户插件模块
│ │ ├─ src
│ │ │ ├─ main/java/top/continew/admin/tenant
│ │ │ │ ├─ api租户相关公共业务 API 实现)
│ │ │ │ ├─ controller租户相关 API
│ │ │ │ ├─ service租户相关业务接口及实现类
│ │ │ │ ├─ mapper租户相关 Mapper
│ │ │ │ ├─ model租户相关模型
│ │ │ │ │ ├─ entity租户相关实体
│ │ │ │ │ ├─ query租户相关查询条件
│ │ │ │ │ ├─ req租户相关请求参数Request
│ │ │ │ │ └─ resp租户相关响应参数Response
│ │ │ │ ├─ enums租户相关枚举
│ │ │ │ ├─ constant租户相关常量类
│ │ │ │ ├─ util租户相关工具类
│ │ │ │ └─ config租户相关配置
│ │ │ └─ test测试相关代码目录
│ │ └─ pom.xml
├─ continew-plugin插件模块存放代码生成、任务调度等扩展模块,后续会进行插件化改造)
│ ├─ continew-plugin-schedule任务调度插件模块
│ │ ├─ src
│ │ │ ├─ main/java/top/continew/admin/schedule
│ │ │ │ ├─ controller(任务调度相关 API
│ │ │ │ ├─ service代码生成器相关业务接口及实现类
│ │ │ │ ├─ api任务调度中心相关 Feign API
│ │ │ │ ├─ api(任务调度中心相关 API
│ │ │ │ ├─ config任务调度相关配置
│ │ │ │ ├─ constant任务调度相关常量
│ │ │ │ ├─ enums任务调度相关枚举
│ │ │ │ ├─ model任务调度相关模型
│ │ │ │ │ ├─ query任务调度相关查询条件
│ │ │ │ │ ├─ req任务调度相关请求参数Request
│ │ │ │ │ └─ resp任务调度相关响应参数Response
│ │ │ │ enums任务调度相关枚举
│ │ │ │ ├─ constant任务调度相关常量类
│ │ │ │ ├─ exception任务调度相关异常
│ │ │ ├─ annotation任务调度相关注解
│ │ │ │ └─ config任务调度相关配置
│ │ │ │ │ ├─ req任务调度相关请求对象Request
│ │ │ │ │ └─ resp任务调度相关响应对象Response
│ │ │ │ service代码生成器相关业务接口及实现类
│ │ │ └─ test测试相关代码目录
│ │ └─ pom.xml
├─ continew-plugin-open能力开放插件模块
│ │ ├─ src
│ │ │ ├─ main/java/top/continew/admin/open
│ │ │ │ ├─ mapper代码生成器相关 Mapper
│ │ │ │ ├─ model能力开放相关模型
│ │ │ │ │ ├─ entity能力开放相关实体对象
│ │ │ │ │ ├─ query能力开放相关查询条件
│ │ │ │ │ ├─ req能力开放相关请求对象Request
│ │ │ │ │ └─ resp能力开放相关响应对象Response
│ │ │ │ └─ service能力开放相关业务接口及实现类
│ │ │ └─ test测试相关代码目录
│ │ └─ pom.xml
│ ├─ continew-plugin-generator代码生成器插件模块
│ │ ├─ src
│ │ │ ├─ main
│ │ │ │ ├─ java/top/continew/admin/generator
│ │ │ │ │ ├─ controller(代码生成器相关 API
│ │ │ │ │ ├─ service(代码生成器相关业务接口及实现类
│ │ │ │ │ ├─ config(代码生成器相关配置
│ │ │ │ │ ├─ enums(代码生成器相关枚举
│ │ │ │ │ ├─ mapper代码生成器相关 Mapper
│ │ │ │ │ ├─ model代码生成器相关模型
│ │ │ │ │ │ ├─ entity代码生成器相关实体
│ │ │ │ │ │ ├─ entity代码生成器相关实体对象
│ │ │ │ │ │ ├─ query代码生成器相关查询条件
│ │ │ │ │ │ ├─ req代码生成器相关请求参数Request
│ │ │ │ │ │ └─ resp代码生成器相关响应参数Response
│ │ │ │ │ enums(代码生成器相关枚举
│ │ │ │ │ └─ config代码生成器相关配置
│ │ │ │ │ │ ├─ req代码生成器相关请求对象Request
│ │ │ │ │ │ └─ resp代码生成器相关响应对象Response
│ │ │ │ │ service(代码生成器相关业务接口及实现类
│ │ │ │ └─ resources
│ │ │ │ ─ templates代码生成相关模板目录
│ │ │ │ ├─ backend后端模板目录
│ │ │ │ └─ frontend前端模板目录
│ │ │ │ ─ templates/generator(代码生成相关模板目录)
│ │ │ │ ├─ application.yml代码生成配置文件
│ │ │ │ └─ generator.properties代码生成类型映射配置文件
│ │ │ └─ test测试相关代码目录
│ │ └─ pom.xml
│ └─ pom.xml
├─ continew-common公共模块存放公共工具类公共配置等
│ ├─ src
│ │ ├─ main/java/top/continew/admin/common
│ │ │ ├─ api公共业务 API
│ │ │ ├─ base(公共基类
│ │ │ │ ├─ controller控制器基类
│ │ │ │ ├─ mapperMapper 接口基类)
│ │ │ │ ├─ model公共模型
│ │ │ │ │ ├─ entity实体基类
│ │ │ │ │ └─ resp列表、详情响应基类
│ │ │ │ └─ service业务接口及实现基类
│ │ │ ├─ model公共模型
│ │ │ │ ├─ dto公共数据传输对象DTO
│ │ │ │ └─ req公共请求参数Request
│ │ │ ├─ context公共上下文
│ │ │ ├─ config公共配置
│ │ │ ├─ constant(公共常量
│ │ │ ├─ enums公共枚举
│ │ │ ├─ constant公共常量类
│ │ │ ├─ util公共工具类
│ │ │ └─ config公共配置
│ │ │ crudCRUD 配置
│ │ │ ├─ mybatisMyBatis Plus 配置
│ │ │ ├─ websocketWebsocket 配置)
│ │ │ ├─ doc接口文档配置
│ │ │ ├─ excelExcel 配置)
│ │ │ └─ exception全局异常处理
│ │ │ ├─ model公共模型
│ │ │ │ ├─ dto公共 DTOData Transfer Object
│ │ │ │ ├─ req公共请求对象Request
│ │ │ resp公共响应对象Response
│ │ │ └─ util公共工具类
│ │ └─ test测试相关代码目录
│ └─ pom.xml
├─ continew-extension扩展模块
@@ -479,7 +417,7 @@ continew-admin
├─ .idea
│ └─ icon.pngIDEA 项目图标,实际开发时直接删除)
├─ .image截图目录实际开发时直接删除
├─ .style代码格式、License文件头相关配置目录实际开发时根据需要取舍删除时注意删除 /pom.xml 中的 spotless 插件配置)
├─ .style代码格式、License文件头相关配置目录实际开发时根据需要取舍删除时注意删除 spotless 插件配置)
├─ .gitignoreGit 忽略文件相关配置文件)
├─ docker项目部署相关配置目录实际开发时可备份后直接删除
├─ LICENSE开源协议文件
@@ -489,49 +427,44 @@ continew-admin
└─ pom.xml包含版本锁定及全局插件相关配置
```
## 参与贡献
## 贡献指南
ContiNewContinue New系列项目致力于通过持续迭代为开发者提供舒适的开发体验。作为开源社区我们的初是希望通过开源协作模式,提升技术透明度、放大集体智慧、共创优秀实践,源源不断地为企业级项目开发提供助力。
ContiNew Admin 致力于提供开箱即用持续舒适的开发体验。作为一个开源项目Creator 的初是希望 ContiNew Admin 依托开源协作模式,提升技术透明度、放大集体智慧、共创优秀实践,源源不断地为企业级项目开发提供助力。
我们诚挚邀请广大社区用户为 ContiNew 项目贡献力量,包括但不限于 Issue 排查、测试验证、代码开发与重构等。每一份贡献,都是推动项目进步的重要力量(请查阅 [贡献指南](https://continew.top/about/contributing.html))。欢迎各位感兴趣的小伙伴儿,[添加微信](https://continew.top/discussion.html) 讨论或认领任务。
我们非常欢迎广大社区用户为 ContiNew Admin **贡献(开发,测试、文档、答疑等)** 或优化代码,欢迎各位感兴趣的小伙伴儿,[添加微信](https://continew.top/discussion.html) 讨论或认领任务。
### 分支说明
ContiNew 系列项目采用清晰的分支策略,确保开发与维护有序进行。提交 PR 前,请确认目标分支是否处于活跃维护状态,版本支持情况请查看 [更新日志#版本支持](https://continew.top/docs/admin/changelog/)。
ContiNew Admin 的分支目前分为下个大版本的开发分支和上个大版本的维护分支PR 前请注意对应分支是否处于维护状态,版本支持情况请查看 [更新日志/版本支持](https://continew.top/admin/other/changelog.html#%E7%89%88%E6%9C%AC%E6%94%AF%E6%8C%81)。
| 分支 | 说明 |
| ----- | ------------------------------------------------------------ |
| dev | 开发分支,用于下个大版本的 SNAPSHOT 开发,接受新功能或功能优化 PR |
| x.x.x | 维护分支,用于特定版本(如 vx.x.xbug 修复,仅接受已有功能修复 PR,不接受新功能 |
| dev | 开发分支,默认为下个大版本的 SNAPSHOT 版本,接受新功能或功能优化 PR |
| x.x.x | 维护分支,在 vx.x.x 版本维护期终止前(一般为下个大版本发布前),用于修复上个版本Bug,只接受已有功能修复,不接受新功能 PR |
### 流程步骤
### 贡献代码
若您希望提交新功能或优化现有代码,请遵循以下步骤:
如果您想提交新功能或优化现有代码,可以按照以下步骤操作
1. 在开源平台上将项目 fork 到您的个人仓库
2. 将 fork 的项目克隆到本地开发环境
3. 基于当前维护的分支(如 dev创建新分支如 feat/newFeature请勿直接修改源分支源分支仅做同步 ContiNew 最新代码用
4. 在新分支上进行代码修改完成后提交并 push 到您的远程仓库
5.开源平台上创建 pull request (PR),选择正确的源分支和目标分支,按模板填写说明信息参考 [已合并的 PR](https://github.com/continew-org/continew-admin-ui/pulls?q=is%3Apr+is%3Amerged) 可提高合并率)
6. 提交 PR 后,系统会提示签署 CLA贡献者协议。请确保 commit 使用的邮箱与平台绑定邮箱一致(如果不一致,可以在本地通过 `git reset --soft HEAD~1` 回退,然后使用正确邮箱重新提交,最后 `git push -f` 即可,不需要重新创建 PR然后使用该邮箱签署即可
7. 耐心等待维护者审核并合并您的 PR建议通过交流群进行快捷沟通
8. PR 合并后,下次贡献前请先同步最新代码,再重复步骤 3 开始
1. 首先,在 Gitee 或 Github 上将项目 fork 到您自己的仓库
2. 然后,将 fork 过来的项目(即您的项目)克隆到本地
3. 切换到当前仍在维护的分支(请务必充分了解分支使用说明,可进群联系维护者确认
4. 开始修改代码修改完成后,将代码 commit 并 push 到您的远程仓库
5. Gitee 或 Github 上新建 pull requestpr选择好源和目标,按模板要求填写说明信息后提交即可(多多参考 [批准合并的 pr 记录](https://github.com/continew-org/continew-admin/pulls?q=is%3Apr+is%3Amerged),会大大增加批准合并率)
6. 最后,耐心等待维护者合并您的请求即可
请记住,如果您有任何疑问或需要帮助,我们将随时提供支持。
> [!IMPORTANT]
> 为了确保项目质量和协作效率,请注意以下事项
> 欢迎大家为 ContiNew Admin 贡献代码,我们非常感谢您的支持!为了更好地管理项目,维护者有一些要求
>
> 1. 代码配置文件请参考已有风格,遵循清晰的结构命名规范,提供完善的注释(包括接口文档参数示例),后端代码请符合阿里巴巴 <a href="https://github.com/continew-org/continew-admin/blob/dev/.style/Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C(%E9%BB%84%E5%B1%B1%E7%89%88).pdf" target="_blank">《Java开发手册(黄山版)》</a> 中的代码规范
> 2. 提交后端代码前请关闭所有代码窗口,执行 `mvn compile` 命令进行代码格式化ContiNew 项目后端编译时会自动执行插件进行代码格式修正)。编译通过后请勿再次打开代码窗口,避免不同 IDE 配置导致的格式差异
> 3. 提交时,请按照 [Angular 提交规范](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular) 编写 commit message参考已有风格
> 1. 请确保代码配置文件的结构命名规范良好,完善的代码注释甚至包括接口文档参数示例,并遵循阿里巴巴 <a href="https://github.com/continew-org/continew-admin/blob/dev/.style/Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C(%E9%BB%84%E5%B1%B1%E7%89%88).pdf" target="_blank">《Java开发手册(黄山版)》</a> 中的代码规范,保证代码质量和可维护性
> 2. 提交代码前,请按照 [Angular 提交规范](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular) 编写 commit 的 message建议在 IntelliJ IDEA 中下载并安装 Git Commit Template 插件,以便按照规范进行 commit
> 3. 提交代码之前,请关闭所有代码窗口,执行 `mvn compile` 命令(代码格式化插件会在项目编译时对全局代码进行格式修正),编译通过后,不要再打开查看任何代码窗口,直接提交即可,以免不同的 IDE 配置会自动进行代码格式化
## 反馈交流
感谢您对 ContiNew 开源项目的关注与支持!我们非常重视每一位用户的反馈和建议,这是推动项目不断进步的动力。 欢迎扫描下方二维码加入我们的官方交流群,与项目维护团队及其他大佬用户实时交流探讨。
- 与项目核心团队直接沟通,获取第一手项目动态
- 解决使用过程中遇到的问题,分享经验心得
- 参与功能讨论和需求收集,影响项目未来发展
- 结识志同道合的技术爱好者,扩展人脉圈
欢迎各位小伙伴儿扫描下方二维码加入项目交流群,与项目维护团队及其他大佬用户实时交流讨论。
<div align="left">
<img src=".image/qrcode.jpg" alt="二维码" height="230px" />

View File

@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.admin</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-admin</artifactId>
<version>${revision}</version>
</parent>
@@ -31,18 +31,12 @@
<groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-spring</artifactId>
</dependency>
<!-- Amazon S3Amazon Simple Storage Service亚马逊简单存储服务通用存储协议 S3兼容主流云厂商对象存储后续会移除替换1.x的版本 -->
<!-- Amazon S3Amazon Simple Storage Service亚马逊简单存储服务通用存储协议 S3兼容主流云厂商对象存储 -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</dependency>
<!-- Amazon S3 2.x version Amazon Simple Storage Service亚马逊简单存储服务通用存储协议 S3兼容主流云厂商对象存储 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<!-- FreeMarker模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
@@ -61,33 +55,15 @@
<artifactId>postgresql</artifactId>
</dependency>-->
<!-- Crane4j基于注解的用于完成一切 “根据 A 的 key 值拿到 B再把 B 的属性映射到 A” 这类需求的字段填充框架 -->
<!-- ContiNew Starter 扩展模块 - CURD增删改查 -->
<dependency>
<groupId>cn.crane4j</groupId>
<artifactId>crane4j-spring-boot-starter</artifactId>
</dependency>
<!-- ContiNew Starter JSON 模块 - Jackson -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-json-jackson</artifactId>
</dependency>
<!-- ContiNew Starter 验证模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-validation</artifactId>
</dependency>
<!-- ContiNew Starter 缓存模块 - JetCache -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-cache-jetcache</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
</dependency>
<!-- ContiNew Starter 认证模块 - SaToken -->
<dependency>
<groupId>top.continew.starter</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-satoken</artifactId>
<exclusions>
<exclusion>
@@ -96,57 +72,28 @@
</exclusion>
</exclusions>
</dependency>
<!-- API 接口参数签名 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sign</artifactId>
</dependency>
<!-- ContiNew Starter 认证模块 - JustAuth -->
<dependency>
<groupId>top.continew.starter</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-justauth</artifactId>
</dependency>
<!-- ContiNew Starter 安全模块 - 加密 -->
<!-- ContiNew Starter 缓存模块 - JetCache -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-security-crypto</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-jetcache</artifactId>
</dependency>
<!-- ContiNew Starter 安全模块 - 脱敏 -->
<!-- ContiNew Starter 数据权限模块 - MyBatis Plus -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-security-mask</artifactId>
</dependency>
<!-- ContiNew Starter 限流模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-ratelimiter</artifactId>
</dependency>
<!-- ContiNew Starter 验证码模块 - 图形验证码 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-captcha-graphic</artifactId>
</dependency>
<!-- ContiNew Starter 验证码模块 - 行为验证码 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-captcha-behavior</artifactId>
</dependency>
<!-- ContiNew Starter 消息模块 - 邮件 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-messaging-mail</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
</dependency>
<!-- ContiNew Starter 消息模块 - WebSocket -->
<dependency>
<groupId>top.continew.starter</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-websocket</artifactId>
<exclusions>
<exclusion>
@@ -156,28 +103,52 @@
</exclusions>
</dependency>
<!-- ContiNew Starter 日志模块 - 拦截器版Spring Boot Actuator HttpTrace 增强版) -->
<!-- ContiNew Starter 消息模块 - 邮件 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-log-interceptor</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-mail</artifactId>
</dependency>
<!-- ContiNew Starter 数据权限模块 - MyBatis Plus -->
<!-- ContiNew Starter 验证码模块 - 图形验证码 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-graphic</artifactId>
</dependency>
<!-- ContiNew Starter 扩展模块 - CURD增删改查 -->
<!-- ContiNew Starter 验证码模块 - 行为验证码 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-behavior</artifactId>
</dependency>
<!-- ContiNew Starter 扩展模块 - 租户 -->
<!-- ContiNew Starter 限流模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-ratelimiter</artifactId>
</dependency>
<!-- ContiNew Starter 安全模块 - 加密 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-crypto</artifactId>
</dependency>
<!-- ContiNew Starter 安全模块 - 脱敏 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-mask</artifactId>
</dependency>
<!-- ContiNew Starter 安全模块 - 密码编码器 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-password</artifactId>
</dependency>
<!-- ContiNew Starter JSON 模块 - Jackson -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-json-jackson</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -1,37 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.api.system;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import java.util.List;
/**
* 字典业务 API
*
* @author Charles7c
* @since 2025/7/26 10:16
*/
public interface DictApi {
/**
* 查询字典列表
*
* @return 字典列表(包含枚举字典列表)
*/
List<LabelValueResp> listAll();
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.api.system;
import cn.hutool.core.lang.tree.Tree;
import java.util.List;
/**
* 菜单业务 API
*
* @author Charles7c
* @since 2025/7/26 9:53
*/
public interface MenuApi {
/**
* 查询树结构列表
*
* @param excludeMenuIds 排除的菜单 ID 列表
* @param isSimple 是否是简单树结构
* @return 树结构列表
*/
List<Tree<Long>> listTree(List<Long> excludeMenuIds, boolean isSimple);
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.api.system;
/**
* 角色业务 API
*
* @author Charles7c
* @since 2025/7/26 9:39
*/
public interface RoleApi {
/**
* 根据编码查询 ID
*
* @param code 编码
* @return 角色 ID
*/
Long getIdByCode(String code);
/**
* 更新用户上下文
*
* @param roleId 角色 ID
*/
void updateUserContext(Long roleId);
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.api.system;
import java.util.List;
import java.util.Set;
/**
* 角色和菜单关联业务 API
*
* @author Charles7c
* @since 2025/7/26 9:39
*/
public interface RoleMenuApi {
/**
* 根据菜单 ID 列表查询角色 ID 列表
*
* @param menuIds 菜单 ID 列表Not In
* @return 角色 ID 列表
*/
Set<Long> listRoleIdByNotInMenuIds(List<Long> menuIds);
/**
* 根据角色 ID 列表查询菜单 ID 列表
*
* @param roleIds 角色 ID 列表
* @return 菜单 ID 列表
*/
List<Long> listMenuIdByRoleIds(List<Long> roleIds);
/**
* 根据菜单 ID 列表删除
*
* @param menuIds 菜单 ID 列表Not In
*/
void deleteByNotInMenuIds(List<Long> menuIds);
/**
* 新增
*
* @param menuIds 菜单 ID 列表
* @param roleId 角色 ID
* @return 是否新增成功true成功false无变更/失败)
*/
boolean add(List<Long> menuIds, Long roleId);
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.api.tenant;
import java.util.List;
/**
* 套餐和菜单关联业务 API
*
* @author Charles7c
* @since 2025/7/23 21:13
*/
public interface PackageMenuApi {
/**
* 根据套餐 ID 查询
*
* @param packageId 套餐 ID
* @return 菜单 ID 列表
*/
List<Long> listMenuIdsByPackageId(Long packageId);
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.api.tenant;
/**
* 租户业务 API
*
* @author Charles7c
* @since 2025/7/23 21:13
*/
public interface TenantApi {
/**
* 绑定租户管理员用户
*
* @param tenantId 租户 ID
* @param userId 用户 ID
*/
void bindAdminUser(Long tenantId, Long userId);
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.api.tenant;
import top.continew.admin.common.model.dto.TenantDTO;
/**
* 租户数据 API
*
* @author 小熊
* @author Charles7c
* @since 2024/12/2 20:08
*/
public interface TenantDataApi {
/**
* 初始化数据
*
* @param tenant 租户信息
*/
void init(TenantDTO tenant);
/**
* 清除数据
*/
void clear();
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.base.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sign.template.SaSignTemplate;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.extra.spring.SpringUtil;
import top.continew.admin.common.base.service.BaseService;
import top.continew.admin.common.config.crud.CrudApiPermissionPrefixCache;
import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties;
import top.continew.starter.core.util.ServletUtils;
import top.continew.starter.core.util.SpringWebUtils;
import top.continew.starter.extension.crud.annotation.CrudApi;
import top.continew.starter.extension.crud.controller.AbstractCrudController;
import top.continew.starter.extension.crud.enums.Api;
import java.lang.reflect.Method;
import java.util.Collection;
/**
* 控制器基类
*
* <p>
* 根据实际项目需要,自行重写 CRUD 接口或增加自定义通用业务接口
* </p>
*
* @param <S> 业务接口
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件类型
* @param <C> 创建或修改请求参数类型
* @author Charles7c
* @since 2024/12/6 20:30
*/
public class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, C> extends AbstractCrudController<S, L, D, Q, C> {
@Override
public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class<?> targetClass) throws Exception {
// 忽略带 sign 请求权限校验
SaRequest saRequest = SaHolder.getRequest();
Collection<String> paramNames = saRequest.getParamNames();
if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) {
return;
}
// 忽略接口类或接口方法上带 @SaIgnore 注解的权限校验
if (AnnotationUtil.hasAnnotation(targetMethod, SaIgnore.class) || AnnotationUtil
.hasAnnotation(targetClass, SaIgnore.class)) {
return;
}
// 忽略排除(放行)路径
SaTokenExtensionProperties saTokenExtensionProperties = SpringUtil.getBean(SaTokenExtensionProperties.class);
if (saTokenExtensionProperties.isEnabled()) {
String[] excludePatterns = saTokenExtensionProperties.getSecurity().getExcludes();
if (SpringWebUtils.isMatch(ServletUtils.getRequestPath(), excludePatterns)) {
return;
}
}
// 不需要校验 DICT、DICT_TREE 接口权限
if (Api.DICT.equals(crudApi.value()) || Api.DICT_TREE.equals(crudApi.value())) {
return;
}
// 校验权限例如创建用户接口POST /system/user => 校验 system:user:create 权限
String permissionPrefix = CrudApiPermissionPrefixCache.get(targetClass);
String apiName = getApiName(crudApi.value());
StpUtil.checkPermission("%s:%s".formatted(permissionPrefix, apiName.toLowerCase()));
}
/**
* 获取 API 名称
*
* @param api API
* @return API 名称
*/
public static String getApiName(Api api) {
return switch (api) {
case PAGE, TREE, LIST -> Api.LIST.name();
case DELETE, BATCH_DELETE -> Api.DELETE.name();
default -> api.name();
};
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.base.model.entity;
import lombok.Data;
import java.io.Serial;
/**
* 租户实体类基类
*
* <p>
* 通用字段ID、创建人、创建时间、修改人、修改时间、租户 ID
* </p>
*
* @author Charles7c
* @since 2025/7/17 20:20
*/
@Data
public class TenantBaseDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户 ID
*/
private Long tenantId;
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.base.service;
import top.continew.starter.extension.crud.service.CrudService;
/**
* 业务接口基类
*
* <p>
* 根据实际项目需要,自行重写 CRUD 接口或增加自定义通用业务方法
* </p>
*
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件类型
* @param <C> 创建或修改请求参数类型
* @author Charles7c
* @since 2024/12/6 20:30
*/
public interface BaseService<L, D, Q, C> extends CrudService<L, D, Q, C> {
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.base.service;
import cn.crane4j.core.support.OperateTemplate;
import cn.hutool.extra.spring.SpringUtil;
import top.continew.starter.data.mapper.BaseMapper;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.crud.service.CrudServiceImpl;
/**
* 业务实现基类
*
*
* <p>
* 根据实际项目需要,自行重写 CRUD 接口或增加自定义通用业务方法实现
* </p>
*
* @param <M> Mapper 接口
* @param <T> 实体类型
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件类型
* @param <C> 创建或修改请求参数类型
* @author Charles7c
* @since 2024/12/6 20:30
*/
public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends CrudServiceImpl<M, T, L, D, Q, C> implements BaseService<L, D, Q, C> {
/**
* 填充数据
*
* @param obj 待填充信息
*/
@Override
protected void fill(Object obj) {
if (obj == null) {
return;
}
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);
operateTemplate.execute(obj);
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
import java.util.List;
/**
* 租户扩展配置属性
*
* @author 小熊
* @author Charles7c
* @since 2024/11/29 12:05
*/
@Data
@ConfigurationProperties(prefix = PropertiesConstants.TENANT)
public class TenantExtensionProperties {
/**
* 请求头中租户编码键名默认X-Tenant-Code
*/
private String tenantCodeHeader = "X-Tenant-Code";
/**
* 默认租户 ID默认0
*/
private Long defaultTenantId = 0L;
/**
* 忽略菜单 ID租户不能使用的菜单
*/
private List<Long> ignoreMenus;
/**
* 是否为默认租户
*
* @return 是否为默认租户
*/
public boolean isDefaultTenant() {
return defaultTenantId.equals(TenantContextHolder.getTenantId());
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.crud;
import cn.hutool.core.util.StrUtil;
import top.continew.starter.core.constant.StringConstants;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* CRUD API 权限前缀缓存
*
* @author Charles7c
* @since 2025/7/24 22:14
*/
public class CrudApiPermissionPrefixCache {
private static final Map<Class<?>, String> PERMISSION_PREFIX_CACHE = new HashMap<>();
/**
* 存储CRUD API权限前缀
*
* @param controllerClazz 控制器类
* @param path 路径
*/
public static void put(Class<?> controllerClazz, String path) {
String permissionPrefix = parsePermissionPrefix(path);
PERMISSION_PREFIX_CACHE.put(controllerClazz, permissionPrefix);
}
/**
* 获取CRUD API权限前缀
*
* @param controllerClazz 控制器类
* @return 权限前缀
*/
public static String get(Class<?> controllerClazz) {
return PERMISSION_PREFIX_CACHE.get(controllerClazz);
}
/**
* 清空缓存
*/
public static void clear() {
PERMISSION_PREFIX_CACHE.clear();
}
/**
* 获取所有缓存
*
* @return 所有缓存
*/
public static Map<Class<?>, String> getAll() {
return PERMISSION_PREFIX_CACHE;
}
/**
* 解析权限前缀(解析路径获取模块名和资源名)
*
* <p>
* 例如:/system/user => system:user <br>
* /system/dict/item => system:dictItem
* </p>
*
* @param path 路径
* @return 权限前缀
*/
private static String parsePermissionPrefix(String path) {
List<String> pathSegmentList = StrUtil.splitTrim(path, StringConstants.SLASH);
if (pathSegmentList.size() < 2) {
throw new IllegalArgumentException("无效的 @CrudRequestMapping 路径配置:" + path);
}
String moduleName = pathSegmentList.get(0);
String resourceName = StrUtil.toCamelCase(String.join(StringConstants.UNDERLINE, pathSegmentList
.subList(1, pathSegmentList.size())));
return "%s:%s".formatted(moduleName, resourceName);
}
}

View File

@@ -33,7 +33,6 @@ import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import top.continew.starter.apidoc.autoconfigure.SpringDocExtensionProperties;
import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import java.lang.reflect.Method;
@@ -104,7 +103,7 @@ public class GlobalAuthenticationCustomizer implements GlobalOpenApiCustomizer {
return;
}
Map<String, SecurityScheme> securitySchemes = components.getSecuritySchemes();
List<String> schemeNames = CollUtils.mapToList(securitySchemes.values(), SecurityScheme::getName);
List<String> schemeNames = securitySchemes.values().stream().map(SecurityScheme::getName).toList();
pathItem.readOperations().forEach(operation -> {
SecurityRequirement securityRequirement = new SecurityRequirement();
schemeNames.forEach(securityRequirement::addList);

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.doc;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 全局接口文档配置
*
* @author Charles7c
* @since 2025/6/14 21:22
*/
@Configuration
public class GlobalSpringDocConfiguration {
@Bean
public GroupedOpenApi allApi() {
return GroupedOpenApi.builder()
.group("all")
.displayName("全部接口")
.pathsToMatch("/**")
.packagesToExclude("/error")
.build();
}
@Bean
public GroupedOpenApi commonApi() {
return GroupedOpenApi.builder()
.group("common")
.displayName("通用接口")
.pathsToMatch("/captcha/**", "/dashboard/**")
.build();
}
}

View File

@@ -19,16 +19,14 @@ package top.continew.admin.common.config.doc;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.text.CharSequenceUtil;
import org.springframework.web.method.HandlerMethod;
import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.common.config.crud.CrudApiPermissionPrefixCache;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.annotation.CrudApi;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -162,21 +160,21 @@ public class OperationDescriptionCustomizer {
*
* @param handlerMethod 处理程序方法
* @return 拼接好的权限信息字符串
* @see BaseController#preHandle(CrudApi, Object[], Method, Class)
*/
private String getCrudPermissionInfo(HandlerMethod handlerMethod) {
Class<?> targetClass = handlerMethod.getBeanType();
CrudRequestMapping crudRequestMapping = targetClass.getAnnotation(CrudRequestMapping.class);
CrudRequestMapping crudRequestMapping = handlerMethod.getBeanType().getAnnotation(CrudRequestMapping.class);
CrudApi crudApi = handlerMethod.getMethodAnnotation(CrudApi.class);
if (crudRequestMapping == null || crudApi == null) {
return StringConstants.EMPTY;
return "";
}
if (Api.DICT.equals(crudApi.value()) || Api.DICT_TREE.equals(crudApi.value())) {
return StringConstants.EMPTY;
}
String permissionPrefix = CrudApiPermissionPrefixCache.get(targetClass);
String apiName = BaseController.getApiName(crudApi.value());
String permission = "%s:%s".formatted(permissionPrefix, apiName.toLowerCase());
return "<font style=\"color:red\" class=\"light-red\">CRUD 权限校验:</font></br><font style=\"color:red\" class=\"light-red\">方法:</font><font style=\"color:red\" class=\"light-red\">" + permission + "</font>";
String path = crudRequestMapping.value();
String prefix = String.join(StringConstants.COLON, CharSequenceUtil.splitTrim(path, StringConstants.SLASH));
Api api = crudApi.value();
String apiName = Api.PAGE.equals(api) || Api.TREE.equals(api) ? Api.LIST.name() : api.name();
String permission = "%s:%s".formatted(prefix, apiName.toLowerCase());
return "<font style=\"color:red\" class=\"light-red\">Crud 权限校验:</font></br><font style=\"color:red\" class=\"light-red\">方法:</font><font style=\"color:red\" class=\"light-red\">" + permission + "</font>";
}
}

View File

@@ -19,12 +19,12 @@ package top.continew.admin.common.config.excel;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.extra.spring.SpringUtil;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import top.continew.admin.common.api.system.DictItemApi;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import top.continew.admin.common.service.CommonDictItemService;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
@@ -86,7 +86,7 @@ public class ExcelDictConverter implements Converter<Object> {
if (dictExcelProperty == null) {
throw new IllegalArgumentException("Excel 字典转换器异常:请为字段添加 @DictExcelProperty 注解");
}
DictItemApi dictItemApi = SpringUtil.getBean(DictItemApi.class);
return dictItemApi.listByDictCode(dictExcelProperty.value());
CommonDictItemService dictItemService = SpringUtil.getBean(CommonDictItemService.class);
return dictItemService.listByDictCode(dictExcelProperty.value());
}
}

View File

@@ -21,7 +21,6 @@ import cn.hutool.core.text.CharSequenceUtil;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
@@ -33,13 +32,13 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.exception.BaseException;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.web.model.R;
import org.springframework.validation.BindException;
import java.util.Objects;
/**
* 全局异常处理器
@@ -97,19 +96,16 @@ public class GlobalExceptionHandler {
}
/**
* 参数校验不通过异常
* 方法参数无效异常
* <p>
* {@code @NotBlank}、{@code @NotNull} 等参数验证不通过
* </p>
*/
@ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
public R handleBindException(BindException e, HttpServletRequest request) {
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
String errorMsg = e.getFieldErrors()
.stream()
.findFirst()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.orElse(StringConstants.EMPTY);
String errorMsg = ExceptionUtils.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError())
.getDefaultMessage());
return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMsg);
}

View File

@@ -46,7 +46,7 @@ public class GlobalSaTokenExceptionHandler {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
String errorMsg = switch (e.getType()) {
case NotLoginException.KICK_OUT -> "您已被踢下线";
case NotLoginException.BE_REPLACED -> "您已被顶下线";
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线";
default -> "您的登录状态已过期,请重新登录";
};
return R.fail(String.valueOf(HttpStatus.UNAUTHORIZED.value()), errorMsg);

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.security.crypto.encryptor.IEncryptor;
import top.continew.starter.security.password.constant.PasswordEncoderConstants;
/**
* BCrypt 加/解密处理器(不可逆)
*
* @author Charles7c
* @since 2024/2/8 22:29
*/
public class BCryptEncryptor implements IEncryptor {
private final PasswordEncoder passwordEncoder;
public BCryptEncryptor(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public String encrypt(String plaintext, String password, String publicKey) {
// 如果已经是 BCrypt 加密格式,直接返回
if (PasswordEncoderConstants.BCRYPT_PATTERN.matcher(plaintext).matches()) {
return plaintext;
}
return passwordEncoder.encode(plaintext);
}
@Override
public String decrypt(String ciphertext, String password, String privateKey) {
return ciphertext;
}
}

View File

@@ -14,16 +14,15 @@
* limitations under the License.
*/
package top.continew.admin.common.base.mapper;
package top.continew.admin.common.config.mybatis;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mapper.BaseMapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.extension.datapermission.annotation.DataPermission;
import java.io.Serializable;
import java.util.List;
/**
@@ -59,10 +58,11 @@ public interface DataPermissionMapper<T> extends BaseMapper<T> {
/**
* 根据 ID 删除
*
* @param id id
* @param obj 主键ID或实体
* @param useFill 是否填充
* @return 删除个数
*/
@DataPermission
@Override
int deleteById(@Param("id") Serializable id);
int deleteById(Object obj, boolean useFill);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import cn.hutool.core.convert.Convert;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.starter.extension.datapermission.enums.DataScope;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
import top.continew.starter.extension.datapermission.model.RoleContext;
import top.continew.starter.extension.datapermission.model.UserContext;
import java.util.stream.Collectors;
/**
* 数据权限用户上下文提供者
*
* @author Charles7c
* @since 2023/12/21 21:19
*/
public class DefaultDataPermissionUserContextProvider implements DataPermissionUserContextProvider {
@Override
public boolean isFilter() {
return !UserContextHolder.isAdmin();
}
@Override
public UserContext getUserContext() {
top.continew.admin.common.context.UserContext context = UserContextHolder.getContext();
UserContext userContext = new UserContext();
userContext.setUserId(Convert.toStr(context.getId()));
userContext.setDeptId(Convert.toStr(context.getDeptId()));
userContext.setRoles(context.getRoles()
.stream()
.map(r -> new RoleContext(Convert.toStr(r.getId()), DataScope.valueOf(r.getDataScope().name())))
.collect(Collectors.toSet()));
return userContext;
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import top.continew.admin.common.context.UserContext;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.extension.datapermission.enums.DataScope;
import top.continew.starter.extension.datapermission.model.RoleData;
import top.continew.starter.extension.datapermission.model.UserData;
import top.continew.starter.extension.datapermission.provider.DataPermissionUserDataProvider;
/**
* 数据权限用户数据提供者
*
* @author Charles7c
* @since 2023/12/21 21:19
*/
public class DefaultDataPermissionUserDataProvider implements DataPermissionUserDataProvider {
@Override
public boolean isFilter() {
return !UserContextHolder.isSuperAdmin() && !UserContextHolder.isTenantAdmin();
}
@Override
public UserData getUserData() {
UserContext userContext = UserContextHolder.getContext();
UserData userData = new UserData();
userData.setUserId(userContext.getId());
userData.setDeptId(userContext.getDeptId());
userData.setRoles(CollUtils.mapToSet(userContext.getRoles(), r -> new RoleData(r.getId(), DataScope.valueOf(r
.getDataScope()
.name()))));
return userData;
}
}

View File

@@ -20,7 +20,7 @@ import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.admin.common.base.model.entity.BaseDO;
import top.continew.admin.common.model.entity.BaseDO;
import java.time.LocalDateTime;

View File

@@ -17,13 +17,10 @@
package top.continew.admin.common.config.mybatis;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.continew.starter.extension.datapermission.provider.DataPermissionUserDataProvider;
import java.util.concurrent.TimeUnit;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
/**
* MyBatis Plus 配置
@@ -34,12 +31,6 @@ import java.util.concurrent.TimeUnit;
@Configuration
public class MybatisPlusConfiguration {
// SQL 解析本地缓存
static {
JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache(cache -> cache.maximumSize(1024)
.expireAfterWrite(5, TimeUnit.SECONDS)));
}
/**
* 元对象处理器配置(插入或修改时自动填充)
*/
@@ -49,10 +40,18 @@ public class MybatisPlusConfiguration {
}
/**
* 数据权限用户数据提供者
* 数据权限用户上下文提供者
*/
@Bean
public DataPermissionUserDataProvider dataPermissionUserDataProvider() {
return new DefaultDataPermissionUserDataProvider();
public DataPermissionUserContextProvider dataPermissionUserContextProvider() {
return new DefaultDataPermissionUserContextProvider();
}
/**
* BCrypt 加/解密处理器
*/
@Bean
public BCryptEncryptor bCryptEncryptor(PasswordEncoder passwordEncoder) {
return new BCryptEncryptor(passwordEncoder);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.admin.common.config;
package top.continew.admin.common.config.properties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.admin.common.config;
package top.continew.admin.common.config.properties;
import cn.hutool.extra.spring.SpringUtil;

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.constant;
/**
* 全局常量
*
* @author Charles7c
* @since 2023/2/9 22:11
*/
public class GlobalConstants {
/**
* 根父级 ID
*/
public static final Long ROOT_PARENT_ID = 0L;
/**
* 布尔值常量
*/
public static class Boolean {
/**
* 否
*/
public static final Integer NO = 0;
/**
* 是
*/
public static final Integer YES = 1;
}
private GlobalConstants() {
}
}

View File

@@ -59,32 +59,6 @@ public class RegexConstants {
*/
public static final String PACKAGE_NAME = "^(?:[a-zA-Z_][a-zA-Z0-9_]*\\.)*[a-zA-Z_][a-zA-Z0-9_]*$";
/**
* HTTP URL 正则
*/
public static final String URL_HTTP = "^(https?)://[\\w-+&@#/%?=~_|!:,.;]*[\\w-+&@#/%=~_|]$";
/**
* HTTP URL 正则(非 IP 地址)
*/
public static final String URL_HTTP_NOT_IP = "^(https?:\\/\\/)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(\\/[^\\s]*)?$";
/**
* HTTP HOST 正则
* <p>
* 域名、IPV4、IPV6
* </p>
*/
public static final String HTTP_HOST = "^(" +
// ① 域名
"(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z]{2,63}" + "|" +
// ② IPv4
"(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)" + "|" +
// ③ IPv68 组 1-4 位十六进制,用 : 分隔,支持压缩 0
"(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}" + "|" +
// ④ IPv6 压缩形式(::
"(?:[0-9A-Fa-f]{1,4}:){0,6}::(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}" + ")$";
private RegexConstants() {
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.constant;
/**
* 系统相关常量
*
* @author Charles7c
* @since 2023/2/9 22:11
*/
public class SysConstants {
/**
* 否
*/
public static final Integer NO = 0;
/**
* 是
*/
public static final Integer YES = 1;
/**
* 超管用户 ID
*/
public static final Long SUPER_USER_ID = 1L;
/**
* 顶级部门 ID
*/
public static final Long SUPER_DEPT_ID = 1L;
/**
* 顶级父 ID
*/
public static final Long SUPER_PARENT_ID = 0L;
/**
* 超管角色编码
*/
public static final String SUPER_ROLE_CODE = "admin";
/**
* 普通用户角色编码
*/
public static final String GENERAL_ROLE_CODE = "general";
/**
* 超管角色 ID
*/
public static final Long SUPER_ROLE_ID = 1L;
/**
* 普通用户角色 ID
*/
public static final Long GENERAL_ROLE_ID = 2L;
/**
* 全部权限标识
*/
public static final String ALL_PERMISSION = "*:*:*";
/**
* 登录 URI
*/
public static final String LOGIN_URI = "/auth/login";
/**
* 登出 URI
*/
public static final String LOGOUT_URI = "/auth/logout";
private SysConstants() {
}
}

View File

@@ -17,18 +17,15 @@
package top.continew.admin.common.context;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import top.continew.admin.common.config.TenantExtensionProperties;
import top.continew.admin.common.constant.GlobalConstants;
import top.continew.admin.common.enums.RoleCodeEnum;
import top.continew.starter.core.util.CollUtils;
import top.continew.admin.common.constant.SysConstants;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 用户上下文
@@ -93,25 +90,27 @@ public class UserContext implements Serializable {
*/
private String clientId;
/**
* 租户 ID
*/
private Long tenantId;
public UserContext(Set<String> permissions, Set<RoleContext> roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.setRoles(roles);
this.passwordExpirationDays = passwordExpirationDays;
}
/**
* 设置角色
*
* @param roles 角色
*/
public void setRoles(Set<RoleContext> roles) {
this.roles = roles;
this.roleCodes = CollUtils.mapToSet(roles, RoleContext::getCode);
this.roleCodes = roles.stream().map(RoleContext::getCode).collect(Collectors.toSet());
}
/**
* 是否为管理员
*
* @return truefalse
*/
public boolean isAdmin() {
if (CollUtil.isEmpty(roleCodes)) {
return false;
}
return roleCodes.contains(SysConstants.SUPER_ROLE_CODE);
}
/**
@@ -121,7 +120,7 @@ public class UserContext implements Serializable {
*/
public boolean isPasswordExpired() {
// 永久有效
if (this.passwordExpirationDays == null || this.passwordExpirationDays <= GlobalConstants.Boolean.NO) {
if (this.passwordExpirationDays == null || this.passwordExpirationDays <= SysConstants.NO) {
return false;
}
// 初始密码(第三方登录用户)暂不提示修改
@@ -130,29 +129,4 @@ public class UserContext implements Serializable {
}
return this.pwdResetTime.plusDays(this.passwordExpirationDays).isBefore(LocalDateTime.now());
}
/**
* 是否为超级管理员
*
* @return truefalse
*/
public boolean isSuperAdmin() {
if (CollUtil.isEmpty(roleCodes)) {
return false;
}
return roleCodes.contains(RoleCodeEnum.SUPER_ADMIN.getCode());
}
/**
* 是否为租户管理员
*
* @return truefalse
*/
public boolean isTenantAdmin() {
if (CollUtil.isEmpty(roleCodes)) {
return false;
}
TenantExtensionProperties tenantExtensionProperties = SpringUtil.getBean(TenantExtensionProperties.class);
return !tenantExtensionProperties.isDefaultTenant() && roleCodes.contains(RoleCodeEnum.TENANT_ADMIN.getCode());
}
}

View File

@@ -20,8 +20,7 @@ import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import top.continew.admin.common.api.system.UserApi;
import top.continew.admin.common.service.CommonUserService;
import top.continew.starter.core.util.ExceptionUtils;
/**
@@ -32,8 +31,8 @@ import top.continew.starter.core.util.ExceptionUtils;
*/
public class UserContextHolder {
private static final TransmittableThreadLocal<UserContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
private static final TransmittableThreadLocal<UserExtraContext> EXTRA_CONTEXT_HOLDER = new TransmittableThreadLocal<>();
private static final ThreadLocal<UserContext> CONTEXT_HOLDER = new ThreadLocal<>();
private static final ThreadLocal<UserExtraContext> EXTRA_CONTEXT_HOLDER = new ThreadLocal<>();
private UserContextHolder() {
}
@@ -144,15 +143,6 @@ public class UserContextHolder {
return ExceptionUtils.exToNull(() -> getContext().getId());
}
/**
* 获取租户 ID
*
* @return 租户 ID
*/
public static Long getTenantId() {
return ExceptionUtils.exToNull(() -> getContext().getTenantId());
}
/**
* 获取用户名
*
@@ -178,26 +168,16 @@ public class UserContextHolder {
* @return 用户昵称
*/
public static String getNickname(Long userId) {
return ExceptionUtils.exToNull(() -> SpringUtil.getBean(UserApi.class).getNicknameById(userId));
return ExceptionUtils.exToNull(() -> SpringUtil.getBean(CommonUserService.class).getNicknameById(userId));
}
/**
* 是否为超级管理员
* 是否为管理员
*
* @return truefalse
* @return 是否为管理员
*/
public static boolean isSuperAdmin() {
public static boolean isAdmin() {
StpUtil.checkLogin();
return getContext().isSuperAdmin();
}
/**
* 是否为租户管理员
*
* @return truefalse
*/
public static boolean isTenantAdmin() {
StpUtil.checkLogin();
return getContext().isTenantAdmin();
return getContext().isAdmin();
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.text.CharSequenceUtil;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.annotation.CrudApi;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.AbstractBaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.service.BaseService;
import java.lang.reflect.Method;
import java.util.Collection;
/**
* 控制器基类
*
* @param <S> 业务接口
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件
* @param <C> 创建或修改参数类型
* @author Charles7c
* @since 2024/12/6 20:30
*/
public class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, C> extends AbstractBaseController<S, L, D, Q, C> {
@Override
public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class<?> targetClass) throws Exception {
SaRequest saRequest = SaHolder.getRequest();
Collection<String> paramNames = saRequest.getParamNames();
if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) {
return;
}
if (AnnotationUtil.hasAnnotation(targetMethod, SaIgnore.class) || AnnotationUtil
.hasAnnotation(targetClass, SaIgnore.class)) {
return;
}
CrudRequestMapping crudRequestMapping = targetClass.getDeclaredAnnotation(CrudRequestMapping.class);
String path = crudRequestMapping.value();
String prefix = String.join(StringConstants.COLON, CharSequenceUtil.splitTrim(path, StringConstants.SLASH));
Api api = crudApi.value();
String apiName = Api.PAGE.equals(api) || Api.TREE.equals(api) ? Api.LIST.name() : api.name();
StpUtil.checkPermission("%s:%s".formatted(prefix, apiName.toLowerCase()));
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.enums;
import cn.hutool.extra.spring.SpringUtil;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.admin.common.config.TenantExtensionProperties;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
import java.util.List;
/**
* 角色编码枚举
*
* @author Charles7c
* @since 2025/7/26 19:18
*/
@Getter
@RequiredArgsConstructor
public enum RoleCodeEnum {
/**
* 超级管理员(内置且仅有一位超级管理员)
*/
SUPER_ADMIN("super_admin", "超级管理员"),
/**
* 租户管理员
*/
TENANT_ADMIN("admin", "系统管理员"),
/**
* 系统管理员
*/
SYSTEM_ADMIN("sys_admin", "系统管理员"),
/**
* 普通用户
*/
GENERAL_USER("general", "普通用户");
private final String code;
private final String description;
/**
* 获取超级管理员角色编码列表
*
* @return 超级管理员角色编码列表
*/
public static List<String> getSuperRoleCodes() {
if (TenantContextHolder.isTenantDisabled() || SpringUtil.getBean(TenantExtensionProperties.class)
.isDefaultTenant()) {
return List.of(SUPER_ADMIN.getCode());
}
return List.of(SUPER_ADMIN.getCode(), TENANT_ADMIN.getCode());
}
/**
* 判断是否为超级管理员角色编码
*
* @param code 角色编码
* @return 是否为超级管理员角色编码
*/
public static boolean isSuperRoleCode(String code) {
return getSuperRoleCodes().contains(code);
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.model.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 租户信息
*
* @author Charles7c
* @since 2025/7/23 21:05
*/
@Data
public class TenantDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long id;
/**
* 名称
*/
private String name;
/**
* 管理员用户名
*/
private String adminUsername;
/**
* 管理员密码
*/
private String adminPassword;
/**
* 套餐 ID
*/
private Long packageId;
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.admin.common.base.model.entity;
package top.continew.admin.common.model.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.admin.common.base.model.entity;
package top.continew.admin.common.model.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.admin.common.base.model.entity;
package top.continew.admin.common.model.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
package top.continew.admin.common.base.model.resp;
package top.continew.admin.common.model.resp;
import cn.crane4j.annotation.Assemble;
import cn.crane4j.annotation.Mapping;
import cn.crane4j.annotation.condition.ConditionOnPropertyNotNull;
import cn.idev.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package top.continew.admin.common.base.model.resp;
package top.continew.admin.common.model.resp;
import cn.crane4j.annotation.Assemble;
import cn.crane4j.annotation.Mapping;
import cn.idev.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;

View File

@@ -14,19 +14,19 @@
* limitations under the License.
*/
package top.continew.admin.common.api.system;
package top.continew.admin.common.service;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import java.util.List;
/**
* 字典项业务 API
* 公共字典项业务接口
*
* @author Charles7c
* @since 2025/4/9 20:17
*/
public interface DictItemApi {
public interface CommonDictItemService {
/**
* 根据字典编码查询

View File

@@ -14,19 +14,19 @@
* limitations under the License.
*/
package top.continew.admin.common.api.system;
package top.continew.admin.common.service;
import cn.crane4j.annotation.ContainerMethod;
import cn.crane4j.annotation.MappingType;
import top.continew.admin.common.constant.ContainerConstants;
/**
* 用户业务 API
* 公共用户业务接口
*
* @author Charles7c
* @since 2025/1/9 20:17
*/
public interface UserApi {
public interface CommonUserService {
/**
* 根据 ID 查询昵称
@@ -40,12 +40,4 @@ public interface UserApi {
*/
@ContainerMethod(namespace = ContainerConstants.USER_NICKNAME, type = MappingType.ORDER_OF_KEYS)
String getNicknameById(Long id);
/**
* 重置密码
*
* @param newPassword 新密码
* @param id ID
*/
void resetPassword(String newPassword, Long id);
}

View File

@@ -17,13 +17,18 @@
package top.continew.admin.common.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ReUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import top.continew.admin.common.config.RsaProperties;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import cn.hutool.extra.spring.SpringUtil;
import top.continew.admin.common.config.properties.RsaProperties;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.continew.starter.security.crypto.encryptor.AesEncryptor;
import top.continew.starter.security.crypto.encryptor.IEncryptor;
import java.util.List;
import java.util.stream.Collectors;
/**
* 加密/解密工具类
@@ -83,33 +88,20 @@ public class SecureUtils {
}
/**
* 解密密码
* 对普通加密字段列表进行AES加密优化starter加密模块后优化这个方法
*
* @param encryptedPasswordByRsaPublicKey 密码(已被 Rsa 公钥加密)
* @param errorMsg 错误信息
* @return 解密后的密码
* @param values 待加密内容
* @return 加密后内容
*/
public static String decryptPasswordByRsaPrivateKey(String encryptedPasswordByRsaPublicKey, String errorMsg) {
return decryptPasswordByRsaPrivateKey(encryptedPasswordByRsaPublicKey, errorMsg, false);
}
/**
* 解密密码
*
* @param encryptedPasswordByRsaPublicKey 密码(已被 Rsa 公钥加密)
* @param errorMsg 错误信息
* @param isVerifyPattern 是否验证密码格式
* @return 解密后的密码
*/
public static String decryptPasswordByRsaPrivateKey(String encryptedPasswordByRsaPublicKey,
String errorMsg,
boolean isVerifyPattern) {
String rawPassword = ExceptionUtils.exToNull(() -> decryptByRsaPrivateKey(encryptedPasswordByRsaPublicKey));
ValidationUtils.throwIfBlank(rawPassword, errorMsg);
if (isVerifyPattern) {
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
}
return rawPassword;
public static List<String> encryptFieldByAes(List<String> values) {
IEncryptor encryptor = new AesEncryptor();
CryptoProperties properties = SpringUtil.getBean(CryptoProperties.class);
return values.stream().map(value -> {
try {
return encryptor.encrypt(value, properties.getPassword(), properties.getPublicKey());
} catch (Exception e) {
throw new BusinessException("字段加密异常");
}
}).collect(Collectors.toList());
}
}

View File

@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.admin</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-extension</artifactId>
<version>${revision}</version>
</parent>
@@ -17,7 +17,7 @@
<properties>
<!-- SnailJob 服务端 -->
<snail-job.version>1.5.0</snail-job.version>
<snail-job.version>1.4.0</snail-job.version>
</properties>
<dependencies>

View File

@@ -8,7 +8,7 @@ spring.datasource:
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# # PostgreSQL 配置
# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:continew_admin_job}?options=-c%20TimeZone=Asia/Shanghai&sslmode=prefer&channelBinding=require&stringtype=unspecified
# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:continew_admin_job}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&stringtype=unspecified
# username: ${DB_USER:root}
# password: ${DB_PWD:123456}
# driver-class-name: org.postgresql.Driver

View File

@@ -8,7 +8,7 @@ spring.datasource:
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# # PostgreSQL 配置
# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:continew_admin_job}?options=-c%20TimeZone=Asia/Shanghai&sslmode=prefer&channelBinding=require&stringtype=unspecified
# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:continew_admin_job}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&stringtype=unspecified
# username: ${DB_USER:root}
# password: ${DB_PWD:123456}
# driver-class-name: org.postgresql.Driver

View File

@@ -1,14 +1,14 @@
-- liquibase formatted sql
-- changeset snail-job-server:1.5.0
-- 默认用户admin/admin
INSERT INTO `sj_system_user` (username, password, role)
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
-- changeset snail-job-server:1.1.0
-- 默认命名空间Default
INSERT INTO `sj_namespace` (`id`, `name`, `unique_id`, `create_dt`, `update_dt`, `deleted`)
VALUES (1, 'Default', '764d604ec6fc45f68cd92514c40e9e1a', NOW(), NOW(), 0);
-- 默认用户admin/admin
INSERT INTO `sj_system_user` (username, password, role)
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
-- 默认分组continew-admin
INSERT INTO `sj_group_config` (`id`, `namespace_id`, `group_name`, `description`, `token`, `group_status`, `version`, `group_partition`, `id_generator_mode`, `init_scene`, `create_dt`, `update_dt`)
VALUES (1, '764d604ec6fc45f68cd92514c40e9e1a', 'continew-admin', '默认分组', 'SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj', 1, 1, 0, 2, 1, NOW(), NOW());

View File

@@ -1,6 +1,6 @@
-- liquibase formatted sql
-- changeset snail-job-server:1.5.0
-- changeset snail-job-server:1.1.0
SET NAMES utf8mb4;
CREATE TABLE `sj_namespace`
@@ -81,20 +81,16 @@ CREATE TABLE `sj_notify_recipient`
CREATE TABLE `sj_retry_dead_letter`
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`group_id` bigint(20) NOT NULL COMMENT '组Id',
`scene_name` varchar(64) NOT NULL COMMENT '场景名称',
`scene_id` bigint(20) NOT NULL COMMENT '场景ID',
`idempotent_id` varchar(64) NOT NULL COMMENT '幂等id',
`biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号',
`executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称',
-- jackson 兼容历史数据 预计1.8.0默认改为fury
`serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '执行方法参数序列化器名称',
`args_str` text NOT NULL COMMENT '执行方法参数',
`ext_attrs` text NOT NULL COMMENT '扩展字段',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`scene_name` varchar(64) NOT NULL COMMENT '场景名称',
`idempotent_id` varchar(64) NOT NULL COMMENT '幂等id',
`biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号',
`executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称',
`args_str` text NOT NULL COMMENT '执行方法参数',
`ext_attrs` text NOT NULL COMMENT '扩展字段',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`),
KEY `idx_idempotent_id` (`idempotent_id`),
@@ -110,16 +106,12 @@ CREATE TABLE `sj_retry`
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`group_id` bigint(20) NOT NULL COMMENT '组Id',
`scene_name` varchar(64) NOT NULL COMMENT '场景名称',
`scene_id` bigint(20) NOT NULL COMMENT '场景ID',
`idempotent_id` varchar(64) NOT NULL COMMENT '幂等id',
`biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号',
`executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称',
`args_str` text NOT NULL COMMENT '执行方法参数',
`ext_attrs` text NOT NULL COMMENT '扩展字段',
-- jackson 兼容历史数据 预计1.8.0默认改为fury
`serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '执行方法参数序列化器名称',
`next_trigger_at` bigint(13) NOT NULL COMMENT '下次触发时间',
`retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '重试次数',
`retry_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '重试状态 0、重试中 1、成功 2、最大重试次数',
@@ -130,12 +122,13 @@ CREATE TABLE `sj_retry`
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_biz_no` (`biz_no`),
KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`),
KEY `idx_namespace_id_group_name_retry_status` (`namespace_id`, `group_name`, `retry_status`),
KEY `idx_idempotent_id` (`idempotent_id`),
KEY `idx_retry_status_bucket_index` (`retry_status`, `bucket_index`),
KEY `idx_biz_no` (`biz_no`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_create_dt` (`create_dt`),
UNIQUE KEY `uk_scene_tasktype_idempotentid_deleted` (`scene_id`, `task_type`, `idempotent_id`, `deleted`)
UNIQUE KEY `uk_name_task_type_idempotent_id_deleted` (`namespace_id`, `group_name`, `task_type`, `idempotent_id`, `deleted`)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT ='重试信息表'
@@ -152,7 +145,7 @@ CREATE TABLE `sj_retry_task`
`task_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '重试状态',
`task_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '任务类型 1、重试数据 2、回调数据',
`operation_reason` tinyint(4) NOT NULL DEFAULT 0 COMMENT '操作原因',
`client_info` varchar(128) DEFAULT NULL COMMENT '客户端地址 clientId#ip:port',
`client_info` varchar(128) DEFAULT NULL COMMENT '客户端地址 clientId#ip:port',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
@@ -203,8 +196,6 @@ CREATE TABLE `sj_retry_scene_config`
`cb_trigger_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1、默认等级 2、固定间隔时间 3、CRON 表达式',
`cb_max_count` int(11) NOT NULL DEFAULT 16 COMMENT '回调的最大执行次数',
`cb_trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '回调的最大执行次数',
`owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id',
`labels` varchar(512) NULL DEFAULT '' COMMENT '标签',
`description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
@@ -226,7 +217,6 @@ CREATE TABLE `sj_server_node`
`expire_at` datetime NOT NULL COMMENT '过期时间',
`node_type` tinyint(4) NOT NULL COMMENT '节点类型 1、客户端 2、是服务端',
`ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段',
`labels` varchar(512) NULL DEFAULT '' COMMENT '标签',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
@@ -240,12 +230,12 @@ CREATE TABLE `sj_server_node`
CREATE TABLE `sj_distributed_lock`
(
`name` varchar(64) NOT NULL COMMENT '锁名称',
`lock_until` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '锁定时长',
`locked_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '锁定时间',
`locked_by` varchar(255) NOT NULL COMMENT '锁定者',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`name` varchar(64) NOT NULL COMMENT '锁名称',
`lock_until` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '锁定时长',
`locked_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '锁定时间',
`locked_by` varchar(255) NOT NULL COMMENT '锁定者',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`name`)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
@@ -278,6 +268,19 @@ CREATE TABLE `sj_system_user_permission`
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='系统用户权限表';
CREATE TABLE `sj_sequence_alloc`
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`group_name` varchar(64) NOT NULL DEFAULT '' COMMENT '组名称',
`max_id` bigint(20) NOT NULL DEFAULT 1 COMMENT '最大id',
`step` int(11) NOT NULL DEFAULT 100 COMMENT '步长',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_namespace_id_group_name` (`namespace_id`, `group_name`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='号段模式序号ID分配表';
-- 分布式调度DDL
CREATE TABLE `sj_job`
(
@@ -303,8 +306,7 @@ CREATE TABLE `sj_job`
`bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket',
`resident` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否是常驻任务',
`notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表',
`owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id',
`labels` varchar(512) NULL DEFAULT '' COMMENT '标签',
`owner_id` bigint(20) NULL COMMENT '负责人id',
`description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述',
`ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段',
`deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除',
@@ -457,7 +459,6 @@ CREATE TABLE `sj_workflow`
`notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表',
`bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket',
`version` int(11) NOT NULL COMMENT '版本号',
`owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id',
`ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段',
`deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -507,7 +508,7 @@ CREATE TABLE `sj_workflow_task_batch`
`wf_context` text DEFAULT NULL COMMENT '全局上下文',
`execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '任务执行时间',
`ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段',
`version` int(11) NOT NULL DEFAULT 1 COMMENT '版本号',
`version` int(11) NOT NULL DEFAULT 1 COMMENT '版本号',
`deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
@@ -517,20 +518,4 @@ CREATE TABLE `sj_workflow_task_batch`
KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT ='工作流批次';
CREATE TABLE `sj_job_executor`
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`executor_info` varchar(256) NOT NULL COMMENT '任务执行器名称',
`executor_type` varchar(3) NOT NULL COMMENT '1:java 2:python 3:go',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`),
KEY `idx_create_dt` (`create_dt`)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT ='任务执行器信息';
DEFAULT CHARSET = utf8mb4 COMMENT ='工作流批次';

View File

@@ -1,14 +1,14 @@
-- liquibase formatted sql
-- changeset snail-job-server:1.5.0
-- 默认用户admin/admin
INSERT INTO sj_system_user (username, password, role)
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
-- changeset snail-job-server:1.1.0
-- 默认命名空间Default
INSERT INTO sj_namespace (id, name, unique_id, create_dt, update_dt, deleted)
VALUES (1, 'Default', '764d604ec6fc45f68cd92514c40e9e1a', NOW(), NOW(), 0);
-- 默认用户admin/admin
INSERT INTO sj_system_user (username, password, role)
VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2);
-- 默认分组continew-admin
INSERT INTO sj_group_config (id, namespace_id, group_name, description, token, group_status, version, group_partition, id_generator_mode, init_scene, create_dt, update_dt)
VALUES (1, '764d604ec6fc45f68cd92514c40e9e1a', 'continew-admin', '默认分组', 'SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj', 1, 1, 0, 2, 1, NOW(), NOW());

View File

@@ -1,6 +1,6 @@
-- liquibase formatted sql
-- changeset snail-job-server:1.5.0
-- changeset snail-job-server:1.1.0
-- sj_namespace
CREATE TABLE sj_namespace
(
@@ -122,19 +122,16 @@ COMMENT ON TABLE sj_notify_recipient IS '告警通知接收人';
-- sj_retry_dead_letter
CREATE TABLE sj_retry_dead_letter
(
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name varchar(64) NOT NULL,
group_id bigint NOT NULL,
scene_name varchar(64) NOT NULL,
scene_id bigint NOT NULL,
idempotent_id varchar(64) NOT NULL,
biz_no varchar(64) NOT NULL DEFAULT '',
executor_name varchar(512) NOT NULL DEFAULT '',
serializer_name varchar(32) NOT NULL DEFAULT 'jackson',
args_str text NOT NULL,
ext_attrs text NOT NULL,
create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name varchar(64) NOT NULL,
scene_name varchar(64) NOT NULL,
idempotent_id varchar(64) NOT NULL,
biz_no varchar(64) NOT NULL DEFAULT '',
executor_name varchar(512) NOT NULL DEFAULT '',
args_str text NOT NULL,
ext_attrs text NOT NULL,
create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_sj_retry_dead_letter_01 ON sj_retry_dead_letter (namespace_id, group_name, scene_name);
@@ -145,13 +142,10 @@ CREATE INDEX idx_sj_retry_dead_letter_04 ON sj_retry_dead_letter (create_dt);
COMMENT ON COLUMN sj_retry_dead_letter.id IS '主键';
COMMENT ON COLUMN sj_retry_dead_letter.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_retry_dead_letter.group_name IS '组名称';
COMMENT ON COLUMN sj_retry_dead_letter.group_id IS '组Id';
COMMENT ON COLUMN sj_retry_dead_letter.scene_name IS '场景名称';
COMMENT ON COLUMN sj_retry_dead_letter.scene_id IS '场景ID';
COMMENT ON COLUMN sj_retry_dead_letter.idempotent_id IS '幂等id';
COMMENT ON COLUMN sj_retry_dead_letter.biz_no IS '业务编号';
COMMENT ON COLUMN sj_retry_dead_letter.executor_name IS '执行器名称';
COMMENT ON COLUMN sj_retry_dead_letter.serializer_name IS '执行方法参数序列化器名称';
COMMENT ON COLUMN sj_retry_dead_letter.args_str IS '执行方法参数';
COMMENT ON COLUMN sj_retry_dead_letter.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_retry_dead_letter.create_dt IS '创建时间';
@@ -163,15 +157,12 @@ CREATE TABLE sj_retry
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name varchar(64) NOT NULL,
group_id bigint NOT NULL,
scene_name varchar(64) NOT NULL,
scene_id bigint NOT NULL,
idempotent_id varchar(64) NOT NULL,
biz_no varchar(64) NOT NULL DEFAULT '',
executor_name varchar(512) NOT NULL DEFAULT '',
args_str text NOT NULL,
ext_attrs text NOT NULL,
serializer_name varchar(32) NOT NULL DEFAULT 'jackson',
next_trigger_at bigint NOT NULL,
retry_count int NOT NULL DEFAULT 0,
retry_status smallint NOT NULL DEFAULT 0,
@@ -183,26 +174,24 @@ CREATE TABLE sj_retry
update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX uk_sj_retry_01 ON sj_retry (scene_id, task_type, idempotent_id, deleted);
CREATE UNIQUE INDEX uk_sj_retry_01 ON sj_retry (namespace_id, group_name, task_type, idempotent_id, deleted);
CREATE INDEX idx_sj_retry_01 ON sj_retry (biz_no);
CREATE INDEX idx_sj_retry_02 ON sj_retry (idempotent_id);
CREATE INDEX idx_sj_retry_03 ON sj_retry (retry_status, bucket_index);
CREATE INDEX idx_sj_retry_04 ON sj_retry (parent_id);
CREATE INDEX idx_sj_retry_05 ON sj_retry (create_dt);
CREATE INDEX idx_sj_retry_01 ON sj_retry (namespace_id, group_name, scene_name);
CREATE INDEX idx_sj_retry_02 ON sj_retry (namespace_id, group_name, retry_status);
CREATE INDEX idx_sj_retry_03 ON sj_retry (idempotent_id);
CREATE INDEX idx_sj_retry_04 ON sj_retry (biz_no);
CREATE INDEX idx_sj_retry_05 ON sj_retry (parent_id);
CREATE INDEX idx_sj_retry_06 ON sj_retry (create_dt);
COMMENT ON COLUMN sj_retry.id IS '主键';
COMMENT ON COLUMN sj_retry.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_retry.group_name IS '组名称';
COMMENT ON COLUMN sj_retry.group_id IS '组Id';
COMMENT ON COLUMN sj_retry.scene_name IS '场景名称';
COMMENT ON COLUMN sj_retry.scene_id IS '场景ID';
COMMENT ON COLUMN sj_retry.idempotent_id IS '幂等id';
COMMENT ON COLUMN sj_retry.biz_no IS '业务编号';
COMMENT ON COLUMN sj_retry.executor_name IS '执行器名称';
COMMENT ON COLUMN sj_retry.args_str IS '执行方法参数';
COMMENT ON COLUMN sj_retry.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_retry.serializer_name IS '执行方法参数序列化器名称';
COMMENT ON COLUMN sj_retry.next_trigger_at IS '下次触发时间';
COMMENT ON COLUMN sj_retry.retry_count IS '重试次数';
COMMENT ON COLUMN sj_retry.retry_status IS '重试状态 0、重试中 1、成功 2、最大重试次数';
@@ -298,8 +287,6 @@ CREATE TABLE sj_retry_scene_config
cb_trigger_type smallint NOT NULL DEFAULT 1,
cb_max_count int NOT NULL DEFAULT 16,
cb_trigger_interval varchar(16) NOT NULL DEFAULT '',
owner_id bigint NULL DEFAULT NULL,
labels varchar(512) NULL DEFAULT '',
description varchar(256) NOT NULL DEFAULT '',
create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
@@ -324,8 +311,6 @@ COMMENT ON COLUMN sj_retry_scene_config.cb_status IS '回调状态 0、不开启
COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_type IS '1、默认等级 2、固定间隔时间 3、CRON 表达式';
COMMENT ON COLUMN sj_retry_scene_config.cb_max_count IS '回调的最大执行次数';
COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_interval IS '回调的最大执行次数';
COMMENT ON COLUMN sj_retry_scene_config.owner_id IS '负责人id';
COMMENT ON COLUMN sj_retry_scene_config.labels IS '标签';
COMMENT ON COLUMN sj_retry_scene_config.description IS '描述';
COMMENT ON COLUMN sj_retry_scene_config.create_dt IS '创建时间';
COMMENT ON COLUMN sj_retry_scene_config.update_dt IS '修改时间';
@@ -343,7 +328,6 @@ CREATE TABLE sj_server_node
expire_at timestamp NOT NULL,
node_type smallint NOT NULL,
ext_attrs varchar(256) NULL DEFAULT '',
labels varchar(512) NULL DEFAULT '',
create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@@ -362,7 +346,6 @@ COMMENT ON COLUMN sj_server_node.host_port IS '机器端口';
COMMENT ON COLUMN sj_server_node.expire_at IS '过期时间';
COMMENT ON COLUMN sj_server_node.node_type IS '节点类型 1、客户端 2、是服务端';
COMMENT ON COLUMN sj_server_node.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_server_node.labels IS '标签';
COMMENT ON COLUMN sj_server_node.create_dt IS '创建时间';
COMMENT ON COLUMN sj_server_node.update_dt IS '修改时间';
COMMENT ON TABLE sj_server_node IS '服务器节点';
@@ -370,7 +353,7 @@ COMMENT ON TABLE sj_server_node IS '服务器节点';
-- sj_distributed_lock
CREATE TABLE sj_distributed_lock
(
name varchar(64) NOT NULL PRIMARY KEY,
name varchar(64) NOT NULL,
lock_until timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
locked_at timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
locked_by varchar(255) NOT NULL,
@@ -426,6 +409,27 @@ COMMENT ON COLUMN sj_system_user_permission.create_dt IS '创建时间';
COMMENT ON COLUMN sj_system_user_permission.update_dt IS '修改时间';
COMMENT ON TABLE sj_system_user_permission IS '系统用户权限表';
-- sj_sequence_alloc
CREATE TABLE sj_sequence_alloc
(
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name varchar(64) NOT NULL DEFAULT '',
max_id bigint NOT NULL DEFAULT 1,
step int NOT NULL DEFAULT 100,
update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX uk_sj_sequence_alloc_01 ON sj_sequence_alloc (namespace_id, group_name);
COMMENT ON COLUMN sj_sequence_alloc.id IS '主键';
COMMENT ON COLUMN sj_sequence_alloc.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_sequence_alloc.group_name IS '组名称';
COMMENT ON COLUMN sj_sequence_alloc.max_id IS '最大id';
COMMENT ON COLUMN sj_sequence_alloc.step IS '步长';
COMMENT ON COLUMN sj_sequence_alloc.update_dt IS '更新时间';
COMMENT ON TABLE sj_sequence_alloc IS '号段模式序号ID分配表';
-- sj_job
CREATE TABLE sj_job
(
@@ -451,8 +455,7 @@ CREATE TABLE sj_job
bucket_index int NOT NULL DEFAULT 0,
resident smallint NOT NULL DEFAULT 0,
notify_ids varchar(128) NOT NULL DEFAULT '',
owner_id bigint NULL DEFAULT NULL,
labels varchar(512) NULL DEFAULT '',
owner_id bigint NULL,
description varchar(256) NOT NULL DEFAULT '',
ext_attrs varchar(256) NULL DEFAULT '',
deleted smallint NOT NULL DEFAULT 0,
@@ -487,7 +490,6 @@ COMMENT ON COLUMN sj_job.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_job.resident IS '是否是常驻任务';
COMMENT ON COLUMN sj_job.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_job.owner_id IS '负责人id';
COMMENT ON COLUMN sj_job.labels IS '标签';
COMMENT ON COLUMN sj_job.description IS '描述';
COMMENT ON COLUMN sj_job.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_job.deleted IS '逻辑删除 1、删除';
@@ -714,7 +716,6 @@ CREATE TABLE sj_workflow
notify_ids varchar(128) NOT NULL DEFAULT '',
bucket_index int NOT NULL DEFAULT 0,
version int NOT NULL,
owner_id bigint NULL DEFAULT NULL,
ext_attrs varchar(256) NULL DEFAULT '',
deleted smallint NOT NULL DEFAULT 0,
create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -740,7 +741,6 @@ COMMENT ON COLUMN sj_workflow.wf_context IS '上下文';
COMMENT ON COLUMN sj_workflow.notify_ids IS '通知告警场景配置id列表';
COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket';
COMMENT ON COLUMN sj_workflow.version IS '版本号';
COMMENT ON COLUMN sj_workflow.owner_id IS '负责人id';
COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段';
COMMENT ON COLUMN sj_workflow.deleted IS '逻辑删除 1、删除';
COMMENT ON COLUMN sj_workflow.create_dt IS '创建时间';
@@ -828,28 +828,4 @@ COMMENT ON COLUMN sj_workflow_task_batch.version IS '版本号';
COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '逻辑删除 1、删除';
COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '创建时间';
COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '修改时间';
COMMENT ON TABLE sj_workflow_task_batch IS '工作流批次';
-- sj_job_executor
CREATE TABLE sj_job_executor
(
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name varchar(64) NOT NULL,
executor_info varchar(256) NOT NULL,
executor_type varchar(3) NOT NULL,
create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_sj_job_executor_01 ON sj_job_executor (namespace_id, group_name);
CREATE INDEX idx_sj_job_executor_02 ON sj_job_executor (create_dt);
COMMENT ON COLUMN sj_job_executor.id IS '主键';
COMMENT ON COLUMN sj_job_executor.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_job_executor.group_name IS '组名称';
COMMENT ON COLUMN sj_job_executor.executor_info IS '任务执行器名称';
COMMENT ON COLUMN sj_job_executor.executor_type IS '1:java 2:python 3:go';
COMMENT ON COLUMN sj_job_executor.create_dt IS '创建时间';
COMMENT ON COLUMN sj_job_executor.update_dt IS '修改时间';
COMMENT ON TABLE sj_job_executor IS '任务执行器信息';
COMMENT ON TABLE sj_workflow_task_batch IS '工作流批次';

View File

@@ -2,7 +2,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.admin</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-admin</artifactId>
<version>${revision}</version>
</parent>

View File

@@ -4,12 +4,12 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.admin</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-admin</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-system</artifactId>
<artifactId>continew-module-system</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
@@ -18,7 +18,7 @@
<dependencies>
<!-- 公共模块 -->
<dependency>
<groupId>top.continew.admin</groupId>
<groupId>top.continew</groupId>
<artifactId>continew-common</artifactId>
</dependency>

View File

@@ -24,7 +24,6 @@ import jakarta.servlet.http.HttpServletRequest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import top.continew.admin.auth.model.req.LoginReq;
import top.continew.admin.auth.model.resp.LoginResp;
import top.continew.admin.common.context.RoleContext;
import top.continew.admin.common.context.UserContext;
import top.continew.admin.common.context.UserContextHolder;
@@ -37,13 +36,10 @@ import top.continew.admin.system.service.DeptService;
import top.continew.admin.system.service.OptionService;
import top.continew.admin.system.service.RoleService;
import top.continew.admin.system.service.UserService;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.core.validation.Validator;
import top.continew.starter.core.util.ServletUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.Validator;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
import top.continew.starter.extension.tenant.util.TenantUtils;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -89,28 +85,17 @@ public abstract class AbstractLoginHandler<T extends LoginReq> implements LoginH
*
* @param user 用户信息
* @param client 客户端信息
* @return 登录响应参数
* @return token 令牌信息
*/
protected LoginResp authenticate(UserDO user, ClientResp client) {
protected String authenticate(UserDO user, ClientResp client) {
// 获取权限角色密码过期天数
Long userId = user.getId();
Long tenantId = TenantContextHolder.getTenantId();
CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> {
Set<String> permissions = new HashSet<>();
TenantUtils.execute(tenantId, () -> {
permissions.addAll(roleService.listPermissionByUserId(userId));
});
return permissions;
}, threadPoolTaskExecutor);
CompletableFuture<Set<RoleContext>> roleFuture = CompletableFuture.supplyAsync(() -> {
Set<RoleContext> roles = new HashSet<>();
TenantUtils.execute(tenantId, () -> {
roles.addAll(roleService.listByUserId(userId));
});
return roles;
}, threadPoolTaskExecutor);
CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> roleService
.listPermissionByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Set<RoleContext>> roleFuture = CompletableFuture.supplyAsync(() -> roleService
.listByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()), threadPoolTaskExecutor);
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture);
UserContext userContext = new UserContext(permissionFuture.join(), roleFuture
.join(), passwordExpirationDaysFuture.join());
@@ -123,15 +108,11 @@ public abstract class AbstractLoginHandler<T extends LoginReq> implements LoginH
userContext.setClientType(client.getClientType());
loginParameter.setExtra(CLIENT_ID, client.getClientId());
userContext.setClientId(client.getClientId());
userContext.setTenantId(tenantId);
// 登录并缓存用户信息
StpUtil.login(userContext.getId(), loginParameter.setExtraData(BeanUtil
.beanToMap(new UserExtraContext(ServletUtils.getRequest()))));
UserContextHolder.setContext(userContext);
return LoginResp.builder()
.token(StpUtil.getTokenValue())
.tenantId(TenantContextHolder.isTenantEnabled() ? TenantContextHolder.getTenantId() : null)
.build();
return StpUtil.getTokenValue();
}
/**

View File

@@ -30,14 +30,15 @@ import top.continew.admin.auth.enums.AuthTypeEnum;
import top.continew.admin.auth.model.req.AccountLoginReq;
import top.continew.admin.auth.model.resp.LoginResp;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.constant.GlobalConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.system.enums.PasswordPolicyEnum;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.model.resp.ClientResp;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.core.validation.ValidationUtils;
import java.time.Duration;
@@ -57,18 +58,20 @@ public class AccountLoginHandler extends AbstractLoginHandler<AccountLoginReq> {
@Override
public LoginResp login(AccountLoginReq req, ClientResp client, HttpServletRequest request) {
// 解密密码
String password = SecureUtils.decryptPasswordByRsaPrivateKey(req.getPassword(), "密码解密失败");
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfBlank(rawPassword, "密码解密失败");
// 验证用户名密码
String username = req.getUsername();
UserDO user = userService.getByUsername(username);
boolean isError = ObjectUtil.isNull(user) || !passwordEncoder.matches(password, user.getPassword());
boolean isError = ObjectUtil.isNull(user) || !passwordEncoder.matches(rawPassword, user.getPassword());
// 检查账号锁定状态
this.checkUserLocked(req.getUsername(), request, isError);
ValidationUtils.throwIf(isError, "用户名或密码不正确");
// 检查用户状态
super.checkUserStatus(user);
// 执行认证
return super.authenticate(user, client);
String token = this.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override
@@ -76,7 +79,7 @@ public class AccountLoginHandler extends AbstractLoginHandler<AccountLoginReq> {
super.preLogin(req, client, request);
// 校验验证码
int loginCaptchaEnabled = optionService.getValueByCode2Int("LOGIN_CAPTCHA_ENABLED");
if (GlobalConstants.Boolean.YES.equals(loginCaptchaEnabled)) {
if (SysConstants.YES.equals(loginCaptchaEnabled)) {
ValidationUtils.throwIfBlank(req.getCaptcha(), "验证码不能为空");
ValidationUtils.throwIfBlank(req.getUuid(), "验证码标识不能为空");
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + req.getUuid();
@@ -102,7 +105,7 @@ public class AccountLoginHandler extends AbstractLoginHandler<AccountLoginReq> {
private void checkUserLocked(String username, HttpServletRequest request, boolean isError) {
// 不锁定
int maxErrorCount = optionService.getValueByCode2Int(PasswordPolicyEnum.PASSWORD_ERROR_LOCK_COUNT.name());
if (maxErrorCount <= GlobalConstants.Boolean.NO) {
if (maxErrorCount <= SysConstants.NO) {
return;
}
// 检测是否已被锁定

View File

@@ -26,7 +26,7 @@ import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.model.resp.ClientResp;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.core.validation.ValidationUtils;
/**
* 邮箱登录处理器
@@ -46,7 +46,8 @@ public class EmailLoginHandler extends AbstractLoginHandler<EmailLoginReq> {
// 检查用户状态
super.checkUserStatus(user);
// 执行认证
return super.authenticate(user, client);
String token = super.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override

View File

@@ -26,7 +26,7 @@ import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.model.resp.ClientResp;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.core.validation.ValidationUtils;
/**
* 手机号登录处理器
@@ -46,7 +46,8 @@ public class PhoneLoginHandler extends AbstractLoginHandler<PhoneLoginReq> {
// 检查用户状态
super.checkUserStatus(user);
// 执行认证
return super.authenticate(user, client);
String token = super.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override

View File

@@ -38,10 +38,9 @@ import top.continew.admin.auth.enums.AuthTypeEnum;
import top.continew.admin.auth.model.req.SocialLoginReq;
import top.continew.admin.auth.model.resp.LoginResp;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.enums.GenderEnum;
import top.continew.admin.common.enums.RoleCodeEnum;
import top.continew.admin.system.constant.SystemConstants;
import top.continew.admin.system.enums.MessageTemplateEnum;
import top.continew.admin.system.enums.MessageTypeEnum;
import top.continew.admin.system.model.entity.user.UserDO;
@@ -51,9 +50,9 @@ import top.continew.admin.system.model.resp.ClientResp;
import top.continew.admin.system.service.MessageService;
import top.continew.admin.system.service.UserRoleService;
import top.continew.admin.system.service.UserSocialService;
import top.continew.starter.core.autoconfigure.application.ApplicationProperties;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.core.validation.ValidationUtils;
import java.time.LocalDateTime;
import java.util.Collections;
@@ -73,7 +72,7 @@ public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
private final UserSocialService userSocialService;
private final UserRoleService userRoleService;
private final MessageService messageService;
private final ApplicationProperties applicationProperties;
private final ProjectProperties projectProperties;
@Override
public LoginResp login(SocialLoginReq req, ClientResp client, HttpServletRequest request) {
@@ -108,12 +107,11 @@ public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
user.setGender(GenderEnum.valueOf(authUser.getGender().name()));
}
user.setAvatar(authUser.getAvatar());
user.setDeptId(SystemConstants.SUPER_DEPT_ID);
user.setDeptId(SysConstants.SUPER_DEPT_ID);
user.setStatus(DisEnableStatusEnum.ENABLE);
userService.save(user);
Long userId = user.getId();
userRoleService.assignRolesToUser(Collections.singletonList(roleService
.getIdByCode(RoleCodeEnum.GENERAL_USER.getCode())), userId);
userRoleService.assignRolesToUser(Collections.singletonList(SysConstants.GENERAL_ROLE_ID), userId);
userSocial = new UserSocialDO();
userSocial.setUserId(userId);
userSocial.setSource(source);
@@ -128,7 +126,8 @@ public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
userSocial.setLastLoginTime(LocalDateTime.now());
userSocialService.saveOrUpdate(userSocial);
// 执行认证
return super.authenticate(user, client);
String token = super.authenticate(user, client);
return LoginResp.builder().token(token).build();
}
@Override
@@ -167,7 +166,7 @@ public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
private void sendSecurityMsg(UserDO user) {
MessageTemplateEnum template = MessageTemplateEnum.SOCIAL_REGISTER;
MessageReq req = new MessageReq(MessageTypeEnum.SECURITY);
req.setTitle(template.getTitle().formatted(applicationProperties.getName()));
req.setTitle(template.getTitle().formatted(projectProperties.getName()));
req.setContent(template.getContent().formatted(user.getNickname()));
messageService.add(req, CollUtil.toList(user.getId().toString()));
}

View File

@@ -43,9 +43,9 @@ public class AccountLoginReq extends LoginReq {
private String username;
/**
* 密码
* 密码加密
*/
@Schema(description = "密码", example = "RSA 公钥加密的密码")
@Schema(description = "密码(加密)", example = "HHwZoiBwCfh0xLdWOAd0bHOkEZlIMMOQKJyeFUw9T3ArrhL57od2i42s1o0sSXKkeHPJXvQsninhPFH2lArDDQ==")
@NotBlank(message = "密码不能为空")
private String password;

View File

@@ -20,7 +20,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import top.continew.starter.validation.constraints.Mobile;
import top.continew.starter.core.validation.constraints.Mobile;
import java.io.Serial;

View File

@@ -42,10 +42,4 @@ public class LoginResp implements Serializable {
*/
@Schema(description = "令牌", example = "eyJ0eXAiOiJlV1QiLCJhbGciqiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb29pbiIsImxvZ2luSWQiOjEsInJuU3RyIjoiSjd4SUljYnU5cmNwU09vQ3Uyc1ND1BYYTYycFRjcjAifQ.KUPOYm-2wfuLUSfEEAbpGE527fzmkAJG7sMNcQ0pUZ8")
private String token;
/**
* 租户 ID
*/
@Schema(description = "租户 ID", example = "0")
private Long tenantId;
}

View File

@@ -27,7 +27,6 @@ import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
/**
@@ -141,12 +140,6 @@ public class UserInfoResp implements Serializable {
@Schema(description = "角色编码集合", example = "[\"test\"]")
private Set<String> roles;
/**
* 角色名称列表
*/
@Schema(description = "角色名称列表", example = "测试人员")
private List<String> roleNames;
public LocalDate getRegistrationDate() {
return createTime.toLocalDate();
}

View File

@@ -31,16 +31,16 @@ import top.continew.admin.auth.model.req.LoginReq;
import top.continew.admin.auth.model.resp.LoginResp;
import top.continew.admin.auth.model.resp.RouteResp;
import top.continew.admin.auth.service.AuthService;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.context.RoleContext;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.constant.SystemConstants;
import top.continew.admin.system.enums.MenuTypeEnum;
import top.continew.admin.system.model.resp.ClientResp;
import top.continew.admin.system.model.resp.MenuResp;
import top.continew.admin.system.service.ClientService;
import top.continew.admin.system.service.MenuService;
import top.continew.admin.system.service.RoleService;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
@@ -93,8 +93,8 @@ public class AuthServiceImpl implements AuthService {
}
// 查询菜单列表
Set<MenuResp> menuSet = new LinkedHashSet<>();
if (roleSet.stream().anyMatch(r -> SystemConstants.SUPER_ADMIN_ROLE_ID.equals(r.getId()))) {
menuSet.addAll(menuService.listByRoleId(SystemConstants.SUPER_ADMIN_ROLE_ID));
if (roleSet.stream().anyMatch(r -> SysConstants.SUPER_ROLE_ID.equals(r.getId()))) {
menuSet.addAll(menuService.listByRoleId(SysConstants.SUPER_ROLE_ID));
} else {
roleSet.forEach(r -> menuSet.addAll(menuService.listByRoleId(r.getId())));
}

View File

@@ -35,7 +35,6 @@ import top.continew.admin.common.context.UserExtraContext;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
import java.time.LocalDateTime;
import java.util.*;
@@ -63,24 +62,13 @@ public class OnlineUserServiceImpl implements OnlineUserService {
List<OnlineUserResp> list = new ArrayList<>();
// 查询所有在线 Token
List<String> tokenKeyList = StpUtil.searchTokenValue(StringConstants.EMPTY, 0, -1, false);
Map<Long, List<String>> tokenMap = tokenKeyList.stream()
// 提前映射避免重复调用
Map<Long, List<String>> tokenMap = tokenKeyList.stream().filter(tokenKey -> {
String token = StrUtil.subAfter(tokenKey, StringConstants.COLON, true);
// 忽略已过期或失效 Token
return StpUtil.getStpLogic().getTokenActiveTimeoutByToken(token) >= SaTokenDao.NEVER_EXPIRE;
})
.map(tokenKey -> StrUtil.subAfter(tokenKey, StringConstants.COLON, true))
.map(token -> {
Object loginIdObj = StpUtil.getLoginIdByToken(token);
long tokenTimeout = StpUtil.getStpLogic().getTokenActiveTimeoutByToken(token);
// 将相关信息打包成对象或简单的Entry对便于后续过滤与归类
return new AbstractMap.SimpleEntry<>(token, new AbstractMap.SimpleEntry<>(loginIdObj, tokenTimeout));
})
// 过滤出未过期且loginId存在的Token
.filter(entry -> {
Object loginIdObj = entry.getValue().getKey();
long tokenTimeout = entry.getValue().getValue();
return loginIdObj != null && tokenTimeout >= SaTokenDao.NEVER_EXPIRE;
})
// 此时数据都有效进行收集
.collect(Collectors.groupingBy(entry -> Convert.toLong(entry.getValue().getKey()), Collectors
.mapping(AbstractMap.SimpleEntry::getKey, Collectors.toList())));
.collect(Collectors.groupingBy(token -> Convert.toLong(StpUtil.getLoginIdByToken(token))));
// 筛选数据
for (Map.Entry<Long, List<String>> entry : tokenMap.entrySet()) {
Long userId = entry.getKey();
@@ -89,12 +77,6 @@ public class OnlineUserServiceImpl implements OnlineUserService {
.isMatchClientId(query.getClientId(), userContext)) {
continue;
}
// 只显示本租户数据
if (TenantContextHolder.isTenantEnabled()) {
if (!TenantContextHolder.getTenantId().equals(userContext.getTenantId())) {
continue;
}
}
List<LocalDateTime> loginTimeList = query.getLoginTime();
entry.getValue().parallelStream().forEach(token -> {
UserExtraContext extraContext = UserContextHolder.getExtraContext(token);

View File

@@ -32,7 +32,6 @@ import top.continew.admin.system.mapper.StorageMapper;
import top.continew.admin.system.model.entity.FileDO;
import top.continew.admin.system.model.entity.StorageDO;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.core.util.URLUtils;
import java.util.List;
@@ -129,7 +128,7 @@ public class FileRecorderImpl implements FileRecorder {
return list.get(0);
}
// 结合存储配置进行匹配
List<StorageDO> storageList = storageMapper.selectByIds(CollUtils.mapToList(list, FileDO::getStorageId));
List<StorageDO> storageList = storageMapper.selectByIds(list.stream().map(FileDO::getStorageId).toList());
Map<Long, StorageDO> storageMap = storageList.stream()
.collect(Collectors.toMap(StorageDO::getId, storage -> storage));
return list.stream().filter(file -> {

View File

@@ -20,7 +20,7 @@ import cn.hutool.core.map.MapUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import top.continew.admin.common.constant.GlobalConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.system.enums.OptionCategoryEnum;
import top.continew.admin.system.service.OptionService;
import top.continew.starter.messaging.mail.core.MailConfig;
@@ -52,7 +52,7 @@ public class MailConfigurerImpl implements MailConfigurer {
mailConfig.setPort(MapUtil.getInt(map, "MAIL_PORT"));
mailConfig.setUsername(MapUtil.getStr(map, "MAIL_USERNAME"));
mailConfig.setPassword(MapUtil.getStr(map, "MAIL_PASSWORD"));
mailConfig.setSslEnabled(GlobalConstants.Boolean.YES.equals(MapUtil.getInt(map, "MAIL_SSL_ENABLED")));
mailConfig.setSslEnabled(SysConstants.YES.equals(MapUtil.getInt(map, "MAIL_SSL_ENABLED")));
if (mailConfig.isSslEnabled()) {
mailConfig.setSslPort(MapUtil.getInt(map, "MAIL_SSL_PORT"));
}

View File

@@ -27,7 +27,6 @@ import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.model.query.SmsConfigQuery;
import top.continew.admin.system.model.resp.SmsConfigResp;
import top.continew.admin.system.service.SmsConfigService;
import top.continew.starter.core.util.CollUtils;
import java.util.List;
@@ -62,6 +61,6 @@ public class SmsReadConfigDatabaseImpl implements SmsReadConfig {
if (CollUtil.isEmpty(list)) {
return List.of();
}
return CollUtils.mapToList(list, SmsConfigUtil::from);
return list.stream().map(SmsConfigUtil::from).toList();
}
}

View File

@@ -27,7 +27,7 @@ import java.util.List;
* 数据导入策略
*
* @author Kils
* @since 2024/6/17 18:33
* @since 2024-06-17 18:33
*/
@Getter
@RequiredArgsConstructor

View File

@@ -25,11 +25,11 @@ import cn.hutool.extra.spring.SpringUtil;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.admin.common.constant.GlobalConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.service.OptionService;
import top.continew.admin.system.service.UserPasswordHistoryService;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.core.validation.ValidationUtils;
import java.util.Map;
@@ -47,7 +47,7 @@ public enum PasswordPolicyEnum {
/**
* 密码错误锁定阈值
*/
PASSWORD_ERROR_LOCK_COUNT("密码错误锁定阈值取值范围为 %d-%d", GlobalConstants.Boolean.NO, 10, "由于您连续 %s 次输入错误密码,账号已被锁定 %s 分钟,预计解锁时间为 %s请稍后再试"),
PASSWORD_ERROR_LOCK_COUNT("密码错误锁定阈值取值范围为 %d-%d", SysConstants.NO, 10, "由于您连续 %s 次输入错误密码,账号已被锁定 %s 分钟,预计解锁时间为 %s请稍后再试"),
/**
* 账号锁定时长分钟
@@ -57,12 +57,12 @@ public enum PasswordPolicyEnum {
/**
* 密码有效期
*/
PASSWORD_EXPIRATION_DAYS("密码有效期取值范围为 %d-%d 天", GlobalConstants.Boolean.NO, 999, null),
PASSWORD_EXPIRATION_DAYS("密码有效期取值范围为 %d-%d 天", SysConstants.NO, 999, null),
/**
* 密码到期提醒
*/
PASSWORD_EXPIRATION_WARNING_DAYS("密码到期提醒取值范围为 %d-%d 天", GlobalConstants.Boolean.NO, 998, null) {
PASSWORD_EXPIRATION_WARNING_DAYS("密码到期提醒取值范围为 %d-%d 天", SysConstants.NO, 998, null) {
@Override
public void validateRange(int value, Map<String, String> policyMap) {
if (CollUtil.isEmpty(policyMap)) {
@@ -72,7 +72,7 @@ public enum PasswordPolicyEnum {
Integer passwordExpirationDays = ObjectUtil.defaultIfNull(Convert.toInt(policyMap
.get(PASSWORD_EXPIRATION_DAYS.name())), SpringUtil.getBean(OptionService.class)
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
if (passwordExpirationDays > GlobalConstants.Boolean.NO) {
if (passwordExpirationDays > SysConstants.NO) {
ValidationUtils.throwIf(value >= passwordExpirationDays, "密码到期提醒时间应小于密码有效期");
return;
}
@@ -98,17 +98,16 @@ public enum PasswordPolicyEnum {
/**
* 密码是否必须包含特殊字符
*/
PASSWORD_REQUIRE_SYMBOLS("密码是否必须包含特殊字符取值只能为是(%d或否%d", GlobalConstants.Boolean.NO, GlobalConstants.Boolean.YES, "密码必须包含特殊字符") {
PASSWORD_REQUIRE_SYMBOLS("密码是否必须包含特殊字符取值只能为是(%d或否%d", SysConstants.NO, SysConstants.YES, "密码必须包含特殊字符") {
@Override
public void validateRange(int value, Map<String, String> policyMap) {
ValidationUtils.throwIf(value != GlobalConstants.Boolean.YES && value != GlobalConstants.Boolean.NO, this
.getDescription()
.formatted(GlobalConstants.Boolean.YES, GlobalConstants.Boolean.NO));
ValidationUtils.throwIf(value != SysConstants.YES && value != SysConstants.NO, this.getDescription()
.formatted(SysConstants.YES, SysConstants.NO));
}
@Override
public void validate(String password, int value, UserDO user) {
ValidationUtils.throwIf(value == GlobalConstants.Boolean.YES && !ReUtil
ValidationUtils.throwIf(value == SysConstants.YES && !ReUtil
.isMatch(RegexConstants.SPECIAL_CHARACTER, password), this.getMsg());
}
},
@@ -116,17 +115,16 @@ public enum PasswordPolicyEnum {
/**
* 密码是否允许包含用户名
*/
PASSWORD_ALLOW_CONTAIN_USERNAME("密码是否允许包含用户名取值只能为是(%d或否%d", GlobalConstants.Boolean.NO, GlobalConstants.Boolean.YES, "密码不允许包含正反序用户名") {
PASSWORD_ALLOW_CONTAIN_USERNAME("密码是否允许包含用户名取值只能为是(%d或否%d", SysConstants.NO, SysConstants.YES, "密码不允许包含正反序用户名") {
@Override
public void validateRange(int value, Map<String, String> policyMap) {
ValidationUtils.throwIf(value != GlobalConstants.Boolean.YES && value != GlobalConstants.Boolean.NO, this
.getDescription()
.formatted(GlobalConstants.Boolean.YES, GlobalConstants.Boolean.NO));
ValidationUtils.throwIf(value != SysConstants.YES && value != SysConstants.NO, this.getDescription()
.formatted(SysConstants.YES, SysConstants.NO));
}
@Override
public void validate(String password, int value, UserDO user) {
if (value <= GlobalConstants.Boolean.NO) {
if (value <= SysConstants.NO) {
String username = user.getUsername();
ValidationUtils.throwIf(StrUtil.containsAnyIgnoreCase(password, username, StrUtil
.reverse(username)), this.getMsg());

Some files were not shown because too many files have changed in this diff Show More