Ryuiso 3 долоо хоног өмнө
commit
fcc187fe11
100 өөрчлөгдсөн 7436 нэмэгдсэн , 0 устгасан
  1. 18 0
      .editorconfig
  2. 49 0
      .gitignore
  3. 20 0
      LICENSE
  4. 190 0
      README.md
  5. 31 0
      jk-admin/Dockerfile
  6. 157 0
      jk-admin/pom.xml
  7. 27 0
      jk-admin/src/main/java/org/jk/DromaraApplication.java
  8. 18 0
      jk-admin/src/main/java/org/jk/DromaraServletInitializer.java
  9. 274 0
      jk-admin/src/main/java/org/jk/web/controller/AuthController.java
  10. 157 0
      jk-admin/src/main/java/org/jk/web/controller/CaptchaController.java
  11. 28 0
      jk-admin/src/main/java/org/jk/web/controller/IndexController.java
  12. 25 0
      jk-admin/src/main/java/org/jk/web/domain/vo/CaptchaVo.java
  13. 25 0
      jk-admin/src/main/java/org/jk/web/domain/vo/LoginTenantVo.java
  14. 56 0
      jk-admin/src/main/java/org/jk/web/domain/vo/LoginVo.java
  15. 31 0
      jk-admin/src/main/java/org/jk/web/domain/vo/TenantListVo.java
  16. 163 0
      jk-admin/src/main/java/org/jk/web/listener/UserActionListener.java
  17. 45 0
      jk-admin/src/main/java/org/jk/web/service/IAuthStrategy.java
  18. 108 0
      jk-admin/src/main/java/org/jk/web/service/JkLoginService.java
  19. 254 0
      jk-admin/src/main/java/org/jk/web/service/SysLoginService.java
  20. 115 0
      jk-admin/src/main/java/org/jk/web/service/SysRegisterService.java
  21. 102 0
      jk-admin/src/main/java/org/jk/web/service/impl/EmailAuthStrategy.java
  22. 126 0
      jk-admin/src/main/java/org/jk/web/service/impl/PasswordAuthStrategy.java
  23. 102 0
      jk-admin/src/main/java/org/jk/web/service/impl/SmsAuthStrategy.java
  24. 119 0
      jk-admin/src/main/java/org/jk/web/service/impl/SocialAuthStrategy.java
  25. 111 0
      jk-admin/src/main/java/org/jk/web/service/impl/XcxAuthStrategy.java
  26. 273 0
      jk-admin/src/main/resources/application-dev.yml
  27. 274 0
      jk-admin/src/main/resources/application-prod.yml
  28. 382 0
      jk-admin/src/main/resources/application.yml
  29. 8 0
      jk-admin/src/main/resources/banner.txt
  30. 62 0
      jk-admin/src/main/resources/i18n/messages.properties
  31. 62 0
      jk-admin/src/main/resources/i18n/messages_en_US.properties
  32. 62 0
      jk-admin/src/main/resources/i18n/messages_zh_CN.properties
  33. BIN
      jk-admin/src/main/resources/ip2region.xdb
  34. 129 0
      jk-admin/src/main/resources/logback-plus.xml
  35. 45 0
      jk-admin/src/test/java/org/jk/test/AssertUnitTest.java
  36. 70 0
      jk-admin/src/test/java/org/jk/test/DemoUnitTest.java
  37. 72 0
      jk-admin/src/test/java/org/jk/test/ParamUnitTest.java
  38. 54 0
      jk-admin/src/test/java/org/jk/test/TagUnitTest.java
  39. 185 0
      jk-common/jk-common-bom/pom.xml
  40. 99 0
      jk-common/jk-common-core/pom.xml
  41. 17 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/config/ApplicationConfig.java
  42. 57 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/config/DeepseekConfig.java
  43. 34 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/config/JkConfig.java
  44. 112 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/config/ThreadPoolConfig.java
  45. 41 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/config/ValidatorConfig.java
  46. 30 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/config/properties/ThreadPoolProperties.java
  47. 30 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/CacheConstants.java
  48. 89 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/CacheNames.java
  49. 81 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/Constants.java
  50. 34 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/GlobalConstants.java
  51. 93 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/HttpStatus.java
  52. 59 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/RegexConstants.java
  53. 85 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/SystemConstants.java
  54. 35 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/TenantConstants.java
  55. 78 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/UserConstants.java
  56. 110 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/R.java
  57. 76 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/CompleteTaskDTO.java
  58. 36 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/DeptDTO.java
  59. 41 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/DictDataDTO.java
  60. 41 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/DictTypeDTO.java
  61. 30 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/FlowCopyDTO.java
  62. 45 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/FlowInstanceBizExtDTO.java
  63. 46 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/OssDTO.java
  64. 46 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/PostDTO.java
  65. 42 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/RoleDTO.java
  66. 63 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/StartProcessDTO.java
  67. 30 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/StartProcessReturnDTO.java
  68. 101 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/TaskAssigneeDTO.java
  69. 73 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/UserDTO.java
  70. 72 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/UserOnlineDTO.java
  71. 34 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/event/ProcessDeleteEvent.java
  72. 70 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/event/ProcessEvent.java
  73. 70 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/event/ProcessTaskEvent.java
  74. 31 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/EmailLoginBody.java
  75. 49 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/LoginBody.java
  76. 148 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/LoginUser.java
  77. 32 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/PasswordLoginBody.java
  78. 37 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/RegisterBody.java
  79. 29 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/SmsLoginBody.java
  80. 35 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/SocialLoginBody.java
  81. 56 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/TaskAssigneeBody.java
  82. 24 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/TreeStruct.java
  83. 28 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/XcxLoginBody.java
  84. 27 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/XcxLoginUser.java
  85. 30 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/AppTypeEnum.java
  86. 215 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/BusinessStatusEnum.java
  87. 39 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/DeviceType.java
  88. 146 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/FormatsType.java
  89. 28 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/IconImg.java
  90. 44 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/LoginType.java
  91. 37 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/ProjectTypeEnum.java
  92. 30 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/UserStatus.java
  93. 39 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/UserType.java
  94. 43 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/UserTypeEnum.java
  95. 67 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/ServiceException.java
  96. 62 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/SseException.java
  97. 74 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/base/BaseException.java
  98. 21 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/file/FileException.java
  99. 18 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/file/FileNameLengthLimitExceededException.java
  100. 18 0
      jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/file/FileSizeLimitExceededException.java

+ 18 - 0
.editorconfig

@@ -0,0 +1,18 @@
+# http://editorconfig.org
+root = true
+
+# 空格替代Tab缩进在各种编辑工具下效果一致
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{json,yml,yaml}]
+indent_size = 2
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false

+ 49 - 0
.gitignore

@@ -0,0 +1,49 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.log.gz
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
+
+.flattened-pom.xml

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2019 RuoYi-Vue-Plus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 190 - 0
README.md

