| @ -0,0 +1,166 @@ | |||
| # 默认忽略的文件 | |||
| /shelf/ | |||
| /workspace.xml | |||
| # 基于编辑器的 HTTP 客户端请求 | |||
| /httpRequests/ | |||
| # Datasource local storage ignored files | |||
| /dataSources/ | |||
| /dataSources.local.xml | |||
| .idea | |||
| package-lock.json | |||
| node_modules/ | |||
| unpackage/ | |||
| .hbuilderx | |||
| .vite | |||
| # Created by https://www.toptal.com/developers/gitignore/api/python | |||
| # Edit at https://www.toptal.com/developers/gitignore?templates=python | |||
| ### Python ### | |||
| # Byte-compiled / optimized / DLL files | |||
| __pycache__/ | |||
| *.py[cod] | |||
| *$py.class | |||
| # C extensions | |||
| *.so | |||
| # Distribution / packaging | |||
| packages/ | |||
| accounts/ | |||
| chatgpt/ | |||
| data/ | |||
| .Python | |||
| build/ | |||
| develop-eggs/ | |||
| dist/ | |||
| downloads/ | |||
| eggs/ | |||
| .eggs/ | |||
| lib/ | |||
| lib64/ | |||
| parts/ | |||
| sdist/ | |||
| var/ | |||
| wheels/ | |||
| pip-wheel-metadata/ | |||
| share/python-wheels/ | |||
| *.egg-info/ | |||
| .installed.cfg | |||
| *.egg | |||
| MANIFEST | |||
| dump.rdb | |||
| # PyInstaller | |||
| # Usually these files are written by a python script from a template | |||
| # before PyInstaller builds the exe, so as to inject date/other infos into it. | |||
| *.manifest | |||
| *.spec | |||
| # Installer logs | |||
| pip-log.txt | |||
| pip-delete-this-directory.txt | |||
| # Unit test / coverage reports | |||
| htmlcov/ | |||
| .tox/ | |||
| .nox/ | |||
| .coverage | |||
| .coverage.* | |||
| .cache | |||
| nosetests.xml | |||
| coverage.xml | |||
| *.cover | |||
| *.py,cover | |||
| .hypothesis/ | |||
| .pytest_cache/ | |||
| pytestdebug.log | |||
| # Translations | |||
| *.mo | |||
| *.pot | |||
| # Django stuff: | |||
| *.log | |||
| local_settings.py | |||
| db.sqlite3 | |||
| db.sqlite3-journal | |||
| # Flask stuff: | |||
| instance/ | |||
| .webassets-cache | |||
| # Scrapy stuff: | |||
| .scrapy | |||
| # Sphinx documentation | |||
| docs/_build/ | |||
| doc/_build/ | |||
| # PyBuilder | |||
| target/ | |||
| # Jupyter Notebook | |||
| .ipynb_checkpoints | |||
| # IPython | |||
| profile_default/ | |||
| ipython_config.py | |||
| # pyenv | |||
| .python-version | |||
| # pipenv | |||
| # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | |||
| # However, in case of collaboration, if having platform-specific dependencies or dependencies | |||
| # having no cross-platform support, pipenv may install dependencies that don't work, or not | |||
| # install all needed dependencies. | |||
| #Pipfile.lock | |||
| # PEP 582; used by e.g. github.com/David-OConnor/pyflow | |||
| __pypackages__/ | |||
| # Celery stuff | |||
| celerybeat-schedule | |||
| celerybeat.pid | |||
| # SageMath parsed files | |||
| *.sage.py | |||
| # Environments | |||
| .vscode | |||
| .env.dev | |||
| .venv | |||
| env/ | |||
| venv/ | |||
| ENV/ | |||
| env.bak/ | |||
| venv.bak/ | |||
| *.dev.yaml | |||
| # Spyder project settings | |||
| .spyderproject | |||
| .spyproject | |||
| # Rope project settings | |||
| .ropeproject | |||
| # mkdocs documentation | |||
| /site | |||
| # mypy | |||
| .mypy_cache/ | |||
| .dmypy.json | |||
| dmypy.json | |||
| # Pyre type checker | |||
| .pyre/ | |||
| # pytype static type analyzer | |||
| .pytype/ | |||
| # hide plugin | |||
| *_hide/ | |||
| # End of https://www.toptal.com/developers/gitignore/api/python | |||
| @ -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 | |||
| @ -0,0 +1,48 @@ | |||
| ###################################################################### | |||
| # 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 | |||
| *.xml.versionsBackup | |||
| *.swp | |||
| !*/build/*.java | |||
| !*/build/*.html | |||
| !*/build/*.xml | |||
| .flattened-pom.xml | |||
| @ -0,0 +1,12 @@ | |||
| <component name="ProjectRunConfigurationManager"> | |||
| <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> | |||
| <deployment type="dockerfile"> | |||
| <settings> | |||
| <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.2" /> | |||
| <option name="buildOnly" value="true" /> | |||
| <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" /> | |||
| </settings> | |||
| </deployment> | |||
| <method v="2" /> | |||
| </configuration> | |||
| </component> | |||
| @ -0,0 +1,12 @@ | |||
| <component name="ProjectRunConfigurationManager"> | |||
| <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="演示机"> | |||
| <deployment type="dockerfile"> | |||
| <settings> | |||
| <option name="imageTag" value="ruoyi/ruoyi-server:5.2.2" /> | |||
| <option name="buildOnly" value="true" /> | |||
| <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" /> | |||
| </settings> | |||
| </deployment> | |||
| <method v="2" /> | |||
| </configuration> | |||
| </component> | |||
| @ -0,0 +1,12 @@ | |||
| <component name="ProjectRunConfigurationManager"> | |||
| <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> | |||
| <deployment type="dockerfile"> | |||
| <settings> | |||
| <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.2" /> | |||
| <option name="buildOnly" value="true" /> | |||
| <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" /> | |||
| </settings> | |||
| </deployment> | |||
| <method v="2" /> | |||
| </configuration> | |||
| </component> | |||
| @ -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. | |||
| @ -0,0 +1,182 @@ | |||
| <img src="https://foruda.gitee.com/images/1679673773341074847/178e8451_1766278.png" width="50%" height="50%"> | |||
| <div style="height: 10px; clear: both;"></div> | |||
| - - - | |||
| ## 平台简介 | |||
| [](https://gitee.com/dromara/RuoYi-Vue-Plus) | |||
| [](https://github.com/dromara/RuoYi-Vue-Plus) | |||
| [](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE) | |||
| [](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) | |||
| <br> | |||
| [](https://gitee.com/dromara/RuoYi-Vue-Plus) | |||
| []() | |||
| []() | |||
| []() | |||
| > RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架) | |||
| > 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br> | |||
| 活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源 | |||
| > 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system) | |||
| > 前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui) | |||
| > 文档地址: [plus-doc](https://plus-doc.dromara.org) | |||
| ## 赞助商 | |||
| 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> | |||
| [如何成为赞助商 加群联系作者详谈](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框架 | 采用 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、内存、磁盘监控 | | |||
| | 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 | | |||
| | 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 | | |||
| | 使用案例 | 系统的一些功能案例 | 支持 | 不支持 | | |||
| ## 参考文档 | |||
| 使用框架前请仔细阅读文档重点注意事项 | |||
| <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) | |||
| ## 软件架构图 | |||
|  | |||
| ## 如何参与贡献 | |||
| [参与贡献的方式 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" /> | |||
| ## 演示图例 | |||
| | | | | |||
| |--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| |  |  | | |||
| @ -0,0 +1,546 @@ | |||
| <?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.dromara</groupId> | |||
| <artifactId>ruoyi-vue-plus</artifactId> | |||
| <version>${revision}</version> | |||
| <name>RuoYi-Vue-Plus</name> | |||
| <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url> | |||
| <description>RuoYi-Vue-Plus多租户管理系统</description> | |||
| <properties> | |||
| <revision>5.2.2</revision> | |||
| <spring-boot.version>3.2.9</spring-boot.version> | |||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||
| <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | |||
| <java.version>17</java.version> | |||
| <mybatis.version>3.5.16</mybatis.version> | |||
| <springdoc.version>2.6.0</springdoc.version> | |||
| <therapi-javadoc.version>0.15.0</therapi-javadoc.version> | |||
| <easyexcel.version>4.0.2</easyexcel.version> | |||
| <velocity.version>2.3</velocity.version> | |||
| <satoken.version>1.38.0</satoken.version> | |||
| <mybatis-plus.version>3.5.7</mybatis-plus.version> | |||
| <p6spy.version>3.9.1</p6spy.version> | |||
| <hutool.version>5.8.31</hutool.version> | |||
| <okhttp.version>4.10.0</okhttp.version> | |||
| <spring-boot-admin.version>3.2.3</spring-boot-admin.version> | |||
| <redisson.version>3.34.1</redisson.version> | |||
| <lock4j.version>2.2.7</lock4j.version> | |||
| <dynamic-ds.version>4.3.1</dynamic-ds.version> | |||
| <snailjob.version>1.1.2</snailjob.version> | |||
| <mapstruct-plus.version>1.4.4</mapstruct-plus.version> | |||
| <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version> | |||
| <lombok.version>1.18.34</lombok.version> | |||
| <bouncycastle.version>1.76</bouncycastle.version> | |||
| <justauth.version>1.16.6</justauth.version> | |||
| <!-- 离线IP地址定位库 --> | |||
| <ip2region.version>2.7.0</ip2region.version> | |||
| <undertow.version>2.3.15.Final</undertow.version> | |||
| <!-- OSS 配置 --> | |||
| <aws.sdk.version>2.25.15</aws.sdk.version> | |||
| <aws.crt.version>0.29.13</aws.crt.version> | |||
| <!-- SMS 配置 --> | |||
| <sms4j.version>3.3.2</sms4j.version> | |||
| <!-- 限制框架中的fastjson版本 --> | |||
| <fastjson.version>1.2.83</fastjson.version> | |||
| <!-- 面向运行时的D-ORM依赖 --> | |||
| <anyline.version>8.7.2-20240808</anyline.version> | |||
| <!--工作流配置--> | |||
| <flowable.version>7.0.1</flowable.version> | |||
| <!-- 插件版本 --> | |||
| <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version> | |||
| <maven-war-plugin.version>3.2.2</maven-war-plugin.version> | |||
| <maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison> | |||
| <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version> | |||
| <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version> | |||
| </properties> | |||
| <profiles> | |||
| <profile> | |||
| <id>local</id> | |||
| <properties> | |||
| <!-- 环境标识,需要与配置文件的名称相对应 --> | |||
| <profiles.active>local</profiles.active> | |||
| <logging.level>info</logging.level> | |||
| </properties> | |||
| </profile> | |||
| <profile> | |||
| <id>dev</id> | |||
| <properties> | |||
| <!-- 环境标识,需要与配置文件的名称相对应 --> | |||
| <profiles.active>dev</profiles.active> | |||
| <logging.level>info</logging.level> | |||
| </properties> | |||
| <activation> | |||
| <!-- 默认环境 --> | |||
| <activeByDefault>true</activeByDefault> | |||
| </activation> | |||
| </profile> | |||
| <profile> | |||
| <id>prod</id> | |||
| <properties> | |||
| <profiles.active>prod</profiles.active> | |||
| <logging.level>warn</logging.level> | |||
| </properties> | |||
| </profile> | |||
| </profiles> | |||
| <!-- 依赖声明 --> | |||
| <dependencyManagement> | |||
| <dependencies> | |||
| <!-- spring AI --> | |||
| <dependency> | |||
| <groupId>org.springframework.boot</groupId> | |||
| <artifactId>spring-boot-dependencies</artifactId> | |||
| <version>${spring-boot.version}</version> | |||
| <type>pom</type> | |||
| <scope>import</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.springframework.ai</groupId> | |||
| <artifactId>spring-ai-bom</artifactId> | |||
| <version>0.8.1</version> | |||
| <!-- <version>1.0.0-SNAPSHOT</version>--> | |||
| <type>pom</type> | |||
| <scope>import</scope> | |||
| </dependency> | |||
| <!-- SpringBoot的依赖配置--> | |||
| <dependency> | |||
| <groupId>org.springframework.boot</groupId> | |||
| <artifactId>spring-boot-dependencies</artifactId> | |||
| <version>${spring-boot.version}</version> | |||
| <type>pom</type> | |||
| <scope>import</scope> | |||
| </dependency> | |||
| <!-- hutool 的依赖配置--> | |||
| <dependency> | |||
| <groupId>cn.hutool</groupId> | |||
| <artifactId>hutool-bom</artifactId> | |||
| <version>${hutool.version}</version> | |||
| <type>pom</type> | |||
| <scope>import</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.flowable</groupId> | |||
| <artifactId>flowable-bom</artifactId> | |||
| <version>${flowable.version}</version> | |||
| <type>pom</type> | |||
| <scope>import</scope> | |||
| </dependency> | |||
| <!-- JustAuth 的依赖配置--> | |||
| <dependency> | |||
| <groupId>me.zhyd.oauth</groupId> | |||
| <artifactId>JustAuth</artifactId> | |||
| <version>${justauth.version}</version> | |||
| </dependency> | |||
| <!-- common 的依赖配置--> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-bom</artifactId> | |||
| <version>${revision}</version> | |||
| <type>pom</type> | |||
| <scope>import</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.springdoc</groupId> | |||
| <artifactId>springdoc-openapi-starter-webmvc-api</artifactId> | |||
| <version>${springdoc.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.github.therapi</groupId> | |||
| <artifactId>therapi-runtime-javadoc</artifactId> | |||
| <version>${therapi-javadoc.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.projectlombok</groupId> | |||
| <artifactId>lombok</artifactId> | |||
| <version>${lombok.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.alibaba</groupId> | |||
| <artifactId>easyexcel</artifactId> | |||
| <version>${easyexcel.version}</version> | |||
| </dependency> | |||
| <!-- velocity代码生成使用模板 --> | |||
| <dependency> | |||
| <groupId>org.apache.velocity</groupId> | |||
| <artifactId>velocity-engine-core</artifactId> | |||
| <version>${velocity.version}</version> | |||
| </dependency> | |||
| <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ --> | |||
| <dependency> | |||
| <groupId>cn.dev33</groupId> | |||
| <artifactId>sa-token-spring-boot3-starter</artifactId> | |||
| <version>${satoken.version}</version> | |||
| </dependency> | |||
| <!-- Sa-Token 整合 jwt --> | |||
| <dependency> | |||
| <groupId>cn.dev33</groupId> | |||
| <artifactId>sa-token-jwt</artifactId> | |||
| <version>${satoken.version}</version> | |||
| <exclusions> | |||
| <exclusion> | |||
| <groupId>cn.hutool</groupId> | |||
| <artifactId>hutool-all</artifactId> | |||
| </exclusion> | |||
| </exclusions> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>cn.dev33</groupId> | |||
| <artifactId>sa-token-core</artifactId> | |||
| <version>${satoken.version}</version> | |||
| </dependency> | |||
| <!-- dynamic-datasource 多数据源--> | |||
| <dependency> | |||
| <groupId>com.baomidou</groupId> | |||
| <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> | |||
| <version>${dynamic-ds.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.mybatis</groupId> | |||
| <artifactId>mybatis</artifactId> | |||
| <version>${mybatis.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.baomidou</groupId> | |||
| <artifactId>mybatis-plus-spring-boot3-starter</artifactId> | |||
| <version>${mybatis-plus.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.baomidou</groupId> | |||
| <artifactId>mybatis-plus-annotation</artifactId> | |||
| <version>${mybatis-plus.version}</version> | |||
| </dependency> | |||
| <!-- sql性能分析插件 --> | |||
| <dependency> | |||
| <groupId>p6spy</groupId> | |||
| <artifactId>p6spy</artifactId> | |||
| <version>${p6spy.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.squareup.okhttp3</groupId> | |||
| <artifactId>okhttp</artifactId> | |||
| <version>${okhttp.version}</version> | |||
| </dependency> | |||
| <!-- AWS SDK for Java 2.x --> | |||
| <dependency> | |||
| <groupId>software.amazon.awssdk</groupId> | |||
| <artifactId>s3</artifactId> | |||
| <version>${aws.sdk.version}</version> | |||
| </dependency> | |||
| <!-- 使用AWS基于 CRT 的 S3 客户端 --> | |||
| <dependency> | |||
| <groupId>software.amazon.awssdk.crt</groupId> | |||
| <artifactId>aws-crt</artifactId> | |||
| <version>${aws.crt.version}</version> | |||
| </dependency> | |||
| <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 --> | |||
| <dependency> | |||
| <groupId>software.amazon.awssdk</groupId> | |||
| <artifactId>s3-transfer-manager</artifactId> | |||
| <version>${aws.sdk.version}</version> | |||
| </dependency> | |||
| <!--短信sms4j--> | |||
| <dependency> | |||
| <groupId>org.dromara.sms4j</groupId> | |||
| <artifactId>sms4j-spring-boot-starter</artifactId> | |||
| <version>${sms4j.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>de.codecentric</groupId> | |||
| <artifactId>spring-boot-admin-starter-server</artifactId> | |||
| <version>${spring-boot-admin.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>de.codecentric</groupId> | |||
| <artifactId>spring-boot-admin-starter-client</artifactId> | |||
| <version>${spring-boot-admin.version}</version> | |||
| </dependency> | |||
| <!--redisson--> | |||
| <dependency> | |||
| <groupId>org.redisson</groupId> | |||
| <artifactId>redisson-spring-boot-starter</artifactId> | |||
| <version>${redisson.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.baomidou</groupId> | |||
| <artifactId>lock4j-redisson-spring-boot-starter</artifactId> | |||
| <version>${lock4j.version}</version> | |||
| </dependency> | |||
| <!-- SnailJob Client --> | |||
| <dependency> | |||
| <groupId>com.aizuda</groupId> | |||
| <artifactId>snail-job-client-starter</artifactId> | |||
| <version>${snailjob.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.aizuda</groupId> | |||
| <artifactId>snail-job-client-job-core</artifactId> | |||
| <version>${snailjob.version}</version> | |||
| </dependency> | |||
| <!-- 加密包引入 --> | |||
| <dependency> | |||
| <groupId>org.bouncycastle</groupId> | |||
| <artifactId>bcprov-jdk15to18</artifactId> | |||
| <version>${bouncycastle.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>io.github.linpeilie</groupId> | |||
| <artifactId>mapstruct-plus-spring-boot-starter</artifactId> | |||
| <version>${mapstruct-plus.version}</version> | |||
| </dependency> | |||
| <!-- 离线IP地址定位库 ip2region --> | |||
| <dependency> | |||
| <groupId>org.lionsoul</groupId> | |||
| <artifactId>ip2region</artifactId> | |||
| <version>${ip2region.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>io.undertow</groupId> | |||
| <artifactId>undertow-core</artifactId> | |||
| <version>${undertow.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>io.undertow</groupId> | |||
| <artifactId>undertow-servlet</artifactId> | |||
| <version>${undertow.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>io.undertow</groupId> | |||
| <artifactId>undertow-websockets-jsr</artifactId> | |||
| <version>${undertow.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <artifactId>commons-compress</artifactId> | |||
| <groupId>org.apache.commons</groupId> | |||
| <version>1.26.2</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.alibaba</groupId> | |||
| <artifactId>fastjson</artifactId> | |||
| <version>${fastjson.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-system</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-job</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-generator</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-demo</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 工作流模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-workflow</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| </dependencies> | |||
| </dependencyManagement> | |||
| <modules> | |||
| <module>ruoyi-admin</module> | |||
| <module>ruoyi-common</module> | |||
| <module>ruoyi-extend</module> | |||
| <module>ruoyi-modules</module> | |||
| </modules> | |||
| <packaging>pom</packaging> | |||
| <build> | |||
| <plugins> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-compiler-plugin</artifactId> | |||
| <version>${maven-compiler-plugin.verison}</version> | |||
| <configuration> | |||
| <source>${java.version}</source> | |||
| <target>${java.version}</target> | |||
| <encoding>${project.build.sourceEncoding}</encoding> | |||
| <annotationProcessorPaths> | |||
| <path> | |||
| <groupId>com.github.therapi</groupId> | |||
| <artifactId>therapi-runtime-javadoc-scribe</artifactId> | |||
| <version>${therapi-javadoc.version}</version> | |||
| </path> | |||
| <path> | |||
| <groupId>org.projectlombok</groupId> | |||
| <artifactId>lombok</artifactId> | |||
| <version>${lombok.version}</version> | |||
| </path> | |||
| <path> | |||
| <groupId>org.springframework.boot</groupId> | |||
| <artifactId>spring-boot-configuration-processor</artifactId> | |||
| <version>${spring-boot.version}</version> | |||
| </path> | |||
| <path> | |||
| <groupId>io.github.linpeilie</groupId> | |||
| <artifactId>mapstruct-plus-processor</artifactId> | |||
| <version>${mapstruct-plus.version}</version> | |||
| </path> | |||
| <path> | |||
| <groupId>org.projectlombok</groupId> | |||
| <artifactId>lombok-mapstruct-binding</artifactId> | |||
| <version>${mapstruct-plus.lombok.version}</version> | |||
| </path> | |||
| </annotationProcessorPaths> | |||
| <compilerArgs> | |||
| <arg>-parameters</arg> | |||
| </compilerArgs> | |||
| </configuration> | |||
| </plugin> | |||
| <!-- 单元测试使用 --> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-surefire-plugin</artifactId> | |||
| <version>${maven-surefire-plugin.version}</version> | |||
| <configuration> | |||
| <argLine>-Dfile.encoding=UTF-8</argLine> | |||
| <!-- 根据打包环境执行对应的@Tag测试方法 --> | |||
| <groups>${profiles.active}</groups> | |||
| <!-- 排除标签 --> | |||
| <excludedGroups>exclude</excludedGroups> | |||
| </configuration> | |||
| </plugin> | |||
| <!-- 统一版本号管理 --> | |||
| <plugin> | |||
| <groupId>org.codehaus.mojo</groupId> | |||
| <artifactId>flatten-maven-plugin</artifactId> | |||
| <version>${flatten-maven-plugin.version}</version> | |||
| <configuration> | |||
| <updatePomFile>true</updatePomFile> | |||
| <flattenMode>resolveCiFriendliesOnly</flattenMode> | |||
| </configuration> | |||
| <executions> | |||
| <execution> | |||
| <id>flatten</id> | |||
| <phase>process-resources</phase> | |||
| <goals> | |||
| <goal>flatten</goal> | |||
| </goals> | |||
| </execution> | |||
| <execution> | |||
| <id>flatten.clean</id> | |||
| <phase>clean</phase> | |||
| <goals> | |||
| <goal>clean</goal> | |||
| </goals> | |||
| </execution> | |||
| </executions> | |||
| </plugin> | |||
| </plugins> | |||
| <resources> | |||
| <resource> | |||
| <directory>src/main/resources</directory> | |||
| <!-- 关闭过滤 --> | |||
| <filtering>false</filtering> | |||
| </resource> | |||
| <resource> | |||
| <directory>src/main/resources</directory> | |||
| <!-- 引入所有 匹配文件进行过滤 --> | |||
| <includes> | |||
| <include>application*</include> | |||
| <include>bootstrap*</include> | |||
| <include>banner*</include> | |||
| </includes> | |||
| <!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 --> | |||
| <filtering>true</filtering> | |||
| </resource> | |||
| </resources> | |||
| </build> | |||
| <repositories> | |||
| <repository> | |||
| <id>public</id> | |||
| <name>huawei nexus</name> | |||
| <url>https://mirrors.huaweicloud.com/repository/maven/</url> | |||
| <releases> | |||
| <enabled>true</enabled> | |||
| </releases> | |||
| </repository> | |||
| <!-- 从spring仓库下载 --> | |||
| <repository> | |||
| <id>sping-snapshot</id> | |||
| <name>sping snapshot</name> | |||
| <url>https://repo.spring.io/milestone</url> | |||
| <!-- <url>https://repo.spring.io/snapshot</url>--> | |||
| <!-- <releases>--> | |||
| <!-- <enabled>false</enabled>--> | |||
| <!-- </releases>--> | |||
| <snapshots> | |||
| <enabled>false</enabled> | |||
| </snapshots> | |||
| </repository> | |||
| </repositories> | |||
| <pluginRepositories> | |||
| <pluginRepository> | |||
| <id>public</id> | |||
| <name>huawei nexus</name> | |||
| <url>https://mirrors.huaweicloud.com/repository/maven/</url> | |||
| <releases> | |||
| <enabled>true</enabled> | |||
| </releases> | |||
| <snapshots> | |||
| <enabled>false</enabled> | |||
| </snapshots> | |||
| </pluginRepository> | |||
| </pluginRepositories> | |||
| </project> | |||
| @ -0,0 +1,26 @@ | |||
| # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/ | |||
| FROM bellsoft/liberica-openjdk-debian:17.0.11-cds | |||
| #FROM bellsoft/liberica-openjdk-debian:21.0.3-cds | |||
| #FROM findepi/graalvm:java17-native | |||
| LABEL maintainer="Lion Li" | |||
| RUN mkdir -p /ruoyi/server/logs \ | |||
| /ruoyi/server/temp \ | |||
| /ruoyi/skywalking/agent | |||
| WORKDIR /ruoyi/server | |||
| ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" | |||
| EXPOSE ${SERVER_PORT} | |||
| ADD ./target/ruoyi-admin.jar ./app.jar | |||
| ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \ | |||
| # 应用名称 如果想区分集群节点监控 改成不同的名称即可 | |||
| #-Dskywalking.agent.service_name=ruoyi-server \ | |||
| #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \ | |||
| -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \ | |||
| -jar app.jar | |||
| @ -0,0 +1,159 @@ | |||
| <?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>ruoyi-vue-plus</artifactId> | |||
| <groupId>org.dromara</groupId> | |||
| <version>${revision}</version> | |||
| </parent> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <packaging>jar</packaging> | |||
| <artifactId>ruoyi-admin</artifactId> | |||
| <description> | |||
| web服务入口 | |||
| </description> | |||
| <dependencies> | |||
| <!-- Mysql驱动包 --> | |||
| <dependency> | |||
| <groupId>com.mysql</groupId> | |||
| <artifactId>mysql-connector-j</artifactId> | |||
| </dependency> | |||
| <!-- <!– mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 –>--> | |||
| <!-- <!– Oracle –>--> | |||
| <!-- <dependency>--> | |||
| <!-- <groupId>com.oracle.database.jdbc</groupId>--> | |||
| <!-- <artifactId>ojdbc8</artifactId>--> | |||
| <!-- </dependency>--> | |||
| <!-- <!– 兼容oracle低版本 –>--> | |||
| <!-- <dependency>--> | |||
| <!-- <groupId>com.oracle.database.nls</groupId>--> | |||
| <!-- <artifactId>orai18n</artifactId>--> | |||
| <!-- </dependency>--> | |||
| <!-- <!– PostgreSql –>--> | |||
| <!-- <dependency>--> | |||
| <!-- <groupId>org.postgresql</groupId>--> | |||
| <!-- <artifactId>postgresql</artifactId>--> | |||
| <!-- </dependency>--> | |||
| <!-- <!– SqlServer –>--> | |||
| <!-- <dependency>--> | |||
| <!-- <groupId>com.microsoft.sqlserver</groupId>--> | |||
| <!-- <artifactId>mssql-jdbc</artifactId>--> | |||
| <!-- </dependency>--> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-doc</artifactId> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-social</artifactId> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-ratelimiter</artifactId> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-mail</artifactId> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-system</artifactId> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>h-project</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-job</artifactId> | |||
| </dependency> | |||
| <!-- 代码生成--> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-generator</artifactId> | |||
| </dependency> | |||
| <!-- demo模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-demo</artifactId> | |||
| </dependency> | |||
| <!-- 工作流模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-workflow</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> | |||
| @ -0,0 +1,23 @@ | |||
| package org.dromara; | |||
| import org.springframework.boot.SpringApplication; | |||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
| import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; | |||
| /** | |||
| * 启动程序 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @SpringBootApplication | |||
| 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("(♥◠‿◠)ノ゙ RuoYi-Vue-Plus启动成功 ლ(´ڡ`ლ)゙"); | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package org.dromara; | |||
| import org.springframework.boot.builder.SpringApplicationBuilder; | |||
| import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; | |||
| /** | |||
| * web容器中进行部署 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public class DromaraServletInitializer extends SpringBootServletInitializer { | |||
| @Override | |||
| protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { | |||
| return application.sources(DromaraApplication.class); | |||
| } | |||
| } | |||
| @ -0,0 +1,234 @@ | |||
| package org.dromara.web.controller; | |||
| import cn.dev33.satoken.annotation.SaIgnore; | |||
| import cn.dev33.satoken.exception.NotLoginException; | |||
| 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.dromara.common.core.constant.UserConstants; | |||
| import org.dromara.common.core.domain.R; | |||
| import org.dromara.common.core.domain.model.LoginBody; | |||
| import org.dromara.common.core.domain.model.RegisterBody; | |||
| import org.dromara.common.core.domain.model.SocialLoginBody; | |||
| import org.dromara.common.core.utils.*; | |||
| import org.dromara.common.encrypt.annotation.ApiEncrypt; | |||
| import org.dromara.common.json.utils.JsonUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.common.social.config.properties.SocialLoginConfigProperties; | |||
| import org.dromara.common.social.config.properties.SocialProperties; | |||
| import org.dromara.common.social.utils.SocialUtils; | |||
| import org.dromara.common.sse.dto.SseMessageDto; | |||
| import org.dromara.common.sse.utils.SseMessageUtils; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.system.domain.bo.SysTenantBo; | |||
| import org.dromara.system.domain.vo.SysClientVo; | |||
| import org.dromara.system.domain.vo.SysTenantVo; | |||
| import org.dromara.system.service.ISysClientService; | |||
| import org.dromara.system.service.ISysConfigService; | |||
| import org.dromara.system.service.ISysSocialService; | |||
| import org.dromara.system.service.ISysTenantService; | |||
| import org.dromara.web.domain.vo.LoginTenantVo; | |||
| import org.dromara.web.domain.vo.LoginVo; | |||
| import org.dromara.web.domain.vo.TenantListVo; | |||
| import org.dromara.web.service.IAuthStrategy; | |||
| import org.dromara.web.service.SysLoginService; | |||
| import org.dromara.web.service.SysRegisterService; | |||
| import org.springframework.validation.annotation.Validated; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import java.net.URL; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ScheduledExecutorService; | |||
| import java.util.concurrent.TimeUnit; | |||
| /** | |||
| * 认证 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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 ScheduledExecutorService scheduledExecutorService; | |||
| /** | |||
| * 登录方法 | |||
| * | |||
| * @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 (!UserConstants.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("欢迎登录RuoYi-Vue-Plus后台管理系统"); | |||
| dto.setUserIds(List.of(userId)); | |||
| SseMessageUtils.publishMessage(dto); | |||
| }, 5, TimeUnit.SECONDS); | |||
| return R.ok(loginVo); | |||
| } | |||
| /** | |||
| * 第三方登录请求 | |||
| * | |||
| * @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); | |||
| } | |||
| /** | |||
| * 第三方登录回调业务处理 绑定授权 | |||
| * | |||
| * @param loginBody 请求体 | |||
| * @return 结果 | |||
| */ | |||
| @PostMapping("/social/callback") | |||
| public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) { | |||
| // 获取第三方登录信息 | |||
| 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(); | |||
| } | |||
| /** | |||
| * 取消授权 | |||
| * | |||
| * @param socialId socialId | |||
| */ | |||
| @DeleteMapping(value = "/unlock/{socialId}") | |||
| public R<Void> unlockSocial(@PathVariable Long socialId) { | |||
| 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 租户列表 | |||
| */ | |||
| @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.equals(vo.getDomain(), host)); | |||
| result.setVoList(CollUtil.isNotEmpty(list) ? list : voList); | |||
| return R.ok(result); | |||
| } | |||
| } | |||
| @ -0,0 +1,136 @@ | |||
| package org.dromara.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.dromara.common.core.constant.Constants; | |||
| import org.dromara.common.core.constant.GlobalConstants; | |||
| import org.dromara.common.core.domain.R; | |||
| import org.dromara.common.core.utils.SpringUtils; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | |||
| import org.dromara.common.mail.config.properties.MailProperties; | |||
| import org.dromara.common.mail.utils.MailUtils; | |||
| import org.dromara.common.ratelimiter.annotation.RateLimiter; | |||
| import org.dromara.common.ratelimiter.enums.LimitType; | |||
| import org.dromara.common.redis.utils.RedisUtils; | |||
| import org.dromara.common.web.config.properties.CaptchaProperties; | |||
| import org.dromara.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.dromara.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 Lion Li | |||
| */ | |||
| @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 邮箱 | |||
| */ | |||
| @RateLimiter(key = "#email", time = 60, count = 1) | |||
| @GetMapping("/resource/email/code") | |||
| public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) { | |||
| if (!mailProperties.getEnabled()) { | |||
| return R.fail("当前系统没有开启邮箱功能!"); | |||
| } | |||
| 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()); | |||
| return R.fail(e.getMessage()); | |||
| } | |||
| return R.ok(); | |||
| } | |||
| /** | |||
| * 生成验证码 | |||
| */ | |||
| @RateLimiter(time = 60, count = 10, limitType = LimitType.IP) | |||
| @GetMapping("/auth/code") | |||
| public R<CaptchaVo> getCode() { | |||
| CaptchaVo captchaVo = new CaptchaVo(); | |||
| boolean captchaEnabled = captchaProperties.getEnable(); | |||
| if (!captchaEnabled) { | |||
| captchaVo.setCaptchaEnabled(false); | |||
| return R.ok(captchaVo); | |||
| } | |||
| // 保存验证码信息 | |||
| String uuid = IdUtil.simpleUUID(); | |||
| String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid; | |||
| // 生成验证码 | |||
| CaptchaType captchaType = captchaProperties.getType(); | |||
| boolean isMath = CaptchaType.MATH == captchaType; | |||
| Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength(); | |||
| CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length); | |||
| AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz()); | |||
| captcha.setGenerator(codeGenerator); | |||
| captcha.createCode(); | |||
| // 如果是数学验证码,使用SpEL表达式处理验证码结果 | |||
| String code = captcha.getCode(); | |||
| if (isMath) { | |||
| 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.setUuid(uuid); | |||
| captchaVo.setImg(captcha.getImageBase64()); | |||
| return R.ok(captchaVo); | |||
| } | |||
| } | |||
| @ -0,0 +1,32 @@ | |||
| package org.dromara.web.controller; | |||
| import cn.dev33.satoken.annotation.SaIgnore; | |||
| import org.dromara.common.core.config.RuoYiConfig; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import lombok.RequiredArgsConstructor; | |||
| import org.springframework.web.bind.annotation.GetMapping; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| /** | |||
| * 首页 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @SaIgnore | |||
| @RequiredArgsConstructor | |||
| @RestController | |||
| public class IndexController { | |||
| /** | |||
| * 系统基础配置 | |||
| */ | |||
| private final RuoYiConfig ruoyiConfig; | |||
| /** | |||
| * 访问首页,提示语 | |||
| */ | |||
| @GetMapping("/") | |||
| public String index() { | |||
| return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); | |||
| } | |||
| } | |||
| @ -0,0 +1,25 @@ | |||
| package org.dromara.web.domain.vo; | |||
| import lombok.Data; | |||
| /** | |||
| * 验证码信息 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @Data | |||
| public class CaptchaVo { | |||
| /** | |||
| * 是否开启验证码 | |||
| */ | |||
| private Boolean captchaEnabled = true; | |||
| private String uuid; | |||
| /** | |||
| * 验证码图片 | |||
| */ | |||
| private String img; | |||
| } | |||
| @ -0,0 +1,25 @@ | |||
| package org.dromara.web.domain.vo; | |||
| import lombok.Data; | |||
| import java.util.List; | |||
| /** | |||
| * 登录租户对象 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @Data | |||
| public class LoginTenantVo { | |||
| /** | |||
| * 租户开关 | |||
| */ | |||
| private Boolean tenantEnabled; | |||
| /** | |||
| * 租户对象列表 | |||
| */ | |||
| private List<TenantListVo> voList; | |||
| } | |||
| @ -0,0 +1,54 @@ | |||
| package org.dromara.web.domain.vo; | |||
| import com.fasterxml.jackson.annotation.JsonProperty; | |||
| import lombok.Data; | |||
| /** | |||
| * 登录验证信息 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,31 @@ | |||
| package org.dromara.web.domain.vo; | |||
| import org.dromara.system.domain.vo.SysTenantVo; | |||
| import io.github.linpeilie.annotations.AutoMapper; | |||
| import lombok.Data; | |||
| /** | |||
| * 租户列表 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Data | |||
| @AutoMapper(target = SysTenantVo.class) | |||
| public class TenantListVo { | |||
| /** | |||
| * 租户编号 | |||
| */ | |||
| private String tenantId; | |||
| /** | |||
| * 企业名称 | |||
| */ | |||
| private String companyName; | |||
| /** | |||
| * 域名 | |||
| */ | |||
| private String domain; | |||
| } | |||
| @ -0,0 +1,165 @@ | |||
| package org.dromara.web.listener; | |||
| import cn.dev33.satoken.config.SaTokenConfig; | |||
| import cn.dev33.satoken.listener.SaTokenListener; | |||
| import cn.dev33.satoken.stp.SaLoginModel; | |||
| import cn.dev33.satoken.stp.StpUtil; | |||
| 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.dromara.common.core.constant.CacheConstants; | |||
| import org.dromara.common.core.constant.Constants; | |||
| import org.dromara.common.core.domain.dto.UserOnlineDTO; | |||
| import org.dromara.common.core.utils.MessageUtils; | |||
| import org.dromara.common.core.utils.ServletUtils; | |||
| import org.dromara.common.core.utils.SpringUtils; | |||
| import org.dromara.common.core.utils.ip.AddressUtils; | |||
| import org.dromara.common.log.event.LogininforEvent; | |||
| import org.dromara.common.redis.utils.RedisUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.web.service.SysLoginService; | |||
| import org.springframework.stereotype.Component; | |||
| import java.time.Duration; | |||
| /** | |||
| * 用户行为 侦听器的实现 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @RequiredArgsConstructor | |||
| @Component | |||
| @Slf4j | |||
| public class UserActionListener implements SaTokenListener { | |||
| private final SaTokenConfig tokenConfig; | |||
| private final SysLoginService loginService; | |||
| /** | |||
| * 每次登录时触发 | |||
| */ | |||
| @Override | |||
| public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { | |||
| 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) loginModel.getExtra(LoginHelper.USER_NAME_KEY); | |||
| String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY); | |||
| dto.setUserName(username); | |||
| dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY)); | |||
| dto.setDeviceType(loginModel.getDevice()); | |||
| dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY)); | |||
| TenantHelper.dynamic(tenantId, () -> { | |||
| if(tokenConfig.getTimeout() == -1) { | |||
| RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto); | |||
| } else { | |||
| RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.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) loginModel.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 tokenValue, Object loginId, long timeout) { | |||
| } | |||
| } | |||
| @ -0,0 +1,46 @@ | |||
| package org.dromara.web.service; | |||
| import org.dromara.common.core.exception.ServiceException; | |||
| import org.dromara.common.core.utils.SpringUtils; | |||
| import org.dromara.system.domain.SysClient; | |||
| import org.dromara.system.domain.vo.SysClientVo; | |||
| import org.dromara.web.domain.vo.LoginVo; | |||
| /** | |||
| * 授权策略 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| 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); | |||
| } | |||
| @ -0,0 +1,246 @@ | |||
| package org.dromara.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.dromara.common.core.constant.CacheConstants; | |||
| import org.dromara.common.core.constant.Constants; | |||
| import org.dromara.common.core.constant.TenantConstants; | |||
| import org.dromara.common.core.domain.dto.RoleDTO; | |||
| import org.dromara.common.core.domain.model.LoginUser; | |||
| import org.dromara.common.core.enums.LoginType; | |||
| import org.dromara.common.core.enums.TenantStatus; | |||
| import org.dromara.common.core.exception.ServiceException; | |||
| import org.dromara.common.core.exception.user.UserException; | |||
| import org.dromara.common.core.utils.*; | |||
| import org.dromara.common.log.event.LogininforEvent; | |||
| import org.dromara.common.mybatis.helper.DataPermissionHelper; | |||
| import org.dromara.common.redis.utils.RedisUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.common.tenant.exception.TenantException; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.system.domain.SysUser; | |||
| import org.dromara.system.domain.bo.SysSocialBo; | |||
| import org.dromara.system.domain.vo.*; | |||
| import org.dromara.system.mapper.SysUserMapper; | |||
| import org.dromara.system.service.*; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.stereotype.Service; | |||
| import java.time.Duration; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| import java.util.function.Supplier; | |||
| /** | |||
| * 登录校验方法 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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 SysUserMapper userMapper; | |||
| /** | |||
| * 绑定第三方用户 | |||
| * | |||
| * @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(); | |||
| loginUser.setTenantId(user.getTenantId()); | |||
| loginUser.setUserId(user.getUserId()); | |||
| loginUser.setDeptId(user.getDeptId()); | |||
| loginUser.setUsername(user.getUserName()); | |||
| loginUser.setNickname(user.getNickName()); | |||
| loginUser.setUserType(user.getUserType()); | |||
| loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId())); | |||
| loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId())); | |||
| 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(user.getUserId()); | |||
| loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.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 (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) { | |||
| return; | |||
| } | |||
| if (StringUtils.isBlank(tenantId)) { | |||
| throw new TenantException("tenant.number.not.blank"); | |||
| } | |||
| SysTenantVo tenant = tenantService.queryByTenantId(tenantId); | |||
| if (ObjectUtil.isNull(tenant)) { | |||
| log.info("登录租户:{} 不存在.", tenantId); | |||
| throw new TenantException("tenant.not.exists"); | |||
| } else if (TenantStatus.DISABLE.getCode().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"); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,115 @@ | |||
| package org.dromara.web.service; | |||
| import cn.dev33.satoken.secure.BCrypt; | |||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||
| import lombok.RequiredArgsConstructor; | |||
| import org.dromara.common.core.constant.Constants; | |||
| import org.dromara.common.core.constant.GlobalConstants; | |||
| import org.dromara.common.core.domain.model.RegisterBody; | |||
| import org.dromara.common.core.enums.UserType; | |||
| import org.dromara.common.core.exception.user.CaptchaException; | |||
| import org.dromara.common.core.exception.user.CaptchaExpireException; | |||
| import org.dromara.common.core.exception.user.UserException; | |||
| import org.dromara.common.core.utils.MessageUtils; | |||
| import org.dromara.common.core.utils.ServletUtils; | |||
| import org.dromara.common.core.utils.SpringUtils; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import org.dromara.common.log.event.LogininforEvent; | |||
| import org.dromara.common.redis.utils.RedisUtils; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.common.web.config.properties.CaptchaProperties; | |||
| import org.dromara.system.domain.SysUser; | |||
| import org.dromara.system.domain.bo.SysUserBo; | |||
| import org.dromara.system.mapper.SysUserMapper; | |||
| import org.dromara.system.service.ISysUserService; | |||
| import org.springframework.stereotype.Service; | |||
| /** | |||
| * 注册校验方法 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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.REGISTER, MessageUtils.message("user.jcaptcha.expire")); | |||
| throw new CaptchaExpireException(); | |||
| } | |||
| if (!code.equalsIgnoreCase(captcha)) { | |||
| recordLogininfor(tenantId, username, Constants.REGISTER, 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); | |||
| } | |||
| } | |||
| @ -0,0 +1,103 @@ | |||
| package org.dromara.web.service.impl; | |||
| import cn.dev33.satoken.stp.SaLoginModel; | |||
| import cn.dev33.satoken.stp.StpUtil; | |||
| import cn.hutool.core.util.ObjectUtil; | |||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||
| import lombok.RequiredArgsConstructor; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.dromara.common.core.constant.Constants; | |||
| import org.dromara.common.core.constant.GlobalConstants; | |||
| import org.dromara.common.core.domain.model.EmailLoginBody; | |||
| import org.dromara.common.core.domain.model.LoginUser; | |||
| import org.dromara.common.core.enums.LoginType; | |||
| import org.dromara.common.core.enums.UserStatus; | |||
| import org.dromara.common.core.exception.user.CaptchaExpireException; | |||
| import org.dromara.common.core.exception.user.UserException; | |||
| import org.dromara.common.core.utils.MessageUtils; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import org.dromara.common.core.utils.ValidatorUtils; | |||
| import org.dromara.common.json.utils.JsonUtils; | |||
| import org.dromara.common.redis.utils.RedisUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.system.domain.SysUser; | |||
| import org.dromara.system.domain.vo.SysClientVo; | |||
| import org.dromara.system.domain.vo.SysUserVo; | |||
| import org.dromara.system.mapper.SysUserMapper; | |||
| import org.dromara.web.domain.vo.LoginVo; | |||
| import org.dromara.web.service.IAuthStrategy; | |||
| import org.dromara.web.service.SysLoginService; | |||
| import org.springframework.stereotype.Service; | |||
| /** | |||
| * 邮件认证策略 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @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()); | |||
| SaLoginModel model = new SaLoginModel(); | |||
| model.setDevice(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 (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | |||
| log.info("登录用户:{} 已被停用.", email); | |||
| throw new UserException("user.blocked", email); | |||
| } | |||
| return user; | |||
| } | |||
| } | |||
| @ -0,0 +1,123 @@ | |||
| package org.dromara.web.service.impl; | |||
| import cn.dev33.satoken.secure.BCrypt; | |||
| import cn.dev33.satoken.stp.SaLoginModel; | |||
| import cn.dev33.satoken.stp.StpUtil; | |||
| import cn.hutool.core.util.ObjectUtil; | |||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||
| import lombok.RequiredArgsConstructor; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.dromara.common.core.constant.Constants; | |||
| import org.dromara.common.core.constant.GlobalConstants; | |||
| import org.dromara.common.core.domain.model.LoginUser; | |||
| import org.dromara.common.core.domain.model.PasswordLoginBody; | |||
| import org.dromara.common.core.enums.LoginType; | |||
| import org.dromara.common.core.enums.UserStatus; | |||
| import org.dromara.common.core.exception.user.CaptchaException; | |||
| import org.dromara.common.core.exception.user.CaptchaExpireException; | |||
| import org.dromara.common.core.exception.user.UserException; | |||
| import org.dromara.common.core.utils.MessageUtils; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import org.dromara.common.core.utils.ValidatorUtils; | |||
| import org.dromara.common.json.utils.JsonUtils; | |||
| import org.dromara.common.redis.utils.RedisUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.common.web.config.properties.CaptchaProperties; | |||
| import org.dromara.system.domain.SysUser; | |||
| import org.dromara.system.domain.vo.SysClientVo; | |||
| import org.dromara.system.domain.vo.SysUserVo; | |||
| import org.dromara.system.mapper.SysUserMapper; | |||
| import org.dromara.web.domain.vo.LoginVo; | |||
| import org.dromara.web.service.IAuthStrategy; | |||
| import org.dromara.web.service.SysLoginService; | |||
| import org.springframework.stereotype.Service; | |||
| /** | |||
| * 密码认证策略 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @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 = loginBody.getTenantId(); | |||
| 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()); | |||
| SaLoginModel model = new SaLoginModel(); | |||
| model.setDevice(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 (!code.equalsIgnoreCase(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 (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | |||
| log.info("登录用户:{} 已被停用.", username); | |||
| throw new UserException("user.blocked", username); | |||
| } | |||
| return user; | |||
| } | |||
| } | |||
| @ -0,0 +1,102 @@ | |||
| package org.dromara.web.service.impl; | |||
| import cn.dev33.satoken.stp.SaLoginModel; | |||
| import cn.dev33.satoken.stp.StpUtil; | |||
| import cn.hutool.core.util.ObjectUtil; | |||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||
| import lombok.RequiredArgsConstructor; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.dromara.common.core.constant.Constants; | |||
| import org.dromara.common.core.constant.GlobalConstants; | |||
| import org.dromara.common.core.domain.model.LoginUser; | |||
| import org.dromara.common.core.domain.model.SmsLoginBody; | |||
| import org.dromara.common.core.enums.LoginType; | |||
| import org.dromara.common.core.enums.UserStatus; | |||
| import org.dromara.common.core.exception.user.CaptchaExpireException; | |||
| import org.dromara.common.core.exception.user.UserException; | |||
| import org.dromara.common.core.utils.MessageUtils; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import org.dromara.common.core.utils.ValidatorUtils; | |||
| import org.dromara.common.json.utils.JsonUtils; | |||
| import org.dromara.common.redis.utils.RedisUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.system.domain.SysUser; | |||
| import org.dromara.system.domain.vo.SysClientVo; | |||
| import org.dromara.system.domain.vo.SysUserVo; | |||
| import org.dromara.system.mapper.SysUserMapper; | |||
| import org.dromara.web.domain.vo.LoginVo; | |||
| import org.dromara.web.service.IAuthStrategy; | |||
| import org.dromara.web.service.SysLoginService; | |||
| import org.springframework.stereotype.Service; | |||
| /** | |||
| * 短信认证策略 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @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()); | |||
| SaLoginModel model = new SaLoginModel(); | |||
| model.setDevice(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 (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | |||
| log.info("登录用户:{} 已被停用.", phonenumber); | |||
| throw new UserException("user.blocked", phonenumber); | |||
| } | |||
| return user; | |||
| } | |||
| } | |||
| @ -0,0 +1,131 @@ | |||
| package org.dromara.web.service.impl; | |||
| import cn.dev33.satoken.stp.SaLoginModel; | |||
| import cn.dev33.satoken.stp.StpUtil; | |||
| import cn.hutool.core.collection.CollUtil; | |||
| import cn.hutool.core.map.MapUtil; | |||
| import cn.hutool.core.util.ObjectUtil; | |||
| import cn.hutool.http.HttpUtil; | |||
| import cn.hutool.http.Method; | |||
| import lombok.RequiredArgsConstructor; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import me.zhyd.oauth.model.AuthResponse; | |||
| import me.zhyd.oauth.model.AuthUser; | |||
| import org.dromara.common.core.domain.model.LoginUser; | |||
| import org.dromara.common.core.domain.model.SocialLoginBody; | |||
| import org.dromara.common.core.enums.UserStatus; | |||
| import org.dromara.common.core.exception.ServiceException; | |||
| import org.dromara.common.core.exception.user.UserException; | |||
| import org.dromara.common.core.utils.StreamUtils; | |||
| import org.dromara.common.core.utils.ValidatorUtils; | |||
| import org.dromara.common.json.utils.JsonUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.common.social.config.properties.SocialProperties; | |||
| import org.dromara.common.social.utils.SocialUtils; | |||
| import org.dromara.common.tenant.helper.TenantHelper; | |||
| import org.dromara.system.domain.vo.SysClientVo; | |||
| import org.dromara.system.domain.vo.SysSocialVo; | |||
| import org.dromara.system.domain.vo.SysUserVo; | |||
| import org.dromara.system.mapper.SysUserMapper; | |||
| import org.dromara.system.service.ISysSocialService; | |||
| import org.dromara.web.domain.vo.LoginVo; | |||
| import org.dromara.web.service.IAuthStrategy; | |||
| import org.dromara.web.service.SysLoginService; | |||
| import org.springframework.stereotype.Service; | |||
| import java.util.List; | |||
| import java.util.Optional; | |||
| /** | |||
| * 第三方授权策略 | |||
| * | |||
| * @author thiszhc is 三三 | |||
| */ | |||
| @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(); | |||
| if ("GITEE".equals(authUserData.getSource())) { | |||
| // 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖 | |||
| HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus") | |||
| .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken())) | |||
| .executeAsync(); | |||
| HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus") | |||
| .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken())) | |||
| .executeAsync(); | |||
| } | |||
| 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()); | |||
| SaLoginModel model = new SaLoginModel(); | |||
| model.setDevice(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 (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | |||
| log.info("登录用户:{} 已被停用.", ""); | |||
| throw new UserException("user.blocked", ""); | |||
| } | |||
| return user; | |||
| } | |||
| } | |||
| @ -0,0 +1,92 @@ | |||
| package org.dromara.web.service.impl; | |||
| import cn.dev33.satoken.stp.SaLoginModel; | |||
| import cn.dev33.satoken.stp.StpUtil; | |||
| import cn.hutool.core.util.ObjectUtil; | |||
| import lombok.RequiredArgsConstructor; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.dromara.common.core.domain.model.XcxLoginBody; | |||
| import org.dromara.common.core.domain.model.XcxLoginUser; | |||
| import org.dromara.common.core.enums.UserStatus; | |||
| import org.dromara.common.core.utils.ValidatorUtils; | |||
| import org.dromara.common.json.utils.JsonUtils; | |||
| import org.dromara.common.satoken.utils.LoginHelper; | |||
| import org.dromara.system.domain.SysClient; | |||
| import org.dromara.system.domain.vo.SysClientVo; | |||
| import org.dromara.system.domain.vo.SysUserVo; | |||
| import org.dromara.web.domain.vo.LoginVo; | |||
| import org.dromara.web.service.IAuthStrategy; | |||
| import org.dromara.web.service.SysLoginService; | |||
| import org.springframework.stereotype.Service; | |||
| /** | |||
| * 小程序认证策略 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @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(); | |||
| // todo 以下自行实现 | |||
| // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid | |||
| String openid = ""; | |||
| // 框架登录不限制从什么表查询 只要最终构建出 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); | |||
| SaLoginModel model = new SaLoginModel(); | |||
| model.setDevice(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 (UserStatus.DISABLE.getCode().equals(user.getStatus())) { | |||
| log.info("登录用户:{} 已被停用.", openid); | |||
| // todo 用户已被停用 业务逻辑自行实现 | |||
| } | |||
| return user; | |||
| } | |||
| } | |||
| @ -0,0 +1,271 @@ | |||
| --- # 监控中心配置 | |||
| 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: ruoyi | |||
| password: 123456 | |||
| --- # snail-job 配置 | |||
| snail-job: | |||
| enabled: false | |||
| # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务 | |||
| group: "ruoyi_group" | |||
| # SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表 | |||
| token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" | |||
| server: | |||
| host: 127.0.0.1 | |||
| port: 17888 | |||
| # 详见 script/sql/snail_job.sql `sj_namespace` 表 | |||
| namespace: ${spring.profiles.active} | |||
| # 随主应用端口飘逸 | |||
| port: 2${server.port} | |||
| --- # 数据源配置 | |||
| spring: | |||
| ai: | |||
| openai: | |||
| api-key: sk-xlyuEOAO6w9vZ74GF2876d40Cd6449FeB1F2F131F37c8421 | |||
| base-url: https://api.gpts.vin | |||
| ollama: | |||
| # base-url: http://192.168.1.173:11434 | |||
| base-url: http://localhost:11434 | |||
| chat: | |||
| model: qwen2:0.5b | |||
| 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://localhost:3306/project-manage?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/project-manage?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: 192.168.1.173 | |||
| host: localhost | |||
| # 端口,默认为6379 | |||
| port: 6379 | |||
| # 数据库索引 | |||
| database: 11 | |||
| # redis 密码必须配置 | |||
| # password: ruoyi123 | |||
| # 连接超时时间 | |||
| timeout: 10s | |||
| # 是否开启ssl | |||
| ssl.enabled: false | |||
| # redisson 配置 | |||
| redisson: | |||
| # redis key前缀 | |||
| keyPrefix: | |||
| # 线程池数量 | |||
| threads: 4 | |||
| # Netty线程池数量 | |||
| nettyThreads: 8 | |||
| # 单节点配置 | |||
| singleServerConfig: | |||
| # 客户端名称 | |||
| clientName: ${ruoyi.name} | |||
| # 最小空闲连接数 | |||
| connectionMinimumIdleSize: 8 | |||
| # 连接池大小 | |||
| connectionPoolSize: 32 | |||
| # 连接空闲超时,单位:毫秒 | |||
| idleConnectionTimeout: 10000 | |||
| # 命令等待超时,单位:毫秒 | |||
| timeout: 3000 | |||
| # 发布和订阅连接池大小 | |||
| subscriptionConnectionPoolSize: 50 | |||
| --- # mail 邮件发送 | |||
| mail: | |||
| enabled: true | |||
| host: smtp.qq.com | |||
| port: 587 | |||
| # 是否需要用户名密码验证 | |||
| auth: true | |||
| # 发送方,遵循RFC-822标准 | |||
| from: 3539386898@qq.com | |||
| # 用户名(注意:如果使用foxmail邮箱,此处user为qq号) | |||
| user: 3539386898@qq.com | |||
| # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助) | |||
| pass: bgweltknwwacdbcj | |||
| # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。 | |||
| starttlsEnable: true | |||
| # 使用SSL安全连接 | |||
| sslEnable: false | |||
| # 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 | |||
| @ -0,0 +1,263 @@ | |||
| --- # 临时文件存储位置 避免临时文件被系统清理报错 | |||
| spring.servlet.multipart.location: /ruoyi/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: ruoyi | |||
| password: 123456 | |||
| --- # snail-job 配置 | |||
| snail-job: | |||
| enabled: false | |||
| # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务 | |||
| group: "ruoyi_group" | |||
| # SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表 | |||
| token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" | |||
| server: | |||
| host: 127.0.0.1 | |||
| port: 17888 | |||
| # 详见 script/sql/snail_job.sql `sj_namespace` 表 | |||
| namespace: ${spring.profiles.active} | |||
| # 随主应用端口飘逸 | |||
| port: 2${server.port} | |||
| --- # 数据源配置 | |||
| spring: | |||
| datasource: | |||
| type: com.zaxxer.hikari.HikariDataSource | |||
| # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content | |||
| dynamic: | |||
| # 性能分析插件(有性能损耗 不建议生产环境使用) | |||
| p6spy: false | |||
| # 设置默认的数据源或者数据源组,默认值即为 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://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true | |||
| username: root | |||
| password: root | |||
| # 从库数据源 | |||
| 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: 0 | |||
| # redis 密码必须配置 | |||
| password: ruoyi123 | |||
| # 连接超时时间 | |||
| timeout: 10s | |||
| # 是否开启ssl | |||
| ssl.enabled: false | |||
| # redisson 配置 | |||
| redisson: | |||
| # redis key前缀 | |||
| keyPrefix: | |||
| # 线程池数量 | |||
| threads: 16 | |||
| # Netty线程池数量 | |||
| nettyThreads: 32 | |||
| # 单节点配置 | |||
| singleServerConfig: | |||
| # 客户端名称 | |||
| clientName: ${ruoyi.name} | |||
| # 最小空闲连接数 | |||
| 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 | |||
| @ -0,0 +1,297 @@ | |||
| # 项目相关配置 | |||
| ruoyi: | |||
| # 名称 | |||
| name: RuoYi-Vue-Plus | |||
| # 版本 | |||
| version: ${revision} | |||
| # 版权年份 | |||
| copyrightYear: 2024 | |||
| captcha: | |||
| enable: true | |||
| # 页面 <参数设置> 可开启关闭 验证码校验 | |||
| # 验证码类型 math 数组计算 char 字符验证 | |||
| type: MATH | |||
| # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰 | |||
| category: CIRCLE | |||
| # 数字验证码位数 | |||
| numberLength: 1 | |||
| # 字符验证码长度 | |||
| charLength: 4 | |||
| # 开发环境配置 | |||
| server: | |||
| # 服务器的HTTP端口,默认为8080 | |||
| port: 8080 | |||
| 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 | |||
| # 日志配置 | |||
| logging: | |||
| level: | |||
| org.dromara: @logging.level@ | |||
| org.springframework: warn | |||
| org.mybatis.spring.mapper: error | |||
| config: classpath:logback-plus.xml | |||
| # 用户配置 | |||
| user: | |||
| password: | |||
| # 密码最大错误次数 | |||
| maxRetryCount: 5 | |||
| # 密码锁定时间(默认10分钟) | |||
| lockTime: 10 | |||
| # Spring配置 | |||
| spring: | |||
| application: | |||
| name: ${ruoyi.name} | |||
| threads: | |||
| # 开启虚拟线程 仅jdk21可用 | |||
| virtual: | |||
| enabled: false | |||
| # 资源信息 | |||
| messages: | |||
| # 国际化资源文件路径 | |||
| basename: i18n/messages | |||
| profiles: | |||
| active: @profiles.active@ | |||
| # 文件上传 | |||
| servlet: | |||
| multipart: | |||
| # 单个文件大小 | |||
| max-file-size: 10MB | |||
| # 设置总上传的文件大小 | |||
| max-request-size: 20MB | |||
| 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 | |||
| # swagger 文档配置 | |||
| - /*/api-docs | |||
| - /*/api-docs/** | |||
| # 多租户配置 | |||
| tenant: | |||
| # 是否开启 | |||
| enable: false | |||
| # 排除表 | |||
| 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 | |||
| # MyBatisPlus配置 | |||
| # https://baomidou.com/config/ | |||
| mybatis-plus: | |||
| # 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper | |||
| mapperPackage: org.dromara.**.mapper | |||
| # 对应的 XML 文件位置 | |||
| mapperLocations: classpath*:mapper/**/*Mapper.xml | |||
| # 实体扫描,多个package用逗号或者分号分隔 | |||
| typeAliasesPackage: org.dromara.**.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 | |||
| swagger-ui: | |||
| # 持久化认证数据 | |||
| persistAuthorization: true | |||
| # OpenAPI文档的路径 | |||
| # path: /v3/api-docs | |||
| info: | |||
| # 标题 | |||
| title: '标题:${ruoyi.name}多租户管理系统_接口文档' | |||
| # 描述 | |||
| description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...' | |||
| # 版本 | |||
| version: '版本号: ${ruoyi.version}' | |||
| # 作者信息 | |||
| contact: | |||
| name: Lion Li | |||
| email: crazylionli@163.com | |||
| url: https://gitee.com/dromara/RuoYi-Vue-Plus | |||
| components: | |||
| # 鉴权方式配置 | |||
| security-schemes: | |||
| apiKey: | |||
| type: APIKEY | |||
| in: HEADER | |||
| name: ${sa-token.token-name} | |||
| #这里定义了两个分组,可定义多个,也可以不定义 | |||
| group-configs: | |||
| # - group: 1.演示模块 | |||
| # packages-to-scan: org.dromara.demo | |||
| - group: 2.通用模块 | |||
| packages-to-scan: org.dromara.web | |||
| # - group: 3.系统模块 | |||
| # packages-to-scan: org.dromara.system | |||
| # - group: 4.代码生成模块 | |||
| # packages-to-scan: org.dromara.generator | |||
| # - group: 5.项目管理 | |||
| # packages-to-scan: org.dromara.project | |||
| # 防止XSS攻击 | |||
| xss: | |||
| # 过滤开关 | |||
| enabled: true | |||
| # 排除链接(多个用逗号分隔) | |||
| excludes: /system/notice | |||
| # 匹配链接 | |||
| urlPatterns: /system/*,/monitor/*,/tool/* | |||
| # 全局线程池相关配置 | |||
| # 如使用JDK21请直接使用虚拟线程 不要开启此配置 | |||
| thread-pool: | |||
| # 是否开启线程池 | |||
| enabled: false | |||
| # 队列最大长度 | |||
| queueCapacity: 128 | |||
| # 线程池维护线程所允许的空闲时间 | |||
| keepAliveSeconds: 300 | |||
| --- # 分布式锁 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: '*' | |||
| --- #flowable配置 | |||
| flowable: | |||
| # 开关 用于启动/停用工作流 | |||
| enabled: true | |||
| process.enabled: ${flowable.enabled} | |||
| eventregistry.enabled: ${flowable.enabled} | |||
| async-executor-activate: false #关闭定时任务JOB | |||
| # 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。 | |||
| database-schema-update: true | |||
| activity-font-name: 宋体 | |||
| label-font-name: 宋体 | |||
| annotation-font-name: 宋体 | |||
| # 关闭各个模块生成表,目前只使用工作流基础表 | |||
| idm: | |||
| enabled: false | |||
| cmmn: | |||
| enabled: false | |||
| dmn: | |||
| enabled: false | |||
| app: | |||
| enabled: false | |||
| @ -0,0 +1,8 @@ | |||
| Application Version: ${revision} | |||
| Spring Boot Version: ${spring-boot.version} | |||
| __________ _____.___.__ ____ ____ __________.__ | |||
| \______ \__ __ ____\__ | |__| \ \ / /_ __ ____ \______ \ | __ __ ______ | |||
| | _/ | \/ _ \/ | | | ______ \ Y / | \_/ __ \ ______ | ___/ | | | \/ ___/ | |||
| | | \ | ( <_> )____ | | /_____/ \ /| | /\ ___/ /_____/ | | | |_| | /\___ \ | |||
| |____|_ /____/ \____// ______|__| \___/ |____/ \___ > |____| |____/____//____ > | |||
| \/ \/ \/ \/ | |||
| @ -0,0 +1,61 @@ | |||
| #错误消息 | |||
| 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.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=对不起,您的租户已过期,请联系管理员 | |||
| @ -0,0 +1,61 @@ | |||
| #错误消息 | |||
| 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.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. | |||
| @ -0,0 +1,61 @@ | |||
| #错误消息 | |||
| 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.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=对不起,您的租户已过期,请联系管理员 | |||
| @ -0,0 +1,129 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <configuration> | |||
| <property name="log.path" value="./logs"/> | |||
| <property name="console.log.pattern" | |||
| value="%red(%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</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</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> | |||
| @ -0,0 +1,45 @@ | |||
| package org.dromara.test; | |||
| import org.junit.jupiter.api.Assertions; | |||
| import org.junit.jupiter.api.DisplayName; | |||
| import org.junit.jupiter.api.Test; | |||
| /** | |||
| * 断言单元测试案例 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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); | |||
| } | |||
| } | |||
| @ -0,0 +1,70 @@ | |||
| package org.dromara.test; | |||
| import org.dromara.common.core.config.RuoYiConfig; | |||
| 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 Lion Li | |||
| */ | |||
| @SpringBootTest // 此注解只能在 springboot 主包下使用 需包含 main 方法与 yml 配置文件 | |||
| @DisplayName("单元测试案例") | |||
| public class DemoUnitTest { | |||
| @Autowired | |||
| private RuoYiConfig ruoYiConfig; | |||
| @DisplayName("测试 @SpringBootTest @Test @DisplayName 注解") | |||
| @Test | |||
| public void testTest() { | |||
| System.out.println(ruoYiConfig); | |||
| } | |||
| @Disabled | |||
| @DisplayName("测试 @Disabled 注解") | |||
| @Test | |||
| public void testDisabled() { | |||
| System.out.println(ruoYiConfig); | |||
| } | |||
| @Timeout(value = 2L, unit = TimeUnit.SECONDS) | |||
| @DisplayName("测试 @Timeout 注解") | |||
| @Test | |||
| public void testTimeout() throws InterruptedException { | |||
| Thread.sleep(3000); | |||
| System.out.println(ruoYiConfig); | |||
| } | |||
| @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 =================="); | |||
| } | |||
| } | |||
| @ -0,0 +1,72 @@ | |||
| package org.dromara.test; | |||
| import org.dromara.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 Lion Li | |||
| */ | |||
| @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 =================="); | |||
| } | |||
| } | |||
| @ -0,0 +1,54 @@ | |||
| package org.dromara.test; | |||
| import org.junit.jupiter.api.*; | |||
| import org.springframework.boot.test.context.SpringBootTest; | |||
| /** | |||
| * 标签单元测试案例 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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 =================="); | |||
| } | |||
| } | |||
| @ -0,0 +1,46 @@ | |||
| <?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>ruoyi-vue-plus</artifactId> | |||
| <groupId>org.dromara</groupId> | |||
| <version>${revision}</version> | |||
| </parent> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <modules> | |||
| <module>ruoyi-common-bom</module> | |||
| <module>ruoyi-common-social</module> | |||
| <module>ruoyi-common-core</module> | |||
| <module>ruoyi-common-doc</module> | |||
| <module>ruoyi-common-excel</module> | |||
| <module>ruoyi-common-idempotent</module> | |||
| <module>ruoyi-common-job</module> | |||
| <module>ruoyi-common-log</module> | |||
| <module>ruoyi-common-mail</module> | |||
| <module>ruoyi-common-mybatis</module> | |||
| <module>ruoyi-common-oss</module> | |||
| <module>ruoyi-common-ratelimiter</module> | |||
| <module>ruoyi-common-redis</module> | |||
| <module>ruoyi-common-satoken</module> | |||
| <module>ruoyi-common-security</module> | |||
| <module>ruoyi-common-sms</module> | |||
| <module>ruoyi-common-web</module> | |||
| <module>ruoyi-common-translation</module> | |||
| <module>ruoyi-common-sensitive</module> | |||
| <module>ruoyi-common-json</module> | |||
| <module>ruoyi-common-encrypt</module> | |||
| <module>ruoyi-common-tenant</module> | |||
| <module>ruoyi-common-websocket</module> | |||
| <module>ruoyi-common-sse</module> | |||
| </modules> | |||
| <artifactId>ruoyi-common</artifactId> | |||
| <packaging>pom</packaging> | |||
| <description> | |||
| common 通用模块 | |||
| </description> | |||
| </project> | |||
| @ -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.dromara</groupId> | |||
| <artifactId>ruoyi-common-bom</artifactId> | |||
| <version>${revision}</version> | |||
| <packaging>pom</packaging> | |||
| <description> | |||
| ruoyi-common-bom common依赖项 | |||
| </description> | |||
| <properties> | |||
| <revision>5.2.2</revision> | |||
| </properties> | |||
| <dependencyManagement> | |||
| <dependencies> | |||
| <!-- 核心模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-core</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 接口模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-doc</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- excel --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-excel</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 幂等 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-idempotent</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 调度模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-job</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 日志记录 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-log</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 邮件服务 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-mail</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 数据库服务 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-mybatis</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- OSS --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-oss</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 限流 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-ratelimiter</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 缓存服务 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-redis</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- satoken --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-satoken</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 安全模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-security</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 短信模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-sms</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-social</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- web服务 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-web</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 翻译模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-translation</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 脱敏模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-sensitive</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 序列化模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-json</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 数据库加解密模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-encrypt</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- 租户模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-tenant</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- WebSocket模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-websocket</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| <!-- SSE模块 --> | |||
| <dependency> | |||
| <groupId>org.dromara</groupId> | |||
| <artifactId>ruoyi-common-sse</artifactId> | |||
| <version>${revision}</version> | |||
| </dependency> | |||
| </dependencies> | |||
| </dependencyManagement> | |||
| </project> | |||
| @ -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.dromara</groupId> | |||
| <artifactId>ruoyi-common</artifactId> | |||
| <version>${revision}</version> | |||
| </parent> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <artifactId>ruoyi-common-core</artifactId> | |||
| <description> | |||
| ruoyi-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> | |||
| @ -0,0 +1,17 @@ | |||
| package org.dromara.common.core.config; | |||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | |||
| import org.springframework.context.annotation.EnableAspectJAutoProxy; | |||
| import org.springframework.scheduling.annotation.EnableAsync; | |||
| /** | |||
| * 程序注解配置 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @AutoConfiguration | |||
| @EnableAspectJAutoProxy | |||
| @EnableAsync(proxyTargetClass = true) | |||
| public class ApplicationConfig { | |||
| } | |||
| @ -0,0 +1,52 @@ | |||
| package org.dromara.common.core.config; | |||
| import cn.hutool.core.util.ArrayUtil; | |||
| import org.dromara.common.core.exception.ServiceException; | |||
| import org.dromara.common.core.utils.SpringUtils; | |||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | |||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | |||
| import org.springframework.core.task.VirtualThreadTaskExecutor; | |||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | |||
| import java.util.Arrays; | |||
| import java.util.concurrent.Executor; | |||
| /** | |||
| * 异步配置 | |||
| * <p> | |||
| * 如果未使用虚拟线程则生效 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @AutoConfiguration | |||
| public class AsyncConfig implements AsyncConfigurer { | |||
| /** | |||
| * 自定义 @Async 注解使用系统线程池 | |||
| */ | |||
| @Override | |||
| public Executor getAsyncExecutor() { | |||
| if(SpringUtils.isVirtual()) { | |||
| return new VirtualThreadTaskExecutor("async-"); | |||
| } | |||
| return SpringUtils.getBean("scheduledExecutorService"); | |||
| } | |||
| /** | |||
| * 异步执行异常处理 | |||
| */ | |||
| @Override | |||
| public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | |||
| return (throwable, method, objects) -> { | |||
| throwable.printStackTrace(); | |||
| StringBuilder sb = new StringBuilder(); | |||
| sb.append("Exception message - ").append(throwable.getMessage()) | |||
| .append(", Method name - ").append(method.getName()); | |||
| if (ArrayUtil.isNotEmpty(objects)) { | |||
| sb.append(", Parameter value - ").append(Arrays.toString(objects)); | |||
| } | |||
| throw new ServiceException(sb.toString()); | |||
| }; | |||
| } | |||
| } | |||
| @ -0,0 +1,33 @@ | |||
| package org.dromara.common.core.config; | |||
| import lombok.Data; | |||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||
| import org.springframework.stereotype.Component; | |||
| /** | |||
| * 读取项目相关配置 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Data | |||
| @Component | |||
| @ConfigurationProperties(prefix = "ruoyi") | |||
| public class RuoYiConfig { | |||
| /** | |||
| * 项目名称 | |||
| */ | |||
| private String name; | |||
| /** | |||
| * 版本 | |||
| */ | |||
| private String version; | |||
| /** | |||
| * 版权年份 | |||
| */ | |||
| private String copyrightYear; | |||
| } | |||
| @ -0,0 +1,78 @@ | |||
| package org.dromara.common.core.config; | |||
| import jakarta.annotation.PreDestroy; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.apache.commons.lang3.concurrent.BasicThreadFactory; | |||
| import org.dromara.common.core.config.properties.ThreadPoolProperties; | |||
| import org.dromara.common.core.utils.Threads; | |||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | |||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||
| import org.springframework.context.annotation.Bean; | |||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | |||
| import java.util.concurrent.ScheduledExecutorService; | |||
| import java.util.concurrent.ScheduledThreadPoolExecutor; | |||
| import java.util.concurrent.ThreadPoolExecutor; | |||
| /** | |||
| * 线程池配置 | |||
| * | |||
| * @author Lion Li | |||
| **/ | |||
| @Slf4j | |||
| @AutoConfiguration | |||
| @EnableConfigurationProperties(ThreadPoolProperties.class) | |||
| public class ThreadPoolConfig { | |||
| /** | |||
| * 核心线程数 = cpu 核心数 + 1 | |||
| */ | |||
| private final int core = Runtime.getRuntime().availableProcessors() + 1; | |||
| private ScheduledExecutorService scheduledExecutorService; | |||
| @Bean(name = "threadPoolTaskExecutor") | |||
| @ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true") | |||
| public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) { | |||
| ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | |||
| executor.setCorePoolSize(core); | |||
| executor.setMaxPoolSize(core * 2); | |||
| executor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); | |||
| executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds()); | |||
| executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); | |||
| return executor; | |||
| } | |||
| /** | |||
| * 执行周期性或定时任务 | |||
| */ | |||
| @Bean(name = "scheduledExecutorService") | |||
| protected ScheduledExecutorService scheduledExecutorService() { | |||
| ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core, | |||
| new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), | |||
| new ThreadPoolExecutor.CallerRunsPolicy()) { | |||
| @Override | |||
| protected void afterExecute(Runnable r, Throwable t) { | |||
| super.afterExecute(r, t); | |||
| Threads.printException(r, t); | |||
| } | |||
| }; | |||
| this.scheduledExecutorService = scheduledThreadPoolExecutor; | |||
| return scheduledThreadPoolExecutor; | |||
| } | |||
| /** | |||
| * 销毁事件 | |||
| */ | |||
| @PreDestroy | |||
| public void destroy() { | |||
| try { | |||
| log.info("====关闭后台任务任务线程池===="); | |||
| Threads.shutdownAndAwaitTermination(scheduledExecutorService); | |||
| } catch (Exception e) { | |||
| log.error(e.getMessage(), e); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,40 @@ | |||
| package org.dromara.common.core.config; | |||
| import jakarta.validation.Validator; | |||
| import org.hibernate.validator.HibernateValidator; | |||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | |||
| import org.springframework.context.MessageSource; | |||
| import org.springframework.context.annotation.Bean; | |||
| import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; | |||
| import java.util.Properties; | |||
| /** | |||
| * 校验框架配置类 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @AutoConfiguration | |||
| 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(); | |||
| // 设置 快速异常返回 | |||
| properties.setProperty("hibernate.validator.fail_fast", "true"); | |||
| factoryBean.setValidationProperties(properties); | |||
| // 加载配置 | |||
| factoryBean.afterPropertiesSet(); | |||
| return factoryBean.getValidator(); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| package org.dromara.common.core.config.properties; | |||
| import lombok.Data; | |||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||
| /** | |||
| * 线程池 配置属性 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Data | |||
| @ConfigurationProperties(prefix = "thread-pool") | |||
| public class ThreadPoolProperties { | |||
| /** | |||
| * 是否开启线程池 | |||
| */ | |||
| private boolean enabled; | |||
| /** | |||
| * 队列最大长度 | |||
| */ | |||
| private int queueCapacity; | |||
| /** | |||
| * 线程池维护线程所允许的空闲时间 | |||
| */ | |||
| private int keepAliveSeconds; | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| package org.dromara.common.core.constant; | |||
| /** | |||
| * 缓存的key 常量 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| 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:"; | |||
| } | |||
| @ -0,0 +1,73 @@ | |||
| package org.dromara.common.core.constant; | |||
| /** | |||
| * 缓存组名称常量 | |||
| * <p> | |||
| * key 格式为 cacheNames#ttl#maxIdleTime#maxSize | |||
| * <p> | |||
| * ttl 过期时间 如果设置为0则不过期 默认为0 | |||
| * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0 | |||
| * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0 | |||
| * <p> | |||
| * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public interface CacheNames { | |||
| /** | |||
| * 演示案例 | |||
| */ | |||
| String DEMO_CACHE = "demo:cache#60s#10m#20"; | |||
| /** | |||
| * 系统配置 | |||
| */ | |||
| String SYS_CONFIG = "sys_config"; | |||
| /** | |||
| * 数据字典 | |||
| */ | |||
| String SYS_DICT = "sys_dict"; | |||
| /** | |||
| * 租户 | |||
| */ | |||
| 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"; | |||
| /** | |||
| * OSS配置 | |||
| */ | |||
| String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config"; | |||
| /** | |||
| * 在线用户 | |||
| */ | |||
| String ONLINE_TOKEN = "online_tokens"; | |||
| } | |||
| @ -0,0 +1,81 @@ | |||
| package org.dromara.common.core.constant; | |||
| /** | |||
| * 通用常量信息 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| 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; | |||
| /** | |||
| * 令牌 | |||
| */ | |||
| String TOKEN = "token"; | |||
| /** | |||
| * 顶级部门id | |||
| */ | |||
| Long TOP_PARENT_ID = 0L; | |||
| } | |||
| @ -0,0 +1,34 @@ | |||
| package org.dromara.common.core.constant; | |||
| /** | |||
| * 全局的key常量 (业务无关的key) | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| 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:"; | |||
| } | |||
| @ -0,0 +1,93 @@ | |||
| package org.dromara.common.core.constant; | |||
| /** | |||
| * 返回状态码 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| 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; | |||
| } | |||
| @ -0,0 +1,54 @@ | |||
| package org.dromara.common.core.constant; | |||
| import cn.hutool.core.lang.RegexPool; | |||
| /** | |||
| * 常用正则表达式字符串 | |||
| * <p> | |||
| * 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/ | |||
| * | |||
| * @author Feng | |||
| */ | |||
| public interface RegexConstants extends RegexPool { | |||
| /** | |||
| * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | |||
| */ | |||
| String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$"; | |||
| /** | |||
| * 权限标识必须符合 tool:build:list 格式,或者空字符串 | |||
| */ | |||
| 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]$"; | |||
| } | |||
| @ -0,0 +1,45 @@ | |||
| package org.dromara.common.core.constant; | |||
| /** | |||
| * 租户常量信息 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public interface TenantConstants { | |||
| /** | |||
| * 租户正常状态 | |||
| */ | |||
| String NORMAL = "0"; | |||
| /** | |||
| * 租户封禁状态 | |||
| */ | |||
| String DISABLE = "1"; | |||
| /** | |||
| * 超级管理员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"; | |||
| } | |||
| @ -0,0 +1,152 @@ | |||
| package org.dromara.common.core.constant; | |||
| /** | |||
| * 用户常量信息 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| public interface UserConstants { | |||
| /** | |||
| * 平台内系统用户的唯一标志 | |||
| */ | |||
| String SYS_USER = "SYS_USER"; | |||
| /** | |||
| * 正常状态 | |||
| */ | |||
| String NORMAL = "0"; | |||
| /** | |||
| * 异常状态 | |||
| */ | |||
| String EXCEPTION = "1"; | |||
| /** | |||
| * 用户正常状态 | |||
| */ | |||
| String USER_NORMAL = "0"; | |||
| /** | |||
| * 用户封禁状态 | |||
| */ | |||
| String USER_DISABLE = "1"; | |||
| /** | |||
| * 角色正常状态 | |||
| */ | |||
| String ROLE_NORMAL = "0"; | |||
| /** | |||
| * 角色封禁状态 | |||
| */ | |||
| String ROLE_DISABLE = "1"; | |||
| /** | |||
| * 部门正常状态 | |||
| */ | |||
| String DEPT_NORMAL = "0"; | |||
| /** | |||
| * 部门停用状态 | |||
| */ | |||
| String DEPT_DISABLE = "1"; | |||
| /** | |||
| * 岗位正常状态 | |||
| */ | |||
| String POST_NORMAL = "0"; | |||
| /** | |||
| * 岗位停用状态 | |||
| */ | |||
| String POST_DISABLE = "1"; | |||
| /** | |||
| * 字典正常状态 | |||
| */ | |||
| String DICT_NORMAL = "0"; | |||
| /** | |||
| * 通用存在标志 | |||
| */ | |||
| String DEL_FLAG_NORMAL = "0"; | |||
| /** | |||
| * 通用删除标志 | |||
| */ | |||
| String DEL_FLAG_REMOVED = "2"; | |||
| /** | |||
| * 是否为系统默认(是) | |||
| */ | |||
| String YES = "Y"; | |||
| /** | |||
| * 是否菜单外链(是) | |||
| */ | |||
| String YES_FRAME = "0"; | |||
| /** | |||
| * 是否菜单外链(否) | |||
| */ | |||
| String NO_FRAME = "1"; | |||
| /** | |||
| * 菜单正常状态 | |||
| */ | |||
| String MENU_NORMAL = "0"; | |||
| /** | |||
| * 菜单停用状态 | |||
| */ | |||
| String MENU_DISABLE = "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"; | |||
| /** | |||
| * 用户名长度限制 | |||
| */ | |||
| int USERNAME_MIN_LENGTH = 2; | |||
| int USERNAME_MAX_LENGTH = 20; | |||
| /** | |||
| * 密码长度限制 | |||
| */ | |||
| int PASSWORD_MIN_LENGTH = 5; | |||
| int PASSWORD_MAX_LENGTH = 20; | |||
| /** | |||
| * 超级管理员ID | |||
| */ | |||
| Long SUPER_ADMIN_ID = 1L; | |||
| } | |||
| @ -0,0 +1,110 @@ | |||
| package org.dromara.common.core.domain; | |||
| import org.dromara.common.core.constant.HttpStatus; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 响应信息主体 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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(); | |||
| } | |||
| } | |||
| @ -0,0 +1,46 @@ | |||
| package org.dromara.common.core.domain.dto; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| /** | |||
| * OSS对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,42 @@ | |||
| package org.dromara.common.core.domain.dto; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 角色 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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:仅本人数据权限) | |||
| */ | |||
| private String dataScope; | |||
| } | |||
| @ -0,0 +1,73 @@ | |||
| package org.dromara.common.core.domain.dto; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| import java.util.Date; | |||
| /** | |||
| * 用户 | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,72 @@ | |||
| package org.dromara.common.core.domain.dto; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 当前在线会话 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,41 @@ | |||
| package org.dromara.common.core.domain.event; | |||
| import lombok.Data; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 总体流程监听 | |||
| * | |||
| * @author may | |||
| */ | |||
| @Data | |||
| public class ProcessEvent implements Serializable { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| /** | |||
| * 流程定义key | |||
| */ | |||
| private String key; | |||
| /** | |||
| * 业务id | |||
| */ | |||
| private String businessKey; | |||
| /** | |||
| * 状态 | |||
| */ | |||
| private String status; | |||
| /** | |||
| * 当为true时为申请人节点办理 | |||
| */ | |||
| private boolean submit; | |||
| } | |||
| @ -0,0 +1,40 @@ | |||
| package org.dromara.common.core.domain.event; | |||
| import lombok.Data; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 流程办理监听 | |||
| * | |||
| * @author may | |||
| */ | |||
| @Data | |||
| public class ProcessTaskEvent implements Serializable { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| /** | |||
| * 流程定义key | |||
| */ | |||
| private String key; | |||
| /** | |||
| * 审批节点key | |||
| */ | |||
| private String taskDefinitionKey; | |||
| /** | |||
| * 任务id | |||
| */ | |||
| private String taskId; | |||
| /** | |||
| * 业务id | |||
| */ | |||
| private String businessKey; | |||
| } | |||
| @ -0,0 +1,31 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import jakarta.validation.constraints.Email; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| /** | |||
| * 邮件登录对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,48 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import lombok.Data; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 用户登录对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,142 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import org.dromara.common.core.domain.dto.RoleDTO; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| import java.io.Serializable; | |||
| import java.util.List; | |||
| import java.util.Set; | |||
| /** | |||
| * 登录用户身份权限 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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; | |||
| /** | |||
| * 数据权限 当前角色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; | |||
| } | |||
| } | |||
| @ -0,0 +1,33 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| import org.hibernate.validator.constraints.Length; | |||
| import static org.dromara.common.core.constant.UserConstants.*; | |||
| /** | |||
| * 密码登录对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Data | |||
| @EqualsAndHashCode(callSuper = true) | |||
| public class PasswordLoginBody extends LoginBody { | |||
| /** | |||
| * 用户名 | |||
| */ | |||
| @NotBlank(message = "{user.username.not.blank}") | |||
| @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}") | |||
| private String username; | |||
| /** | |||
| * 用户密码 | |||
| */ | |||
| @NotBlank(message = "{user.password.not.blank}") | |||
| @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}") | |||
| private String password; | |||
| } | |||
| @ -0,0 +1,35 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| import org.hibernate.validator.constraints.Length; | |||
| import static org.dromara.common.core.constant.UserConstants.*; | |||
| /** | |||
| * 用户注册对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Data | |||
| @EqualsAndHashCode(callSuper = true) | |||
| public class RegisterBody extends LoginBody { | |||
| /** | |||
| * 用户名 | |||
| */ | |||
| @NotBlank(message = "{user.username.not.blank}") | |||
| @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}") | |||
| private String username; | |||
| /** | |||
| * 用户密码 | |||
| */ | |||
| @NotBlank(message = "{user.password.not.blank}") | |||
| @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}") | |||
| private String password; | |||
| private String userType; | |||
| } | |||
| @ -0,0 +1,29 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| /** | |||
| * 短信登录对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,35 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| /** | |||
| * 三方登录对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,28 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| /** | |||
| * 三方登录对象 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Data | |||
| @EqualsAndHashCode(callSuper = true) | |||
| public class XcxLoginBody extends LoginBody { | |||
| /** | |||
| * 小程序id(多个小程序时使用) | |||
| */ | |||
| private String appid; | |||
| /** | |||
| * 小程序code | |||
| */ | |||
| @NotBlank(message = "{xcx.code.not.blank}") | |||
| private String xcxCode; | |||
| } | |||
| @ -0,0 +1,27 @@ | |||
| package org.dromara.common.core.domain.model; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| /** | |||
| * 小程序登录用户身份权限 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Data | |||
| @EqualsAndHashCode(callSuper = true) | |||
| @NoArgsConstructor | |||
| public class XcxLoginUser extends LoginUser { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| /** | |||
| * openid | |||
| */ | |||
| private String openid; | |||
| } | |||
| @ -0,0 +1,152 @@ | |||
| package org.dromara.common.core.enums; | |||
| import cn.hutool.core.util.StrUtil; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Getter; | |||
| import org.dromara.common.core.exception.ServiceException; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import java.util.Arrays; | |||
| /** | |||
| * 业务状态枚举 | |||
| * | |||
| * @author may | |||
| */ | |||
| @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; | |||
| /** | |||
| * 获取业务状态 | |||
| * | |||
| * @param status 状态 | |||
| */ | |||
| public static String findByStatus(String status) { | |||
| if (StringUtils.isBlank(status)) { | |||
| return StrUtil.EMPTY; | |||
| } | |||
| return Arrays.stream(BusinessStatusEnum.values()) | |||
| .filter(statusEnum -> statusEnum.getStatus().equals(status)) | |||
| .findFirst() | |||
| .map(BusinessStatusEnum::getDesc) | |||
| .orElse(StrUtil.EMPTY); | |||
| } | |||
| /** | |||
| * 启动流程校验 | |||
| * | |||
| * @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("流程状态为空!"); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,37 @@ | |||
| package org.dromara.common.core.enums; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Getter; | |||
| /** | |||
| * 设备类型 | |||
| * 针对一套 用户体系 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Getter | |||
| @AllArgsConstructor | |||
| public enum DeviceType { | |||
| /** | |||
| * pc端 | |||
| */ | |||
| PC("pc"), | |||
| /** | |||
| * app端 | |||
| */ | |||
| APP("app"), | |||
| /** | |||
| * 小程序端 | |||
| */ | |||
| XCX("xcx"), | |||
| /** | |||
| * social第三方端 | |||
| */ | |||
| SOCIAL("social"); | |||
| private final String device; | |||
| } | |||
| @ -0,0 +1,44 @@ | |||
| package org.dromara.common.core.enums; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Getter; | |||
| /** | |||
| * 登录类型 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @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; | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| package org.dromara.common.core.enums; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Getter; | |||
| /** | |||
| * 用户状态 | |||
| * | |||
| * @author LionLi | |||
| */ | |||
| @Getter | |||
| @AllArgsConstructor | |||
| public enum TenantStatus { | |||
| /** | |||
| * 正常 | |||
| */ | |||
| OK("0", "正常"), | |||
| /** | |||
| * 停用 | |||
| */ | |||
| DISABLE("1", "停用"), | |||
| /** | |||
| * 删除 | |||
| */ | |||
| DELETED("2", "删除"); | |||
| private final String code; | |||
| private final String info; | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| package org.dromara.common.core.enums; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Getter; | |||
| /** | |||
| * 用户状态 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| @Getter | |||
| @AllArgsConstructor | |||
| public enum UserStatus { | |||
| /** | |||
| * 正常 | |||
| */ | |||
| OK("0", "正常"), | |||
| /** | |||
| * 停用 | |||
| */ | |||
| DISABLE("1", "停用"), | |||
| /** | |||
| * 删除 | |||
| */ | |||
| DELETED("2", "删除"); | |||
| private final String code; | |||
| private final String info; | |||
| } | |||
| @ -0,0 +1,37 @@ | |||
| package org.dromara.common.core.enums; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Getter; | |||
| /** | |||
| * 设备类型 | |||
| * 针对多套 用户体系 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @Getter | |||
| @AllArgsConstructor | |||
| public enum UserType { | |||
| /** | |||
| * pc端 | |||
| */ | |||
| SYS_USER("sys_user"), | |||
| /** | |||
| * app端 | |||
| */ | |||
| APP_USER("app_user"); | |||
| 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); | |||
| } | |||
| } | |||
| @ -0,0 +1,59 @@ | |||
| package org.dromara.common.core.exception; | |||
| import lombok.*; | |||
| import java.io.Serial; | |||
| /** | |||
| * 业务异常 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| @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; | |||
| } | |||
| @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; | |||
| } | |||
| } | |||
| @ -0,0 +1,74 @@ | |||
| package org.dromara.common.core.exception.base; | |||
| import lombok.AllArgsConstructor; | |||
| import org.dromara.common.core.utils.MessageUtils; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| import lombok.NoArgsConstructor; | |||
| import java.io.Serial; | |||
| /** | |||
| * 基础异常 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| @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; | |||
| } | |||
| } | |||
| @ -0,0 +1,21 @@ | |||
| package org.dromara.common.core.exception.file; | |||
| import org.dromara.common.core.exception.base.BaseException; | |||
| import java.io.Serial; | |||
| /** | |||
| * 文件信息异常类 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| public class FileException extends BaseException { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| public FileException(String code, Object[] args) { | |||
| super("file", code, args, null); | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package org.dromara.common.core.exception.file; | |||
| import java.io.Serial; | |||
| /** | |||
| * 文件名称超长限制异常类 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| public class FileNameLengthLimitExceededException extends FileException { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| public FileNameLengthLimitExceededException(int defaultFileNameLength) { | |||
| super("upload.filename.exceed.length", new Object[]{defaultFileNameLength}); | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package org.dromara.common.core.exception.file; | |||
| import java.io.Serial; | |||
| /** | |||
| * 文件名大小限制异常类 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| public class FileSizeLimitExceededException extends FileException { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| public FileSizeLimitExceededException(long defaultMaxSize) { | |||
| super("upload.exceed.maxSize", new Object[]{defaultMaxSize}); | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package org.dromara.common.core.exception.user; | |||
| import java.io.Serial; | |||
| /** | |||
| * 验证码错误异常类 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| public class CaptchaException extends UserException { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| public CaptchaException() { | |||
| super("user.jcaptcha.error"); | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package org.dromara.common.core.exception.user; | |||
| import java.io.Serial; | |||
| /** | |||
| * 验证码失效异常类 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| public class CaptchaExpireException extends UserException { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| public CaptchaExpireException() { | |||
| super("user.jcaptcha.expire"); | |||
| } | |||
| } | |||
| @ -0,0 +1,20 @@ | |||
| package org.dromara.common.core.exception.user; | |||
| import org.dromara.common.core.exception.base.BaseException; | |||
| import java.io.Serial; | |||
| /** | |||
| * 用户信息异常类 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| public class UserException extends BaseException { | |||
| @Serial | |||
| private static final long serialVersionUID = 1L; | |||
| public UserException(String code, Object... args) { | |||
| super("user", code, args, null); | |||
| } | |||
| } | |||
| @ -0,0 +1,52 @@ | |||
| package org.dromara.common.core.factory; | |||
| import cn.hutool.core.lang.PatternPool; | |||
| import org.dromara.common.core.constant.RegexConstants; | |||
| import java.util.regex.Pattern; | |||
| /** | |||
| * 正则表达式模式池工厂 | |||
| * <p>初始化的时候将正则表达式加入缓存池当中</p> | |||
| * <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p> | |||
| * | |||
| * @author 21001 | |||
| */ | |||
| public class RegexPatternPoolFactory extends PatternPool { | |||
| /** | |||
| * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | |||
| */ | |||
| public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE); | |||
| /** | |||
| * 身份证号码(后6位) | |||
| */ | |||
| public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6); | |||
| /** | |||
| * QQ号码 | |||
| */ | |||
| public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER); | |||
| /** | |||
| * 邮政编码 | |||
| */ | |||
| public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE); | |||
| /** | |||
| * 注册账号 | |||
| */ | |||
| public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT); | |||
| /** | |||
| * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 | |||
| */ | |||
| public static final Pattern PASSWORD = get(RegexConstants.PASSWORD); | |||
| /** | |||
| * 通用状态(0表示正常,1表示停用) | |||
| */ | |||
| public static final Pattern STATUS = get(RegexConstants.STATUS); | |||
| } | |||
| @ -0,0 +1,31 @@ | |||
| package org.dromara.common.core.factory; | |||
| import org.dromara.common.core.utils.StringUtils; | |||
| import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; | |||
| import org.springframework.core.env.PropertiesPropertySource; | |||
| import org.springframework.core.env.PropertySource; | |||
| import org.springframework.core.io.support.DefaultPropertySourceFactory; | |||
| import org.springframework.core.io.support.EncodedResource; | |||
| import java.io.IOException; | |||
| /** | |||
| * yml 配置源工厂 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public class YmlPropertySourceFactory extends DefaultPropertySourceFactory { | |||
| @Override | |||
| public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { | |||
| String sourceName = resource.getResource().getFilename(); | |||
| if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) { | |||
| YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); | |||
| factory.setResources(resource.getResource()); | |||
| factory.afterPropertiesSet(); | |||
| return new PropertiesPropertySource(sourceName, factory.getObject()); | |||
| } | |||
| return super.createPropertySource(name, resource); | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package org.dromara.common.core.service; | |||
| /** | |||
| * 通用 参数配置服务 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public interface ConfigService { | |||
| /** | |||
| * 根据参数 key 获取参数值 | |||
| * | |||
| * @param configKey 参数 key | |||
| * @return 参数值 | |||
| */ | |||
| String getConfigValue(String configKey); | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package org.dromara.common.core.service; | |||
| /** | |||
| * 通用 部门服务 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public interface DeptService { | |||
| /** | |||
| * 通过部门ID查询部门名称 | |||
| * | |||
| * @param deptIds 部门ID串逗号分隔 | |||
| * @return 部门名称串逗号分隔 | |||
| */ | |||
| String selectDeptNameByIds(String deptIds); | |||
| } | |||
| @ -0,0 +1,67 @@ | |||
| package org.dromara.common.core.service; | |||
| import java.util.Map; | |||
| /** | |||
| * 通用 字典服务 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public interface DictService { | |||
| /** | |||
| * 分隔符 | |||
| */ | |||
| String SEPARATOR = ","; | |||
| /** | |||
| * 根据字典类型和字典值获取字典标签 | |||
| * | |||
| * @param dictType 字典类型 | |||
| * @param dictValue 字典值 | |||
| * @return 字典标签 | |||
| */ | |||
| default String getDictLabel(String dictType, String dictValue) { | |||
| return getDictLabel(dictType, dictValue, SEPARATOR); | |||
| } | |||
| /** | |||
| * 根据字典类型和字典标签获取字典值 | |||
| * | |||
| * @param dictType 字典类型 | |||
| * @param dictLabel 字典标签 | |||
| * @return 字典值 | |||
| */ | |||
| default String getDictValue(String dictType, String dictLabel) { | |||
| return getDictValue(dictType, dictLabel, SEPARATOR); | |||
| } | |||
| /** | |||
| * 根据字典类型和字典值获取字典标签 | |||
| * | |||
| * @param dictType 字典类型 | |||
| * @param dictValue 字典值 | |||
| * @param separator 分隔符 | |||
| * @return 字典标签 | |||
| */ | |||
| String getDictLabel(String dictType, String dictValue, String separator); | |||
| /** | |||
| * 根据字典类型和字典标签获取字典值 | |||
| * | |||
| * @param dictType 字典类型 | |||
| * @param dictLabel 字典标签 | |||
| * @param separator 分隔符 | |||
| * @return 字典值 | |||
| */ | |||
| String getDictValue(String dictType, String dictLabel, String separator); | |||
| /** | |||
| * 获取字典下所有的字典值与标签 | |||
| * | |||
| * @param dictType 字典类型 | |||
| * @return dictValue为key,dictLabel为值组成的Map | |||
| */ | |||
| Map<String, String> getAllDictByDictType(String dictType); | |||
| } | |||
| @ -0,0 +1,29 @@ | |||
| package org.dromara.common.core.service; | |||
| import org.dromara.common.core.domain.dto.OssDTO; | |||
| import java.util.List; | |||
| /** | |||
| * 通用 OSS服务 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public interface OssService { | |||
| /** | |||
| * 通过ossId查询对应的url | |||
| * | |||
| * @param ossIds ossId串逗号分隔 | |||
| * @return url串逗号分隔 | |||
| */ | |||
| String selectUrlByIds(String ossIds); | |||
| /** | |||
| * 通过ossId查询列表 | |||
| * | |||
| * @param ossIds ossId串逗号分隔 | |||
| * @return 列表 | |||
| */ | |||
| List<OssDTO> selectByIds(String ossIds); | |||
| } | |||
| @ -0,0 +1,85 @@ | |||
| package org.dromara.common.core.service; | |||
| import org.dromara.common.core.domain.dto.UserDTO; | |||
| import java.util.List; | |||
| /** | |||
| * 通用 用户服务 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| public interface UserService { | |||
| /** | |||
| * 通过用户ID查询用户账户 | |||
| * | |||
| * @param userId 用户ID | |||
| * @return 用户账户 | |||
| */ | |||
| String selectUserNameById(Long userId); | |||
| /** | |||
| * 通过用户ID查询用户账户 | |||
| * | |||
| * @param userId 用户ID | |||
| * @return 用户名称 | |||
| */ | |||
| String selectNicknameById(Long userId); | |||
| /** | |||
| * 通过用户ID查询用户账户 | |||
| * | |||
| * @param userIds 用户ID 多个用逗号隔开 | |||
| * @return 用户名称 | |||
| */ | |||
| String selectNicknameByIds(String userIds); | |||
| /** | |||
| * 通过用户ID查询用户手机号 | |||
| * | |||
| * @param userId 用户id | |||
| * @return 用户手机号 | |||
| */ | |||
| String selectPhonenumberById(Long userId); | |||
| /** | |||
| * 通过用户ID查询用户邮箱 | |||
| * | |||
| * @param userId 用户id | |||
| * @return 用户邮箱 | |||
| */ | |||
| String selectEmailById(Long userId); | |||
| /** | |||
| * 通过用户ID查询用户列表 | |||
| * | |||
| * @param userIds 用户ids | |||
| * @return 用户列表 | |||
| */ | |||
| List<UserDTO> selectListByIds(List<Long> userIds); | |||
| /** | |||
| * 通过角色ID查询用户ID | |||
| * | |||
| * @param roleIds 角色ids | |||
| * @return 用户ids | |||
| */ | |||
| List<Long> selectUserIdsByRoleIds(List<Long> roleIds); | |||
| /** | |||
| * 通过角色ID查询用户 | |||
| * | |||
| * @param roleIds 角色ids | |||
| * @return 用户 | |||
| */ | |||
| List<UserDTO> selectUsersByRoleIds(List<Long> roleIds); | |||
| /** | |||
| * 通过部门ID查询用户 | |||
| * | |||
| * @param deptIds 部门ids | |||
| * @return 用户 | |||
| */ | |||
| List<UserDTO> selectUsersByDeptIds(List<Long> deptIds); | |||
| } | |||
| @ -0,0 +1,76 @@ | |||
| package org.dromara.common.core.service; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| /** | |||
| * 通用 工作流服务 | |||
| * | |||
| * @author may | |||
| */ | |||
| public interface WorkflowService { | |||
| /** | |||
| * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 | |||
| * | |||
| * @param businessKeys 业务id | |||
| * @return 结果 | |||
| */ | |||
| boolean deleteRunAndHisInstance(List<String> businessKeys); | |||
| /** | |||
| * 获取当前流程状态 | |||
| * | |||
| * @param taskId 任务id | |||
| */ | |||
| String getBusinessStatusByTaskId(String taskId); | |||
| /** | |||
| * 获取当前流程状态 | |||
| * | |||
| * @param businessKey 业务id | |||
| */ | |||
| String getBusinessStatus(String businessKey); | |||
| /** | |||
| * 设置流程变量(全局变量) | |||
| * | |||
| * @param taskId 任务id | |||
| * @param variableName 变量名称 | |||
| * @param value 变量值 | |||
| */ | |||
| void setVariable(String taskId, String variableName, Object value); | |||
| /** | |||
| * 设置流程变量(全局变量) | |||
| * | |||
| * @param taskId 任务id | |||
| * @param variables 流程变量 | |||
| */ | |||
| void setVariables(String taskId, Map<String, Object> variables); | |||
| /** | |||
| * 设置流程变量(本地变量,非全局变量) | |||
| * | |||
| * @param taskId 任务id | |||
| * @param variableName 变量名称 | |||
| * @param value 变量值 | |||
| */ | |||
| void setVariableLocal(String taskId, String variableName, Object value); | |||
| /** | |||
| * 设置流程变量(本地变量,非全局变量) | |||
| * | |||
| * @param taskId 任务id | |||
| * @param variables 流程变量 | |||
| */ | |||
| void setVariablesLocal(String taskId, Map<String, Object> variables); | |||
| /** | |||
| * 按照业务id查询流程实例id | |||
| * | |||
| * @param businessKey 业务id | |||
| * @return 结果 | |||
| */ | |||
| String getInstanceIdByBusinessKey(String businessKey); | |||
| } | |||
| @ -0,0 +1,168 @@ | |||
| package org.dromara.common.core.utils; | |||
| import lombok.AccessLevel; | |||
| import lombok.NoArgsConstructor; | |||
| import org.apache.commons.lang3.time.DateFormatUtils; | |||
| import java.lang.management.ManagementFactory; | |||
| import java.text.ParseException; | |||
| import java.text.SimpleDateFormat; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import java.time.LocalTime; | |||
| import java.time.ZoneId; | |||
| import java.time.ZonedDateTime; | |||
| import java.util.Date; | |||
| /** | |||
| * 时间工具类 | |||
| * | |||
| * @author ruoyi | |||
| */ | |||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | |||
| public class DateUtils extends org.apache.commons.lang3.time.DateUtils { | |||
| public static final String YYYY = "yyyy"; | |||
| public static final String YYYY_MM = "yyyy-MM"; | |||
| public static final String YYYY_MM_DD = "yyyy-MM-dd"; | |||
| public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; | |||
| public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; | |||
| private static final String[] PARSE_PATTERNS = { | |||
| "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", | |||
| "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", | |||
| "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; | |||
| /** | |||
| * 获取当前Date型日期 | |||
| * | |||
| * @return Date() 当前日期 | |||
| */ | |||
| public static Date getNowDate() { | |||
| return new Date(); | |||
| } | |||
| /** | |||
| * 获取当前日期, 默认格式为yyyy-MM-dd | |||
| * | |||
| * @return String | |||
| */ | |||
| public static String getDate() { | |||
| return dateTimeNow(YYYY_MM_DD); | |||
| } | |||
| public static String getTime() { | |||
| return dateTimeNow(YYYY_MM_DD_HH_MM_SS); | |||
| } | |||
| public static String dateTimeNow() { | |||
| return dateTimeNow(YYYYMMDDHHMMSS); | |||
| } | |||
| public static String dateTimeNow(final String format) { | |||
| return parseDateToStr(format, new Date()); | |||
| } | |||
| public static String dateTime(final Date date) { | |||
| return parseDateToStr(YYYY_MM_DD, date); | |||
| } | |||
| public static String parseDateToStr(final String format, final Date date) { | |||
| return new SimpleDateFormat(format).format(date); | |||
| } | |||
| public static Date dateTime(final String format, final String ts) { | |||
| try { | |||
| return new SimpleDateFormat(format).parse(ts); | |||
| } catch (ParseException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| /** | |||
| * 日期路径 即年/月/日 如2018/08/08 | |||
| */ | |||
| public static String datePath() { | |||
| Date now = new Date(); | |||
| return DateFormatUtils.format(now, "yyyy/MM/dd"); | |||
| } | |||
| /** | |||
| * 日期路径 即年/月/日 如20180808 | |||
| */ | |||
| public static String dateTime() { | |||
| Date now = new Date(); | |||
| return DateFormatUtils.format(now, "yyyyMMdd"); | |||
| } | |||
| /** | |||
| * 日期型字符串转化为日期 格式 | |||
| */ | |||
| public static Date parseDate(Object str) { | |||
| if (str == null) { | |||
| return null; | |||
| } | |||
| try { | |||
| return parseDate(str.toString(), PARSE_PATTERNS); | |||
| } catch (ParseException e) { | |||
| return null; | |||
| } | |||
| } | |||
| /** | |||
| * 获取服务器启动时间 | |||
| */ | |||
| public static Date getServerStartDate() { | |||
| long time = ManagementFactory.getRuntimeMXBean().getStartTime(); | |||
| return new Date(time); | |||
| } | |||
| /** | |||
| * 计算相差天数 | |||
| */ | |||
| public static int differentDaysByMillisecond(Date date1, Date date2) { | |||
| return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); | |||
| } | |||
| /** | |||
| * 计算两个时间差 | |||
| */ | |||
| public static String getDatePoor(Date endDate, Date nowDate) { | |||
| long nd = 1000 * 24 * 60 * 60; | |||
| long nh = 1000 * 60 * 60; | |||
| long nm = 1000 * 60; | |||
| // long ns = 1000; | |||
| // 获得两个时间的毫秒时间差异 | |||
| long diff = endDate.getTime() - nowDate.getTime(); | |||
| // 计算差多少天 | |||
| long day = diff / nd; | |||
| // 计算差多少小时 | |||
| long hour = diff % nd / nh; | |||
| // 计算差多少分钟 | |||
| long min = diff % nd % nh / nm; | |||
| // 计算差多少秒//输出结果 | |||
| // long sec = diff % nd % nh % nm / ns; | |||
| return day + "天" + hour + "小时" + min + "分钟"; | |||
| } | |||
| /** | |||
| * 增加 LocalDateTime ==> Date | |||
| */ | |||
| public static Date toDate(LocalDateTime temporalAccessor) { | |||
| ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); | |||
| return Date.from(zdt.toInstant()); | |||
| } | |||
| /** | |||
| * 增加 LocalDate ==> Date | |||
| */ | |||
| public static Date toDate(LocalDate temporalAccessor) { | |||
| LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); | |||
| ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); | |||
| return Date.from(zdt.toInstant()); | |||
| } | |||
| } | |||
| @ -0,0 +1,93 @@ | |||
| package org.dromara.common.core.utils; | |||
| import cn.hutool.core.collection.CollUtil; | |||
| import cn.hutool.core.map.MapUtil; | |||
| import cn.hutool.core.util.ObjectUtil; | |||
| import io.github.linpeilie.Converter; | |||
| import lombok.AccessLevel; | |||
| import lombok.NoArgsConstructor; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| /** | |||
| * Mapstruct 工具类 | |||
| * <p>参考文档:<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p> | |||
| * | |||
| * | |||
| * @author Michelle.Chung | |||
| */ | |||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | |||
| public class MapstructUtils { | |||
| private final static Converter CONVERTER = SpringUtils.getBean(Converter.class); | |||
| /** | |||
| * 将 T 类型对象,转换为 desc 类型的对象并返回 | |||
| * | |||
| * @param source 数据来源实体 | |||
| * @param desc 描述对象 转换后的对象 | |||
| * @return desc | |||
| */ | |||
| public static <T, V> V convert(T source, Class<V> desc) { | |||
| if (ObjectUtil.isNull(source)) { | |||
| return null; | |||
| } | |||
| if (ObjectUtil.isNull(desc)) { | |||
| return null; | |||
| } | |||
| return CONVERTER.convert(source, desc); | |||
| } | |||
| /** | |||
| * 将 T 类型对象,按照配置的映射字段规则,给 desc 类型的对象赋值并返回 desc 对象 | |||
| * | |||
| * @param source 数据来源实体 | |||
| * @param desc 转换后的对象 | |||
| * @return desc | |||
| */ | |||
| public static <T, V> V convert(T source, V desc) { | |||
| if (ObjectUtil.isNull(source)) { | |||
| return null; | |||
| } | |||
| if (ObjectUtil.isNull(desc)) { | |||
| return null; | |||
| } | |||
| return CONVERTER.convert(source, desc); | |||
| } | |||
| /** | |||
| * 将 T 类型的集合,转换为 desc 类型的集合并返回 | |||
| * | |||
| * @param sourceList 数据来源实体列表 | |||
| * @param desc 描述对象 转换后的对象 | |||
| * @return desc | |||
| */ | |||
| public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) { | |||
| if (ObjectUtil.isNull(sourceList)) { | |||
| return null; | |||
| } | |||
| if (CollUtil.isEmpty(sourceList)) { | |||
| return CollUtil.newArrayList(); | |||
| } | |||
| return CONVERTER.convert(sourceList, desc); | |||
| } | |||
| /** | |||
| * 将 Map 转换为 beanClass 类型的集合并返回 | |||
| * | |||
| * @param map 数据来源 | |||
| * @param beanClass bean类 | |||
| * @return bean对象 | |||
| */ | |||
| public static <T> T convert(Map<String, Object> map, Class<T> beanClass) { | |||
| if (MapUtil.isEmpty(map)) { | |||
| return null; | |||
| } | |||
| if (ObjectUtil.isNull(beanClass)) { | |||
| return null; | |||
| } | |||
| return CONVERTER.convert(map, beanClass); | |||
| } | |||
| } | |||
| @ -0,0 +1,33 @@ | |||
| package org.dromara.common.core.utils; | |||
| import lombok.AccessLevel; | |||
| import lombok.NoArgsConstructor; | |||
| import org.springframework.context.MessageSource; | |||
| import org.springframework.context.NoSuchMessageException; | |||
| import org.springframework.context.i18n.LocaleContextHolder; | |||
| /** | |||
| * 获取i18n资源文件 | |||
| * | |||
| * @author Lion Li | |||
| */ | |||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | |||
| public class MessageUtils { | |||
| private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class); | |||
| /** | |||
| * 根据消息键和参数 获取消息 委托给spring messageSource | |||
| * | |||
| * @param code 消息键 | |||
| * @param args 参数 | |||
| * @return 获取国际化翻译值 | |||
| */ | |||
| public static String message(String code, Object... args) { | |||
| try { | |||
| return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale()); | |||
| } catch (NoSuchMessageException e) { | |||
| return code; | |||
| } | |||
| } | |||
| } | |||