@@ -0,0 +1,190 @@
+<img src="https://foruda.gitee.com/images/1679673773341074847/178e8451_1766278.png" width="50%" height="50%">
+<div style="height: 10px; clear: both;"></div>
+
+- - -
+## 平台简介
+
+[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
+[![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
+[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
+[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
+<br>
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.5.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
+[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
+[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
+
+> Dromara RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
+
+> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
+活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源
+
+> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
+
+> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
+> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)<br>
+> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
+> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
+
+> 文档地址: [plus-doc](https://plus-doc.dromara.org) 国内加速: [plus-doc.top](https://plus-doc.top)
+
+## 赞助商
+
+MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
+CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
+数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
+引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
+<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
+Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
+aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
+Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
+
+[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
+
+# 本框架与RuoYi的功能差异
+
+| 功能          | 本框架                                                                                                               | RuoYi                                                                              |
+|-------------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
+| 前端项目        | 采用 Vue3 + TS + ElementPlus 重写                                                                                     | 基于Vue2/Vue3 + JS                                                                   | 
+| 后端项目结构      | 采用插件化 + 扩展包形式 结构解耦 易于扩展                                                                                           | 模块相互注入耦合严重难以扩展                                                                     | 
+| 后端代码风格      | 严格遵守Alibaba规范与项目统一配置的代码格式化                                                                                        | 代码书写与常规结构不同阅读障碍大                                                                   |
+| Web容器       | 采用 Undertow 基于 XNIO 的高性能容器                                                                                        | 采用 Tomcat                                                                          |
+| 权限认证        | 采用 Sa-Token、Jwt 静态使用功能齐全 低耦合 高扩展                                                                                  | Spring Security 配置繁琐扩展性极差                                                          |
+| 权限注解        | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式        | 只支持是否存在匹配                                                                          |
+| 三方鉴权        | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证                                                                               | 无                                                                                  |
+| 关系数据库支持     | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例)      | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换                                                    |
+| 缓存数据库       | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列                                                                             | Redis 简单 get set 支持                                                                |
+| Redis客户端    | 采用 Redisson Redis官方推荐 基于Netty的客户端工具<br/>支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan<br/>支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐<br/>连接池采用 common-pool Bug多经常性出问题              |
+| 缓存注解        | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能<br/>例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存                                      | 需手动编写Redis代码逻辑                                                                     |
+| ORM框架       | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多<br/>例如多租户插件 分页插件 乐观锁插件等等                                             | 采用 Mybatis 基于XML需要手写SQL                                                            |
+| SQL监控       | 采用 p6spy 可输出完整SQL与执行时间监控                                                                                          | log输出 需手动拼接sql与参数无法快速查看调试问题                                                        |
+| 数据分页        | 采用 Mybatis-Plus 分页插件<br/>框架对其进行了扩展 对象化分页对象 支持多种方式传参 支持前端多排序 复杂排序                                                  | 采用 PageHelper 仅支持单查询分页 参数只能从param传 只能单排序 功能扩展性差 体验不好                               |
+| 数据权限        | 采用 Mybatis-Plus 插件 自行分析拼接SQL 无感式过滤<br/>只需为Mapper设置好注解条件 支持多种自定义 不限于部门角色                                           | 采用 注解+aop 实现 基于部门角色 生成的sql兼容性差 不支持其他业务扩展<br/>生成sql后需手动拼接到具体业务sql上 对于多个Mapper查询不起作用 |
+| 数据脱敏        | 采用 注解 + jackson 序列化期间脱敏 支持不同模块不同的脱敏条件<br/>支持多种策略 如身份证、手机号、地址、邮箱、银行卡等 可自行扩展                                        | 无                                                                                  |
+| 数据加解密       | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密<br/>支持多种策略 如BASE64、AES、RSA、SM2、SM4等                                              | 无                                                                                  |
+| 接口传输加密      | 采用 动态 AES + RSA 加密请求 body 每一次请求秘钥都不同大幅度降低可破解性                                                                     | 无                                                                                  |
+| 数据翻译        | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译<br/>支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现                   | 无                                                                                  |
+| 多数据源框架      | 采用 dynamic-datasource 支持市面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源            | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差                                                     |
+| 多数据源事务      | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚                                                                          | 不支持                                                                                |
+| 数据库连接池      | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下                                                                        | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般                                               |
+| 数据库主键       | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁                                                                  | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一                                                     |
+| WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物                                                         | 无                                                                                  |
+| SSE推送       | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步                                                                               | 无                                                                                  |
+| 序列化         | 采用 Jackson Spring官方内置序列化 靠谱!!!                                                                                    | 采用 fastjson bugjson 远近闻名                                                           | 
+| 分布式幂等       | 参考美团GTIS防重系统简化实现(细节可看文档)                                                                                          | 手动编写注解基于aop实现                                                                      |
+| 分布式锁        | 采用 Lock4j 底层基于 Redisson                                                                                           | 无                                                                                  |
+| 分布式任务调度     | 采用 SnailJob 天生支持分布式 统一的管理中心 支持多种数据库 支持分片重试DAG任务流等                                                                 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造                                                   | 
+| 文件存储        | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储                                                     | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应                                                    |
+| 云存储         | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家                                                                          | 不支持                                                                                |
+| 短信          | 采用 sms4j 短信融合包 支持数十种短信厂家 只需在yml配置好厂家密钥即可使用 可多厂家共用                                                                 | 不支持                                                                                |
+| 邮件          | 采用 mail-api 通用协议支持大部分邮件厂商                                                                                         | 不支持                                                                                |
+| 接口文档        | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了                                                     | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成                                                | 
+| 校验框架        | 采用 Validation 支持注解与工具类校验 注解支持国际化                                                                                  | 仅支持注解 且注解不支持国际化                                                                    |
+| Excel框架     | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                   | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           |
+| 工作流支持       | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能                                                                                   | 无                                                                                  |
+| 工具类框架       | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码                                                       | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等                                            | 
+| 监控框架        | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控                                    | 无                                                                                  | 
+| 链路追踪        | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗<br/>用了它即可实时查看请求经过的每一处每一个节点                                            | 无                                                                                  |
+| 代码生成器       | 只需设计好表结构 一键生成所有crud代码与页面<br/>降低80%的开发量 把精力都投入到业务设计上<br/>框架为其适配MP、SpringDoc规范化代码 同时支持动态多数据源代码生成                    | 代码生成原生结构 只支持单数据源生成                                                                 |
+| 部署方式        | 支持 Docker 编排 一键搭建所有环境 让开发人员从此不再为搭建环境而烦恼                                                                           | 原生jar部署 其他环境需手动下载安装 自行搭建                                                           | 
+| 项目路径修改      | 提供详细的修改方案文档 并为其做了一些改动 非常简单即可修改成自己想要的                                                                              | 需要做很多改造 文档说明有限                                                                     |
+| 国际化         | 基于请求头动态返回不同语种的文本内容 开发难度低 有对应的工具类 支持大部分注解内容国际化                                                                     | 只提供基础功能 其他需自行编写扩展                                                                  |
+| 代码单例测试      | 提供单例测试 使用方式编写方法与maven多环境单测插件                                                                                      | 只提供基础功能 其他需自行编写扩展                                                                  |
+| Demo案例      | 提供框架功能的实际使用案例 单独一个模块提供了很多很全                                                                                       | 无                                                                                  |
+
+
+## 本框架与RuoYi的业务差异
+
+| 业务     | 功能说明                                                                 | 本框架 | RuoYi            |
+|--------|----------------------------------------------------------------------|-----|------------------|
+| 租户管理   | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等                                      | 支持  | 无                |
+| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等                                          | 支持  | 无                |
+| 客户端管理  | 系统内对接的所有客户端管理 如: pc端、小程序端等<br>支持动态授权登录方式 如: 短信登录、密码登录等 支持动态控制token时效 | 支持  | 无                |
+| 用户管理   | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等                                       | 支持  | 支持               |
+| 部门管理   | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限                                       | 支持  | 支持               |
+| 岗位管理   | 配置系统用户所属担任职务                                                         | 支持  | 支持               |
+| 菜单管理   | 配置系统菜单、操作权限、按钮权限标识等                                                  | 支持  | 支持               |
+| 角色管理   | 角色菜单权限分配、设置角色按机构进行数据范围权限划分                                           | 支持  | 支持               |
+| 字典管理   | 对系统中经常使用的一些较为固定的数据进行维护                                               | 支持  | 支持               |
+| 参数管理   | 对系统动态配置常用参数                                                          | 支持  | 支持               |
+| 通知公告   | 系统通知公告信息发布维护                                                         | 支持  | 支持               |
+| 操作日志   | 系统正常操作日志记录和查询 系统异常信息日志记录和查询                                          | 支持  | 支持               |
+| 登录日志   | 系统登录日志记录查询包含登录异常                                                     | 支持  | 支持               |
+| 文件管理   | 系统文件展示、上传、下载、删除等管理                                                   | 支持  | 无                |
+| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理                                       | 支持  | 无                |
+| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作                                                | 支持  | 支持               |
+| 定时任务   | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等                                      | 支持  | 仅支持任务与日志管理       |
+| 代码生成   | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载                              | 支持  | 仅支持单数据源          |
+| 系统接口   | 根据业务代码自动生成相关的api接口文档                                                 | 支持  | 支持               |
+| 服务监控   | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等                                  | 支持  | 仅支持单机CPU、内存、磁盘监控 |
+| 缓存监控   | 对系统的缓存信息查询,命令统计等。                                                    | 支持  | 支持               |
+| 使用案例   | 系统的一些功能案例                                                            | 支持  | 不支持              |
+
+## 参考文档
+
+使用框架前请仔细阅读文档重点注意事项
+<br>
+>[初始化项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
+>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/init)
+>
+>[专栏与视频 入门必看](https://plus-doc.dromara.org/#/common/column)
+>>[https://plus-doc.dromara.org/#/common/column](https://plus-doc.dromara.org/#/common/column)
+>
+>[部署项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
+>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
+>
+>[如何加群](https://plus-doc.dromara.org/#/common/add_group)
+>>[https://plus-doc.dromara.org/#/common/add_group](https://plus-doc.dromara.org/#/common/add_group)
+>
+>[参考文档 Wiki](https://plus-doc.dromara.org)
+>>[https://plus-doc.dromara.org](https://plus-doc.dromara.org)
+
+## 软件架构图
+
+![Plus部署架构图](https://foruda.gitee.com/images/1678981882624240692/ae2a3f3e_1766278.png "Plus部署架构图.png")
+
+## 如何参与贡献
+
+[参与贡献的方式 https://plus-doc.dromara.org/#/common/contribution](https://plus-doc.dromara.org/#/common/contribution)
+
+## 捐献作者
+作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭  
+<img src="https://foruda.gitee.com/images/1678975784848381069/d8661ed9_1766278.png" width="300px" height="450px" />
+<img src="https://foruda.gitee.com/images/1678975801230205215/6f96229d_1766278.png" width="300px" height="450px" />
+
+## 演示图例
+
+|                                                                                            |                                                                                            |
+|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
+| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1735829153637063344/3c21fd4c_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1735829181303499815/4522cefa_1419627.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1735829377205259767/76a705d7_1419627.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1722959592856812900/e2d0d342_1419627.png "屏幕截图") |
+| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |
+
+
+
+
+
+
+
+
+
+
+
+

+ 31 - 0
jk-admin/Dockerfile

@@ -0,0 +1,31 @@
+# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
+FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
+#FROM findepi/graalvm:java17-native
+
+LABEL maintainer="Lion Li"
+
+RUN mkdir -p /jk/server/logs \
+    /jk/server/temp \
+    /jk/skywalking/agent
+
+WORKDIR /jk/server
+
+ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+
+EXPOSE ${SERVER_PORT}
+# 暴露 snail job 客户端端口 用于定时任务调度中心通信
+EXPOSE ${SNAIL_PORT}
+
+ADD ./target/jk-admin.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
+           -Dsnail-job.port=${SNAIL_PORT} \
+           # 应用名称 如果想区分集群节点监控 改成不同的名称即可
+           #-Dskywalking.agent.service_name=jk-server \
+           #-javaagent:/jk/skywalking/agent/skywalking-agent.jar \
+           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+           -jar app.jar
+

+ 157 - 0
jk-admin/pom.xml

@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>JK-RAG-BACKEND</artifactId>
+        <groupId>org.jk</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>jk-admin</artifactId>
+
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+
+<!--        &lt;!&ndash; mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 &ndash;&gt;-->
+<!--        &lt;!&ndash; Oracle &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>com.oracle.database.jdbc</groupId>-->
+<!--            <artifactId>ojdbc8</artifactId>-->
+<!--        </dependency>-->
+<!--        &lt;!&ndash; 兼容oracle低版本 &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>com.oracle.database.nls</groupId>-->
+<!--            <artifactId>orai18n</artifactId>-->
+<!--        </dependency>-->
+<!--        &lt;!&ndash; PostgreSql &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>org.postgresql</groupId>-->
+<!--            <artifactId>postgresql</artifactId>-->
+<!--        </dependency>-->
+<!--        &lt;!&ndash; SqlServer &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>com.microsoft.sqlserver</groupId>-->
+<!--            <artifactId>mssql-jdbc</artifactId>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-common-doc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-common-social</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-common-ratelimiter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-common-mail</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-system</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-job</artifactId>
+        </dependency>
+
+        <!-- 代码生成-->
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-generator</artifactId>
+        </dependency>
+
+        <!--  demo模块  -->
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-demo</artifactId>
+        </dependency>
+
+        <!--  工作流模块  -->
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-workflow</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jk</groupId>
+            <artifactId>jk-rag</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>de.codecentric</groupId>
+            <artifactId>spring-boot-admin-starter-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- skywalking 整合 logback -->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.skywalking</groupId>-->
+<!--            <artifactId>apm-toolkit-logback-1.x</artifactId>-->
+<!--            <version>${与你的agent探针版本保持一致}</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.skywalking</groupId>-->
+<!--            <artifactId>apm-toolkit-trace</artifactId>-->
+<!--            <version>${与你的agent探针版本保持一致}</version>-->
+<!--        </dependency>-->
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>${maven-jar-plugin.version}</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>${maven-war-plugin.version}</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 27 - 0
jk-admin/src/main/java/org/jk/DromaraApplication.java

@@ -0,0 +1,27 @@
+package org.jk;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 启动程序
+ *
+ * @author jk
+ */
+
+@SpringBootApplication
+@EnableAsync
+@EnableScheduling // 启用定时任务
+public class DromaraApplication {
+
+    public static void main(String[] args) {
+        SpringApplication application = new SpringApplication(DromaraApplication.class);
+        application.setApplicationStartup(new BufferingApplicationStartup(2048));
+        application.run(args);
+        System.out.println("(♥◠‿◠)ノ゙  JK-RAG-BACKEND启动成功   ლ(´ڡ`ლ)゙");
+    }
+
+}

+ 18 - 0
jk-admin/src/main/java/org/jk/DromaraServletInitializer.java

@@ -0,0 +1,18 @@
+package org.jk;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ *
+ * @author jk
+ */
+public class DromaraServletInitializer extends SpringBootServletInitializer {
+
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+        return application.sources(DromaraApplication.class);
+    }
+
+}

+ 274 - 0
jk-admin/src/main/java/org/jk/web/controller/AuthController.java

@@ -0,0 +1,274 @@
+package org.jk.web.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
+import org.jk.common.core.constant.SystemConstants;
+import org.jk.common.core.domain.R;
+import org.jk.common.core.domain.model.LoginBody;
+import org.jk.common.core.domain.model.LoginUser;
+import org.jk.common.core.domain.model.RegisterBody;
+import org.jk.common.core.domain.model.SocialLoginBody;
+import org.jk.common.core.utils.*;
+import org.jk.common.core.utils.*;
+import org.jk.common.encrypt.annotation.ApiEncrypt;
+import org.jk.common.json.utils.JsonUtils;
+import org.jk.common.ratelimiter.annotation.RateLimiter;
+import org.jk.common.ratelimiter.enums.LimitType;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.common.social.config.properties.SocialLoginConfigProperties;
+import org.jk.common.social.config.properties.SocialProperties;
+import org.jk.common.social.utils.SocialUtils;
+import org.jk.common.sse.dto.SseMessageDto;
+import org.jk.common.sse.utils.SseMessageUtils;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.system.domain.bo.SysTenantBo;
+import org.jk.system.domain.vo.SysClientVo;
+import org.jk.system.domain.vo.SysTenantVo;
+import org.jk.system.service.*;
+import org.jk.web.domain.vo.LoginTenantVo;
+import org.jk.web.domain.vo.LoginVo;
+import org.jk.web.domain.vo.TenantListVo;
+import org.jk.web.service.IAuthStrategy;
+//import org.jk.web.service.JkLoginService;
+import org.jk.web.service.SysLoginService;
+import org.jk.web.service.SysRegisterService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 认证
+ *
+ * @author jk
+ */
+@Slf4j
+@SaIgnore
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+    private final SocialProperties socialProperties;
+    private final SysLoginService loginService;
+    private final SysRegisterService registerService;
+    private final ISysConfigService configService;
+    private final ISysTenantService tenantService;
+    private final ISysSocialService socialUserService;
+    private final ISysClientService clientService;
+    private final ISysUserService userService;
+    private final ScheduledExecutorService scheduledExecutorService;
+
+    //private final JkLoginService jkLoginService;
+
+
+    /**
+     * 登录方法
+     *
+     * @param body 登录信息
+     * @return 结果
+     */
+    @ApiEncrypt
+    @PostMapping("/login")
+    public R<LoginVo> login(@RequestBody String body) {
+        LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        // 授权类型和客户端id
+        String clientId = loginBody.getClientId();
+        String grantType = loginBody.getGrantType();
+        SysClientVo client = clientService.queryByClientId(clientId);
+        // 查询不到 client 或 client 内不包含 grantType
+        if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
+            log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
+            return R.fail(MessageUtils.message("auth.grant.type.error"));
+        } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
+            return R.fail(MessageUtils.message("auth.grant.type.blocked"));
+        }
+        // 校验租户
+        //loginService.checkTenant(loginBody.getTenantId());
+        // 登录
+        LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
+
+        Long userId = LoginHelper.getUserId();
+        scheduledExecutorService.schedule(() -> {
+            SseMessageDto dto = new SseMessageDto();
+            dto.setMessage("欢迎登录建科RAG后台管理系统");
+            dto.setUserIds(List.of(userId));
+            SseMessageUtils.publishMessage(dto);
+        }, 5, TimeUnit.SECONDS);
+        return R.ok(loginVo);
+    }
+
+    /**
+     * 获取跳转URL
+     *
+     * @param source 登录来源
+     * @return 结果
+     */
+    @GetMapping("/binding/{source}")
+    public R<String> authBinding(@PathVariable("source") String source,
+                                 @RequestParam String tenantId, @RequestParam String domain) {
+        SocialLoginConfigProperties obj = socialProperties.getType().get(source);
+        if (ObjectUtil.isNull(obj)) {
+            return R.fail(source + "平台账号暂不支持");
+        }
+        AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
+        Map<String, String> map = new HashMap<>();
+        map.put("tenantId", tenantId);
+        map.put("domain", domain);
+        map.put("state", AuthStateUtils.createState());
+        String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
+        return R.ok("操作成功", authorizeUrl);
+    }
+
+    /**
+     * 前端回调绑定授权(需要token)
+     *
+     * @param loginBody 请求体
+     * @return 结果
+     */
+    @PostMapping("/social/callback")
+    public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
+        // 校验token
+        StpUtil.checkLogin();
+        // 获取第三方登录信息
+        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+                loginBody.getSource(), loginBody.getSocialCode(),
+                loginBody.getSocialState(), socialProperties);
+        AuthUser authUserData = response.getData();
+        // 判断授权响应是否成功
+        if (!response.ok()) {
+            return R.fail(response.getMsg());
+        }
+        loginService.socialRegister(authUserData);
+        return R.ok();
+    }
+
+
+    /**
+     * 取消授权(需要token)
+     *
+     * @param socialId socialId
+     */
+    @DeleteMapping(value = "/unlock/{socialId}")
+    public R<Void> unlockSocial(@PathVariable Long socialId) {
+        // 校验token
+        StpUtil.checkLogin();
+        Boolean rows = socialUserService.deleteWithValidById(socialId);
+        return rows ? R.ok() : R.fail("取消授权失败");
+    }
+
+
+    /**
+     * 退出登录
+     */
+    @PostMapping("/logout")
+    public R<Void> logout() {
+        loginService.logout();
+        return R.ok("退出成功");
+    }
+
+    /**
+     * 用户注册
+     */
+    @ApiEncrypt
+    @PostMapping("/register")
+    public R<Void> register(@Validated @RequestBody RegisterBody user) {
+        if (!configService.selectRegisterEnabled(user.getTenantId())) {
+            return R.fail("当前系统没有开启注册功能!");
+        }
+        registerService.register(user);
+        return R.ok();
+    }
+
+    /**
+     * 登录页面租户下拉框
+     *
+     * @return 租户列表
+     */
+    @RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
+    @GetMapping("/tenant/list")
+    public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
+        // 返回对象
+        LoginTenantVo result = new LoginTenantVo();
+        boolean enable = TenantHelper.isEnable();
+        result.setTenantEnabled(enable);
+        // 如果未开启租户这直接返回
+        if (!enable) {
+            return R.ok(result);
+        }
+
+        List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
+        List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
+        try {
+            // 如果只超管返回所有租户
+            if (LoginHelper.isSuperAdmin()) {
+                result.setVoList(voList);
+                return R.ok(result);
+            }
+        } catch (NotLoginException ignored) {
+        }
+
+        // 获取域名
+        String host;
+        String referer = request.getHeader("referer");
+        if (StringUtils.isNotBlank(referer)) {
+            // 这里从referer中取值是为了本地使用hosts添加虚拟域名,方便本地环境调试
+            host = referer.split("//")[1].split("/")[0];
+        } else {
+            host = new URL(request.getRequestURL().toString()).getHost();
+        }
+        // 根据域名进行筛选
+        List<TenantListVo> list = StreamUtils.filter(voList, vo ->
+            StringUtils.equalsIgnoreCase(vo.getDomain(), host));
+        result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
+        return R.ok(result);
+    }
+
+    /**
+     * token校验
+     * @param token
+     * @return
+     */
+    @GetMapping("/checkToken/{token}")
+    public R<LoginUser> checkToken(@PathVariable("token")  String token){
+        StpUtil.setTokenValue(token);
+        StpUtil.checkLogin();
+        LoginUser loginUser = LoginHelper.getLoginUser(token);
+        log.info("checkToken:loginUser"+loginUser);
+        return R.ok(loginUser);
+    }
+
+    /**
+     * 建科用户登录方法
+     *
+     * @param loginBody 登录信息
+     * @return 结果
+     */
+//    @PostMapping("/jk_code_login")
+//    public R<LoginVo> jkLogin(@RequestBody LoginBody loginBody) throws IOException {
+//        LoginVo loginVo  = jkLoginService.login(loginBody.getCode(),loginBody.getRedirectUrl());
+//        return R.ok(loginVo);
+//    }
+
+
+}

+ 157 - 0
jk-admin/src/main/java/org/jk/web/controller/CaptchaController.java

@@ -0,0 +1,157 @@
+package org.jk.web.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.RandomUtil;
+import jakarta.validation.constraints.NotBlank;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jk.common.core.constant.Constants;
+import org.jk.common.core.constant.GlobalConstants;
+import org.jk.common.core.domain.R;
+import org.jk.common.core.exception.ServiceException;
+import org.jk.common.core.utils.SpringUtils;
+import org.jk.common.core.utils.StringUtils;
+import org.jk.common.core.utils.reflect.ReflectUtils;
+import org.jk.common.mail.config.properties.MailProperties;
+import org.jk.common.mail.utils.MailUtils;
+import org.jk.common.ratelimiter.annotation.RateLimiter;
+import org.jk.common.ratelimiter.enums.LimitType;
+import org.jk.common.redis.utils.RedisUtils;
+import org.jk.common.web.config.properties.CaptchaProperties;
+import org.jk.common.web.enums.CaptchaType;
+import org.dromara.sms4j.api.SmsBlend;
+import org.dromara.sms4j.api.entity.SmsResponse;
+import org.dromara.sms4j.core.factory.SmsFactory;
+import org.jk.web.domain.vo.CaptchaVo;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Duration;
+import java.util.LinkedHashMap;
+
+/**
+ * 验证码操作处理
+ *
+ * @author jk
+ */
+@SaIgnore
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class CaptchaController {
+
+    private final CaptchaProperties captchaProperties;
+    private final MailProperties mailProperties;
+
+    /**
+     * 短信验证码
+     *
+     * @param phonenumber 用户手机号
+     */
+    @RateLimiter(key = "#phonenumber", time = 60, count = 1)
+    @GetMapping("/resource/sms/code")
+    public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
+        String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
+        String code = RandomUtil.randomNumbers(4);
+        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        // 验证码模板id 自行处理 (查数据库或写死均可)
+        String templateId = "";
+        LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
+        map.put("code", code);
+        SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
+        SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
+        if (!smsResponse.isSuccess()) {
+            log.error("验证码短信发送异常 => {}", smsResponse);
+            return R.fail(smsResponse.getData().toString());
+        }
+        return R.ok();
+    }
+
+    /**
+     * 邮箱验证码
+     *
+     * @param email 邮箱
+     */
+    @GetMapping("/resource/email/code")
+    public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
+        if (!mailProperties.getEnabled()) {
+            return R.fail("当前系统没有开启邮箱功能!");
+        }
+        SpringUtils.getAopProxy(this).emailCodeImpl(email);
+        return R.ok();
+    }
+
+    /**
+     * 邮箱验证码
+     * 独立方法避免验证码关闭之后仍然走限流
+     */
+    @RateLimiter(key = "#email", time = 60, count = 1)
+    public void emailCodeImpl(String email) {
+        String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
+        String code = RandomUtil.randomNumbers(4);
+        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        try {
+            MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
+        } catch (Exception e) {
+            log.error("验证码短信发送异常 => {}", e.getMessage());
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/auth/code")
+    public R<CaptchaVo> getCode() {
+        boolean captchaEnabled = captchaProperties.getEnable();
+        if (!captchaEnabled) {
+            CaptchaVo captchaVo = new CaptchaVo();
+            captchaVo.setCaptchaEnabled(false);
+            return R.ok(captchaVo);
+        }
+        return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
+    }
+
+    /**
+     * 生成验证码
+     * 独立方法避免验证码关闭之后仍然走限流
+     */
+    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+    public CaptchaVo getCodeImpl() {
+        // 保存验证码信息
+        String uuid = IdUtil.simpleUUID();
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
+        // 生成验证码
+        CaptchaType captchaType = captchaProperties.getType();
+        CodeGenerator codeGenerator;
+        if (CaptchaType.MATH == captchaType) {
+            codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
+        } else {
+            codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
+        }
+        AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
+        captcha.setGenerator(codeGenerator);
+        captcha.createCode();
+        // 如果是数学验证码,使用SpEL表达式处理验证码结果
+        String code = captcha.getCode();
+        if (CaptchaType.MATH == captchaType) {
+            ExpressionParser parser = new SpelExpressionParser();
+            Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
+            code = exp.getValue(String.class);
+        }
+        RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        CaptchaVo captchaVo = new CaptchaVo();
+        captchaVo.setUuid(uuid);
+        captchaVo.setImg(captcha.getImageBase64());
+        return captchaVo;
+    }
+
+}

+ 28 - 0
jk-admin/src/main/java/org/jk/web/controller/IndexController.java

@@ -0,0 +1,28 @@
+package org.jk.web.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.jk.common.core.utils.SpringUtils;
+import org.jk.common.core.utils.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 首页
+ *
+ * @author jk
+ */
+@SaIgnore
+@RequiredArgsConstructor
+@RestController
+public class IndexController {
+
+    /**
+     * 访问首页,提示语
+     */
+    @GetMapping("/")
+    public String index() {
+        return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName());
+    }
+
+}

+ 25 - 0
jk-admin/src/main/java/org/jk/web/domain/vo/CaptchaVo.java

@@ -0,0 +1,25 @@
+package org.jk.web.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 验证码信息
+ *
+ * @author jk
+ */
+@Data
+public class CaptchaVo {
+
+    /**
+     * 是否开启验证码
+     */
+    private Boolean captchaEnabled = true;
+
+    private String uuid;
+
+    /**
+     * 验证码图片
+     */
+    private String img;
+
+}

+ 25 - 0
jk-admin/src/main/java/org/jk/web/domain/vo/LoginTenantVo.java

@@ -0,0 +1,25 @@
+package org.jk.web.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 登录租户对象
+ *
+ * @author jk
+ */
+@Data
+public class LoginTenantVo {
+
+    /**
+     * 租户开关
+     */
+    private Boolean tenantEnabled;
+
+    /**
+     * 租户对象列表
+     */
+    private List<TenantListVo> voList;
+
+}

+ 56 - 0
jk-admin/src/main/java/org/jk/web/domain/vo/LoginVo.java

@@ -0,0 +1,56 @@
+package org.jk.web.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 登录验证信息
+ *
+ * @author jk
+ */
+@Data
+public class LoginVo {
+
+    /**
+     * 授权令牌
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+
+    /**
+     * 刷新令牌
+     */
+    @JsonProperty("refresh_token")
+    private String refreshToken;
+
+    /**
+     * 授权令牌 access_token 的有效期
+     */
+    @JsonProperty("expire_in")
+    private Long expireIn;
+
+    /**
+     * 刷新令牌 refresh_token 的有效期
+     */
+    @JsonProperty("refresh_expire_in")
+    private Long refreshExpireIn;
+
+    /**
+     * 应用id
+     */
+    @JsonProperty("client_id")
+    private String clientId;
+
+    /**
+     * 令牌权限
+     */
+    private String scope;
+
+    /**
+     * 用户 openid
+     */
+    private String openid;
+
+    private String tennatId;
+
+}

+ 31 - 0
jk-admin/src/main/java/org/jk/web/domain/vo/TenantListVo.java

@@ -0,0 +1,31 @@
+package org.jk.web.domain.vo;
+
+import org.jk.system.domain.vo.SysTenantVo;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+/**
+ * 租户列表
+ *
+ * @author jk
+ */
+@Data
+@AutoMapper(target = SysTenantVo.class)
+public class TenantListVo {
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 企业名称
+     */
+    private String companyName;
+
+    /**
+     * 域名
+     */
+    private String domain;
+
+}

+ 163 - 0
jk-admin/src/main/java/org/jk/web/listener/UserActionListener.java

@@ -0,0 +1,163 @@
+package org.jk.web.listener;
+
+import cn.dev33.satoken.listener.SaTokenListener;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jk.common.core.constant.CacheConstants;
+import org.jk.common.core.constant.Constants;
+import org.jk.common.core.domain.dto.UserOnlineDTO;
+import org.jk.common.core.utils.MessageUtils;
+import org.jk.common.core.utils.ServletUtils;
+import org.jk.common.core.utils.SpringUtils;
+import org.jk.common.core.utils.ip.AddressUtils;
+import org.jk.common.log.event.LogininforEvent;
+import org.jk.common.redis.utils.RedisUtils;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.web.service.SysLoginService;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+
+/**
+ * 用户行为 侦听器的实现
+ *
+ * @author jk
+ */
+@RequiredArgsConstructor
+@Component
+@Slf4j
+public class UserActionListener implements SaTokenListener {
+
+    private final SysLoginService loginService;
+
+    /**
+     * 每次登录时触发
+     */
+    @Override
+    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
+        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
+        String ip = ServletUtils.getClientIP();
+        UserOnlineDTO dto = new UserOnlineDTO();
+        dto.setIpaddr(ip);
+        dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+        dto.setBrowser(userAgent.getBrowser().getName());
+        dto.setOs(userAgent.getOs().getName());
+        dto.setLoginTime(System.currentTimeMillis());
+        dto.setTokenId(tokenValue);
+        String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
+        String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
+        dto.setUserName(username);
+        dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
+        dto.setDeviceType(loginParameter.getDeviceType());
+        dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            if(loginParameter.getTimeout() == -1) {
+                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
+            } else {
+                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
+            }
+        });
+        // 记录登录日志
+        LogininforEvent logininforEvent = new LogininforEvent();
+        logininforEvent.setTenantId(tenantId);
+        logininforEvent.setUsername(username);
+        logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
+        logininforEvent.setMessage(MessageUtils.message("user.login.success"));
+        logininforEvent.setRequest(ServletUtils.getRequest());
+        SpringUtils.context().publishEvent(logininforEvent);
+        // 更新登录信息
+        loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
+        log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次注销时触发
+     */
+    @Override
+    public void doLogout(String loginType, Object loginId, String tokenValue) {
+        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+        });
+        log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次被踢下线时触发
+     */
+    @Override
+    public void doKickout(String loginType, Object loginId, String tokenValue) {
+        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+        });
+        log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次被顶下线时触发
+     */
+    @Override
+    public void doReplaced(String loginType, Object loginId, String tokenValue) {
+        String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
+        TenantHelper.dynamic(tenantId, () -> {
+            RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
+        });
+        log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
+    }
+
+    /**
+     * 每次被封禁时触发
+     */
+    @Override
+    public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
+    }
+
+    /**
+     * 每次被解封时触发
+     */
+    @Override
+    public void doUntieDisable(String loginType, Object loginId, String service) {
+    }
+
+    /**
+     * 每次打开二级认证时触发
+     */
+    @Override
+    public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
+    }
+
+    /**
+     * 每次创建Session时触发
+     */
+    @Override
+    public void doCloseSafe(String loginType, String tokenValue, String service) {
+    }
+
+    /**
+     * 每次创建Session时触发
+     */
+    @Override
+    public void doCreateSession(String id) {
+    }
+
+    /**
+     * 每次注销Session时触发
+     */
+    @Override
+    public void doLogoutSession(String id) {
+    }
+
+    /**
+     * 每次Token续期时触发
+     */
+    @Override
+    public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
+    }
+}

+ 45 - 0
jk-admin/src/main/java/org/jk/web/service/IAuthStrategy.java

@@ -0,0 +1,45 @@
+package org.jk.web.service;
+
+
+import org.jk.common.core.exception.ServiceException;
+import org.jk.common.core.utils.SpringUtils;
+import org.jk.system.domain.vo.SysClientVo;
+import org.jk.web.domain.vo.LoginVo;
+
+/**
+ * 授权策略
+ *
+ * @author jk
+ */
+public interface IAuthStrategy {
+
+    String BASE_NAME = "AuthStrategy";
+
+    /**
+     * 登录
+     *
+     * @param body      登录对象
+     * @param client    授权管理视图对象
+     * @param grantType 授权类型
+     * @return 登录验证信息
+     */
+    static LoginVo login(String body, SysClientVo client, String grantType) {
+        // 授权类型和客户端id
+        String beanName = grantType + BASE_NAME;
+        if (!SpringUtils.containsBean(beanName)) {
+            throw new ServiceException("授权类型不正确!");
+        }
+        IAuthStrategy instance = SpringUtils.getBean(beanName);
+        return instance.login(body, client);
+    }
+
+    /**
+     * 登录
+     *
+     * @param body   登录对象
+     * @param client 授权管理视图对象
+     * @return 登录验证信息
+     */
+    LoginVo login(String body, SysClientVo client);
+
+}

+ 108 - 0
jk-admin/src/main/java/org/jk/web/service/JkLoginService.java

@@ -0,0 +1,108 @@
+//package org.jk.web.service;
+//
+//import cn.dev33.satoken.stp.StpUtil;
+//import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+//import cn.hutool.crypto.digest.BCrypt;
+//import lombok.RequiredArgsConstructor;
+//import lombok.extern.slf4j.Slf4j;
+//import org.jk.common.core.config.JkConfig;
+//import org.jk.common.core.constant.SystemConstants;
+//import org.jk.common.core.domain.model.LoginUser;
+//import org.jk.common.core.enums.LoginType;
+//import org.jk.common.core.exception.user.UserException;
+//import org.jk.common.core.utils.DateUtils;
+//import org.jk.common.core.utils.ServletUtils;
+//import org.jk.common.satoken.utils.LoginHelper;
+//import org.jk.common.tenant.helper.TenantHelper;
+//import org.jk.system.domain.SysUser;
+//import org.jk.system.domain.vo.SysClientVo;
+//import org.jk.system.domain.vo.SysUserVo;
+////import org.jk.rag.service.IJkApiService;
+//import org.jk.system.service.ISysClientService;
+//import org.jk.system.service.ISysConfigService;
+//import org.jk.system.service.ISysUserService;
+//import org.jk.web.domain.vo.LoginVo;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.stereotype.Service;
+//
+//import java.io.IOException;
+//import java.util.Map;
+//
+///**
+// * 建科登录校验方法
+// *
+// * @author takai
+// */
+//@Slf4j
+//@Service
+//@RequiredArgsConstructor
+//public class JkLoginService
+//{
+//    private final ISysUserService userService;
+//
+//    private final ISysConfigService configService;
+//
+//    //private final IJkApiService jkApiService;
+//
+//    private final SysLoginService loginService;
+//
+//    private final ISysClientService clientService;
+//
+//    @Autowired
+//    JkConfig jkConfig;
+//
+//
+//    /**
+//     * 记录登录信息
+//     *
+//     * @param userId 用户ID
+//     */
+//    public void recordLoginInfo(String userId)
+//    {
+//        SysUser sysUser = new SysUser();
+//        sysUser.setUserId(Long.parseLong(userId));
+//        sysUser.setLoginIp(ServletUtils.getClientIP());
+//        sysUser.setLoginDate(DateUtils.getNowDate());
+//        userService.updateUserProfile(sysUser);
+//    }
+//
+//
+//    public LoginVo login(String code,String redirectUrl) throws IOException {
+//        log.info("登录code:{}", code);
+//        log.info("登录redirectUrl:{}", redirectUrl);
+//        String username;
+//        // 用户验证
+//        Map<String, Object> resJson = jkApiService.getJkToken(code,redirectUrl);
+//        username = String.valueOf(resJson.get("userId"));
+//        SysUserVo sysUserVo = userService.selectUserByUserName(username);
+//        SysClientVo client = clientService.queryByClientId("e5cd7e4891bf95d1d19206ce24a7b32e");
+//        String password = sysUserVo.getPassword();
+//        LoginUser loginUser = TenantHelper.dynamic(sysUserVo.getTenantId(), () -> {
+//            if (SystemConstants.DISABLE.equals(sysUserVo.getStatus())) {
+//                log.info("登录用户:{} 已被停用.", username);
+//                throw new UserException("user.blocked", username);
+//            }
+//            loginService.checkLogin(LoginType.PASSWORD, sysUserVo.getTenantId(), username, () -> !BCrypt.checkpw(password, password));
+//            // 此处可根据登录用户的数据不同 自行创建 loginUser
+//            return loginService.buildLoginUser(sysUserVo);
+//        });
+//        loginUser.setClientKey(client.getClientKey());
+//        loginUser.setDeviceType(client.getDeviceType());
+//        SaLoginParameter model = new SaLoginParameter();
+//        model.setDeviceType(client.getDeviceType());
+//        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+//        // 例如: 后台用户30分钟过期 app用户1天过期
+//        model.setTimeout(client.getTimeout());
+//        model.setActiveTimeout(client.getActiveTimeout());
+//        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+//        // 生成token
+//        LoginHelper.login(loginUser, model);
+//
+//        LoginVo loginVo = new LoginVo();
+//        loginVo.setAccessToken(StpUtil.getTokenValue());
+//        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+//        loginVo.setClientId(client.getClientId());
+//        return loginVo;
+//    }
+//
+//}

+ 254 - 0
jk-admin/src/main/java/org/jk/web/service/SysLoginService.java

@@ -0,0 +1,254 @@
+package org.jk.web.service;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Opt;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.lock.annotation.Lock4j;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthUser;
+import org.jk.common.core.constant.CacheConstants;
+import org.jk.common.core.constant.Constants;
+import org.jk.common.core.constant.SystemConstants;
+import org.jk.common.core.constant.TenantConstants;
+import org.jk.common.core.domain.dto.PostDTO;
+import org.jk.common.core.domain.dto.RoleDTO;
+import org.jk.common.core.domain.model.LoginUser;
+import org.jk.common.core.enums.LoginType;
+import org.jk.common.core.exception.ServiceException;
+import org.jk.common.core.exception.user.UserException;
+import org.jk.common.core.utils.*;
+import org.jk.common.log.event.LogininforEvent;
+import org.jk.common.mybatis.helper.DataPermissionHelper;
+import org.jk.common.redis.utils.RedisUtils;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.common.tenant.exception.TenantException;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.system.domain.SysUser;
+import org.jk.system.domain.bo.SysSocialBo;
+import org.jk.system.domain.vo.*;
+import org.jk.system.mapper.SysUserMapper;
+import org.jk.system.service.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * 登录校验方法
+ *
+ * @author jk
+ */
+@RequiredArgsConstructor
+@Slf4j
+@Service
+public class SysLoginService {
+
+    @Value("${user.password.maxRetryCount}")
+    private Integer maxRetryCount;
+
+    @Value("${user.password.lockTime}")
+    private Integer lockTime;
+
+    private final ISysTenantService tenantService;
+    private final ISysPermissionService permissionService;
+    private final ISysSocialService sysSocialService;
+    private final ISysRoleService roleService;
+    private final ISysDeptService deptService;
+    private final ISysPostService postService;
+    private final SysUserMapper userMapper;
+    private RedisCache redisCache;
+
+
+    /**
+     * 绑定第三方用户
+     *
+     * @param authUserData 授权响应实体
+     */
+    @Lock4j
+    public void socialRegister(AuthUser authUserData) {
+        String authId = authUserData.getSource() + authUserData.getUuid();
+        // 第三方用户信息
+        SysSocialBo bo = BeanUtil.toBean(authUserData, SysSocialBo.class);
+        BeanUtil.copyProperties(authUserData.getToken(), bo);
+        Long userId = LoginHelper.getUserId();
+        bo.setUserId(userId);
+        bo.setAuthId(authId);
+        bo.setOpenId(authUserData.getUuid());
+        bo.setUserName(authUserData.getUsername());
+        bo.setNickName(authUserData.getNickname());
+        List<SysSocialVo> checkList = sysSocialService.selectByAuthId(authId);
+        if (CollUtil.isNotEmpty(checkList)) {
+            throw new ServiceException("此三方账号已经被绑定!");
+        }
+        // 查询是否已经绑定用户
+        SysSocialBo params = new SysSocialBo();
+        params.setUserId(userId);
+        params.setSource(bo.getSource());
+        List<SysSocialVo> list = sysSocialService.queryList(params);
+        if (CollUtil.isEmpty(list)) {
+            // 没有绑定用户, 新增用户信息
+            sysSocialService.insertByBo(bo);
+        } else {
+            // 更新用户信息
+            bo.setId(list.get(0).getId());
+            sysSocialService.updateByBo(bo);
+            // 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
+            // throw new ServiceException("此平台账号已经被绑定!");
+        }
+    }
+
+
+    /**
+     * 退出登录
+     */
+    public void logout() {
+        try {
+            LoginUser loginUser = LoginHelper.getLoginUser();
+            if (ObjectUtil.isNull(loginUser)) {
+                return;
+            }
+            if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
+                // 超级管理员 登出清除动态租户
+                TenantHelper.clearDynamic();
+            }
+            recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
+        } catch (NotLoginException ignored) {
+        } finally {
+            try {
+                StpUtil.logout();
+            } catch (NotLoginException ignored) {
+            }
+        }
+    }
+
+    /**
+     * 记录登录信息
+     *
+     * @param tenantId 租户ID
+     * @param username 用户名
+     * @param status   状态
+     * @param message  消息内容
+     */
+    public void recordLogininfor(String tenantId, String username, String status, String message) {
+        LogininforEvent logininforEvent = new LogininforEvent();
+        logininforEvent.setTenantId(tenantId);
+        logininforEvent.setUsername(username);
+        logininforEvent.setStatus(status);
+        logininforEvent.setMessage(message);
+        logininforEvent.setRequest(ServletUtils.getRequest());
+        SpringUtils.context().publishEvent(logininforEvent);
+    }
+
+    /**
+     * 构建登录用户
+     */
+    public LoginUser buildLoginUser(SysUserVo user) {
+        LoginUser loginUser = new LoginUser();
+        Long userId = user.getUserId();
+        loginUser.setTenantId(user.getTenantId());
+        loginUser.setUserId(userId);
+        loginUser.setDeptId(user.getDeptId());
+        loginUser.setUsername(user.getUserName());
+        loginUser.setNickname(user.getNickName());
+        loginUser.setUserType(user.getUserType());
+        loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
+        loginUser.setRolePermission(permissionService.getRolePermission(userId));
+        if (ObjectUtil.isNotNull(user.getDeptId())) {
+            Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
+            loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
+            loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
+        }
+        List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
+        List<SysPostVo> posts = postService.selectPostsByUserId(userId);
+        loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
+        loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
+        return loginUser;
+    }
+
+    /**
+     * 记录登录信息
+     *
+     * @param userId 用户ID
+     */
+    public void recordLoginInfo(Long userId, String ip) {
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
+        sysUser.setLoginIp(ip);
+        sysUser.setLoginDate(DateUtils.getNowDate());
+        sysUser.setUpdateBy(userId);
+        DataPermissionHelper.ignore(() -> userMapper.updateById(sysUser));
+    }
+
+    /**
+     * 登录校验
+     */
+    public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
+        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
+        String loginFail = Constants.LOGIN_FAIL;
+
+        // 获取用户登录错误次数,默认为0 (可自定义限制策略 例如: key + username + ip)
+        int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
+        // 锁定时间内登录 则踢出
+        if (errorNumber >= maxRetryCount) {
+            recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+            throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
+        }
+
+        if (supplier.get()) {
+            // 错误次数递增
+            errorNumber++;
+            RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
+            // 达到规定错误次数 则锁定登录
+            if (errorNumber >= maxRetryCount) {
+                recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+                throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
+            } else {
+                // 未达到规定错误次数
+                recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
+                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
+            }
+        }
+
+        // 登录成功 清空错误次数
+        RedisUtils.deleteObject(errorKey);
+    }
+
+    /**
+     * 校验租户
+     *
+     * @param tenantId 租户ID
+     */
+    public void checkTenant(String tenantId) {
+        if (!TenantHelper.isEnable()) {
+            return;
+        }
+        if (StringUtils.isBlank(tenantId)) {
+            throw new TenantException("tenant.number.not.blank");
+        }
+        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
+            return;
+        }
+        SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
+        if (ObjectUtil.isNull(tenant)) {
+            log.info("登录租户:{} 不存在.", tenantId);
+            throw new TenantException("tenant.not.exists");
+        } else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
+            log.info("登录租户:{} 已被停用.", tenantId);
+            throw new TenantException("tenant.blocked");
+        } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
+            && new Date().after(tenant.getExpireTime())) {
+            log.info("登录租户:{} 已超过有效期.", tenantId);
+            throw new TenantException("tenant.expired");
+        }
+    }
+
+
+}

+ 115 - 0
jk-admin/src/main/java/org/jk/web/service/SysRegisterService.java

@@ -0,0 +1,115 @@
+package org.jk.web.service;
+
+import cn.hutool.crypto.digest.BCrypt;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.jk.common.core.constant.Constants;
+import org.jk.common.core.constant.GlobalConstants;
+import org.jk.common.core.domain.model.RegisterBody;
+import org.jk.common.core.enums.UserType;
+import org.jk.common.core.exception.user.CaptchaException;
+import org.jk.common.core.exception.user.CaptchaExpireException;
+import org.jk.common.core.exception.user.UserException;
+import org.jk.common.core.utils.MessageUtils;
+import org.jk.common.core.utils.ServletUtils;
+import org.jk.common.core.utils.SpringUtils;
+import org.jk.common.core.utils.StringUtils;
+import org.jk.common.log.event.LogininforEvent;
+import org.jk.common.redis.utils.RedisUtils;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.common.web.config.properties.CaptchaProperties;
+import org.jk.system.domain.SysUser;
+import org.jk.system.domain.bo.SysUserBo;
+import org.jk.system.mapper.SysUserMapper;
+import org.jk.system.service.ISysUserService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 注册校验方法
+ *
+ * @author jk
+ */
+@RequiredArgsConstructor
+@Service
+public class SysRegisterService {
+
+    private final ISysUserService userService;
+    private final SysUserMapper userMapper;
+    private final CaptchaProperties captchaProperties;
+
+    /**
+     * 注册
+     */
+    public void register(RegisterBody registerBody) {
+        String tenantId = registerBody.getTenantId();
+        String username = registerBody.getUsername();
+        String password = registerBody.getPassword();
+        // 校验用户类型是否存在
+        String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
+
+        boolean captchaEnabled = captchaProperties.getEnable();
+        // 验证码开关
+        if (captchaEnabled) {
+            validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
+        }
+        SysUserBo sysUser = new SysUserBo();
+        sysUser.setUserName(username);
+        sysUser.setNickName(username);
+        sysUser.setPassword(BCrypt.hashpw(password));
+        sysUser.setUserType(userType);
+
+        boolean exist = TenantHelper.dynamic(tenantId, () -> {
+            return userMapper.exists(new LambdaQueryWrapper<SysUser>()
+                .eq(SysUser::getUserName, sysUser.getUserName()));
+        });
+        if (exist) {
+            throw new UserException("user.register.save.error", username);
+        }
+        boolean regFlag = userService.registerUser(sysUser, tenantId);
+        if (!regFlag) {
+            throw new UserException("user.register.error");
+        }
+        recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param username 用户名
+     * @param code     验证码
+     * @param uuid     唯一标识
+     */
+    public void validateCaptcha(String tenantId, String username, String code, String uuid) {
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
+        String captcha = RedisUtils.getCacheObject(verifyKey);
+        RedisUtils.deleteObject(verifyKey);
+        if (captcha == null) {
+            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        if (!StringUtils.equalsIgnoreCase(code, captcha)) {
+            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+            throw new CaptchaException();
+        }
+    }
+
+    /**
+     * 记录登录信息
+     *
+     * @param tenantId 租户ID
+     * @param username 用户名
+     * @param status   状态
+     * @param message  消息内容
+     * @return
+     */
+    private void recordLogininfor(String tenantId, String username, String status, String message) {
+        LogininforEvent logininforEvent = new LogininforEvent();
+        logininforEvent.setTenantId(tenantId);
+        logininforEvent.setUsername(username);
+        logininforEvent.setStatus(status);
+        logininforEvent.setMessage(message);
+        logininforEvent.setRequest(ServletUtils.getRequest());
+        SpringUtils.context().publishEvent(logininforEvent);
+    }
+
+}

+ 102 - 0
jk-admin/src/main/java/org/jk/web/service/impl/EmailAuthStrategy.java

@@ -0,0 +1,102 @@
+package org.jk.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jk.common.core.constant.Constants;
+import org.jk.common.core.constant.GlobalConstants;
+import org.jk.common.core.constant.SystemConstants;
+import org.jk.common.core.domain.model.EmailLoginBody;
+import org.jk.common.core.domain.model.LoginUser;
+import org.jk.common.core.enums.LoginType;
+import org.jk.common.core.exception.user.CaptchaExpireException;
+import org.jk.common.core.exception.user.UserException;
+import org.jk.common.core.utils.MessageUtils;
+import org.jk.common.core.utils.StringUtils;
+import org.jk.common.core.utils.ValidatorUtils;
+import org.jk.common.json.utils.JsonUtils;
+import org.jk.common.redis.utils.RedisUtils;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.system.domain.SysUser;
+import org.jk.system.domain.vo.SysClientVo;
+import org.jk.system.domain.vo.SysUserVo;
+import org.jk.system.mapper.SysUserMapper;
+import org.jk.web.domain.vo.LoginVo;
+import org.jk.web.service.IAuthStrategy;
+import org.jk.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 邮件认证策略
+ *
+ * @author jk
+ */
+@Slf4j
+@Service("email" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class EmailAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+    private final SysUserMapper userMapper;
+
+    @Override
+    public LoginVo login(String body, SysClientVo client) {
+        EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        String tenantId = loginBody.getTenantId();
+        String email = loginBody.getEmail();
+        String emailCode = loginBody.getEmailCode();
+        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+            SysUserVo user = loadUserByEmail(email);
+            loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
+            // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
+            return loginService.buildLoginUser(user);
+        });
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    /**
+     * 校验邮箱验证码
+     */
+    private boolean validateEmailCode(String tenantId, String email, String emailCode) {
+        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
+        if (StringUtils.isBlank(code)) {
+            loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        return code.equals(emailCode);
+    }
+
+    private SysUserVo loadUserByEmail(String email) {
+        SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", email);
+            throw new UserException("user.not.exists", email);
+        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", email);
+            throw new UserException("user.blocked", email);
+        }
+        return user;
+    }
+
+}

+ 126 - 0
jk-admin/src/main/java/org/jk/web/service/impl/PasswordAuthStrategy.java

@@ -0,0 +1,126 @@
+package org.jk.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.digest.BCrypt;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jk.common.core.constant.Constants;
+import org.jk.common.core.constant.GlobalConstants;
+import org.jk.common.core.constant.SystemConstants;
+import org.jk.common.core.domain.model.LoginUser;
+import org.jk.common.core.domain.model.PasswordLoginBody;
+import org.jk.common.core.enums.LoginType;
+import org.jk.common.core.exception.user.CaptchaException;
+import org.jk.common.core.exception.user.CaptchaExpireException;
+import org.jk.common.core.exception.user.UserException;
+import org.jk.common.core.utils.MessageUtils;
+import org.jk.common.core.utils.StringUtils;
+import org.jk.common.core.utils.ValidatorUtils;
+import org.jk.common.json.utils.JsonUtils;
+import org.jk.common.redis.utils.RedisUtils;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.common.web.config.properties.CaptchaProperties;
+import org.jk.system.domain.SysUser;
+import org.jk.system.domain.vo.SysClientVo;
+import org.jk.system.domain.vo.SysUserVo;
+import org.jk.system.mapper.SysUserMapper;
+import org.jk.web.domain.vo.LoginVo;
+import org.jk.web.service.IAuthStrategy;
+import org.jk.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 密码认证策略
+ *
+ * @author jk
+ */
+@Slf4j
+@Service("password" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class PasswordAuthStrategy implements IAuthStrategy {
+
+    private final CaptchaProperties captchaProperties;
+    private final SysLoginService loginService;
+    private final SysUserMapper userMapper;
+
+    @Override
+    public LoginVo login(String body, SysClientVo client) {
+        PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        String tenantId = userMapper.getTenantIdByUserName(loginBody.getUsername(),null);
+        //String tenantId = loginBody.getTenantId();
+        if(null == tenantId) {
+            throw new RuntimeException("账号密码错误");
+        }
+        String username = loginBody.getUsername();
+        String password = loginBody.getPassword();
+        String code = loginBody.getCode();
+        String uuid = loginBody.getUuid();
+        boolean captchaEnabled = captchaProperties.getEnable();
+        // 验证码开关
+        if (captchaEnabled) {
+            validateCaptcha(tenantId, username, code, uuid);
+        }
+        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+            SysUserVo user = loadUserByUsername(username);
+            loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+            // 此处可根据登录用户的数据不同 自行创建 loginUser
+            return loginService.buildLoginUser(user);
+        });
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param username 用户名
+     * @param code     验证码
+     * @param uuid     唯一标识
+     */
+    private void validateCaptcha(String tenantId, String username, String code, String uuid) {
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
+        String captcha = RedisUtils.getCacheObject(verifyKey);
+        RedisUtils.deleteObject(verifyKey);
+        if (captcha == null) {
+            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        if (!StringUtils.equalsIgnoreCase(code, captcha)) {
+            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+            throw new CaptchaException();
+        }
+    }
+
+    private SysUserVo loadUserByUsername(String username) {
+        SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", username);
+            throw new UserException("user.not.exists", username);
+        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new UserException("user.blocked", username);
+        }
+        return user;
+    }
+
+}

+ 102 - 0
jk-admin/src/main/java/org/jk/web/service/impl/SmsAuthStrategy.java

@@ -0,0 +1,102 @@
+package org.jk.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jk.common.core.constant.Constants;
+import org.jk.common.core.constant.GlobalConstants;
+import org.jk.common.core.constant.SystemConstants;
+import org.jk.common.core.domain.model.LoginUser;
+import org.jk.common.core.domain.model.SmsLoginBody;
+import org.jk.common.core.enums.LoginType;
+import org.jk.common.core.exception.user.CaptchaExpireException;
+import org.jk.common.core.exception.user.UserException;
+import org.jk.common.core.utils.MessageUtils;
+import org.jk.common.core.utils.StringUtils;
+import org.jk.common.core.utils.ValidatorUtils;
+import org.jk.common.json.utils.JsonUtils;
+import org.jk.common.redis.utils.RedisUtils;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.system.domain.SysUser;
+import org.jk.system.domain.vo.SysClientVo;
+import org.jk.system.domain.vo.SysUserVo;
+import org.jk.system.mapper.SysUserMapper;
+import org.jk.web.domain.vo.LoginVo;
+import org.jk.web.service.IAuthStrategy;
+import org.jk.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 短信认证策略
+ *
+ * @author jk
+ */
+@Slf4j
+@Service("sms" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SmsAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+    private final SysUserMapper userMapper;
+
+    @Override
+    public LoginVo login(String body, SysClientVo client) {
+        SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        String tenantId = loginBody.getTenantId();
+        String phonenumber = loginBody.getPhonenumber();
+        String smsCode = loginBody.getSmsCode();
+        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+            SysUserVo user = loadUserByPhonenumber(phonenumber);
+            loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
+            // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
+            return loginService.buildLoginUser(user);
+        });
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    /**
+     * 校验短信验证码
+     */
+    private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
+        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
+        if (StringUtils.isBlank(code)) {
+            loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        return code.equals(smsCode);
+    }
+
+    private SysUserVo loadUserByPhonenumber(String phonenumber) {
+        SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", phonenumber);
+            throw new UserException("user.not.exists", phonenumber);
+        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", phonenumber);
+            throw new UserException("user.blocked", phonenumber);
+        }
+        return user;
+    }
+
+}

+ 119 - 0
jk-admin/src/main/java/org/jk/web/service/impl/SocialAuthStrategy.java

@@ -0,0 +1,119 @@
+package org.jk.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import org.jk.common.core.constant.SystemConstants;
+import org.jk.common.core.domain.model.LoginUser;
+import org.jk.common.core.domain.model.SocialLoginBody;
+import org.jk.common.core.exception.ServiceException;
+import org.jk.common.core.exception.user.UserException;
+import org.jk.common.core.utils.StreamUtils;
+import org.jk.common.core.utils.ValidatorUtils;
+import org.jk.common.json.utils.JsonUtils;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.common.social.config.properties.SocialProperties;
+import org.jk.common.social.utils.SocialUtils;
+import org.jk.common.tenant.helper.TenantHelper;
+import org.jk.system.domain.vo.SysClientVo;
+import org.jk.system.domain.vo.SysSocialVo;
+import org.jk.system.domain.vo.SysUserVo;
+import org.jk.system.mapper.SysUserMapper;
+import org.jk.system.service.ISysSocialService;
+import org.jk.web.domain.vo.LoginVo;
+import org.jk.web.service.IAuthStrategy;
+import org.jk.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 第三方授权策略
+ *
+ * @author jk
+ */
+@Slf4j
+@Service("social" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SocialAuthStrategy implements IAuthStrategy {
+
+    private final SocialProperties socialProperties;
+    private final ISysSocialService sysSocialService;
+    private final SysUserMapper userMapper;
+    private final SysLoginService loginService;
+
+    /**
+     * 登录-第三方授权登录
+     *
+     * @param body     登录信息
+     * @param client   客户端信息
+     */
+    @Override
+    public LoginVo login(String body, SysClientVo client) {
+        SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+                loginBody.getSource(), loginBody.getSocialCode(),
+                loginBody.getSocialState(), socialProperties);
+        if (!response.ok()) {
+            throw new ServiceException(response.getMsg());
+        }
+        AuthUser authUserData = response.getData();
+
+        List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
+        if (CollUtil.isEmpty(list)) {
+            throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
+        }
+        SysSocialVo social;
+        if (TenantHelper.isEnable()) {
+            Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
+            if (opt.isEmpty()) {
+                throw new ServiceException("对不起,你没有权限登录当前租户!");
+            }
+            social = opt.get();
+        } else {
+            social = list.get(0);
+        }
+        LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
+            SysUserVo user = loadUser(social.getUserId());
+            // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
+            return loginService.buildLoginUser(user);
+        });
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    private SysUserVo loadUser(Long userId) {
+        SysUserVo user = userMapper.selectVoById(userId);
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", "");
+            throw new UserException("user.not.exists", "");
+        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", "");
+            throw new UserException("user.blocked", "");
+        }
+        return user;
+    }
+
+}

+ 111 - 0
jk-admin/src/main/java/org/jk/web/service/impl/XcxAuthStrategy.java

@@ -0,0 +1,111 @@
+package org.jk.web.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
+import org.jk.common.core.constant.SystemConstants;
+import org.jk.common.core.domain.model.XcxLoginBody;
+import org.jk.common.core.domain.model.XcxLoginUser;
+import org.jk.common.core.exception.ServiceException;
+import org.jk.common.core.utils.ValidatorUtils;
+import org.jk.common.json.utils.JsonUtils;
+import org.jk.common.satoken.utils.LoginHelper;
+import org.jk.system.domain.vo.SysClientVo;
+import org.jk.system.domain.vo.SysUserVo;
+import org.jk.web.domain.vo.LoginVo;
+import org.jk.web.service.IAuthStrategy;
+import org.jk.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 小程序认证策略
+ *
+ * @author jk
+ */
+@Slf4j
+@Service("xcx" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class XcxAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+
+    @Override
+    public LoginVo login(String body, SysClientVo client) {
+        XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        // xcxCode 为 小程序调用 wx.login 授权后获取
+        String xcxCode = loginBody.getXcxCode();
+        // 多个小程序识别使用
+        String appid = loginBody.getAppid();
+
+        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
+        AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
+            .clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
+            .ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
+        AuthCallback authCallback = new AuthCallback();
+        authCallback.setCode(xcxCode);
+        AuthResponse<AuthUser> resp = authRequest.login(authCallback);
+        String openid, unionId;
+        if (resp.ok()) {
+            AuthToken token = resp.getData().getToken();
+            openid = token.getOpenId();
+            // 微信小程序只有关联到微信开放平台下之后才能获取到 unionId,因此unionId不一定能返回。
+            unionId = token.getUnionId();
+        } else {
+            throw new ServiceException(resp.getMsg());
+        }
+        // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
+        SysUserVo user = loadUserByOpenid(openid);
+        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
+        XcxLoginUser loginUser = new XcxLoginUser();
+        loginUser.setTenantId(user.getTenantId());
+        loginUser.setUserId(user.getUserId());
+        loginUser.setUsername(user.getUserName());
+        loginUser.setNickname(user.getNickName());
+        loginUser.setUserType(user.getUserType());
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        loginUser.setOpenid(openid);
+
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        loginVo.setOpenid(openid);
+        return loginVo;
+    }
+
+    private SysUserVo loadUserByOpenid(String openid) {
+        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
+        // todo 自行实现 userService.selectUserByOpenid(openid);
+        SysUserVo user = new SysUserVo();
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", openid);
+            // todo 用户不存在 业务逻辑自行实现
+        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", openid);
+            // todo 用户已被停用 业务逻辑自行实现
+        }
+        return user;
+    }
+
+}

+ 273 - 0
jk-admin/src/main/resources/application-dev.yml

@@ -0,0 +1,273 @@
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: false
+  url: http://localhost:9090/admin
+  instance:
+    service-host-type: IP
+    metadata:
+      username: ${spring.boot.admin.client.username}
+      userpassword: ${spring.boot.admin.client.password}
+  username: @monitor.username@
+  password: @monitor.password@
+
+--- # snail-job 配置
+snail-job:
+  enabled: true
+  # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
+  group: "ruoyi_group"
+  # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表
+  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
+  server:
+    host: 127.0.0.1
+    port: 17888
+  # 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
+  namespace: ${spring.profiles.active}
+  # 随主应用端口漂移
+  port: 2${server.port}
+  # 客户端ip指定
+  host:
+
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+    dynamic:
+      # 性能分析插件(有性能损耗 不建议生产环境使用)
+      p6spy: true
+      # 设置默认的数据源或者数据源组,默认值即为 master
+      primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
+      datasource:
+        # 主库数据源
+        master:
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
+          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
+          url: jdbc:mysql://10.168.100.10:3306/jk_rag?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+          username: takai
+          password: T@kai2025
+#          url: jdbc:mysql://localhost:3306/jk_rag?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+#          username: root
+#          password: 123456
+#        # 从库数据源
+#        slave:
+#          lazy: true
+#          type: ${spring.datasource.type}
+#          driverClassName: com.mysql.cj.jdbc.Driver
+#          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+#          username:
+#          password:
+#        oracle:
+#          type: ${spring.datasource.type}
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//localhost:1521/XE
+#          username: ROOT
+#          password: root
+#        postgres:
+#          type: ${spring.datasource.type}
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          type: ${spring.datasource.type}
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
+      hikari:
+        # 最大连接池数量
+        maxPoolSize: 20
+        # 最小空闲线程数量
+        minIdle: 10
+        # 配置获取连接等待超时的时间
+        connectionTimeout: 30000
+        # 校验超时时间
+        validationTimeout: 5000
+        # 空闲连接存活最大时间,默认10分钟
+        idleTimeout: 600000
+        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+        maxLifetime: 1800000
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
+
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
+spring.data:
+  redis:
+    # 地址
+    host: localhost
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 1
+    # redis 密码必须配置
+    #password: jk123
+    # 连接超时时间
+    timeout: 10s
+    # 是否开启ssl
+    ssl.enabled: false
+
+# redisson 配置
+redisson:
+  # redis key前缀
+  keyPrefix:
+  # 线程池数量
+  threads: 4
+  # Netty线程池数量
+  nettyThreads: 8
+  # 单节点配置
+  singleServerConfig:
+    # 客户端名称 不能用中文
+    clientName: JK-RAG-BACKEND
+    # 最小空闲连接数
+    connectionMinimumIdleSize: 8
+    # 连接池大小
+    connectionPoolSize: 32
+    # 连接空闲超时,单位:毫秒
+    idleConnectionTimeout: 10000
+    # 命令等待超时,单位:毫秒
+    timeout: 3000
+    # 发布和订阅连接池大小
+    subscriptionConnectionPoolSize: 50
+
+--- # mail 邮件发送
+mail:
+  enabled: false
+  host: smtp.163.com
+  port: 465
+  # 是否需要用户名密码验证
+  auth: true
+  # 发送方,遵循RFC-822标准
+  from: xxx@163.com
+  # 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
+  user: xxx@163.com
+  # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
+  pass: xxxxxxxxxx
+  # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
+  starttlsEnable: true
+  # 使用SSL安全连接
+  sslEnable: true
+  # SMTP超时时长,单位毫秒,缺省值不超时
+  timeout: 0
+  # Socket连接超时值,单位毫秒,缺省值不超时
+  connectionTimeout: 0
+
+--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
+# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
+sms:
+  # 配置源类型用于标定配置来源(interface,yaml)
+  config-type: yaml
+  # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
+  restricted: true
+  # 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
+  minute-max: 1
+  # 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
+  account-max: 30
+  # 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
+  blends:
+    # 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
+    # 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
+    config1:
+      # 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
+      supplier: alibaba
+      # 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。
+      access-key-id: 您的accessKey
+      # 称为accessSecret有些称之为apiSecret
+      access-key-secret: 您的accessKeySecret
+      signature: 您的短信签名
+      sdk-app-id: 您的sdkAppId
+    config2:
+      # 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
+      supplier: tencent
+      access-key-id: 您的accessKey
+      access-key-secret: 您的accessKeySecret
+      signature: 您的短信签名
+      sdk-app-id: 您的sdkAppId
+
+
+--- # 三方授权
+justauth:
+  # 前端外网访问地址
+  address: http://localhost:80
+  type:
+    maxkey:
+      # maxkey 服务器地址
+      # 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
+      server-url: http://sso.maxkey.top
+      client-id: 876892492581044224
+      client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
+      redirect-uri: ${justauth.address}/social-callback?source=maxkey
+    topiam:
+      # topiam 服务器地址
+      server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
+      client-id: 449c4*********937************759
+      client-secret: ac7***********1e0************28d
+      redirect-uri: ${justauth.address}/social-callback?source=topiam
+      scopes: [openid, email, phone, profile]
+    qq:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=qq
+      union-id: false
+    weibo:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=weibo
+    gitee:
+      client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
+      client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
+      redirect-uri: ${justauth.address}/social-callback?source=gitee
+    dingtalk:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=dingtalk
+    baidu:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=baidu
+    csdn:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=csdn
+    coding:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=coding
+      coding-group-name: xx
+    oschina:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=oschina
+    alipay_wallet:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
+      alipay-public-key: MIIB**************DAQAB
+    wechat_open:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=wechat_open
+    wechat_mp:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
+    wechat_enterprise:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
+      agent-id: 1000002
+    gitlab:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitlab
+    gitea:
+      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+      # gitea 服务器地址
+      server-url: https://demo.gitea.com
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitea

+ 274 - 0
jk-admin/src/main/resources/application-prod.yml

@@ -0,0 +1,274 @@
+--- # 临时文件存储位置 避免临时文件被系统清理报错
+spring.servlet.multipart.location: /jk/server/temp
+
+--- # 监控中心配置
+spring.boot.admin.client:
+  # 增加客户端开关
+  enabled: false
+  url: http://localhost:9090/admin
+  instance:
+    service-host-type: IP
+    metadata:
+      username: ${spring.boot.admin.client.username}
+      userpassword: ${spring.boot.admin.client.password}
+  username: @monitor.username@
+  password: @monitor.password@
+
+--- # snail-job 配置
+snail-job:
+  enabled: true
+  # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
+  group: "ruoyi_group"
+  # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表
+  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
+  server:
+    host: 127.0.0.1
+    port: 17888
+  # 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
+  namespace: ${spring.profiles.active}
+  # 随主应用端口漂移
+  port: 2${server.port}
+  # 客户端ip指定
+  host:
+
+--- # 数据源配置
+spring:
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
+    dynamic:
+      # 性能分析插件(有性能损耗 不建议生产环境使用)
+      p6spy: false
+      # 设置默认的数据源或者数据源组,默认值即为 master
+      primary: master
+      # 严格模式 匹配不到数据源则报错
+      strict: true
+      jackson:
+        time-zone: Asia/Shanghai # 覆盖 Jackson 默认 UTC 时区
+      datasource:
+        # 主库数据源
+        master:
+          type: ${spring.datasource.type}
+          driverClassName: com.mysql.cj.jdbc.Driver
+          # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
+          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
+          url: jdbc:mysql://10.1.14.18:3306/jk_rag?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+          username: test
+          password: J7#mQ!vP9xL@nR2sK&ea
+#        # 从库数据源
+#        slave:
+#          lazy: true
+#          type: ${spring.datasource.type}
+#          driverClassName: com.mysql.cj.jdbc.Driver
+#          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+#          username:
+#          password:
+#        oracle:
+#          type: ${spring.datasource.type}
+#          driverClassName: oracle.jdbc.OracleDriver
+#          url: jdbc:oracle:thin:@//localhost:1521/XE
+#          username: ROOT
+#          password: root
+#        postgres:
+#          type: ${spring.datasource.type}
+#          driverClassName: org.postgresql.Driver
+#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
+#          username: root
+#          password: root
+#        sqlserver:
+#          type: ${spring.datasource.type}
+#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
+#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
+#          username: SA
+#          password: root
+      hikari:
+        # 最大连接池数量
+        maxPoolSize: 20
+        # 最小空闲线程数量
+        minIdle: 10
+        # 配置获取连接等待超时的时间
+        connectionTimeout: 30000
+        # 校验超时时间
+        validationTimeout: 5000
+        # 空闲连接存活最大时间,默认10分钟
+        idleTimeout: 600000
+        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
+        maxLifetime: 3600000
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
+
+--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
+spring.data:
+  redis:
+    # 地址
+    host: localhost
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 1
+    # redis 密码必须配置
+    #password: jk123
+    # 连接超时时间
+    timeout: 10s
+    # 是否开启ssl
+    ssl.enabled: false
+
+# redisson 配置
+redisson:
+  # redis key前缀
+  keyPrefix:
+  # 线程池数量
+  threads: 16
+  # Netty线程池数量
+  nettyThreads: 32
+  # 单节点配置
+  singleServerConfig:
+    # 客户端名称 不能用中文
+    clientName: JK-RAG-BACKEND
+    # 最小空闲连接数
+    connectionMinimumIdleSize: 32
+    # 连接池大小
+    connectionPoolSize: 64
+    # 连接空闲超时,单位:毫秒
+    idleConnectionTimeout: 10000
+    # 命令等待超时,单位:毫秒
+    timeout: 3000
+    # 发布和订阅连接池大小
+    subscriptionConnectionPoolSize: 50
+
+--- # mail 邮件发送
+mail:
+  enabled: false
+  host: smtp.163.com
+  port: 465
+  # 是否需要用户名密码验证
+  auth: true
+  # 发送方,遵循RFC-822标准
+  from: xxx@163.com
+  # 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
+  user: xxx@163.com
+  # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
+  pass: xxxxxxxxxx
+  # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
+  starttlsEnable: true
+  # 使用SSL安全连接
+  sslEnable: true
+  # SMTP超时时长,单位毫秒,缺省值不超时
+  timeout: 0
+  # Socket连接超时值,单位毫秒,缺省值不超时
+  connectionTimeout: 0
+
+--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
+# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
+sms:
+  # 配置源类型用于标定配置来源(interface,yaml)
+  config-type: yaml
+  # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
+  restricted: true
+  # 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
+  minute-max: 1
+  # 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
+  account-max: 30
+  # 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
+  blends:
+    # 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
+    # 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
+    config1:
+      # 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
+      supplier: alibaba
+      # 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。
+      access-key-id: 您的accessKey
+      # 称为accessSecret有些称之为apiSecret
+      access-key-secret: 您的accessKeySecret
+      signature: 您的短信签名
+      sdk-app-id: 您的sdkAppId
+    config2:
+      # 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
+      supplier: tencent
+      access-key-id: 您的accessKey
+      access-key-secret: 您的accessKeySecret
+      signature: 您的短信签名
+      sdk-app-id: 您的sdkAppId
+
+--- # 三方授权
+justauth:
+  # 前端外网访问地址
+  address: http://localhost:80
+  type:
+    maxkey:
+      # maxkey 服务器地址
+      # 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
+      server-url: http://sso.maxkey.top
+      client-id: 876892492581044224
+      client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
+      redirect-uri: ${justauth.address}/social-callback?source=maxkey
+    topiam:
+      # topiam 服务器地址
+      server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
+      client-id: 449c4*********937************759
+      client-secret: ac7***********1e0************28d
+      redirect-uri: ${justauth.address}/social-callback?source=topiam
+      scopes: [ openid, email, phone, profile ]
+    qq:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=qq
+      union-id: false
+    weibo:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=weibo
+    gitee:
+      client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
+      client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
+      redirect-uri: ${justauth.address}/social-callback?source=gitee
+    dingtalk:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=dingtalk
+    baidu:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=baidu
+    csdn:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=csdn
+    coding:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=coding
+      coding-group-name: xx
+    oschina:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=oschina
+    alipay_wallet:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
+      alipay-public-key: MIIB**************DAQAB
+    wechat_open:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=wechat_open
+    wechat_mp:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
+    wechat_enterprise:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
+      agent-id: 1000002
+    gitlab:
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitlab
+    gitea:
+      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+      # gitea 服务器地址
+      server-url: https://demo.gitea.com
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitea

+ 382 - 0
jk-admin/src/main/resources/application.yml

@@ -0,0 +1,382 @@
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 9080
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  # undertow 配置
+  undertow:
+    # HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
+    max-http-post-size: -1
+    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
+    # 每块buffer的空间大小,越小的空间被利用越充分
+    buffer-size: 512
+    # 是否分配的直接内存
+    direct-buffers: true
+    threads:
+      # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
+      io: 8
+      # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
+      worker: 256
+
+captcha:
+  # 是否启用验证码校验
+  enable: true
+  # 验证码类型 math 数组计算 char 字符验证
+  type: MATH
+  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
+  category: CIRCLE
+  # 数字验证码位数
+  numberLength: 1
+  # 字符验证码长度
+  charLength: 4
+
+# 日志配置
+logging:
+  level:
+    org.jk: @logging.level@
+    org.springframework: warn
+    org.mybatis.spring.mapper: error
+    org.apache.fury: warn
+  config: classpath:logback-plus.xml
+
+# 用户配置
+user:
+  password:
+    # 密码最大错误次数
+    maxRetryCount: 5
+    # 密码锁定时间(默认10分钟)
+    lockTime: 10
+
+# Spring配置
+spring:
+  application:
+    name: JK-RAG-BACKEND
+  threads:
+    # 开启虚拟线程 仅jdk21可用
+    virtual:
+      enabled: false
+  task:
+    execution:
+      # 从 springboot 3.5 开始 spring自带线程池
+      # 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
+      thread-name-prefix: async-
+      # 由spring自己初始化线程池
+      mode: force
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: i18n/messages
+  profiles:
+    active: @profiles.active@
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size: 50MB
+      # 设置总上传的文件大小
+      max-request-size: 300MB
+  mvc:
+    # 设置静态资源路径 防止所有请求都去查静态资源
+    static-path-pattern: /static/**
+    format:
+      date-time: yyyy-MM-dd HH:mm:ss
+  jackson:
+    # 日期格式化
+    date-format: yyyy-MM-dd HH:mm:ss
+    serialization:
+      # 格式化输出
+      indent_output: false
+      # 忽略无法转换的对象
+      fail_on_empty_beans: false
+    deserialization:
+      # 允许对象忽略json中不存在的属性
+      fail_on_unknown_properties: false
+
+# Sa-Token配置
+sa-token:
+  # token名称 (同时也是cookie名称)
+  token-name: Authorization
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # jwt秘钥
+  jwt-secret-key: abcdefghijklmnopqrstuvwxyz
+
+# security配置
+security:
+  # 排除路径
+  excludes:
+    - /*.html
+    - /**/*.html
+    - /**/*.css
+    - /**/*.js
+    - /favicon.ico
+    - /error
+    - /*/api-docs
+    - /*/api-docs/**
+    - /warm-flow-ui/config
+    - /deepseek/api/updateDocumentByPython
+    - /resource/sse/send
+    - /deepseek/api/chat
+    - /deepseek/api/dialog/list/*
+    - /deepseek/api/application/list
+    - /deepseek/api/presets/*
+    - /deepseek/api/async/completions
+    - /deepseek/api/dialog/save
+    - /deepseek/evaluation/editEvaluationTaskByPython
+    - /deepseek/api/preChat
+
+# 多租户配置
+tenant:
+  # 是否开启
+  enable: true
+  # 排除表
+  excludes:
+    - sys_menu
+    - sys_tenant
+    - sys_tenant_package
+    - sys_role_dept
+    - sys_role_menu
+    - sys_user_post
+    - sys_user_role
+    - sys_client
+    - sys_oss_config
+    - flow_spel
+
+# MyBatisPlus配置
+# https://baomidou.com/config/
+mybatis-plus:
+  # 自定义配置 是否全局开启逻辑删除 关闭后 所有逻辑删除功能将失效
+  enableLogicDelete: true
+  # 多包名使用 例如 org.jk.**.mapper,org.xxx.**.mapper
+  mapperPackage: org.jk.**.mapper
+  # 对应的 XML 文件位置
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 实体扫描,多个package用逗号或者分号分隔
+  typeAliasesPackage: org.jk.**.domain
+  global-config:
+    dbConfig:
+      # 主键类型
+      # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
+      # 如需改为自增 需要将数据库表全部设置为自增
+      idType: ASSIGN_ID
+
+# 数据加密
+mybatis-encryptor:
+  # 是否开启加密
+  enable: false
+  # 默认加密算法
+  algorithm: BASE64
+  # 编码方式 BASE64/HEX。默认BASE64
+  encode: BASE64
+  # 安全秘钥 对称算法的秘钥 如:AES,SM4
+  password:
+  # 公私钥 非对称算法的公私钥 如:SM2,RSA
+  publicKey:
+  privateKey:
+
+# api接口加密
+api-decrypt:
+  # 是否开启全局接口加密
+  enabled: true
+  # AES 加密头标识
+  headerFlag: encrypt-key
+  # 响应加密公钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换
+  # 对应前端解密私钥 MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
+  publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ==
+  # 请求解密私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换
+  # 对应前端加密公钥 MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
+  privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=
+
+springdoc:
+  api-docs:
+    # 是否开启接口文档
+    enabled: true
+  info:
+    # 标题
+    title: '标题:JK-RAG-BACKEND多租户管理系统_接口文档'
+    # 描述
+    description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
+    # 版本
+    version: '版本号: ${project.version}'
+    # 作者信息
+    contact:
+      name: Lion Li
+      email: crazylionli@163.com
+      url: https://gitee.com/dromara/RuoYi-Vue-Plus
+  #这里定义了两个分组,可定义多个,也可以不定义
+  group-configs:
+    - group: 1.演示模块
+      packages-to-scan: org.jk.demo
+    - group: 2.通用模块
+      packages-to-scan: org.jk.web
+    - group: 3.系统模块
+      packages-to-scan: org.jk.system
+    - group: 4.代码生成模块
+      packages-to-scan: org.jk.generator
+    - group: 5.工作流模块
+      packages-to-scan: org.jk.workflow
+    - group: 6.RAG模块
+      packages-to-scan: org.jk.rag
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接
+  excludeUrls:
+    - /system/notice
+
+
+--- # 分布式锁 lock4j 全局配置
+lock4j:
+  # 获取分布式锁超时时间,默认为 3000 毫秒
+  acquire-timeout: 3000
+  # 分布式锁的超时时间,默认为 30 秒
+  expire: 30000
+
+--- # Actuator 监控端点的配置项
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS
+    logfile:
+      external-file: ./logs/sys-console.log
+
+--- # 默认/推荐使用sse推送
+sse:
+  enabled: true
+  path: /resource/sse
+
+--- # websocket
+websocket:
+  # 如果关闭 需要和前端开关一起关闭
+  enabled: false
+  # 路径
+  path: /resource/websocket
+  # 设置访问源地址
+  allowedOrigins: '*'
+
+--- # warm-flow工作流配置
+warm-flow:
+  # 是否开启工作流,默认true
+  enabled: true
+  # 是否开启设计器ui
+  ui: true
+  # 是否显示流程图顶部文字
+  top-text-show: true
+  # 是否渲染节点悬浮提示,默认true
+  node-tooltip: true
+  # 默认Authorization,如果有多个token,用逗号分隔
+  token-name: ${sa-token.token-name},clientid
+#智谱
+bigmodel:
+  #api key
+  bigModelApiKey: cd9be3722186284317d5435cfefc7fe8.IuNZdtvNUJhHmg36
+  #baseurl
+  baseurl: https://open.bigmodel.cn/api/llm-application/open
+  #sse
+  sse:  /model-api/{id}/sse-invoke
+  #文档图片列表
+  image:  /document/slice/image_list/{id}
+  #知识列表
+  document: /document
+  #知识库列表
+  knowledge: /knowledge
+  #应用列表
+  application: /application
+  #同步GLM-4
+  completions: https://open.bigmodel.cn/api/paas/v4/chat/completions
+  #切片信息
+  sliceInfo: /v2/{request_id}/slice_info
+  #异步GLM-4
+  asyncCompletions: https://open.bigmodel.cn/api/paas/v4/async/chat/completions
+  #异步GLM-4调用结束后使用任务结果查询
+  asyncResult: https://open.bigmodel.cn/api/paas/v4/async-result/{id}
+  #智能体会话
+  assistant: https://open.bigmodel.cn/api/paas/v4/assistant
+  #上传用于模型微调、知识库、Batch、文件抽取等功能所使用的文件。
+  uploadFile: https://open.bigmodel.cn/api/paas/v4/files
+  #模型列表
+  modelList: https://open.bigmodel.cn/api/biz/model/trial/list
+  #提示词
+  prompt: 你是总结和提问大师。 """应用的名称:{{应用名称}} 这是用户的历史对话记录:{{历史对话}}""" 你只根据用户的对话记录,推演出用户接下来可能提出的问题,不要杜撰问题。可以参考应用的名称和应用的简介。 你必须遵守以下要求:1. 不要输出用户问过的问题;2. 你需要输出3个问题供用户选择。3. 你只需要输出问题,不需要解释,不需要提问。4. 你的问题可以是空的,但你不能杜撰问题。5. 问题需要站在使用这个应用的人的视角提出,因此你要注意提问的语气和人称代词。不要用您这个字。你一定要按照以下格式输出:{"问题":["xxx","xxx","xxx"]}
+  #消息是否Base64编码
+  contentBase64Encode: true
+deepseek:
+  #base url
+  #baseurl: http://10.168.100.17:18079
+  #baseurl: http://xia0miduo.gicp.net:6001
+  baseurl: http://10.168.100.17:6000
+  #baseurl: http://10.1.14.16:6000
+  #  baseurl: http://192.168.3.209:18078
+  #创建知识库
+  createKnowledge: /rag/create_collection
+  #删除知识库
+  deleteKnowledge: /rag/delete_knowledge
+  #获取切片列表
+  slicePage: /rag/search_word
+  #删除切片
+  deleteSlice: /rag/delete_slice
+  #切片详情
+  sliceDetail: /rag/search
+  #编辑切片信息
+  updateSlice: /rag/update_slice
+  #上传文件
+  uploadKnowledge: /upload_knowledge
+  #聊天
+  chat: /rag/chat
+  #异步提示词(还想问)
+  asyncCompletions: /rag/query
+  #删除知识文件
+  deleteDoc: /rag/delete_doc
+  #新增切片
+  addSlice: /rag/insert_slice
+  #获取排序切片列表
+  searchSlice: /rag/slice/search
+  #联网搜索结果
+  webSearch: /web/search
+  #deepseek单独聊天
+  baseChat: http://10.1.27.4:18078
+  #导入文档
+  importDocument: /rag/copy_multi_docs_to_collection
+  #取消上传
+  cancelTask: /rag/cancel_task
+  #语音转文本
+  audio: /ai/translate/audio
+  #评测
+  evaluate: /rag/evaluate
+  tokenSpeed: /rag/token_speed
+  #清除脏数据接口
+  baseCleanUrl: http://10.1.14.16:9090/cleanup
+  prompt: 你是总结和提问大师。你只根据用户的对话记录,根据对话记录生成可能会问的问题,不要杜撰问题。问题的答案必须来自上面的对话记录。 你必须遵守以下要求:1. 不要输出用户问过的问题;2. 你需要输出3个问题供用户选择。3. 你只需要输出问题,不需要解释,不需要提问。4. 你的问题可以是空的,但你不能杜撰问题。5. 问题需要站在使用这个应用的人的视角提出,因此你要注意提问的语气和人称代词。不要用您这个字。6.生成的问题,必须能从提供的对话记录中找到答案。你一定要按照以下格式输出:{'问题':['xxx','xxx','xxx']}
+jk:
+  #Appid
+  iamAppid: e971e84b574c40b2
+  #Appsecret
+  iamAppsecret: 51e7f274f71f4887b25015fc0743e871
+  #clientID
+  iamClientID: e97f94cf93761f4d69e8
+  #clientSecret
+  iamClientSecret: 13845f824b5d9f4f2a58159109aac317263d
+  #用户接口url
+  iamUserUrl: http://esc.sribs.com.cn:8080/outapi/api/v1/account/list
+  #用户回调接口,每次用户接口调用成功后都要回调一次接口
+  iamCallbackUrl: http://esc.sribs.com.cn:8080/outapi/api/v1/account/callback
+  #部门接口url
+  iamDeptUrl: http://esc.sribs.com.cn:8080/outapi/api/v1/org/listAll
+  #岗位接口url
+  iamPostUrl: http://esc.sribs.com.cn:8080/outapi/api/v1/job/listAll
+  #code换取tokenurl
+  iamTokenUrl: http://esc.sribs.com.cn:8080/esc-sso/oauth2.0/accessToken
+  #token换取用户信息
+  iamProfileUrl: http://esc.sribs.com.cn:8080/esc-sso/oauth2.0/profile
+  #虚拟密码
+  jkPsw: GybjcxhDe8XQtabXVP1A

+ 8 - 0
jk-admin/src/main/resources/banner.txt

@@ -0,0 +1,8 @@
+Application Version: ${revision}
+Spring Boot Version: ${spring-boot.version}
+     ____.____  __.        __________    _____    ________         __________    _____  _________  ____  __.___________ _______  ________
+    |    |    |/ _|        \______   \  /  _  \  /  _____/         \______   \  /  _  \ \_   ___ \|    |/ _|\_   _____/ \      \ \______ \
+    |    |      <    ______ |       _/ /  /_\  \/   \  ___   ______ |    |  _/ /  /_\  \/    \  \/|      <   |    __)_  /   |   \ |    |  \
+/\__|    |    |  \  /_____/ |    |   \/    |    \    \_\  \ /_____/ |    |   \/    |    \     \___|    |  \  |        \/    |    \|    `   \
+\________|____|__ \         |____|_  /\____|__  /\______  /         |______  /\____|__  /\______  /____|__ \/_______  /\____|__  /_______  /
+                 \/                \/         \/        \/                 \/         \/        \/        \/        \/         \/        \/

+ 62 - 0
jk-admin/src/main/resources/i18n/messages.properties

@@ -0,0 +1,62 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+user.password.not.blank=用户密码不能为空
+user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
+user.password.not.valid=* 5-50个字符
+user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
+user.email.not.valid=邮箱格式错误
+user.email.not.blank=邮箱不能为空
+user.phonenumber.not.blank=用户手机号不能为空
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.blocked=认证权限类型已禁用
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
+email.code.not.blank=邮箱验证码不能为空
+email.code.retry.limit.count=邮箱验证码输入错误{0}次
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
+xcx.code.not.blank=小程序[code]不能为空
+social.source.not.blank=第三方登录平台[source]不能为空
+social.code.not.blank=第三方登录平台[code]不能为空
+social.state.not.blank=第三方登录平台[state]不能为空
+##租户
+tenant.number.not.blank=租户编号不能为空
+tenant.not.exists=对不起, 您的租户不存在,请联系管理员
+tenant.blocked=对不起,您的租户已禁用,请联系管理员
+tenant.expired=对不起,您的租户已过期,请联系管理员

+ 62 - 0
jk-admin/src/main/resources/i18n/messages_en_US.properties

@@ -0,0 +1,62 @@
+#错误消息
+not.null=* Required fill in
+user.jcaptcha.error=Captcha error
+user.jcaptcha.expire=Captcha invalid
+user.not.exists=Sorry, your account: {0} does not exist
+user.password.not.match=User does not exist/Password error
+user.password.retry.limit.count=Password input error {0} times
+user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
+user.password.delete=Sorry, your account:{0} has been deleted
+user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
+role.blocked=Role disabled,please contact administrators
+user.logout.success=Exit successful
+length.not.valid=The length must be between {min} and {max} characters
+user.username.not.blank=Username cannot be blank
+user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
+user.username.length.valid=Account length must be between {min} and {max} characters
+user.password.not.blank=Password cannot be empty
+user.password.length.valid=Password length must be between {min} and {max} characters
+user.password.not.valid=* 5-50 characters
+user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character
+user.email.not.valid=Mailbox format error
+user.email.not.blank=Mailbox cannot be blank
+user.phonenumber.not.blank=Phone number cannot be blank
+user.mobile.phone.number.not.valid=Phone number format error
+user.login.success=Login successful
+user.register.success=Register successful
+user.register.save.error=Failed to save user {0}, The registered account already exists
+user.register.error=Register failed, please contact system administrator
+user.notfound=Please login again
+user.forcelogout=The administrator is forced to exit,please login again
+user.unknown.error=Unknown error, please login again
+auth.grant.type.error=Auth grant type error
+auth.grant.type.blocked=Auth grant type disabled
+auth.grant.type.not.blank=Auth grant type cannot be blank
+auth.clientid.not.blank=Auth clientid cannot be blank
+##文件上传消息
+upload.exceed.maxSize=The uploaded file size exceeds the limit file size!<br/>the maximum allowed file size is:{0}MB!
+upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
+##权限
+no.permission=You do not have permission to the data,please contact your administrator to add permissions [{0}]
+no.create.permission=You do not have permission to create data,please contact your administrator to add permissions [{0}]
+no.update.permission=You do not have permission to modify data,please contact your administrator to add permissions [{0}]
+no.delete.permission=You do not have permission to delete data,please contact your administrator to add permissions [{0}]
+no.export.permission=You do not have permission to export data,please contact your administrator to add permissions [{0}]
+no.view.permission=You do not have permission to view data,please contact your administrator to add permissions [{0}]
+repeat.submit.message=Repeat submit is not allowed, please try again later
+rate.limiter.message=Visit too frequently, please try again later
+sms.code.not.blank=Sms code cannot be blank
+sms.code.retry.limit.count=Sms code input error {0} times
+sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
+email.code.not.blank=Email code cannot be blank
+email.code.retry.limit.count=Email code input error {0} times
+email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
+xcx.code.not.blank=Mini program [code] cannot be blank
+social.source.not.blank=Social login platform [source] cannot be blank
+social.code.not.blank=Social login platform [code] cannot be blank
+social.state.not.blank=Social login platform [state] cannot be blank
+##租户
+tenant.number.not.blank=Tenant number cannot be blank
+tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
+tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
+tenant.expired=Sorry, your tenant has expired. Please contact the administrator.

+ 62 - 0
jk-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -0,0 +1,62 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+user.password.not.blank=用户密码不能为空
+user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
+user.password.not.valid=* 5-50个字符
+user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
+user.email.not.valid=邮箱格式错误
+user.email.not.blank=邮箱不能为空
+user.phonenumber.not.blank=用户手机号不能为空
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.blocked=认证权限类型已禁用
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=点击过于频繁,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
+email.code.not.blank=邮箱验证码不能为空
+email.code.retry.limit.count=邮箱验证码输入错误{0}次
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
+xcx.code.not.blank=小程序[code]不能为空
+social.source.not.blank=第三方登录平台[source]不能为空
+social.code.not.blank=第三方登录平台[code]不能为空
+social.state.not.blank=第三方登录平台[state]不能为空
+##租户
+tenant.number.not.blank=租户编号不能为空
+tenant.not.exists=对不起, 您的租户不存在,请联系管理员
+tenant.blocked=对不起,您的租户已禁用,请联系管理员
+tenant.expired=对不起,您的租户已过期,请联系管理员

BIN
jk-admin/src/main/resources/ip2region.xdb


+ 129 - 0
jk-admin/src/main/resources/logback-plus.xml

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <property name="log.path" value="/www/logs"/>
+    <property name="console.log.pattern"
+              value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${console.log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- 控制台输出 -->
+    <appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-console.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大 1天 -->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <!-- 系统日志输出 -->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- info异步输出 -->
+    <appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_info"/>
+    </appender>
+
+    <!-- error异步输出 -->
+    <appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <!-- 添加附加的appender,最多只能添加一个 -->
+        <appender-ref ref="file_error"/>
+    </appender>
+
+    <!-- 整合 skywalking 控制台输出 tid -->
+<!--    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">-->
+<!--        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!--            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!--                <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!--            </layout>-->
+<!--            <charset>utf-8</charset>-->
+<!--        </encoder>-->
+<!--    </appender>-->
+
+    <!-- 整合 skywalking 推送采集日志 -->
+<!--    <appender name="sky_log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">-->
+<!--        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
+<!--            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
+<!--                <pattern>[%tid] ${console.log.pattern}</pattern>-->
+<!--            </layout>-->
+<!--            <charset>utf-8</charset>-->
+<!--        </encoder>-->
+<!--    </appender>-->
+
+    <!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="console" />
+        <appender-ref ref="async_info" />
+        <appender-ref ref="async_error" />
+        <appender-ref ref="file_console" />
+<!--        <appender-ref ref="sky_log"/>-->
+    </root>
+
+</configuration>

+ 45 - 0
jk-admin/src/test/java/org/jk/test/AssertUnitTest.java

@@ -0,0 +1,45 @@
+package org.jk.test;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * 断言单元测试案例
+ *
+ * @author jk
+ */
+@DisplayName("断言单元测试案例")
+public class AssertUnitTest {
+
+    @DisplayName("测试 assertEquals 方法")
+    @Test
+    public void testAssertEquals() {
+        Assertions.assertEquals("666", new String("666"));
+        Assertions.assertNotEquals("666", new String("666"));
+    }
+
+    @DisplayName("测试 assertSame 方法")
+    @Test
+    public void testAssertSame() {
+        Object obj = new Object();
+        Object obj1 = obj;
+        Assertions.assertSame(obj, obj1);
+        Assertions.assertNotSame(obj, obj1);
+    }
+
+    @DisplayName("测试 assertTrue 方法")
+    @Test
+    public void testAssertTrue() {
+        Assertions.assertTrue(true);
+        Assertions.assertFalse(true);
+    }
+
+    @DisplayName("测试 assertNull 方法")
+    @Test
+    public void testAssertNull() {
+        Assertions.assertNull(null);
+        Assertions.assertNotNull(null);
+    }
+
+}

+ 70 - 0
jk-admin/src/test/java/org/jk/test/DemoUnitTest.java

@@ -0,0 +1,70 @@
+package org.jk.test;
+
+import org.jk.common.web.config.properties.CaptchaProperties;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 单元测试案例
+ *
+ * @author jk
+ */
+@SpringBootTest // 此注解只能在 springboot 主包下使用 需包含 main 方法与 yml 配置文件
+@DisplayName("单元测试案例")
+public class DemoUnitTest {
+
+    @Autowired
+    private CaptchaProperties captchaProperties;
+
+    @DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
+    @Test
+    public void testTest() {
+        System.out.println(captchaProperties);
+    }
+
+    @Disabled
+    @DisplayName("测试 @Disabled 注解")
+    @Test
+    public void testDisabled() {
+        System.out.println(captchaProperties);
+    }
+
+    @Timeout(value = 2L, unit = TimeUnit.SECONDS)
+    @DisplayName("测试 @Timeout 注解")
+    @Test
+    public void testTimeout() throws InterruptedException {
+        Thread.sleep(3000);
+        System.out.println(captchaProperties);
+    }
+
+
+    @DisplayName("测试 @RepeatedTest 注解")
+    @RepeatedTest(3)
+    public void testRepeatedTest() {
+        System.out.println(666);
+    }
+
+    @BeforeAll
+    public static void testBeforeAll() {
+        System.out.println("@BeforeAll ==================");
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+    @AfterAll
+    public static void testAfterAll() {
+        System.out.println("@AfterAll ==================");
+    }
+
+}

+ 72 - 0
jk-admin/src/test/java/org/jk/test/ParamUnitTest.java

@@ -0,0 +1,72 @@
+package org.jk.test;
+
+import org.jk.common.core.enums.UserType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * 带参数单元测试案例
+ *
+ * @author jk
+ */
+@DisplayName("带参数单元测试案例")
+public class ParamUnitTest {
+
+    @DisplayName("测试 @ValueSource 注解")
+    @ParameterizedTest
+    @ValueSource(strings = {"t1", "t2", "t3"})
+    public void testValueSource(String str) {
+        System.out.println(str);
+    }
+
+    @DisplayName("测试 @NullSource 注解")
+    @ParameterizedTest
+    @NullSource
+    public void testNullSource(String str) {
+        System.out.println(str);
+    }
+
+    @DisplayName("测试 @EnumSource 注解")
+    @ParameterizedTest
+    @EnumSource(UserType.class)
+    public void testEnumSource(UserType type) {
+        System.out.println(type.getUserType());
+    }
+
+    @DisplayName("测试 @MethodSource 注解")
+    @ParameterizedTest
+    @MethodSource("getParam")
+    public void testMethodSource(String str) {
+        System.out.println(str);
+    }
+
+    public static Stream<String> getParam() {
+        List<String> list = new ArrayList<>();
+        list.add("t1");
+        list.add("t2");
+        list.add("t3");
+        return list.stream();
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+
+}

+ 54 - 0
jk-admin/src/test/java/org/jk/test/TagUnitTest.java

@@ -0,0 +1,54 @@
+package org.jk.test;
+
+import org.junit.jupiter.api.*;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 标签单元测试案例
+ *
+ * @author jk
+ */
+@SpringBootTest
+@DisplayName("标签单元测试案例")
+public class TagUnitTest {
+
+    @Tag("dev")
+    @DisplayName("测试 @Tag dev")
+    @Test
+    public void testTagDev() {
+        System.out.println("dev");
+    }
+
+    @Tag("prod")
+    @DisplayName("测试 @Tag prod")
+    @Test
+    public void testTagProd() {
+        System.out.println("prod");
+    }
+
+    @Tag("local")
+    @DisplayName("测试 @Tag local")
+    @Test
+    public void testTagLocal() {
+        System.out.println("local");
+    }
+
+    @Tag("exclude")
+    @DisplayName("测试 @Tag exclude")
+    @Test
+    public void testTagExclude() {
+        System.out.println("exclude");
+    }
+
+    @BeforeEach
+    public void testBeforeEach() {
+        System.out.println("@BeforeEach ==================");
+    }
+
+    @AfterEach
+    public void testAfterEach() {
+        System.out.println("@AfterEach ==================");
+    }
+
+
+}

+ 185 - 0
jk-common/jk-common-bom/pom.xml

@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         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>
+
+    <groupId>org.jk</groupId>
+    <artifactId>jk-common-bom</artifactId>
+    <version>${revision}</version>
+    <packaging>pom</packaging>
+
+    <description>
+        jk-common-bom common依赖项
+    </description>
+
+    <properties>
+        <revision>5.5.1</revision>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- 核心模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-core</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 接口模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-doc</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- excel -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-excel</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 幂等 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-idempotent</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 调度模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-job</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 日志记录 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-log</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 邮件服务 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-mail</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 数据库服务 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-mybatis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- OSS -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-oss</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 限流 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-ratelimiter</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 缓存服务 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-redis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- satoken -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-satoken</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 安全模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-security</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 短信模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-sms</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-social</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- web服务 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-web</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 翻译模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-translation</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 脱敏模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-sensitive</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 序列化模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-json</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 数据库加解密模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-encrypt</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 租户模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-tenant</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- WebSocket模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-websocket</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- SSE模块 -->
+            <dependency>
+                <groupId>org.jk</groupId>
+                <artifactId>jk-common-sse</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+
+</project>

+ 99 - 0
jk-common/jk-common-core/pom.xml

@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.jk</groupId>
+        <artifactId>jk-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jk-common-core</artifactId>
+
+    <description>
+        jk-common-core 核心模块
+    </description>
+
+    <dependencies>
+        <!-- Spring框架基本的核心工具 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+
+        <!-- SpringWeb模块 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+        <!-- 自定义验证注解 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!--常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- servlet包 -->
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-extra</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!--  自动生成YML配置关联JSON文件  -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-properties-migrator</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.github.linpeilie</groupId>
+            <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 离线IP地址定位库 -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 17 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/config/ApplicationConfig.java

@@ -0,0 +1,17 @@
+package org.jk.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * 程序注解配置
+ *
+ * @author jk
+ */
+@AutoConfiguration
+@EnableAspectJAutoProxy
+@EnableAsync(proxyTargetClass = true)
+public class ApplicationConfig {
+
+}

+ 57 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/config/DeepseekConfig.java

@@ -0,0 +1,57 @@
+package org.jk.common.core.config;
+
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "deepseek")
+public class DeepseekConfig {
+
+    private String baseurl;
+
+    private String createKnowledge;
+
+    private String deleteKnowledge;
+
+    private String slicePage;
+
+    private String deleteSlice;
+
+    private String sliceDetail;
+
+    private String updateSlice;
+
+    private String uploadKnowledge;
+
+    private String chat;
+
+    private String asyncCompletions;
+
+    private String prompt;
+
+    private String deleteDoc;
+
+    private String addSlice;
+
+    private String searchSlice;
+
+    private String webSearch;
+
+    private String baseChat;
+
+    private String importDocument;
+
+    private String cancelTask;
+
+    private Boolean contentBase64Encode;
+
+    private String evaluate;
+
+    private String baseCleanUrl;
+
+    private String tokenSpeed;
+
+}

+ 34 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/config/JkConfig.java

@@ -0,0 +1,34 @@
+package org.jk.common.core.config;
+
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@Data
+@ConfigurationProperties(prefix = "jk")
+public class JkConfig {
+
+    private String iamAppid;
+
+    private String iamAppsecret;
+
+    private String iamClientID;
+
+    private String iamClientSecret;
+
+    private String iamUserUrl;
+
+    private String iamCallbackUrl;
+
+    private String iamDeptUrl;
+
+    private String iamPostUrl;
+
+    private String iamTokenUrl;
+
+    private String  iamProfileUrl;
+
+    private String jkPsw;
+}

+ 112 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/config/ThreadPoolConfig.java

@@ -0,0 +1,112 @@
+package org.jk.common.core.config;
+
+import jakarta.annotation.PreDestroy;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.jk.common.core.config.properties.ThreadPoolProperties;
+import org.jk.common.core.utils.SpringUtils;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
+
+import java.util.concurrent.*;
+
+/**
+ * 线程池配置
+ *
+ * @author jk
+ **/
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(ThreadPoolProperties.class)
+public class ThreadPoolConfig {
+
+    /**
+     * 核心线程数 = cpu 核心数 + 1
+     */
+    private final int core = Runtime.getRuntime().availableProcessors() + 1;
+
+    private ScheduledExecutorService scheduledExecutorService;
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService() {
+        // daemon 必须为 true
+        BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
+        if (SpringUtils.isVirtual()) {
+            builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
+        } else {
+            builder.namingPattern("schedule-pool-%d");
+        }
+        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
+            builder.build(),
+            new ThreadPoolExecutor.CallerRunsPolicy()) {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t) {
+                super.afterExecute(r, t);
+                printException(r, t);
+            }
+        };
+        this.scheduledExecutorService = scheduledThreadPoolExecutor;
+        return scheduledThreadPoolExecutor;
+    }
+
+    /**
+     * 销毁事件
+     * 停止线程池
+     * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
+     * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
+     * 如果仍然超時,則強制退出.
+     * 另对在shutdown时线程本身被调用中断做了处理.
+     */
+    @PreDestroy
+    public void destroy() {
+        try {
+            log.info("====关闭后台任务任务线程池====");
+            ScheduledExecutorService pool = scheduledExecutorService;
+            if (pool != null && !pool.isShutdown()) {
+                pool.shutdown();
+                try {
+                    if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+                        pool.shutdownNow();
+                        if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+                            log.info("Pool did not terminate");
+                        }
+                    }
+                } catch (InterruptedException ie) {
+                    pool.shutdownNow();
+                    Thread.currentThread().interrupt();
+                }
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 打印线程异常信息
+     */
+    public static void printException(Runnable r, Throwable t) {
+        if (t == null && r instanceof Future<?>) {
+            try {
+                Future<?> future = (Future<?>) r;
+                if (future.isDone()) {
+                    future.get();
+                }
+            } catch (CancellationException ce) {
+                t = ce;
+            } catch (ExecutionException ee) {
+                t = ee.getCause();
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null) {
+            log.error(t.getMessage(), t);
+        }
+    }
+
+}

+ 41 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/config/ValidatorConfig.java

@@ -0,0 +1,41 @@
+package org.jk.common.core.config;
+
+import jakarta.validation.Validator;
+import org.hibernate.validator.HibernateValidator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+import java.util.Properties;
+
+/**
+ * 校验框架配置类
+ *
+ * @author jk
+ */
+@AutoConfiguration(before = ValidationAutoConfiguration.class)
+public class ValidatorConfig {
+
+    /**
+     * 配置校验框架 快速失败模式
+     */
+    @Bean
+    public Validator validator(MessageSource messageSource) {
+        try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
+            // 国际化
+            factoryBean.setValidationMessageSource(messageSource);
+            // 设置使用 HibernateValidator 校验器
+            factoryBean.setProviderClass(HibernateValidator.class);
+            Properties properties = new Properties();
+            // 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
+            properties.setProperty("hibernate.validator.fail_fast", "true");
+            factoryBean.setValidationProperties(properties);
+            // 加载配置
+            factoryBean.afterPropertiesSet();
+            return factoryBean.getValidator();
+        }
+    }
+
+}

+ 30 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/config/properties/ThreadPoolProperties.java

@@ -0,0 +1,30 @@
+package org.jk.common.core.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 线程池 配置属性
+ *
+ * @author jk
+ */
+@Data
+@ConfigurationProperties(prefix = "thread-pool")
+public class ThreadPoolProperties {
+
+    /**
+     * 是否开启线程池
+     */
+    private boolean enabled;
+
+    /**
+     * 队列最大长度
+     */
+    private int queueCapacity;
+
+    /**
+     * 线程池维护线程所允许的空闲时间
+     */
+    private int keepAliveSeconds;
+
+}

+ 30 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/CacheConstants.java

@@ -0,0 +1,30 @@
+package org.jk.common.core.constant;
+
+/**
+ * 缓存的key 常量
+ *
+ * @author jk
+ */
+public interface CacheConstants {
+
+    /**
+     * 在线用户 redis key
+     */
+    String ONLINE_TOKEN_KEY = "online_tokens:";
+
+    /**
+     * 参数管理 cache key
+     */
+    String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+
+}

+ 89 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/CacheNames.java

@@ -0,0 +1,89 @@
+package org.jk.common.core.constant;
+
+/**
+ * 缓存组名称常量
+ * <p>
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
+ * <p>
+ * ttl 过期时间 如果设置为0则不过期 默认为0
+ * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
+ * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
+ * local 默认开启本地缓存为1 关闭本地缓存为0
+ * <p>
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
+ *
+ * @author jk
+ */
+public interface CacheNames {
+
+    /**
+     * 演示案例
+     */
+    String DEMO_CACHE = "demo:cache#60s#10m#20";
+
+    /**
+     * 系统配置
+     */
+    String SYS_CONFIG = "sys_config";
+
+    /**
+     * 数据字典
+     */
+    String SYS_DICT = "sys_dict";
+
+    /**
+     * 数据字典类型
+     */
+    String SYS_DICT_TYPE = "sys_dict_type";
+
+    /**
+     * 租户
+     */
+    String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
+
+    /**
+     * 客户端
+     */
+    String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
+
+    /**
+     * 用户账户
+     */
+    String SYS_USER_NAME = "sys_user_name#30d";
+
+    /**
+     * 用户名称
+     */
+    String SYS_NICKNAME = "sys_nickname#30d";
+
+    /**
+     * 部门
+     */
+    String SYS_DEPT = "sys_dept#30d";
+
+    /**
+     * OSS内容
+     */
+    String SYS_OSS = "sys_oss#30d";
+
+    /**
+     * 角色自定义权限
+     */
+    String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
+
+    /**
+     * 部门及以下权限
+     */
+    String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
+
+    /**
+     * OSS配置
+     */
+    String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
+
+    /**
+     * 在线用户
+     */
+    String ONLINE_TOKEN = "online_tokens";
+
+}

+ 81 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/Constants.java

@@ -0,0 +1,81 @@
+package org.jk.common.core.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author jk
+ */
+public interface Constants {
+
+    /**
+     * UTF-8 字符集
+     */
+    String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    String GBK = "GBK";
+
+    /**
+     * www主域
+     */
+    String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 顶级父级id
+     */
+    Long TOP_PARENT_ID = 0L;
+
+    /**
+     * 加密头
+     */
+    String ENCRYPT_HEADER = "ENC_";
+
+}
+

+ 34 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/GlobalConstants.java

@@ -0,0 +1,34 @@
+package org.jk.common.core.constant;
+
+/**
+ * 全局的key常量 (业务无关的key)
+ *
+ * @author jk
+ */
+public interface GlobalConstants {
+
+    /**
+     * 全局 redis key (业务无关的key)
+     */
+    String GLOBAL_REDIS_KEY = "global:";
+
+    /**
+     * 验证码 redis key
+     */
+    String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+    /**
+     * 防重提交 redis key
+     */
+    String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+    /**
+     * 限流 redis key
+     */
+    String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+    /**
+     * 三方认证 redis key
+     */
+    String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
+}

+ 93 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/HttpStatus.java

@@ -0,0 +1,93 @@
+package org.jk.common.core.constant;
+
+/**
+ * 返回状态码
+ *
+ * @author jk
+ */
+public interface HttpStatus {
+    /**
+     * 操作成功
+     */
+    int SUCCESS = 200;
+
+    /**
+     * 对象创建成功
+     */
+    int CREATED = 201;
+
+    /**
+     * 请求已经被接受
+     */
+    int ACCEPTED = 202;
+
+    /**
+     * 操作已经执行成功,但是没有返回数据
+     */
+    int NO_CONTENT = 204;
+
+    /**
+     * 资源已被移除
+     */
+    int MOVED_PERM = 301;
+
+    /**
+     * 重定向
+     */
+    int SEE_OTHER = 303;
+
+    /**
+     * 资源没有被修改
+     */
+    int NOT_MODIFIED = 304;
+
+    /**
+     * 参数列表错误(缺少,格式不匹配)
+     */
+    int BAD_REQUEST = 400;
+
+    /**
+     * 未授权
+     */
+    int UNAUTHORIZED = 401;
+
+    /**
+     * 访问受限,授权过期
+     */
+    int FORBIDDEN = 403;
+
+    /**
+     * 资源,服务未找到
+     */
+    int NOT_FOUND = 404;
+
+    /**
+     * 不允许的http方法
+     */
+    int BAD_METHOD = 405;
+
+    /**
+     * 资源冲突,或者资源被锁
+     */
+    int CONFLICT = 409;
+
+    /**
+     * 不支持的数据,媒体类型
+     */
+    int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 系统内部错误
+     */
+    int ERROR = 500;
+
+    /**
+     * 接口未实现
+     */
+    int NOT_IMPLEMENTED = 501;
+
+    /**
+     * 系统警告消息
+     */
+    int WARN = 601;
+}

+ 59 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/RegexConstants.java

@@ -0,0 +1,59 @@
+package org.jk.common.core.constant;
+
+import cn.hutool.core.lang.RegexPool;
+
+/**
+ * 常用正则表达式字符串
+ * <p>
+ * 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/
+ *
+ * @author jk
+ */
+public interface RegexConstants extends RegexPool {
+
+    /**
+     * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
+     */
+    String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
+
+    /**
+     * 权限标识必须符合以下格式:
+     * 1. 标准格式:xxx:yyy:zzz
+     * - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*`
+     * - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*`
+     * - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*`
+     * 2. 允许空字符串(""),表示没有权限标识
+     */
+    String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
+
+    /**
+     * 身份证号码(后6位)
+     */
+    String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
+
+    /**
+     * QQ号码
+     */
+    String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
+
+    /**
+     * 邮政编码
+     */
+    String POSTAL_CODE = "^[1-9]\\d{5}$";
+
+    /**
+     * 注册账号
+     */
+    String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
+
+    /**
+     * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
+     */
+    String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
+
+    /**
+     * 通用状态(0表示正常,1表示停用)
+     */
+    String STATUS = "^[01]$";
+
+}

+ 85 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/SystemConstants.java

@@ -0,0 +1,85 @@
+package org.jk.common.core.constant;
+
+/**
+ * 系统常量信息
+ *
+ * @author jk
+ */
+public interface SystemConstants {
+
+    /**
+     * 正常状态
+     */
+    String NORMAL = "0";
+
+    /**
+     * 异常状态
+     */
+    String DISABLE = "1";
+
+    /**
+     * 是否为系统默认(是)
+     */
+    String YES = "Y";
+
+    /**
+     * 是否为系统默认(否)
+     */
+    String NO = "N";
+
+    /**
+     * 是否菜单外链(是)
+     */
+    String YES_FRAME = "0";
+
+    /**
+     * 是否菜单外链(否)
+     */
+    String NO_FRAME = "1";
+
+    /**
+     * 菜单类型(目录)
+     */
+    String TYPE_DIR = "M";
+
+    /**
+     * 菜单类型(菜单)
+     */
+    String TYPE_MENU = "C";
+
+    /**
+     * 菜单类型(按钮)
+     */
+    String TYPE_BUTTON = "F";
+
+    /**
+     * Layout组件标识
+     */
+    String LAYOUT = "Layout";
+
+    /**
+     * ParentView组件标识
+     */
+    String PARENT_VIEW = "ParentView";
+
+    /**
+     * InnerLink组件标识
+     */
+    String INNER_LINK = "InnerLink";
+
+    /**
+     * 超级管理员ID
+     */
+    Long SUPER_ADMIN_ID = 1L;
+
+    /**
+     * 根部门祖级列表
+     */
+    String ROOT_DEPT_ANCESTORS = "0";
+
+    /**
+     * 默认部门 ID
+     */
+    Long DEFAULT_DEPT_ID = 100L;
+
+}

+ 35 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/TenantConstants.java

@@ -0,0 +1,35 @@
+package org.jk.common.core.constant;
+
+/**
+ * 租户常量信息
+ *
+ * @author jk
+ */
+public interface TenantConstants {
+
+    /**
+     * 超级管理员ID
+     */
+    Long SUPER_ADMIN_ID = 1L;
+
+    /**
+     * 超级管理员角色 roleKey
+     */
+    String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+    /**
+     * 租户管理员角色 roleKey
+     */
+    String TENANT_ADMIN_ROLE_KEY = "admin";
+
+    /**
+     * 租户管理员角色名称
+     */
+    String TENANT_ADMIN_ROLE_NAME = "管理员";
+
+    /**
+     * 默认租户ID
+     */
+    String DEFAULT_TENANT_ID = "000000";
+
+}

+ 78 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/constant/UserConstants.java

@@ -0,0 +1,78 @@
+package org.jk.common.core.constant;
+
+/**
+ * 用户常量信息
+ *
+ * @author takai
+ */
+public class UserConstants
+{
+    /**
+     * 平台内系统用户的唯一标志
+     */
+    public static final String SYS_USER = "SYS_USER";
+
+    /** 正常状态 */
+    public static final String NORMAL = "0";
+
+    /** 异常状态 */
+    public static final String EXCEPTION = "1";
+
+    /** 用户封禁状态 */
+    public static final String USER_DISABLE = "1";
+
+    /** 角色封禁状态 */
+    public static final String ROLE_DISABLE = "1";
+
+    /** 部门正常状态 */
+    public static final String DEPT_NORMAL = "0";
+
+    /** 部门停用状态 */
+    public static final String DEPT_DISABLE = "1";
+
+    /** 字典正常状态 */
+    public static final String DICT_NORMAL = "0";
+
+    /** 是否为系统默认(是) */
+    public static final String YES = "Y";
+
+    /** 是否菜单外链(是) */
+    public static final String YES_FRAME = "0";
+
+    /** 是否菜单外链(否) */
+    public static final String NO_FRAME = "1";
+
+    /** 菜单类型(目录) */
+    public static final String TYPE_DIR = "M";
+
+    /** 菜单类型(菜单) */
+    public static final String TYPE_MENU = "C";
+
+    /** 菜单类型(按钮) */
+    public static final String TYPE_BUTTON = "F";
+
+    /** Layout组件标识 */
+    public final static String LAYOUT = "Layout";
+
+    /** ParentView组件标识 */
+    public final static String PARENT_VIEW = "ParentView";
+
+    /** InnerLink组件标识 */
+    public final static String INNER_LINK = "InnerLink";
+
+    /** 校验是否唯一的返回标识 */
+    public final static boolean UNIQUE = true;
+    public final static boolean NOT_UNIQUE = false;
+
+    /**
+     * 用户名长度限制
+     */
+    public static final int USERNAME_MIN_LENGTH = 2;
+    public static final int USERNAME_MAX_LENGTH = 20;
+
+    /**
+     * 密码长度限制
+     */
+    public static final int PASSWORD_MIN_LENGTH = 5;
+    public static final int PASSWORD_MAX_LENGTH = 20;
+}

+ 110 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/R.java

@@ -0,0 +1,110 @@
+package org.jk.common.core.domain;
+
+import org.jk.common.core.constant.HttpStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 响应信息主体
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class R<T> implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 成功
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 失败
+     */
+    public static final int FAIL = 500;
+
+    private int code;
+
+    private String msg;
+
+    private T data;
+
+    public static <T> R<T> ok() {
+        return restResult(null, SUCCESS, "操作成功");
+    }
+
+    public static <T> R<T> ok(T data) {
+        return restResult(data, SUCCESS, "操作成功");
+    }
+
+    public static <T> R<T> ok(String msg) {
+        return restResult(null, SUCCESS, msg);
+    }
+
+    public static <T> R<T> ok(String msg, T data) {
+        return restResult(data, SUCCESS, msg);
+    }
+
+    public static <T> R<T> fail() {
+        return restResult(null, FAIL, "操作失败");
+    }
+
+    public static <T> R<T> fail(String msg) {
+        return restResult(null, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(T data) {
+        return restResult(data, FAIL, "操作失败");
+    }
+
+    public static <T> R<T> fail(String msg, T data) {
+        return restResult(data, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(int code, String msg) {
+        return restResult(null, code, msg);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static <T> R<T> warn(String msg) {
+        return restResult(null, HttpStatus.WARN, msg);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static <T> R<T> warn(String msg, T data) {
+        return restResult(data, HttpStatus.WARN, msg);
+    }
+
+    private static <T> R<T> restResult(T data, int code, String msg) {
+        R<T> r = new R<>();
+        r.setCode(code);
+        r.setData(data);
+        r.setMsg(msg);
+        return r;
+    }
+
+    public static <T> Boolean isError(R<T> ret) {
+        return !isSuccess(ret);
+    }
+
+    public static <T> Boolean isSuccess(R<T> ret) {
+        return R.SUCCESS == ret.getCode();
+    }
+}

+ 76 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/CompleteTaskDTO.java

@@ -0,0 +1,76 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 办理任务请求对象
+ *
+ * @author jk
+ */
+@Data
+public class CompleteTaskDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+    /**
+     * 附件id
+     */
+    private String fileId;
+
+    /**
+     * 抄送人员
+     */
+    private List<FlowCopyDTO> flowCopyList;
+
+    /**
+     * 消息类型
+     */
+    private List<String> messageType;
+
+    /**
+     * 办理意见
+     */
+    private String message;
+
+    /**
+     * 消息通知
+     */
+    private String notice;
+
+    /**
+     * 办理人(可不填 用于覆盖当前节点办理人)
+     */
+    private String handler;
+
+    /**
+     * 流程变量
+     */
+    private Map<String, Object> variables;
+
+    /**
+     * 扩展变量(此处为逗号分隔的ossId)
+     */
+    private String ext;
+
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            return new HashMap<>(16);
+        }
+        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+        return variables;
+    }
+
+}

+ 36 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/DeptDTO.java

@@ -0,0 +1,36 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 部门
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class DeptDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 父部门ID
+     */
+    private Long parentId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+}

+ 41 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/DictDataDTO.java

@@ -0,0 +1,41 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典数据DTO
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class DictDataDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典标签
+     */
+    private String dictLabel;
+
+    /**
+     * 字典键值
+     */
+    private String dictValue;
+
+    /**
+     * 是否默认(Y是 N否)
+     */
+    private String isDefault;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 41 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/DictTypeDTO.java

@@ -0,0 +1,41 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典类型DTO
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class DictTypeDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典主键
+     */
+    private Long dictId;
+
+    /**
+     * 字典名称
+     */
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 30 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/FlowCopyDTO.java

@@ -0,0 +1,30 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 抄送
+ *
+ * @author jk
+ */
+@Data
+public class FlowCopyDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+}

+ 45 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/FlowInstanceBizExtDTO.java

@@ -0,0 +1,45 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 流程实例业务扩展对象
+ *
+ * @author jk
+ * @date 2025-08-05
+ */
+@Data
+public class FlowInstanceBizExtDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 流程实例ID
+     */
+    private Long instanceId;
+
+    /**
+     * 业务ID
+     */
+    private String businessId;
+
+    /**
+     * 业务编码
+     */
+    private String businessCode;
+
+    /**
+     * 业务标题
+     */
+    private String businessTitle;
+
+}

+ 46 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/OssDTO.java

@@ -0,0 +1,46 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * OSS对象
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class OssDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 对象存储主键
+     */
+    private Long ossId;
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 原名
+     */
+    private String originalName;
+
+    /**
+     * 文件后缀名
+     */
+    private String fileSuffix;
+
+    /**
+     * URL地址
+     */
+    private String url;
+
+}

+ 46 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/PostDTO.java

@@ -0,0 +1,46 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 岗位
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class PostDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位ID
+     */
+    private Long postId;
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+    /**
+     * 岗位编码
+     */
+    private String postCode;
+
+    /**
+     * 岗位名称
+     */
+    private String postName;
+
+    /**
+     * 岗位类别编码
+     */
+    private String postCategory;
+
+}

+ 42 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/RoleDTO.java

@@ -0,0 +1,42 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 角色
+ *
+ * @author jk
+ */
+
+@Data
+@NoArgsConstructor
+public class RoleDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    private String roleName;
+
+    /**
+     * 角色权限
+     */
+    private String roleKey;
+
+    /**
+     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
+     */
+    private String dataScope;
+
+}

+ 63 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/StartProcessDTO.java

@@ -0,0 +1,63 @@
+package org.jk.common.core.domain.dto;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 启动流程对象
+ *
+ * @author jk
+ */
+@Data
+public class StartProcessDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 业务唯一值id
+     */
+    private String businessId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 办理人(可不填 用于覆盖当前节点办理人)
+     */
+    private String handler;
+
+    /**
+     * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
+     */
+    private Map<String, Object> variables;
+
+    /**
+     * 流程业务扩展信息
+     */
+    private FlowInstanceBizExtDTO bizExt;
+
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            return new HashMap<>(16);
+        }
+        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+        return variables;
+    }
+
+    public FlowInstanceBizExtDTO getBizExt() {
+        if (ObjectUtil.isNull(bizExt)) {
+            bizExt = new FlowInstanceBizExtDTO();
+        }
+        return bizExt;
+    }
+}

+ 30 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/StartProcessReturnDTO.java

@@ -0,0 +1,30 @@
+package org.jk.common.core.domain.dto;
+
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 启动流程返回对象
+ *
+ * @author jk
+ */
+@Data
+public class StartProcessReturnDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 流程实例id
+     */
+    private Long processInstanceId;
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+}

+ 101 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/TaskAssigneeDTO.java

@@ -0,0 +1,101 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 任务受让人
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class TaskAssigneeDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 总大小
+     */
+    private Long total = 0L;
+
+    /**
+     *
+     */
+    private List<TaskHandler> list;
+
+    public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
+        this.total = total;
+        this.list = list;
+    }
+
+    /**
+     * 将源列表转换为 TaskHandler 列表
+     *
+     * @param <T>              通用类型
+     * @param sourceList       待转换的源列表
+     * @param storageId        提取 storageId 的函数
+     * @param handlerCode      提取 handlerCode 的函数
+     * @param handlerName      提取 handlerName 的函数
+     * @param groupName        提取 groupName 的函数
+     * @param createTimeMapper 提取 createTime 的函数
+     * @return 转换后的 TaskHandler 列表
+     */
+    public static <T> List<TaskHandler> convertToHandlerList(
+        List<T> sourceList,
+        Function<T, String> storageId,
+        Function<T, String> handlerCode,
+        Function<T, String> handlerName,
+        Function<T, String> groupName,
+        Function<T, Date> createTimeMapper) {
+        return sourceList.stream()
+            .map(item -> new TaskHandler(
+                storageId.apply(item),
+                handlerCode.apply(item),
+                handlerName.apply(item),
+                groupName.apply(item),
+                createTimeMapper.apply(item)
+            )).collect(Collectors.toList());
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TaskHandler {
+
+        /**
+         * 主键
+         */
+        private String storageId;
+
+        /**
+         * 权限编码
+         */
+        private String handlerCode;
+
+        /**
+         * 权限名称
+         */
+        private String handlerName;
+
+        /**
+         * 权限分组
+         */
+        private String groupName;
+
+        /**
+         * 创建时间
+         */
+        private Date createTime;
+    }
+
+}

+ 73 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/UserDTO.java

@@ -0,0 +1,73 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 用户
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class UserDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 用户类型(sys_user系统用户)
+     */
+    private String userType;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String phonenumber;
+
+    /**
+     * 用户性别(0男 1女 2未知)
+     */
+    private String sex;
+
+    /**
+     * 帐号状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 72 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/dto/UserOnlineDTO.java

@@ -0,0 +1,72 @@
+package org.jk.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 当前在线会话
+ *
+ * @author jk
+ */
+
+@Data
+@NoArgsConstructor
+public class UserOnlineDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 会话编号
+     */
+    private String tokenId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 客户端
+     */
+    private String clientKey;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地址
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+}

+ 34 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/event/ProcessDeleteEvent.java

@@ -0,0 +1,34 @@
+package org.jk.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 删除流程监听
+ *
+ * @author jk
+ */
+@Data
+public class ProcessDeleteEvent implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 业务id
+     */
+    private String businessId;
+
+}

+ 70 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/event/ProcessEvent.java

@@ -0,0 +1,70 @@
+package org.jk.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 总体流程监听
+ *
+ * @author jk
+ */
+@Data
+public class ProcessEvent implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 实例id
+     */
+    private Long instanceId;
+
+    /**
+     * 业务id
+     */
+    private String businessId;
+
+    /**
+     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+     */
+    private Integer nodeType;
+
+    /**
+     * 流程节点编码
+     */
+    private String nodeCode;
+
+    /**
+     * 流程节点名称
+     */
+    private String nodeName;
+
+    /**
+     * 流程状态
+     */
+    private String status;
+
+    /**
+     * 办理参数
+     */
+    private Map<String, Object> params;
+
+    /**
+     * 当为true时为申请人节点办理
+     */
+    private Boolean submit;
+
+}

+ 70 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/event/ProcessTaskEvent.java

@@ -0,0 +1,70 @@
+package org.jk.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 流程任务监听
+ *
+ * @author jk
+ */
+@Data
+public class ProcessTaskEvent implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
+     */
+    private Integer nodeType;
+
+    /**
+     * 流程节点编码
+     */
+    private String nodeCode;
+
+    /**
+     * 流程节点名称
+     */
+    private String nodeName;
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+    /**
+     * 实例id
+     */
+    private Long instanceId;
+
+    /**
+     * 业务id
+     */
+    private String businessId;
+
+    /**
+     * 流程状态
+     */
+    private String status;
+
+    /**
+     * 办理参数
+     */
+    private Map<String, Object> params;
+
+}

+ 31 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/EmailLoginBody.java

@@ -0,0 +1,31 @@
+package org.jk.common.core.domain.model;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 邮件登录对象
+ *
+ * @author jk
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class EmailLoginBody extends LoginBody {
+
+    /**
+     * 邮箱
+     */
+    @NotBlank(message = "{user.email.not.blank}")
+    @Email(message = "{user.email.not.valid}")
+    private String email;
+
+    /**
+     * 邮箱code
+     */
+    @NotBlank(message = "{email.code.not.blank}")
+    private String emailCode;
+
+}

+ 49 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/LoginBody.java

@@ -0,0 +1,49 @@
+package org.jk.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 用户登录对象
+ *
+ * @author jk
+ */
+
+@Data
+public class LoginBody implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 客户端id
+     */
+    @NotBlank(message = "{auth.clientid.not.blank}")
+    private String clientId;
+
+    /**
+     * 授权类型
+     */
+    @NotBlank(message = "{auth.grant.type.not.blank}")
+    private String grantType;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 验证码
+     */
+    private String code;
+
+    /**
+     * 唯一标识
+     */
+    private String uuid;
+
+    private String redirectUrl;
+}

+ 148 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/LoginUser.java

@@ -0,0 +1,148 @@
+package org.jk.common.core.domain.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.jk.common.core.domain.dto.PostDTO;
+import org.jk.common.core.domain.dto.RoleDTO;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 部门类别编码
+     */
+    private String deptCategory;
+
+    /**
+     * 部门名
+     */
+    private String deptName;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 用户类型
+     */
+    private String userType;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 菜单权限
+     */
+    private Set<String> menuPermission;
+
+    /**
+     * 角色权限
+     */
+    private Set<String> rolePermission;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 角色对象
+     */
+    private List<RoleDTO> roles;
+
+    /**
+     * 岗位对象
+     */
+    private List<PostDTO> posts;
+
+    /**
+     * 数据权限 当前角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 客户端
+     */
+    private String clientKey;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * 获取登录id
+     */
+    public String getLoginId() {
+        if (userType == null) {
+            throw new IllegalArgumentException("用户类型不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        return userType + ":" + userId;
+    }
+
+}

+ 32 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/PasswordLoginBody.java

@@ -0,0 +1,32 @@
+package org.jk.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 密码登录对象
+ *
+ * @author jk
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PasswordLoginBody extends LoginBody {
+
+    /**
+     * 用户名
+     */
+    @NotBlank(message = "{user.username.not.blank}")
+    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @NotBlank(message = "{user.password.not.blank}")
+    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+//    @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
+    private String password;
+
+}

+ 37 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/RegisterBody.java

@@ -0,0 +1,37 @@
+package org.jk.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 用户注册对象
+ *
+ * @author jk
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterBody extends LoginBody {
+
+    /**
+     * 用户名
+     */
+    @NotBlank(message = "{user.username.not.blank}")
+    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @NotBlank(message = "{user.password.not.blank}")
+    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
+//    @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
+    private String password;
+
+    /**
+     * 用户类型
+     */
+    private String userType;
+
+}

+ 29 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/SmsLoginBody.java

@@ -0,0 +1,29 @@
+package org.jk.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 短信登录对象
+ *
+ * @author jk
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SmsLoginBody extends LoginBody {
+
+    /**
+     * 手机号
+     */
+    @NotBlank(message = "{user.phonenumber.not.blank}")
+    private String phonenumber;
+
+    /**
+     * 短信code
+     */
+    @NotBlank(message = "{sms.code.not.blank}")
+    private String smsCode;
+
+}

+ 35 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/SocialLoginBody.java

@@ -0,0 +1,35 @@
+package org.jk.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 三方登录对象
+ *
+ * @author jk
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SocialLoginBody extends LoginBody {
+
+    /**
+     * 第三方登录平台
+     */
+    @NotBlank(message = "{social.source.not.blank}")
+    private String source;
+
+    /**
+     * 第三方登录code
+     */
+    @NotBlank(message = "{social.code.not.blank}")
+    private String socialCode;
+
+    /**
+     * 第三方登录socialState
+     */
+    @NotBlank(message = "{social.state.not.blank}")
+    private String socialState;
+
+}

+ 56 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/TaskAssigneeBody.java

@@ -0,0 +1,56 @@
+package org.jk.common.core.domain.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 任务受让人
+ *
+ * @author jk
+ */
+@Data
+@NoArgsConstructor
+public class TaskAssigneeBody implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 权限编码
+     */
+    private String handlerCode;
+
+    /**
+     * 权限名称
+     */
+    private String handlerName;
+
+    /**
+     * 权限分组
+     */
+    private String groupId;
+
+    /**
+     * 开始时间
+     */
+    private String beginTime;
+
+    /**
+     * 结束时间
+     */
+    private String endTime;
+
+    /**
+     * 当前页
+     */
+    private Integer pageNum = 1;
+
+    /**
+     * 每页显示条数
+     */
+    private Integer pageSize = 10;
+
+}

+ 24 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/TreeStruct.java

@@ -0,0 +1,24 @@
+package org.jk.common.core.domain.model;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TreeStruct类
+ *
+ * @author takai
+ */
+@Data
+public class TreeStruct
+{
+    private static final long serialVersionUID = 1L;
+
+
+    private String value;
+
+    private String label;
+
+    private List<TreeStruct> children = new ArrayList<TreeStruct>();
+}

+ 28 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/XcxLoginBody.java

@@ -0,0 +1,28 @@
+package org.jk.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 三方登录对象
+ *
+ * @author jk
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class XcxLoginBody extends LoginBody {
+
+    /**
+     * 小程序id(多个小程序时使用)
+     */
+    private String appid;
+
+    /**
+     * 小程序code
+     */
+    @NotBlank(message = "{xcx.code.not.blank}")
+    private String xcxCode;
+
+}

+ 27 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/domain/model/XcxLoginUser.java

@@ -0,0 +1,27 @@
+package org.jk.common.core.domain.model;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 小程序登录用户身份权限
+ *
+ * @author jk
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * openid
+     */
+    private String openid;
+
+}

+ 30 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/AppTypeEnum.java

@@ -0,0 +1,30 @@
+package org.jk.common.core.enums;
+
+/**
+ * 应用分组
+ *
+ * @author takai
+ */
+public enum AppTypeEnum
+{
+    SPECIALIZED("60", "专业知识"), MANAGEMENT("61", "职能管理"), PROJECT("62", "项目级应用");
+
+    private final String code;
+    private final String info;
+
+    AppTypeEnum(String code, String info)
+    {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public String getInfo()
+    {
+        return info;
+    }
+}

+ 215 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/BusinessStatusEnum.java

@@ -0,0 +1,215 @@
+package org.jk.common.core.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jk.common.core.exception.ServiceException;
+import org.jk.common.core.utils.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 业务状态枚举
+ *
+ * @author jk
+ */
+@Getter
+@AllArgsConstructor
+public enum BusinessStatusEnum {
+
+    /**
+     * 已撤销
+     */
+    CANCEL("cancel", "已撤销"),
+
+    /**
+     * 草稿
+     */
+    DRAFT("draft", "草稿"),
+
+    /**
+     * 待审核
+     */
+    WAITING("waiting", "待审核"),
+
+    /**
+     * 已完成
+     */
+    FINISH("finish", "已完成"),
+
+    /**
+     * 已作废
+     */
+    INVALID("invalid", "已作废"),
+
+    /**
+     * 已退回
+     */
+    BACK("back", "已退回"),
+
+    /**
+     * 已终止
+     */
+    TERMINATION("termination", "已终止");
+
+    /**
+     * 状态
+     */
+    private final String status;
+
+    /**
+     * 描述
+     */
+    private final String desc;
+
+    private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
+        .collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
+
+    /**
+     * 根据状态获取对应的 BusinessStatusEnum 枚举
+     *
+     * @param status 业务状态码
+     * @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null
+     */
+    public static BusinessStatusEnum getByStatus(String status) {
+        // 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null
+        return STATUS_MAP.get(status);
+    }
+
+    /**
+     * 根据状态获取对应的业务状态描述信息
+     *
+     * @param status 业务状态码
+     * @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串
+     */
+    public static String findByStatus(String status) {
+        if (StringUtils.isBlank(status)) {
+            return StrUtil.EMPTY;
+        }
+        BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
+        return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
+    }
+
+    /**
+     * 判断是否为指定的状态之一:草稿、已撤销或已退回
+     *
+     * @param status 要检查的状态
+     * @return 如果状态为草稿、已撤销或已退回之一,则返回 true;否则返回 false
+     */
+    public static boolean isDraftOrCancelOrBack(String status) {
+        return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
+    }
+
+    /**
+     * 判断是否为撤销,退回,作废,终止
+     *
+     * @param status status
+     * @return 结果
+     */
+    public static boolean initialState(String status) {
+        return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
+    }
+
+    /**
+     * 获取运行中的实例状态列表
+     *
+     * @return 包含运行中实例状态的不可变列表
+     * (包含 DRAFT、WAITING、BACK 和 CANCEL 状态)
+     */
+    public static List<String> runningStatus() {
+        return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
+    }
+
+    /**
+     * 获取结束实例的状态列表
+     *
+     * @return 包含结束实例状态的不可变列表
+     * (包含 FINISH、INVALID 和 TERMINATION 状态)
+     */
+    public static List<String> finishStatus() {
+        return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
+    }
+
+    /**
+     * 启动流程校验
+     *
+     * @param status 状态
+     */
+    public static void checkStartStatus(String status) {
+        if (WAITING.getStatus().equals(status)) {
+            throw new ServiceException("该单据已提交过申请,正在审批中!");
+        } else if (FINISH.getStatus().equals(status)) {
+            throw new ServiceException("该单据已完成申请!");
+        } else if (INVALID.getStatus().equals(status)) {
+            throw new ServiceException("该单据已作废!");
+        } else if (TERMINATION.getStatus().equals(status)) {
+            throw new ServiceException("该单据已终止!");
+        } else if (StringUtils.isBlank(status)) {
+            throw new ServiceException("流程状态为空!");
+        }
+    }
+
+    /**
+     * 撤销流程校验
+     *
+     * @param status 状态
+     */
+    public static void checkCancelStatus(String status) {
+        if (CANCEL.getStatus().equals(status)) {
+            throw new ServiceException("该单据已撤销!");
+        } else if (FINISH.getStatus().equals(status)) {
+            throw new ServiceException("该单据已完成申请!");
+        } else if (INVALID.getStatus().equals(status)) {
+            throw new ServiceException("该单据已作废!");
+        } else if (TERMINATION.getStatus().equals(status)) {
+            throw new ServiceException("该单据已终止!");
+        } else if (BACK.getStatus().equals(status)) {
+            throw new ServiceException("该单据已退回!");
+        } else if (StringUtils.isBlank(status)) {
+            throw new ServiceException("流程状态为空!");
+        }
+    }
+
+    /**
+     * 驳回流程校验
+     *
+     * @param status 状态
+     */
+    public static void checkBackStatus(String status) {
+        if (BACK.getStatus().equals(status)) {
+            throw new ServiceException("该单据已退回!");
+        } else if (FINISH.getStatus().equals(status)) {
+            throw new ServiceException("该单据已完成申请!");
+        } else if (INVALID.getStatus().equals(status)) {
+            throw new ServiceException("该单据已作废!");
+        } else if (TERMINATION.getStatus().equals(status)) {
+            throw new ServiceException("该单据已终止!");
+        } else if (CANCEL.getStatus().equals(status)) {
+            throw new ServiceException("该单据已撤销!");
+        } else if (StringUtils.isBlank(status)) {
+            throw new ServiceException("流程状态为空!");
+        }
+    }
+
+    /**
+     * 作废,终止流程校验
+     *
+     * @param status 状态
+     */
+    public static void checkInvalidStatus(String status) {
+        if (FINISH.getStatus().equals(status)) {
+            throw new ServiceException("该单据已完成申请!");
+        } else if (INVALID.getStatus().equals(status)) {
+            throw new ServiceException("该单据已作废!");
+        } else if (TERMINATION.getStatus().equals(status)) {
+            throw new ServiceException("该单据已终止!");
+        } else if (StringUtils.isBlank(status)) {
+            throw new ServiceException("流程状态为空!");
+        }
+    }
+
+}

+ 39 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/DeviceType.java

@@ -0,0 +1,39 @@
+package org.jk.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 设备类型
+ *
+ * @author jk
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+    /**
+     * pc端
+     */
+    PC("pc"),
+
+    /**
+     * app端
+     */
+    APP("app"),
+
+    /**
+     * 小程序端
+     */
+    XCX("xcx"),
+
+    /**
+     * 第三方社交登录平台
+     */
+    SOCIAL("social");
+
+    /**
+     * 设备标识
+     */
+    private final String device;
+}

+ 146 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/FormatsType.java

@@ -0,0 +1,146 @@
+package org.jk.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jk.common.core.utils.StringUtils;
+
+/*
+ * 日期格式
+ * "yyyy":4位数的年份,例如:2023年表示为"2023"。
+ * "yy":2位数的年份,例如:2023年表示为"23"。
+ * "MM":2位数的月份,取值范围为01到12,例如:7月表示为"07"。
+ * "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。
+ * "dd":2位数的日期,取值范围为01到31,例如:22日表示为"22"。
+ * "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。
+ * "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
+ * "E":星期的缩写,例如:星期三表示为"Wed"。
+ * "DDD" 或 "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。
+ * 时间格式
+ * "HH":24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。
+ * "hh":12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。
+ * "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。
+ * "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。
+ * "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。
+ */
+
+/**
+ * 日期格式与时间格式枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum FormatsType {
+
+    /**
+     * 例如:2023年表示为"23"
+     */
+    YY("yy"),
+
+    /**
+     * 例如:2023年表示为"2023"
+     */
+    YYYY("yyyy"),
+
+    /**
+     * 例例如,2023年7月可以表示为 "2023-07"
+     */
+    YYYY_MM("yyyy-MM"),
+
+    /**
+     * 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22"
+     */
+    YYYY_MM_DD("yyyy-MM-dd"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
+     */
+    YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
+
+    /**
+     * 例如:下午3点30分45秒,表示为 "15:30:45"
+     */
+    HH_MM_SS("HH:mm:ss"),
+
+    /**
+     * 例例如,2023年7月可以表示为 "2023/07"
+     */
+    YYYY_MM_SLASH("yyyy/MM"),
+
+    /**
+     * 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22"
+     */
+    YYYY_MM_DD_SLASH("yyyy/MM/dd"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
+
+    /**
+     * 例例如,2023年7月可以表示为 "2023.07"
+     */
+    YYYY_MM_DOT("yyyy.MM"),
+
+    /**
+     * 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22"
+     */
+    YYYY_MM_DD_DOT("yyyy.MM.dd"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
+     */
+    YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
+
+    /**
+     * 例如,2023年7月可以表示为 "202307"
+     */
+    YYYYMM("yyyyMM"),
+
+    /**
+     * 例如,2023年7月22日可以表示为 "20230722"
+     */
+    YYYYMMDD("yyyyMMdd"),
+
+    /**
+     * 例如,2023年7月22日下午3点可以表示为 "2023072215"
+     */
+    YYYYMMDDHH("yyyyMMddHH"),
+
+    /**
+     * 例如,2023年7月22日下午3点30分可以表示为 "202307221530"
+     */
+    YYYYMMDDHHMM("yyyyMMddHHmm"),
+
+    /**
+     * 例如,2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
+     */
+    YYYYMMDDHHMMSS("yyyyMMddHHmmss");
+
+    /**
+     * 时间格式
+     */
+    private final String timeFormat;
+
+    public static FormatsType getFormatsType(String str) {
+        for (FormatsType value : values()) {
+            if (StringUtils.contains(str, value.getTimeFormat())) {
+                return value;
+            }
+        }
+        throw new RuntimeException("'FormatsType' not found By " + str);
+    }
+}

+ 28 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/IconImg.java

@@ -0,0 +1,28 @@
+package org.jk.common.core.enums;
+
+public enum IconImg {
+
+    QUESTION("question"),
+
+    BOOK("book"),
+
+    SEAL("seal"),
+
+    WRENCH("wrench"),
+
+    TAG("tag"),
+
+    HORN("horn"),
+
+    HOUSE("house");
+
+    private final String value;
+
+    IconImg(String value){
+        this.value = value;
+    }
+
+    public String getValue(){
+        return value;
+    }
+}

+ 44 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/LoginType.java

@@ -0,0 +1,44 @@
+package org.jk.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 登录类型
+ *
+ * @author jk
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginType {
+
+    /**
+     * 密码登录
+     */
+    PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
+
+    /**
+     * 短信登录
+     */
+    SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
+
+    /**
+     * 邮箱登录
+     */
+    EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
+
+    /**
+     * 小程序登录
+     */
+    XCX("", "");
+
+    /**
+     * 登录重试超出限制提示
+     */
+    final String retryLimitExceed;
+
+    /**
+     * 登录重试限制计数提示
+     */
+    final String retryLimitCount;
+}

+ 37 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/ProjectTypeEnum.java

@@ -0,0 +1,37 @@
+package org.jk.common.core.enums;
+
+/**
+ * 应用分组
+ *
+ * @author takai
+ */
+public enum ProjectTypeEnum
+{
+    HJL("0", "慧监理", "a34851c530ee0d07d1154beb47bb93dd"), HXG("1", "慧项管","f47ac10b58cc4372a5670e06f141669d");
+
+    private final String code;
+    private final String info;
+    private final String clientId;
+
+    ProjectTypeEnum(String code, String info, String clientId)
+    {
+        this.code = code;
+        this.info = info;
+        this.clientId = clientId;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public String getInfo()
+    {
+        return info;
+    }
+
+    public String getClientId()
+    {
+        return clientId;
+    }
+}

+ 30 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/UserStatus.java

@@ -0,0 +1,30 @@
+package org.jk.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户状态
+ *
+ * @author jk
+ */
+@Getter
+@AllArgsConstructor
+public enum UserStatus {
+    /**
+     * 正常
+     */
+    OK("0", "正常"),
+    /**
+     * 停用
+     */
+    DISABLE("1", "停用"),
+    /**
+     * 删除
+     */
+    DELETED("2", "删除");
+
+    private final String code;
+    private final String info;
+
+}

+ 39 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/UserType.java

@@ -0,0 +1,39 @@
+package org.jk.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.jk.common.core.utils.StringUtils;
+
+/**
+ * 用户类型
+ *
+ * @author jk
+ */
+@Getter
+@AllArgsConstructor
+public enum UserType {
+
+    /**
+     * 后台系统用户
+     */
+    SYS_USER("sys_user"),
+
+    /**
+     * 移动客户端用户
+     */
+    APP_USER("app_user");
+
+    /**
+     * 用户类型标识(用于 token、权限识别等)
+     */
+    private final String userType;
+
+    public static UserType getUserType(String str) {
+        for (UserType value : values()) {
+            if (StringUtils.contains(str, value.getUserType())) {
+                return value;
+            }
+        }
+        throw new RuntimeException("'UserType' not found By " + str);
+    }
+}

+ 43 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/enums/UserTypeEnum.java

@@ -0,0 +1,43 @@
+package org.jk.common.core.enums;
+
+/**
+ * 用户状态
+ *
+ * @author takai
+ */
+public enum UserTypeEnum
+{
+    SYS_USER("00", "系统用户"), JK_USER("01", "建科用户"), HJL_USER("02", "慧监理第三方用户"), HXG_USER("03", "慧项管第三方用户"), OTHER_USER("09", "其他用户");
+
+    private final String code;
+    private final String info;
+
+    UserTypeEnum(String code, String info)
+    {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public String getInfo()
+    {
+        return info;
+    }
+
+    // 新增:通过code获取info的方法
+    public static String getInfoByCode(String code) {
+        // 遍历所有枚举值
+        for (UserTypeEnum userType : values()) {
+            // 找到匹配的code
+            if (userType.code.equals(code)) {
+                return userType.info;
+            }
+        }
+        // 没有找到对应code时返回null或默认值
+        return null;
+    }
+}

+ 67 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/ServiceException.java

@@ -0,0 +1,67 @@
+package org.jk.common.core.exception;
+
+import cn.hutool.core.text.StrFormatter;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 业务异常(支持占位符 {} )
+ *
+ * @author jk
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class ServiceException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误明细,内部调试错误
+     */
+    private String detailMessage;
+
+    public ServiceException(String message) {
+        this.message = message;
+    }
+
+    public ServiceException(String message, Integer code) {
+        this.message = message;
+        this.code = code;
+    }
+
+    public ServiceException(String message, Object... args) {
+        this.message = StrFormatter.format(message, args);
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public ServiceException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public ServiceException setDetailMessage(String detailMessage) {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+}

+ 62 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/SseException.java

@@ -0,0 +1,62 @@
+package org.jk.common.core.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * sse 特制异常
+ *
+ * @author LionLi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class SseException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误明细,内部调试错误
+     */
+    private String detailMessage;
+
+    public SseException(String message) {
+        this.message = message;
+    }
+
+    public SseException(String message, Integer code) {
+        this.message = message;
+        this.code = code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public SseException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public SseException setDetailMessage(String detailMessage) {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+}

+ 74 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/base/BaseException.java

@@ -0,0 +1,74 @@
+package org.jk.common.core.exception.base;
+
+import lombok.AllArgsConstructor;
+import org.jk.common.core.utils.MessageUtils;
+import org.jk.common.core.utils.StringUtils;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 基础异常
+ *
+ * @author jk
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 所属模块
+     */
+    private String module;
+
+    /**
+     * 错误码
+     */
+    private String code;
+
+    /**
+     * 错误码对应的参数
+     */
+    private Object[] args;
+
+    /**
+     * 错误消息
+     */
+    private String defaultMessage;
+
+    public BaseException(String module, String code, Object[] args) {
+        this(module, code, args, null);
+    }
+
+    public BaseException(String module, String defaultMessage) {
+        this(module, null, null, defaultMessage);
+    }
+
+    public BaseException(String code, Object[] args) {
+        this(null, code, args, null);
+    }
+
+    public BaseException(String defaultMessage) {
+        this(null, null, null, defaultMessage);
+    }
+
+    @Override
+    public String getMessage() {
+        String message = null;
+        if (!StringUtils.isEmpty(code)) {
+            message = MessageUtils.message(code, args);
+        }
+        if (message == null) {
+            message = defaultMessage;
+        }
+        return message;
+    }
+
+}

+ 21 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/file/FileException.java

@@ -0,0 +1,21 @@
+package org.jk.common.core.exception.file;
+
+import org.jk.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 文件信息异常类
+ *
+ * @author jk
+ */
+public class FileException extends BaseException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public FileException(String code, Object[] args) {
+        super("file", code, args, null);
+    }
+
+}

+ 18 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/file/FileNameLengthLimitExceededException.java

@@ -0,0 +1,18 @@
+package org.jk.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 文件名称超长限制异常类
+ *
+ * @author jk
+ */
+public class FileNameLengthLimitExceededException extends FileException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public FileNameLengthLimitExceededException(int defaultFileNameLength) {
+        super("upload.filename.exceed.length", new Object[]{defaultFileNameLength});
+    }
+}

+ 18 - 0
jk-common/jk-common-core/src/main/java/org/jk/common/core/exception/file/FileSizeLimitExceededException.java

@@ -0,0 +1,18 @@
+package org.jk.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 文件名大小限制异常类
+ *
+ * @author jk
+ */
+public class FileSizeLimitExceededException extends FileException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public FileSizeLimitExceededException(long defaultMaxSize) {
+        super("upload.exceed.maxSize", new Object[]{defaultMaxSize});
+    }
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно