mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-09-26 21:26:39 +08:00
更新子模块 ruoyi-plus-soybean 的路径和远程仓库地址。
This commit is contained in:
parent
eed5a226f9
commit
049934932d
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@ -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
|
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@ -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
|
12
.run/ruoyi-monitor-admin.run.xml
Normal file
12
.run/ruoyi-monitor-admin.run.xml
Normal file
@ -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.3.1" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
||||
</settings>
|
||||
</deployment>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
12
.run/ruoyi-server.run.xml
Normal file
12
.run/ruoyi-server.run.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||
<deployment type="dockerfile">
|
||||
<settings>
|
||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.3.1" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
||||
</settings>
|
||||
</deployment>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
12
.run/ruoyi-snailjob-server.run.xml
Normal file
12
.run/ruoyi-snailjob-server.run.xml
Normal file
@ -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.3.1" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
||||
</settings>
|
||||
</deployment>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
106
DEPLOYMENT_GUIDE.md
Normal file
106
DEPLOYMENT_GUIDE.md
Normal file
@ -0,0 +1,106 @@
|
||||
# RuoYi-Vue-Plus 系统部署指南
|
||||
|
||||
## 一、环境准备
|
||||
1. **JDK**:需要 JDK 17 或 JDK 21
|
||||
2. **数据库**:支持 MySQL/Oracle/PostgreSQL/SQLServer
|
||||
3. **Redis**:5.0+ 版本
|
||||
4. **Maven**:3.6+ 版本
|
||||
5. **Node.js**:前端需要(建议 v18+)
|
||||
|
||||
## 二、后端配置部署
|
||||
1. **核心配置文件**:
|
||||
- `ruoyi-admin/src/main/resources/application.yml` - 主配置文件
|
||||
- `application-dev.yml`/`application-prod.yml` - 环境配置
|
||||
|
||||
2. **关键配置项**:
|
||||
```yaml
|
||||
# 数据库配置
|
||||
spring.datasource:
|
||||
url: jdbc:mysql://localhost:3306/ry-vue-plus
|
||||
username: root
|
||||
password: 123456
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# Redis配置
|
||||
spring.redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: ruoyi123
|
||||
```
|
||||
|
||||
3. **数据库初始化**:
|
||||
- 执行对应数据库的SQL脚本(位于`script/sql/`目录)
|
||||
- 例如MySQL:执行`mysql_ry_vue_5.X.sql`
|
||||
|
||||
4. **启动方式**:
|
||||
必须启动基础建设: mysql redis admin
|
||||
可选启动基础建设: minio(影响文件上传) monitor(影响监控) snailjob(影响定时任务)
|
||||
MonitorAdminApplication 为 Admin监控服务(非必要 可参考对应文档关闭 搭建Admin监控)
|
||||
SnailJobServerApplication 为 任务调度中心服务(非必要 可参考对应文档关闭 搭建调度中心)
|
||||
DromaraApplication 为 主应用服务
|
||||
需优先启动 MonitorAdminApplication 与 SnailJobServerApplication 具体配置方式参考对应文档
|
||||
最后启动 主服务 DromaraApplication
|
||||
工作流相关初始化使用 工作流初始化
|
||||
|
||||
## 三、前端部署
|
||||
克隆仓库
|
||||
git clone https://gitee.com/xlsea/ruoyi-plus-soybean.git
|
||||
cd ruoyi-plus-soybean
|
||||
安装 pnpm (如果未安装)
|
||||
npm install pnpm -g
|
||||
设置淘宝镜像
|
||||
|
||||
pnpm config set registry https://registry.npmmirror.com
|
||||
安装依赖
|
||||
pnpm install
|
||||
运行开发服务器
|
||||
pnpm dev
|
||||
构建生产版本
|
||||
pnpm build
|
||||
|
||||
## 四、Docker部署(可选)
|
||||
1. **使用docker-compose**:
|
||||
```bash
|
||||
cd script/docker
|
||||
docker-compose up -d
|
||||
```
|
||||
包含的服务:
|
||||
- Redis
|
||||
- MinIO
|
||||
- RuoYi主服务
|
||||
- 监控中心
|
||||
|
||||
2. **独立容器运行**:
|
||||
```bash
|
||||
docker run -d -p 8080:8080 ruoyi/ruoyi-server:5.3.1
|
||||
```
|
||||
|
||||
## 五、监控中心
|
||||
1. **监控服务**:
|
||||
- 访问地址:`http://localhost:9090/admin`
|
||||
- 默认账号:ruoyi/123456
|
||||
|
||||
2. **任务调度中心**:
|
||||
- 访问地址:`http://localhost:8800`
|
||||
- 配置见`application-dev.yml`中的`snail-job`部分
|
||||
|
||||
## 六、注意事项
|
||||
1. 首次启动会自动初始化系统数据
|
||||
2. 默认管理员账号:admin/admin123
|
||||
3. 生产环境建议:
|
||||
- 修改默认密码
|
||||
- 关闭Swagger(设置`springdoc.swagger-ui.enabled=false`)
|
||||
- 配置HTTPS
|
||||
|
||||
完整文档参考:[RuoYi-Vue-Plus文档](https://plus-doc.dromara.org)
|
||||
|
||||
ruoyi-vue-plus/
|
||||
├── ruoyi-admin/ # 后台核心模块
|
||||
├── ruoyi-common/ # 公共模块库
|
||||
├── ruoyi-modules/ # 业务模块
|
||||
├── ruoyi-extend/ # 扩展模块
|
||||
├── ruoyi-plus-soybean/ # 前端Vue3项目
|
||||
├── script/ # 部署脚本
|
||||
├── target/ # 编译输出
|
||||
├── pom.xml # Maven父工程配置
|
||||
└── README.md # 项目说明
|
20
LICENSE
Normal file
20
LICENSE
Normal file
@ -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.
|
185
README.md
Normal file
185
README.md
Normal file
@ -0,0 +1,185 @@
|
||||
<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://gitcode.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)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
> Dromara RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
|
||||
|
||||
> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
|
||||
活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源
|
||||
|
||||
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
|
||||
|
||||
> 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
|
||||
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
|
||||
|
||||
> 文档地址: [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>
|
||||
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><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" />
|
||||
|
||||
## 演示图例
|
||||
|
||||
| | |
|
||||
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
941
RuoYi-Vue-Plus二次开发最佳实践.md
Normal file
941
RuoYi-Vue-Plus二次开发最佳实践.md
Normal file
@ -0,0 +1,941 @@
|
||||
# RuoYi-Vue-Plus 二次开发最佳实践
|
||||
|
||||
本文档旨在为基于 RuoYi-Vue-Plus (Soybean Admin Pro 版本前端) 进行二次开发的开发者提供一套实践指南,帮助大家更高效、规范地进行项目开发。
|
||||
|
||||
## 1. 引言
|
||||
|
||||
### 1.1 文档目的与核心读者
|
||||
|
||||
本文档的核心目标是为基于 RuoYi-Vue-Plus (特别是采用 Soybean Admin Pro 前端技术栈的版本) 进行二次开发的开发者提供一套清晰、实用的指南,**专注于如何高效、规范地设计、开发和集成全新的业务模块**。本文档尤其强调如何结合 Cursor 这一现代化 AI 辅助开发工具,进一步提升开发效率和代码质量。
|
||||
|
||||
Cursor是一款基于AI的智能编辑器,它能够理解您的代码库和开发意图,提供高质量的代码补全、重构建议和智能问答能力。使用Cursor进行RuoYi-Vue-Plus项目的二次开发,能够显著减少重复性工作,提高代码生成效率,并保持项目代码风格的一致性。
|
||||
|
||||
无论您是需要为现有系统添加新的业务功能,还是希望基于 RuoYi-Vue-Plus 快速构建包含自定义模块的企业级应用,本文档都将力求为您提供从概念到实践的全方位指导,同时充分利用 Cursor 提供的智能代码辅助功能。
|
||||
|
||||
### 1.2 RuoYi-Vue-Plus 模块化特性简介
|
||||
|
||||
RuoYi-Vue-Plus 在设计上充分考虑了系统的可扩展性和模块化。后端采用多模块 Maven 项目结构,前端也支持模块化的视图和路由管理。这种设计使得开发者可以相对独立地开发和维护各个业务模块,降低了模块间的耦合度,提高了开发效率和系统的可维护性。
|
||||
|
||||
"模块"通常指一组相关联的业务功能单元,例如"订单管理模块"、"产品管理模块"或"客户关系模块",它们在后端有独立的 Maven 模块(通常在 `ruoyi-modules`下),在前端有对应的视图组件、API 服务和路由配置。
|
||||
|
||||
通过使用 Cursor 进行开发,您可以借助其强大的代码智能补全和上下文理解能力,使模块化开发更加高效。Cursor能够:
|
||||
|
||||
- 分析现有代码结构,快速理解项目架构和组件关系
|
||||
- 根据上下文提供符合项目规范的代码片段
|
||||
- 协助生成前后端一致的API接口和数据模型
|
||||
- 智能重构和改进现有代码,保持风格一致性
|
||||
- 减少常见错误和提高代码质量
|
||||
|
||||
### 1.3 二次开发新模块的核心原则
|
||||
|
||||
在进行新模块开发时,我们遵循以下核心原则:
|
||||
|
||||
* **高内聚、低耦合**:新模块内部功能紧密相关,模块之间尽可能减少直接依赖。
|
||||
* **遵循框架规范**:充分利用并遵循 RuoYi-Vue-Plus 已有的架构设计、代码规范和工具。
|
||||
* **可复用性**:考虑模块内部组件和服务的可复用性,避免代码冗余。
|
||||
* **可维护性**:编写清晰、易懂、易于测试的代码,确保长期维护成本最小化。
|
||||
* **安全性**:从设计之初就考虑新模块的权限控制和数据安全,防范常见的安全风险。
|
||||
* **AI辅助开发**:合理利用 Cursor 提供的 AI 能力进行代码生成、重构和优化,同时保持对生成代码的质量审核。
|
||||
|
||||
### 1.4 使用Cursor提升开发体验
|
||||
|
||||
作为一款智能开发工具,Cursor在RuoYi-Vue-Plus二次开发中可以提供以下关键优势:
|
||||
|
||||
- **快速上手项目**:使用聊天功能直接询问项目结构、核心组件和开发规范
|
||||
- **智能代码生成**:基于项目上下文自动生成符合规范的代码片段和完整模块
|
||||
- **跨语言开发支持**:同时支持后端Java和前端Vue/TypeScript的智能补全和建议
|
||||
- **代码解释和重构**:为复杂代码提供解释,并建议更优的实现方案
|
||||
- **标准化编码**:帮助团队保持一致的代码风格和最佳实践
|
||||
- **学习辅助**:通过提问了解框架各组件的用法,减少学习曲线
|
||||
|
||||
要充分利用Cursor的能力,建议:
|
||||
|
||||
1. 将整个项目代码库导入Cursor工作区
|
||||
2. 在使用AI功能前,确保已浏览关键部分的代码以建立上下文
|
||||
3. 使用具体、明确的提示来获取最佳结果
|
||||
4. 先让Cursor解释代码后再请求修改或生成新代码
|
||||
|
||||
## 2. 环境准备与项目结构
|
||||
|
||||
在开始新模块开发之前,请确保您的开发环境已正确配置,并熟悉项目的基本结构。
|
||||
|
||||
### 2.1 开发环境要求
|
||||
|
||||
* **后端**:
|
||||
* JDK 17+ (请根据项目 `pom.xml` 中指定的 Java 版本为准)
|
||||
* Maven 3.6+
|
||||
* Redis 6.0+
|
||||
* MySQL 8.0+ (或其他兼容数据库,如PostgreSQL, Oracle)
|
||||
* IDE: IntelliJ IDEA (推荐,对 Maven 和 Spring Boot 支持良好)
|
||||
* **前端** (`ruoyi-plus-soybean`):
|
||||
* Node.js 16.x 或 18.x+
|
||||
* 包管理器: pnpm 8.x+ (强烈推荐,项目默认配置)
|
||||
* IDE: VSCode 配合 Cursor 插件 (推荐) 或 Cursor 独立应用
|
||||
* **AI辅助开发工具**:
|
||||
* Cursor IDE (集成了 Claude AI 的智能编辑器)
|
||||
* 配置建议:
|
||||
* 启用代码补全和聊天功能
|
||||
* 设置项目级别的上下文提示,说明项目结构和模块关系
|
||||
* 配置保存检查,确保生成的代码符合项目linting规则
|
||||
* 设置快捷键,加速常用AI操作的执行
|
||||
* 对于团队协作,考虑共享提示模板和命令
|
||||
|
||||
*重要提示*:
|
||||
|
||||
1. 确保所有环境与 RuoYi-Vue-Plus 主项目所依赖的版本兼容。建议查阅项目根目录的 `pom.xml` (后端) 和 `package.json` (前端) 以获取精确的版本信息。
|
||||
2. 使用 Cursor 进行开发时,可以通过命令面板 (Cmd/Ctrl+Shift+P) 选择 "Chat with codebase" 功能,帮助快速理解项目结构和代码逻辑。
|
||||
3. 建议添加对项目特定文件(如README.md、架构图文档、API规范)的上下文,帮助Cursor更好地理解项目规范。
|
||||
|
||||
### 2.2 后端项目结构 (`ruoyi-vue-plus`)
|
||||
|
||||
后端项目采用典型的 Maven 多模块结构,这对于添加新业务模块至关重要:
|
||||
|
||||
* `ruoyi-vue-plus` (根项目)
|
||||
* `pom.xml`: 主 POM 文件,管理所有模块的依赖和插件。**新增业务模块后,需要在此处的 `<modules>` 标签内注册新模块。**
|
||||
* `ruoyi-admin`: 启动和核心管理模块。
|
||||
* `ruoyi-common`: 通用模块父级,包含多个功能明确的子模块:
|
||||
* `ruoyi-common-core`: 核心工具类、常量、枚举、通用 Service/Mapper 接口等。
|
||||
* `ruoyi-common-mybatis`: MyBatis Plus 相关配置和基类。
|
||||
* `ruoyi-common-redis`: Redis 相关配置和工具。
|
||||
* `ruoyi-common-satoken`: SaToken 权限认证相关。
|
||||
* `ruoyi-common-log`: 操作日志和系统日志处理。
|
||||
* `ruoyi-common-web`: Web 相关通用处理,如全局异常、XSS过滤等。
|
||||
* `ruoyi-common-excel`: Excel导入导出处理。
|
||||
* `ruoyi-common-translation`: 数据翻译功能。
|
||||
* `ruoyi-common-tenant`: 多租户支持。
|
||||
* ... (其他通用组件)
|
||||
* `ruoyi-modules`: **所有业务模块的父级模块。您开发的新业务模块将作为其子模块存放。**
|
||||
* `ruoyi-system`: 系统管理模块 (用户、角色、菜单、部门、字典等)。
|
||||
* `ruoyi-generator`: 代码生成器模块。
|
||||
* `ruoyi-job`: 定时任务模块。
|
||||
* `ruoyi-workflow`: 工作流模块(如果已启用)。
|
||||
* `your-new-module`: **您新增的业务模块将放置于此,例如 `ruoyi-pms` (商品管理)。**
|
||||
* `ruoyi-extend`: 扩展模块,如监控等。
|
||||
|
||||
**Cursor使用技巧**:
|
||||
|
||||
- 使用"文件探索"功能快速导航到关键模块的代码文件
|
||||
- 通过聊天功能询问"系统模块之间的依赖关系是什么?"来理解项目架构
|
||||
- 请求Cursor生成新模块的Maven配置文件,如"帮我创建一个名为ruoyi-crm的新业务模块的pom.xml文件"
|
||||
- 让Cursor分析现有模块的结构,如"分析ruoyi-system模块的目录结构和主要类"
|
||||
- 使用Cursor生成符合项目规范的新模块基础结构
|
||||
|
||||
### 2.3 前端项目结构 (`ruoyi-plus-soybean`)
|
||||
|
||||
前端项目基于 Soybean Admin Pro,采用 Vue 3 + TypeScript + Naive UI 技术栈,组织结构清晰:
|
||||
|
||||
* `ruoyi-plus-soybean` (项目根目录)
|
||||
* `src/`: 主要源代码目录。
|
||||
* `views/`: 页面视图组件。**新模块的页面通常会在此目录下创建对应子目录**,例如 `src/views/pms/product/index.vue`。
|
||||
* `service/api/`: API 请求服务。**新模块的后端接口调用会在此目录下创建对应的 `ts` 文件**,例如 `src/service/api/pms/product.ts`。
|
||||
* `router/routes/modules/`: 路由配置文件。**新模块的路由信息会在此目录下创建新的 `ts` 文件**,如 `product.ts`。
|
||||
* `store/modules/`: Pinia 状态管理模块。如果新模块有复杂的状态管理需求,可以在此创建。
|
||||
* `locales/langs/`: 国际化语言包。新模块中需要国际化的文本,其键值对会添加到此处的语言文件中。
|
||||
* `components/`: 通用或业务组件。
|
||||
* `common/`: 通用基础组件。
|
||||
* `custom/`: 业务相关组件。
|
||||
* `advanced/`: 复杂功能组件。
|
||||
* `hooks/`: 自定义 Hooks (Composition API)。
|
||||
* `business/`: 业务相关的钩子函数,如表格操作、权限校验等。
|
||||
* `common/`: 通用钩子函数,如状态管理、UI交互等。
|
||||
* `typings/`: TypeScript 类型定义,特别是 API 相关的类型。
|
||||
* `api/`: 后端接口类型定义。
|
||||
* `utils/`: 工具函数集合。
|
||||
|
||||
**Cursor使用技巧**:
|
||||
|
||||
- 使用聊天功能分析前端项目结构:"分析ruoyi-plus-soybean前端项目的文件组织和主要模块"
|
||||
- 了解组件使用模式:"ruoyi-plus-soybean项目中的表格组件是如何封装的?"
|
||||
- 根据已有模块生成新模块:"基于system模块的用户管理页面,创建一个产品管理页面的基础结构"
|
||||
- 分析Hooks使用方式:"分析项目中useTable这个hook的用法和参数"
|
||||
- 生成符合规范的API服务:"为产品管理模块创建API服务文件,包含列表查询、新增、修改和删除方法"
|
||||
|
||||
### 2.4 代码生成器简介与Cursor协同使用
|
||||
|
||||
RuoYi-Vue-Plus 提供了强大的代码生成器功能(通常在系统后台的"开发工具"→"代码生成"菜单下)。它可以根据数据库表结构自动生成包括后端 Controller, Service, Mapper, Domain (Entity, BO, VO) 以及前端 Vue 页面和 API 调用代码。
|
||||
|
||||
代码生成步骤:
|
||||
|
||||
1. 配置数据源(如果尚未配置)
|
||||
2. 导入要生成代码的数据表
|
||||
3. 编辑表和字段配置(包括字段显示类型、查询方式、是否必填等)
|
||||
4. 生成代码
|
||||
5. 下载代码包或直接生成到项目中
|
||||
|
||||
**强烈建议在创建新模块的基础 CRUD 功能时,优先使用代码生成器**,它可以显著提高开发效率并保证代码结构的统一性。后续可在生成代码的基础上进行业务逻辑的定制和优化。
|
||||
|
||||
**代码生成器与Cursor协同工作**:
|
||||
|
||||
1. 使用代码生成器生成基础CRUD代码
|
||||
2. 将生成的代码导入项目
|
||||
3. 使用Cursor分析生成的代码结构:"分析这个新生成的XXX模块的代码结构和主要功能点"
|
||||
4. 让Cursor帮助优化生成的代码:"优化这个Controller中的分页查询方法,添加更多的查询条件支持"
|
||||
5. 使用Cursor扩展业务逻辑:"在这个Service中添加一个批量处理的方法"
|
||||
6. 请求Cursor解释复杂部分:"解释这段MyBatis XML中的动态SQL语句的功能"
|
||||
|
||||
这种组合利用代码生成器的快速创建能力和Cursor的智能优化能力,可以最大化提升开发效率,同时确保代码质量和一致性。
|
||||
|
||||
## 3. 模块深入分析:以"用户管理"为例
|
||||
|
||||
本章通过对用户管理模块的分析,帮助理解 RuoYi-Vue-Plus 项目的模块设计、分层架构以及常用功能的集成方式。
|
||||
|
||||
### 3.1 使用Cursor理解模块架构
|
||||
|
||||
在开始开发新模块前,彻底理解项目的现有模块结构和设计模式至关重要。Cursor能够帮助开发者快速分析和理解复杂的模块架构,特别是像用户管理这样的核心功能模块。
|
||||
|
||||
以下是使用Cursor理解用户管理模块的有效方式:
|
||||
|
||||
1. **快速概览模块结构**:
|
||||
|
||||
* 使用聊天功能询问:"用户管理模块的主要组件和类有哪些?"
|
||||
* 让Cursor分析关键类之间的关系:"分析SysUserController、SysUserService和SysUserMapper之间的交互流程"
|
||||
2. **分析数据流转**:
|
||||
|
||||
* 请求Cursor绘制数据流程:"从前端请求到后端处理,用户管理模块的数据流是怎样的?"
|
||||
* 了解BO/VO对象转换:"SysUserBo和SysUserVo的区别和转换过程是什么?"
|
||||
3. **分析权限控制**:
|
||||
|
||||
* 理解注解应用:"分析用户管理模块中@SaCheckPermission的使用场景和实现逻辑"
|
||||
* 探索数据权限:"用户管理模块如何实现基于部门的数据权限?"
|
||||
4. **理解业务逻辑**:
|
||||
|
||||
* 让Cursor解释复杂方法:"解释SysUserServiceImpl中的importUser方法的业务逻辑"
|
||||
* 分析关键业务规则:"用户管理模块中的密码策略是如何实现的?"
|
||||
|
||||
通过这些提问和分析,可以快速掌握模块的设计理念和实现细节,为开发新模块打下坚实基础。
|
||||
|
||||
### 3.2 后端实现 (`ruoyi-system` 模块)
|
||||
|
||||
后端实现遵循经典的分层架构:
|
||||
|
||||
* **Controller 层**: 负责接收前端请求,进行初步参数校验和权限验证,然后调用 Service 层处理业务逻辑,并返回统一格式的响应。它主要关注 HTTP 请求和响应的处理。
|
||||
* **Service 层**: 包含核心业务逻辑。负责协调对 Mapper 层的调用,执行复杂的业务规则,处理事务和缓存等。Service 层是业务流程的核心。
|
||||
* **Mapper 层**: 负责与数据库进行直接交互,执行 SQL 语句完成数据的持久化和检索。通常使用 MyBatis Plus。
|
||||
* **Domain 层**: 定义数据对象,包括:
|
||||
* **Entity**: 映射数据库表结构,用于 ORM 操作。
|
||||
* **BO (Business Object)**: 用于 Controller 层接收请求参数和 Service 层内部数据传递,主要负责参数校验。
|
||||
* **VO (View Object)**: 用于 Service 层向 Controller 层返回数据,最终响应给前端,包含前端展示所需的字段,可能进行数据脱敏和翻译。
|
||||
|
||||
**Cursor使用提示**: 可以向Cursor请求"生成一个分析用户管理模块各层次依赖关系的结构图",快速理解分层架构。
|
||||
|
||||
#### 3.2.1 核心功能实现分析
|
||||
|
||||
用户管理模块中的核心功能在后端各层的应用:
|
||||
|
||||
* **权限控制 (SaToken)**: 通过注解或编程式方式在 Controller 层对接口进行访问权限校验。
|
||||
* **Cursor分析点:** 请求"分析SaToken在用户管理模块中的应用方式和关键代码"
|
||||
* **数据权限**: 在 Mapper 层通过注解实现,根据用户角色动态过滤数据访问范围。
|
||||
* **Cursor分析点:** 询问"解释@DataPermission注解在SysUserMapper中的具体作用"
|
||||
* **操作日志**: 通过注解在 Controller 层记录用户的业务操作。
|
||||
* **Cursor分析点:** 请求"展示用户管理模块中操作日志注解的使用示例"
|
||||
* **参数校验**: 主要在 BO 对象上通过 JSR 303/380 注解实现,Controller 层触发校验。
|
||||
* **Cursor分析点:** 询问"用户管理模块使用了哪些校验注解及其作用?"
|
||||
* **Excel 导入/导出**: 利用 EasyExcel 工具类及其注解简化操作。
|
||||
* **Cursor分析点:** 请求"分析用户Excel导入导出功能的核心实现逻辑"
|
||||
* **多租户处理**: 通过框架提供的机制在 Entity 和 Mapper 层实现数据隔离。
|
||||
* **Cursor分析点:** 询问"用户管理模块如何处理多租户数据隔离?"
|
||||
* **事务管理**: 在 Service 层通过 `@Transactional` 注解确保数据库操作的原子性。
|
||||
* **Cursor分析点:** 请求"找出用户管理模块中的事务应用示例"
|
||||
* **缓存使用 (Redis)**: 通过 Spring Cache 注解和工具类提升数据访问性能。
|
||||
* **Cursor分析点:** 询问"用户管理模块的哪些数据使用了缓存,如何实现?"
|
||||
* **国际化**: 通过资源文件和 MessageSource 在后端实现多语言支持。
|
||||
* **Cursor分析点:** 请求"显示用户管理模块中国际化的实现方式"
|
||||
* **统一结果与异常处理**: 使用统一的响应结构和全局异常处理器提升代码一致性和用户体验。
|
||||
* **Cursor分析点:** 询问"用户管理模块如何处理业务异常并返回统一结果?"
|
||||
|
||||
通过Cursor对这些核心功能的分析,可以深入理解框架的设计理念和实现方式,为开发新模块奠定基础。
|
||||
|
||||
### 3.3 前端实现 (`ruoyi-plus-soybean`)
|
||||
|
||||
前端实现主要基于 Vue 3、TypeScript 和 Soybean Admin Pro 框架:
|
||||
|
||||
* **视图组件**: 使用 Vue 3 组件构建页面,通常包含搜索区、操作按钮区、数据表格等。
|
||||
* **Hooks 应用**: 大量使用框架提供的自定义 Hooks (Composables) 封装和复用通用逻辑,如表格数据处理、操作处理、权限校验、字典获取等。
|
||||
* **API 服务**: 集中封装所有与后端交互的 API 请求函数,提供统一的请求处理和类型约束。
|
||||
* **路由配置**: 定义页面访问路径、组件加载和菜单元信息,通常按模块组织。
|
||||
|
||||
**Cursor使用提示**: 可以请求Cursor"分析用户管理前端组件的结构和主要功能块",快速理解前端实现。
|
||||
|
||||
#### 3.3.1 前端功能实现分析
|
||||
|
||||
前端用户管理模块的核心功能实现:
|
||||
|
||||
* **接口调用与数据处理**: 调用封装的 API 服务函数,处理后端数据更新视图。
|
||||
* **Cursor分析点:** 请求"分析用户管理模块的API调用和数据处理流程"
|
||||
* **表单与校验**: 使用 UI 组件库构建表单,配合校验规则进行数据验证。
|
||||
* **Cursor分析点:** 询问"用户表单组件的校验规则是如何设置的?"
|
||||
* **权限控制**: 利用 `useAuth` Hook 判断用户权限,控制页面元素的显示和操作。
|
||||
* **Cursor分析点:** 请求"展示useAuth在用户管理页面中的应用案例"
|
||||
* **国际化**: 使用 `$t` 函数实现界面文本的多语言支持。
|
||||
* **Cursor分析点:** 请求"显示用户管理模块中国际化的实现方式"
|
||||
* **状态管理 (Pinia)**: 管理全局或跨组件共享的状态,如用户认证信息、数据字典等。
|
||||
* **Cursor分析点:** 请求"分析用户管理使用了哪些Pinia状态管理"
|
||||
* **子组件通信**: 通过标准的 Props 和 Emits 实现组件间的数据传递和事件交互。
|
||||
* **Cursor分析点:** 询问"用户管理页面中的组件通信方式有哪些?"
|
||||
|
||||
通过对前端实现的深入分析,结合Cursor的辅助,可以清晰理解Vue 3 + TypeScript的组件化开发模式和最佳实践,为新模块的前端开发提供参考。
|
||||
|
||||
### 3.4 使用Cursor复现模块架构
|
||||
|
||||
通过对用户管理模块的分析,我们可以使用Cursor帮助复现类似的架构到新的业务模块中:
|
||||
|
||||
1. **基础结构生成**:
|
||||
|
||||
* 请求"根据用户管理模块的结构,生成一个产品管理模块的基础结构框架"
|
||||
* 让Cursor生成新模块所需的关键文件列表
|
||||
2. **分层设计复现**:
|
||||
|
||||
* 请求"参考用户管理模块,设计产品管理模块的Entity, BO, VO对象结构"
|
||||
* 让Cursor生成"产品管理模块的Controller、Service、Mapper接口定义"
|
||||
3. **功能特性实现**:
|
||||
|
||||
* 请求"基于用户管理模块,为产品管理实现数据权限控制代码"
|
||||
* 让Cursor生成"产品管理的Excel导入导出功能实现代码"
|
||||
4. **前端组件设计**:
|
||||
|
||||
* 请求"参考用户管理的前端组件,设计产品管理的页面组件结构"
|
||||
* 让Cursor生成"产品管理的API服务和类型定义文件"
|
||||
|
||||
通过这种方式,Cursor能够在理解现有模块架构的基础上,生成高度符合项目规范的新模块代码,大幅提高开发效率并保证代码一致性。
|
||||
|
||||
## 4. 后端开发最佳实践
|
||||
|
||||
本章节将基于对RuoYi-Vue-Plus(特别是用户管理模块)的分析,并结合通用的后端开发原则和Cursor辅助开发能力,总结在进行二次开发时推荐遵循的最佳实践。
|
||||
|
||||
### 4.1 Cursor辅助后端开发技巧
|
||||
|
||||
在进行RuoYi-Vue-Plus的后端开发时,Cursor可以显著提升开发效率和代码质量。以下是一些关键的辅助开发技巧:
|
||||
|
||||
1. **快速理解项目结构**:
|
||||
|
||||
* 使用Cursor分析整体架构:"分析RuoYi-Vue-Plus后端的核心模块结构和依赖关系"
|
||||
* 了解设计模式:"RuoYi-Vue-Plus中应用了哪些设计模式?请举例说明"
|
||||
* 生成模块结构图:"绘制一个RuoYi-Vue-Plus模块依赖关系图"
|
||||
2. **代码生成与完善**:
|
||||
|
||||
* 生成常用类文件:"基于RuoYi规范创建一个ProductController类"
|
||||
* 补充接口方法:"为ProductService接口添加批量导入和导出方法"
|
||||
* 实现复杂逻辑:"实现一个根据销售状态统计产品数量的Service方法"
|
||||
* 增强生成代码:"优化代码生成器生成的ProductServiceImpl类,添加缓存支持"
|
||||
3. **分析与解释现有代码**:
|
||||
|
||||
* 理解原理:"解释SaToken在RuoYi-Vue-Plus中的集成方式和工作原理"
|
||||
* 分析性能:"分析UserServiceImpl中这个查询方法的性能瓶颈"
|
||||
* 学习最佳实践:"RuoYi项目中MyBatis动态SQL的最佳实践有哪些?"
|
||||
4. **代码重构与优化**:
|
||||
|
||||
* 优化复杂方法:"重构这个导入用户的方法,提高可读性和维护性"
|
||||
* 消除代码气味:"这段代码有什么可以改进的地方?"
|
||||
* 提取公共逻辑:"从这几个Service中提取通用的验证逻辑到工具类"
|
||||
5. **排查与修复问题**:
|
||||
|
||||
* 分析错误:"为什么这个查询方法返回的结果和预期不符?"
|
||||
* 性能诊断:"为什么这个接口响应很慢?可能的原因和解决方案"
|
||||
* 解决冲突:"解决这个多租户环境下的数据访问冲突问题"
|
||||
|
||||
通过这些技巧,开发者可以充分利用Cursor的AI能力,加快开发进度,提升代码质量,同时深入理解项目架构和设计理念。
|
||||
|
||||
### 4.2 模块设计与创建
|
||||
|
||||
* **单一职责原则 (SRP)**: 设计模块时,应确保每个模块聚焦于一块明确、独立的业务功能领域(例如,系统管理模块 `ruoyi-system` 负责用户、角色、菜单、部门、字典等;产品管理模块 `ruoyi-pms` 负责产品信息的维护)。避免创建过于庞大、职责不清的"上帝模块",这会导致业务逻辑交织,难以理解、维护和独立升级。
|
||||
|
||||
**使用Cursor进行模块设计:**
|
||||
|
||||
* 分析现有模块分工:"分析RuoYi-Vue-Plus各业务模块的职责边界和交互方式"
|
||||
* 设计新模块:"帮我设计一个订单管理模块的功能和边界,考虑与其他模块的关系"
|
||||
* 避免过度耦合:"检查我设计的这个模块是否存在责任划分不清的问题"
|
||||
|
||||
* **模块命名规范**:
|
||||
* **Maven模块名**: 采用小写字母,多个单词之间使用中横线连接(kebab-case),如 `ruoyi-order-management`, `ruoyi-customer-support`。这与RuoYi-Vue-Plus现有的模块命名风格(如 `ruoyi-common-core`)保持一致。
|
||||
* **Java包名**: 遵循标准的Java包命名约定,通常以反向域名开头,后跟项目名和模块名,全小写。例如:`org.dromara.ordermanagement` 或 `com.yourcompany.yourproject.customersupport`。
|
||||
* **模块依赖管理**:
|
||||
* **清晰的依赖关系**: 业务模块(如 `ruoyi-pms`)应明确依赖其所需的平台公共模块(如 `ruoyi-common-core`, `ruoyi-common-mybatis`, `ruoyi-common-web`, `ruoyi-common-satoken`, `ruoyi-common-excel` 等)。
|
||||
* **避免业务模块间的直接强依赖**: 如果业务模块A需要调用业务模块B的功能,应优先考虑是否可以通过更松耦合的方式实现,例如:
|
||||
* **API调用**: 如果模块未来可能独立部署为微服务,或者希望保持高度的独立性,可以设计模块间的API接口,通过HTTP/RPC调用(如使用OpenFeign,若项目引入了Spring Cloud相关技术栈)。
|
||||
* **异步消息/事件**: 对于非核心业务流程的解耦、最终一致性场景或耗时操作,推荐使用消息队列(如RocketMQ、Kafka,需额外集成)或Spring框架内置的事件发布/监听机制 (`ApplicationEvent`)。
|
||||
* **共享公共服务/DTO**: 如果仅仅是共享一些通用的业务逻辑或数据结构,可以考虑将其抽象并下沉到 `ruoyi-common-core` 或创建一个新的、更细粒度的 `ruoyi-common-{feature}` 模块中。
|
||||
* **依赖的最小化原则**: 只依赖真正需要的模块和库,避免不必要的依赖传递和潜在的冲突。
|
||||
* **新模块创建流程** (回顾PMS模块示例):
|
||||
1. 在 `ruoyi-vue-plus/ruoyi-modules` 父Maven模块下,通过IDE或Maven命令创建新的子模块。
|
||||
2. 在新模块的 `pom.xml` 文件中,将其 `<parent>` 指向 `ruoyi-modules`,并根据新模块的业务需求,添加对RuoYi公共模块(如 `ruoyi-common-core`, `ruoyi-common-mybatis`等)和其他第三方库的依赖。
|
||||
3. 在项目根目录的 `pom.xml` 和 `ruoyi-modules/pom.xml` 文件的 `<modules>` 部分,添加新创建的模块名。
|
||||
4. 在新模块的 `src/main/java/` 目录下,按照既定的包结构(如 `org.dromara.{模块名}`)创建 `controller`, `service`, `mapper`, `domain` (包含 `entity`, `bo`, `vo`) 等子包。
|
||||
5. 在 `src/main/resources/` 目录下,创建 `mapper/{模块名}` 子目录用于存放MyBatis的XML映射文件,以及 `i18n` 目录用于存放国际化属性文件 (如 `messages_zh_CN.properties`, `messages_en_US.properties`)。
|
||||
|
||||
### 4.3 分层架构约定
|
||||
|
||||
RuoYi-Vue-Plus 遵循了经典且职责清晰的分层架构,二次开发时应严格遵守此约定:
|
||||
|
||||
* **Controller (表现层 / 控制层)**:
|
||||
|
||||
* **核心职责**: 作为HTTP请求的直接入口,负责请求的接收、参数的初步绑定与校验(主要通过JSR 303/380注解在BO上完成)、调用相应的Service层方法来执行核心业务逻辑、组装Service层返回的数据并构建统一的响应结果 (`R` 对象) 返回给前端。
|
||||
* **关注点**: HTTP协议相关的处理,如路径映射 (`@RequestMapping`, `@GetMapping`等)、请求参数解析 (`@PathVariable`, `@RequestParam`, `@RequestBody`等)、权限控制 (`@SaCheckPermission`)、操作日志记录 (`@Log`)。
|
||||
* **禁止**: 直接调用Mapper层进行数据库操作;包含复杂的业务流程代码;直接处理数据库实体 `Entity` 的输入输出(应使用BO和VO)。
|
||||
* **Service (业务逻辑层)**:
|
||||
|
||||
* **接口 (`ISyourService.java`)**: 定义业务操作的契约,明确本业务模块能提供的服务能力。面向接口编程是推荐的。
|
||||
* **实现 (`YourServiceImpl.java`)**: 包含核心的业务逻辑实现。负责编排对一个或多个Mapper方法的调用、组合其他Service提供的服务、执行复杂的业务规则校验、管理事务边界、处理缓存逻辑(读/写)、发送领域事件或消息等。
|
||||
* **禁止**: 直接依赖或处理HTTP相关的对象 (如 `HttpServletRequest`, `HttpServletResponse`);将数据库实体 `Entity` 直接返回给Controller层(应转换为VO)。
|
||||
* **事务管理**: 在实现类的公开业务方法上使用 `@Transactional(rollbackFor = Exception.class)` 注解来声明事务边界,确保数据操作的原子性和一致性。
|
||||
* **Mapper (数据访问层)**:
|
||||
|
||||
* **接口 (`YourMapper.java`)**: 继承自框架提供的 `BaseMapperPlus<Entity, Vo>`,并可以定义针对特定业务需求的自定义数据库操作方法。对于复杂的SQL语句,应在对应的XML映射文件中编写。
|
||||
* **核心职责**: 负责与数据库进行直接交互,执行SQL语句完成数据的持久化(增删改)和检索(查)。
|
||||
* **禁止**: 包含任何业务逻辑代码;被Controller层直接调用。
|
||||
* **数据权限**: 在Mapper接口方法上使用框架提供的 `@DataPermission` 注解来实现声明式的数据权限控制。
|
||||
* **Domain (领域对象层)**:
|
||||
|
||||
* **Entity (`YourEntity.java`)**: 数据库表在Java中的映射对象,用于ORM操作,其字段和结构应与数据表严格对应。
|
||||
* **BO (`YourBo.java`)**: Business Object (业务对象),也常作为DTO (Data Transfer Object)。主要用于Controller层接收前端传入的参数,以及在Service层内部进行数据传递。BO对象是进行参数校验 (JSR 303/380注解) 的主要场所。
|
||||
* **VO (`YourVo.java`)**: View Object (视图对象)。主要用于Service层向Controller层返回处理结果,最终序列化为JSON响应给前端。VO的字段应为前端展示所需,并可能包含经过数据脱敏 (`@Sensitive`)、数据翻译 (`@Translation`) 或其他格式化处理的数据。
|
||||
|
||||
**遵循分层架构的好处**:
|
||||
|
||||
* **高内聚、低耦合**: 各层职责单一且明确,层与层之间通过接口或定义好的数据对象进行交互,降低了代码的耦合度。
|
||||
* **可维护性**: 清晰的结构使得代码更易于理解、修改和维护。问题定位也更加方便。
|
||||
* **可测试性**: 可以针对每一层(尤其是Service层和Mapper层)独立编写单元测试和集成测试。
|
||||
* **可扩展性与复用性**: 更容易在不影响其他层的情况下替换某一层或某一部分的具体实现,业务逻辑也更容易被复用。
|
||||
|
||||
### 4.4 Domain 对象规范 (Entity, BO, VO)
|
||||
|
||||
在RuoYi-Vue-Plus中,合理设计和使用Entity、BO、VO对于保持代码清晰和高效至关重要。
|
||||
|
||||
* **Entity (`YourEntity.java`) - 持久化对象**: (如 `SysUser`)
|
||||
|
||||
* **用途**: 直接映射数据库表,作为MyBatis Plus进行ORM操作的基础。
|
||||
* **规范**:
|
||||
* 使用 `@TableName` 指明对应的数据库表名。
|
||||
* 使用 `@TableId` 标记主键字段,并可指定主键策略 (如雪花算法 `IdType.ASSIGN_ID`)。
|
||||
* 对于逻辑删除字段,使用 `@TableLogic` 注解。
|
||||
* 字段命名采用驼峰式,对应数据库表的下划线命名(MyBatis Plus默认支持此转换)。
|
||||
* 继承框架提供的基类 (如 `TenantEntity` 以支持多租户和通用审计字段,或 `BaseEntity` 仅包含审计字段)。
|
||||
* **不应**包含任何业务逻辑方法 (除了简单的、基于自身属性的判断,如 `SysUser::isSuperAdmin`)。
|
||||
* **不应**包含与数据库持久化无关的临时字段或计算字段。
|
||||
* **BO (`YourBo.java`) - 业务/数据传输对象**: (如 `SysUserBo`)
|
||||
|
||||
* **用途**:
|
||||
1. Controller层接收前端HTTP请求体 (`@RequestBody`) 或查询参数时的数据封装对象。
|
||||
2. Service层方法的形式参数类型,用于传递业务操作所需的数据。
|
||||
3. Service层内部复杂业务逻辑处理时的数据载体。
|
||||
* **规范**:
|
||||
* 字段可以部分来源于Entity,也可以包含Entity中没有的、特定于业务操作的辅助字段(例如,`SysUserBo` 中的 `roleIds`, `postIds` 用于关联操作;`params` Map中的 `beginTime`, `endTime` 用于范围查询)。
|
||||
* **必须**承担参数校验的主要职责,在其字段上使用JSR 303/380标准校验注解 (`@NotBlank`, `@NotNull`, `@Size`, `@Min`, `@Max`, `@Email`, `@Pattern`等) 以及框架自定义的校验注解 (如 `@Xss`)。
|
||||
* 可以使用MapStruct Plus的 `@AutoMapper(target = YourEntity.class)` 注解来辅助与Entity对象之间的属性复制,简化转换代码。
|
||||
* **不应**直接用于数据库的持久化操作(应先转换为对应的Entity对象)。
|
||||
* **不应**包含大量与前端展示强相关的逻辑或格式化字段(这些应在VO中处理)。
|
||||
* **VO (`YourVo.java`) - 视图对象**: (如 `SysUserVo`, `SysUserExportVo`, `SysUserImportVo`)
|
||||
|
||||
* **用途**: Service层处理完业务逻辑后,将结果数据封装成VO对象返回给Controller层,最终序列化为JSON响应给前端。
|
||||
* **规范**:
|
||||
* 字段内容应完全根据前端界面的展示需求来定制。可能只包含Entity中的部分字段,也可能包含通过关联查询、计算或数据翻译得到的额外字段。
|
||||
* 对于敏感信息字段(如用户邮箱、手机号),应使用框架提供的 `@Sensitive` 注解进行数据脱敏处理,并可根据权限 (`perms`)决定是否对特定用户展示完整信息。
|
||||
* 对于需要代码/ID转换的字段(如部门ID转部门名称、用户ID转用户昵称、字典值转字典标签、OSS文件ID转URL),应使用框架提供的 `@Translation` 注解进行自动翻译。
|
||||
* 对于导出Excel的场景,应创建专门的ExportVo (如 `SysUserExportVo`),其字段使用 `@ExcelProperty` 注解定义Excel列标题,并使用 `@ExcelDictFormat` 等注解处理字典转换。
|
||||
* 对于从Excel导入数据的场景,应创建专门的ImportVo (如 `SysUserImportVo`),其字段同样使用 `@ExcelProperty` 匹配Excel列,并可使用 `@ExcelDictFormat` 进行字典值的反向转换。
|
||||
* **绝不应**包含用户的密码等高度敏感且不应在前端展示的原始信息。
|
||||
* 可以使用MapStruct Plus的 `@AutoMapper(target = YourEntity.class)` 注解(通常是反向的,从Entity/BO生成VO)来辅助对象属性的复制。
|
||||
* **不应**用于接收前端的输入参数(这是BO的职责)。
|
||||
* **不应**包含业务校验逻辑。
|
||||
* **命名约定**:
|
||||
|
||||
* Entity: 直接使用领域名词,如 `Product`, `Order`。
|
||||
* BO: 通常以 `...Bo` 为后缀,如 `ProductBo`, `OrderQueryBo`。
|
||||
* VO: 通常以 `...Vo` 为后缀,如 `ProductVo`, `OrderDetailVo`。特定用途的VO可以更具体,如 `ProductExportVo`。
|
||||
* **对象转换**:
|
||||
|
||||
* 推荐使用MapStruct Plus (`@AutoMapper`) 或其他成熟的Java Bean映射工具 (如 `cn.hutool.core.bean.BeanUtil.copyProperties`) 来完成Entity, BO, VO之间的属性复制,以减少手动编写大量getter/setter的模板代码,并提高代码的可读性和可维护性。
|
||||
* 进行对象转换时,需注意深拷贝与浅拷贝的问题,特别是当对象中包含集合或复杂嵌套对象时。对于复杂映射逻辑,MapStruct Plus也支持自定义映射方法。
|
||||
* **序列化**: 所有VO对象(以及可能需要缓存的Entity/BO对象)都应实现 `java.io.Serializable` 接口,这是Java序列化的基本要求,对于对象的缓存、分布式Session共享、RPC传输等场景非常重要。
|
||||
|
||||
## 5. 前端开发最佳实践
|
||||
|
||||
基于对RuoYi-Vue-Plus (Soybean Admin Pro 版本前端) 的分析,本章节将提供前端开发的最佳实践指南,帮助开发人员高效地进行二次开发。
|
||||
|
||||
### 5.1 Cursor辅助前端开发技巧
|
||||
|
||||
在进行RuoYi-Vue-Plus的前端开发时,Cursor可以显著提升开发效率和代码质量。以下是一些关键的辅助开发技巧:
|
||||
|
||||
1. **快速理解项目结构**:
|
||||
|
||||
* 分析项目架构:"分析ruoyi-plus-soybean前端项目的整体架构和核心概念"
|
||||
* 查看技术栈:"列出ruoyi-plus-soybean使用的主要技术栈和库"
|
||||
* 生成目录结构图:"绘制一个ruoyi-plus-soybean前端项目的目录结构图"
|
||||
2. **组件开发与完善**:
|
||||
|
||||
* 生成业务组件:"基于项目规范创建一个产品详情页组件"
|
||||
* 添加表单验证:"为这个产品表单添加完整的字段验证规则"
|
||||
* 实现复杂UI:"实现一个带有拖拽排序功能的产品分类管理组件"
|
||||
* 增强现有组件:"为这个表格组件添加导出Excel功能"
|
||||
3. **前端工具类分析**:
|
||||
|
||||
* 理解工具实现:"解释useTable hook的实现原理和主要功能"
|
||||
* 分析性能:"分析这个表单渲染方法的性能瓶颈"
|
||||
* 学习最佳实践:"项目中API请求封装的最佳实践有哪些?"
|
||||
4. **代码重构与优化**:
|
||||
|
||||
* 优化复杂组件:"重构这个多级表单组件,提高可维护性"
|
||||
* 消除代码气味:"这段TypeScript代码有哪些可以改进的地方?"
|
||||
* 提取公共逻辑:"从这几个组件中提取通用的表单处理逻辑到Hook"
|
||||
5. **排查与修复前端问题**:
|
||||
|
||||
* 分析渲染问题:"为什么这个组件首次加载时显示异常?"
|
||||
* 性能诊断:"为什么这个表格组件滚动卡顿?可能的原因和解决方案"
|
||||
* 解决兼容性问题:"解决这个组件在不同浏览器下的显示差异"
|
||||
|
||||
通过这些技巧,开发者可以充分利用Cursor的AI能力,加快前端开发进度,提升代码质量和用户体验。
|
||||
|
||||
### 5.2 前端项目结构与规范
|
||||
|
||||
RuoYi-Vue-Plus (Soybean Admin Pro 版本) 前端项目采用了Vue 3 + TypeScript + Vite的现代化技术栈,在二次开发时应遵循以下结构与规范:
|
||||
|
||||
* **核心目录结构**:
|
||||
* `src/assets/`: 静态资源文件,如图片、图标和全局样式。
|
||||
* `src/components/`: 通用组件库,包含可复用的UI组件。
|
||||
* `src/hooks/`: Vue 3 Composition API的自定义hooks,封装可复用的逻辑。
|
||||
* `src/layouts/`: 布局组件,定义整体页面结构。
|
||||
* `src/router/`: 路由配置,定义应用的导航结构。
|
||||
* `src/service/api/`: API服务,封装与后端的HTTP通信。
|
||||
* `src/store/`: 使用Pinia的全局状态管理。
|
||||
* `src/views/`: 页面组件,通常按业务模块组织。
|
||||
* `src/typings/api/`: TypeScript类型定义,特别是API相关的类型。
|
||||
|
||||
**使用Cursor进行前端结构分析:**
|
||||
|
||||
* 分析目录结构:"分析ruoyi-plus-soybean前端项目的目录结构和主要职责"
|
||||
* 查看组件组织:"ruoyi-plus-soybean中组件是如何组织和复用的?"
|
||||
* 理解技术集成:"项目中Naive UI是如何与Vue 3集成的?"
|
||||
|
||||
* **前端命名规范**:
|
||||
* **文件命名**: 组件文件采用PascalCase(如`UserForm.vue`),工具/服务类文件采用camelCase(如`userService.ts`)。
|
||||
* **目录命名**: 使用kebab-case(如`user-management/`)。
|
||||
* **组件命名**: 组件名应该始终是多个单词的,使用PascalCase,以避免与HTML元素冲突(如`AppButton`而非`Button`)。
|
||||
* **Props命名**: 在组件内部,props应使用camelCase声明,在模板中使用kebab-case传递(Vue自动转换)。
|
||||
* **事件命名**: 使用kebab-case(如`@item-click`),并优先使用原生事件名。
|
||||
* **代码风格与最佳实践**:
|
||||
* **Vue 3 Composition API**: 优先使用Composition API + `<script setup>`语法,利用其更好的类型推断和逻辑组织能力。
|
||||
* **TypeScript类型定义**: 为props、响应式状态、方法参数和返回值提供明确的类型注解,避免使用`any`。
|
||||
* **状态管理**: 局部状态使用`ref`/`reactive`,共享状态使用Pinia store。
|
||||
* **性能优化**: 合理使用`computed`、`v-memo`和组件懒加载,避免不必要的渲染。
|
||||
* **代码分割**: 路由级别的组件应使用动态导入(`import()`)以实现按需加载。
|
||||
|
||||
### 5.3 前端功能模块开发规范
|
||||
|
||||
在RuoYi-Vue-Plus前端二次开发中,为保持一致性和可维护性,应遵循以下规范:
|
||||
|
||||
* **视图组件设计**:
|
||||
* **页面结构**: 遵循"搜索区 + 操作区 + 表格/表单区"的经典布局。
|
||||
* **组件复用**: 将复杂页面拆分为多个小型组件,提高复用性和可维护性。
|
||||
* **状态提升**: 共享状态应提升到合适的层级,避免prop drilling。
|
||||
* **组件通信**: 使用props和emits进行父子组件通信,复杂场景可使用provide/inject或Pinia。
|
||||
|
||||
* **API服务与数据处理**:
|
||||
* **API封装**: 所有后端API调用应在`src/service/api/`下集中管理,按业务模块组织。
|
||||
* **类型定义**: 请求参数和响应数据都应有明确的TypeScript接口定义,位于`src/typings/api/`下。
|
||||
* **错误处理**: 使用统一的错误处理机制,包括网络错误、业务错误和权限错误。
|
||||
* **数据转换**: 在API层处理数据转换,保证组件接收到的数据格式一致。
|
||||
|
||||
* **路由与权限管理**:
|
||||
* **路由配置**: 新模块路由应定义在`src/router/routes/modules/`下,并设置适当的元信息(如title、icon、权限等)。
|
||||
* **权限控制**: 使用`useAuth`钩子和权限指令控制UI元素的显示/隐藏,路由守卫控制页面访问权限。
|
||||
* **菜单集成**: 确保路由配置中包含正确的meta信息,使其能被自动集成到系统菜单中。
|
||||
|
||||
* **样式与主题**:
|
||||
* **样式隔离**: 组件样式应使用scoped或CSS模块化方案,避免全局污染。
|
||||
* **主题兼容**: 支持深色模式和明亮模式,使用CSS变量实现主题切换。
|
||||
* **响应式设计**: 页面布局应考虑不同设备尺寸,使用响应式设计原则。
|
||||
|
||||
* **国际化**:
|
||||
* **文本外部化**: 所有用户可见文本都应通过`$t`函数从语言包获取,而不是硬编码。
|
||||
* **语言包组织**: 新模块的翻译应添加到`src/locales/langs/`下对应的语言文件中。
|
||||
|
||||
* **状态管理**:
|
||||
* **Pinia Store**: 模块级共享状态应创建独立的Pinia store,位于`src/store/modules/`下。
|
||||
* **状态持久化**: 需要持久化的状态应使用storage插件保存到localStorage或sessionStorage。
|
||||
|
||||
* **异常与加载状态**:
|
||||
* **加载指示**: 使用骨架屏、加载动画等提供视觉反馈。
|
||||
* **空状态处理**: 数据为空时显示友好的空状态提示,而非空白界面。
|
||||
* **错误处理**: 捕获并友好展示操作错误,提供重试机制。
|
||||
|
||||
### 5.4 前端组件开发案例
|
||||
|
||||
让我们以一个典型的"产品管理"模块为例,展示如何使用Cursor辅助开发前端组件:
|
||||
|
||||
1. **创建API服务**:
|
||||
* 请求Cursor:"基于RuoYi-Vue-Plus规范,创建产品管理模块的API服务文件,包含列表查询、详情、新增、修改和删除方法"
|
||||
* 得到productApi.ts文件,包含完整的API调用方法和类型定义。
|
||||
|
||||
2. **创建列表页组件**:
|
||||
* 请求Cursor:"基于Naive UI和项目规范,创建一个产品列表页组件,包含搜索条件、操作按钮和数据表格"
|
||||
* 得到ProductList.vue组件,实现了完整的列表功能。
|
||||
|
||||
3. **创建表单组件**:
|
||||
* 请求Cursor:"创建一个产品表单组件,用于新增和编辑产品信息,包含必要的字段验证"
|
||||
* 得到ProductForm.vue组件,可在对话框或抽屉中使用。
|
||||
|
||||
4. **创建路由配置**:
|
||||
* 请求Cursor:"为产品管理模块创建路由配置,包含列表页和详情页"
|
||||
* 得到product.ts路由配置文件,可整合到系统菜单中。
|
||||
|
||||
5. **状态管理**:
|
||||
* 请求Cursor:"创建产品管理的Pinia store,管理产品列表和筛选条件状态"
|
||||
* 得到useProductStore.ts,实现产品数据的集中管理。
|
||||
|
||||
通过Cursor的辅助,可以快速生成符合项目规范的代码,大幅提高开发效率。
|
||||
|
||||
### 5.5 前后端交互最佳实践
|
||||
|
||||
在RuoYi-Vue-Plus项目中,前后端交互是开发过程中的重要环节,应遵循以下最佳实践:
|
||||
|
||||
* **统一的API调用封装**:
|
||||
* 使用项目提供的封装工具(axios或alova)处理HTTP请求。
|
||||
* 统一处理请求头、认证令牌、响应状态和错误。
|
||||
* 按后端API端点组织前端API服务文件。
|
||||
|
||||
* **请求与响应类型定义**:
|
||||
* 为每个API请求定义明确的参数类型和响应类型。
|
||||
* 与后端BO、VO对象保持一致,确保类型安全。
|
||||
* 在TypeScript接口中添加必要的注释,提高代码可读性。
|
||||
|
||||
* **请求状态管理**:
|
||||
* 使用hooks(如useRequest)管理请求状态,包括loading、error和data。
|
||||
* 实现请求取消、防抖、节流等优化机制。
|
||||
* 合理处理并发请求和请求依赖关系。
|
||||
|
||||
* **数据缓存策略**:
|
||||
* 适当使用客户端缓存减少重复请求,提高用户体验。
|
||||
* 实现数据刷新机制,确保数据及时更新。
|
||||
* 考虑使用service worker或localStorage进行离线数据存储。
|
||||
|
||||
* **错误处理与重试**:
|
||||
* 实现统一的错误处理机制,友好展示错误信息。
|
||||
* 针对网络错误提供自动或手动重试机制。
|
||||
* 区分处理业务错误和技术错误。
|
||||
|
||||
* **表单提交与验证**:
|
||||
* 实现前端表单验证,减轻后端验证压力。
|
||||
* 使用统一的表单提交方法,处理提交状态和响应。
|
||||
* 适当使用防重复提交机制,避免用户误操作。
|
||||
|
||||
* **文件上传与下载**:
|
||||
* 封装统一的文件上传组件,支持进度展示、大文件分片上传等。
|
||||
* 实现文件下载功能,支持直接下载和流式下载。
|
||||
* 处理文件格式验证和大小限制。
|
||||
|
||||
通过遵循这些前后端交互最佳实践,可以确保前端应用与后端API的无缝集成,提高开发效率和用户体验。
|
||||
|
||||
## 6. 前后端协作规范
|
||||
|
||||
为了确保RuoYi-Vue-Plus二次开发过程中前后端协作的高效与顺畅,本章节提供了一套协作规范与最佳实践。
|
||||
|
||||
### 6.1 接口设计与文档规范
|
||||
|
||||
在RuoYi-Vue-Plus项目中,良好的接口设计与规范文档是前后端协作的基础:
|
||||
|
||||
* **RESTful API设计**:
|
||||
* 遵循HTTP方法语义:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)。
|
||||
* 使用资源为中心的URL设计,如`/api/system/user/{userId}`。
|
||||
* 使用HTTP状态码正确表达请求结果(200成功、400客户端错误、500服务器错误等)。
|
||||
* 保持接口的向后兼容性,避免随意修改已发布接口的参数结构。
|
||||
|
||||
* **接口文档规范**:
|
||||
* 使用Swagger/Knife4j生成规范的API文档,包含完整的接口信息、参数说明和响应结构。
|
||||
* 对每个接口添加详细注释,说明功能、使用场景、参数要求和业务规则。
|
||||
* 及时更新接口文档,确保与实际代码保持一致。
|
||||
* 在Swagger注解中明确标识接口的权限要求、是否需要认证等信息。
|
||||
|
||||
* **请求与响应格式**:
|
||||
* 请求参数应符合后端BO对象的结构,必填字段明确标识。
|
||||
* 响应统一使用RuoYi-Vue-Plus的`R<T>`通用返回结构,包含业务状态码、提示信息和数据。
|
||||
* 分页查询接口应返回标准化的分页信息结构,包含总记录数、当前页数据等。
|
||||
* 使用恰当的数据类型,特别是日期时间(ISO 8601格式)和数值类型。
|
||||
|
||||
### 6.2 开发协作流程
|
||||
|
||||
有效的协作流程可以大幅提高前后端团队的开发效率:
|
||||
|
||||
* **接口优先原则**:
|
||||
* 在开发新功能前,先由前后端共同确定API接口规范,包括URL、参数结构、响应格式等。
|
||||
* 后端先完成接口的基本骨架实现并提供Mock数据,前端可以据此并行开发。
|
||||
* 使用Swagger/Knife4j作为接口契约,前后端基于此进行独立开发。
|
||||
|
||||
* **代码评审与联调**:
|
||||
* 实施定期代码评审,确保代码质量和一致性。
|
||||
* 安排专门的联调时间,解决前后端交互问题。
|
||||
* 建立明确的Bug反馈与修复流程,快速响应问题。
|
||||
|
||||
* **使用Cursor辅助协作**:
|
||||
* 利用Cursor分析后端实现:"分析这个用户管理接口的参数结构和响应格式"
|
||||
* 让Cursor生成前端调用代码:"基于这个Swagger文档,生成用户管理模块的API调用代码"
|
||||
* 使用Cursor同步修改:"后端修改了用户查询接口,更新对应的前端API调用和类型定义"
|
||||
|
||||
### 6.3 数据结构一致性
|
||||
|
||||
前后端数据结构的一致性是无缝集成的关键:
|
||||
|
||||
* **类型映射**:
|
||||
* 建立Java与TypeScript类型的明确映射关系,如Java的Long/Integer对应TS的number,String对应string等。
|
||||
* 对于特殊类型(如日期时间、枚举值)定义统一的序列化与反序列化规则。
|
||||
* 前端TypeScript接口应与后端BO/VO对象结构保持一致,字段名称和类型对应。
|
||||
|
||||
* **命名一致性**:
|
||||
* API路径、参数名和响应字段应使用一致的命名风格,推荐使用小驼峰式(camelCase)。
|
||||
* 保持前后端对业务概念的命名一致性,避免同一实体在前后端使用不同名称。
|
||||
* 在代码注释中使用一致的业务术语表述。
|
||||
|
||||
* **数据同步策略**:
|
||||
* 使用Swagger生成的接口文档导出API定义,前端可以基于此自动生成TypeScript类型定义。
|
||||
* 后端接口变更时,及时通知前端并更新对应的类型定义。
|
||||
* 考虑使用代码生成工具自动保持前后端数据结构的同步。
|
||||
|
||||
### 6.4 异常处理与状态码规范
|
||||
|
||||
统一的异常处理机制能够提高系统的可靠性和用户体验:
|
||||
|
||||
* **业务状态码**:
|
||||
* 使用RuoYi-Vue-Plus统一的业务状态码体系,不同类型的错误使用不同的状态码范围。
|
||||
* 确保错误信息清晰明确,便于定位问题。
|
||||
* 对安全敏感的异常,在生产环境隐藏技术细节,仅展示用户友好的错误信息。
|
||||
|
||||
* **前端异常处理**:
|
||||
* 实现统一的错误拦截器,根据后端返回的状态码进行相应处理。
|
||||
* 针对不同类型的错误提供不同的用户界面反馈(如表单验证错误、权限错误、系统错误等)。
|
||||
* 特殊业务错误应有专门的处理逻辑,如令牌过期自动跳转登录页面。
|
||||
|
||||
* **后端异常处理**:
|
||||
* 使用全局异常处理器统一捕获并处理异常,转换为标准的响应格式。
|
||||
* 区分业务异常和系统异常,业务异常应给出清晰的错误原因。
|
||||
* 敏感操作应有完整的日志记录,便于问题追踪。
|
||||
|
||||
## 7. 代码规范与风格
|
||||
|
||||
在RuoYi-Vue-Plus项目的二次开发中,遵循统一的代码规范和风格可以提高代码质量、可维护性和团队协作效率。
|
||||
|
||||
### 7.1 后端代码规范
|
||||
|
||||
* **Java编码风格**:
|
||||
* 遵循阿里巴巴Java开发手册规范,结合RuoYi-Vue-Plus项目的特定约定。
|
||||
* 使用统一的代码格式化配置,如缩进(4个空格)、行宽(最大120字符)等。
|
||||
* 遵循Java类和方法的命名约定:类名使用PascalCase,方法名和变量名使用camelCase。
|
||||
* 方法名应反映其功能和意图,如`getUserById`、`updateUserStatus`等。
|
||||
|
||||
* **代码注释规范**:
|
||||
* 类级注释:每个类都应有Javadoc注释,说明其目的、功能和特殊说明。
|
||||
* 方法注释:公共方法应有完整的Javadoc注释,包括功能描述、参数说明、返回值和异常。
|
||||
* 使用`@author`标注代码的作者,便于追踪责任人。
|
||||
* 对于复杂的业务逻辑或算法,添加详细的行内注释说明。
|
||||
|
||||
* **SQL编写规范**:
|
||||
* 优先使用MyBatis Plus提供的方法,避免手写复杂SQL。
|
||||
* 必要的自定义SQL应放在XML文件中,而非注解中,便于维护和阅读。
|
||||
* SQL关键字使用大写,表名和字段名使用小写,提高可读性。
|
||||
* 编写复杂查询时应考虑性能,合理使用索引,避免全表扫描。
|
||||
|
||||
* **安全编码规范**:
|
||||
* 所有用户输入必须进行校验和过滤,防止XSS、SQL注入等安全漏洞。
|
||||
* 敏感数据(如密码)必须加密存储,并在传输和展示时脱敏处理。
|
||||
* 使用参数化查询,避免直接拼接SQL语句。
|
||||
* 权限检查应在所有敏感操作前执行,确保用户只能访问其有权限的资源。
|
||||
|
||||
### 7.2 前端代码规范
|
||||
|
||||
* **Vue组件风格**:
|
||||
* 使用Vue 3的Composition API和`<script setup>`语法组织代码。
|
||||
* 组件文件采用单文件组件(SFC)格式,即`.vue`文件。
|
||||
* 组件名应使用PascalCase并且至少两个单词(如`UserProfile`而非`User`)。
|
||||
* 组件应遵循单一职责原则,过大的组件应拆分为多个小组件。
|
||||
|
||||
* **TypeScript规范**:
|
||||
* 为所有变量、参数、返回值和属性提供明确的类型注解,避免使用`any`。
|
||||
* 使用接口(Interface)定义复杂数据结构,并添加必要的注释说明。
|
||||
* 遵循TypeScript的命名约定:接口名使用PascalCase并以`I`开头,类型别名使用PascalCase,变量和函数使用camelCase。
|
||||
* 利用TypeScript的类型检查能力,在编译时捕获潜在问题。
|
||||
|
||||
* **CSS/SCSS规范**:
|
||||
* 使用scoped属性或CSS Modules隔离组件样式,避免全局污染。
|
||||
* 遵循BEM(Block-Element-Modifier)或类似的CSS命名规范。
|
||||
* 复用项目提供的颜色变量、主题变量和布局工具,保持视觉一致性。
|
||||
* 编写响应式样式,确保在不同设备上的良好表现。
|
||||
|
||||
* **前端代码优化**:
|
||||
* 组件应实现合理的懒加载,特别是大型页面或组件。
|
||||
* 避免不必要的计算和渲染,合理使用`computed`、`watch`和`v-memo`。
|
||||
* 对用户输入添加防抖/节流处理,避免频繁触发事件或API调用。
|
||||
* 使用Vue DevTools进行性能分析,优化重渲染和内存使用。
|
||||
|
||||
### 7.3 使用Cursor提高代码质量
|
||||
|
||||
Cursor作为AI编辑助手,能显著提高代码质量和开发效率:
|
||||
|
||||
* **代码审查辅助**:
|
||||
* 请求Cursor检查代码质量:"分析这个Service类有哪些可以改进的地方"
|
||||
* 进行安全审查:"检查这段代码是否存在安全漏洞"
|
||||
* 寻找性能优化点:"分析这个查询方法的性能,提供优化建议"
|
||||
|
||||
* **代码规范化**:
|
||||
* 统一编码风格:"根据阿里巴巴Java规范格式化这段代码"
|
||||
* 自动添加注释:"为这个复杂方法添加完整的Javadoc注释"
|
||||
* 类型补全:"为这段TypeScript代码添加完整的类型定义"
|
||||
|
||||
* **重构与优化**:
|
||||
* 提取重复逻辑:"从这几个Controller中提取公共验证逻辑"
|
||||
* 简化复杂方法:"重构这个超过100行的方法,提高可读性"
|
||||
* 优化算法:"这个列表处理算法有更高效的实现方式吗?"
|
||||
|
||||
* **学习与最佳实践**:
|
||||
* 分析设计模式:"这段代码使用了什么设计模式?如何改进?"
|
||||
* 学习项目风格:"分析RuoYi-Vue-Plus项目的错误处理机制和最佳实践"
|
||||
* 理解框架特性:"说明MyBatis Plus的性能优化特性及使用方法"
|
||||
|
||||
通过利用Cursor的这些功能,开发团队可以统一代码风格,提高代码质量,减少常见错误,同时加快开发速度。
|
||||
|
||||
## 8. 新模块添加流程与实践
|
||||
|
||||
本章节将完整展示如何在RuoYi-Vue-Plus中添加全新的业务模块,涵盖从需求分析到最终部署的全流程。
|
||||
|
||||
### 8.1 模块需求分析与设计
|
||||
|
||||
在开始开发前,需要对新模块进行全面的需求分析与架构设计:
|
||||
|
||||
* **需求收集与分析**:
|
||||
* 明确新模块的业务目标、功能范围和用户角色。
|
||||
* 绘制业务流程图,理清核心业务规则和数据流向。
|
||||
* 设计数据模型,确定实体关系和关键属性。
|
||||
* 识别与现有模块的交互点和依赖关系。
|
||||
|
||||
* **使用Cursor辅助设计**:
|
||||
* 业务流程分析:"基于这个需求描述,绘制产品管理模块的业务流程图"
|
||||
* 数据模型设计:"根据产品管理需求,设计数据库表结构和实体关系"
|
||||
* 接口规划:"为产品管理模块设计RESTful API接口清单"
|
||||
* 模块架构:"设计产品管理模块的整体架构和组件结构"
|
||||
|
||||
### 8.2 数据库设计与实现
|
||||
|
||||
良好的数据库设计是模块稳定运行的基础:
|
||||
|
||||
* **表结构设计**:
|
||||
* 遵循RuoYi-Vue-Plus的表命名规范,如业务表使用模块前缀(如`pms_product`)。
|
||||
* 包含标准审计字段(如创建时间、创建者、更新时间、更新者)。
|
||||
* 对于多租户场景,添加租户ID字段。
|
||||
* 设计合理的索引,考虑查询性能和数据完整性。
|
||||
|
||||
* **SQL脚本编写**:
|
||||
* 创建数据表的SQL脚本应放在对应的SQL目录下(如`script/sql/mysql/pms_xxx.sql`)。
|
||||
* 编写初始数据插入脚本,包括必要的字典数据、菜单权限等。
|
||||
* 为升级场景准备增量更新脚本,放在`update`子目录下。
|
||||
* 包含必要的注释,说明表的用途和关键字段。
|
||||
|
||||
* **使用Cursor生成SQL**:
|
||||
* 请求:"根据产品管理模块需求,生成创建产品表和分类表的MySQL SQL脚本"
|
||||
* 请求:"为产品管理模块生成菜单和权限的初始化SQL"
|
||||
* 请求:"根据这个ER图,优化产品表索引设计"
|
||||
|
||||
### 8.3 后端模块开发流程
|
||||
|
||||
后端模块开发应遵循RuoYi-Vue-Plus的分层架构和开发规范:
|
||||
|
||||
1. **创建Maven模块**:
|
||||
* 在`ruoyi-modules`下创建新的子模块(如`ruoyi-pms`)。
|
||||
* 配置pom.xml,添加必要的依赖项。
|
||||
* 创建基础包结构(controller, service, mapper, domain等)。
|
||||
|
||||
2. **使用代码生成器**:
|
||||
* 通过RuoYi-Vue-Plus后台的代码生成功能,基于数据表生成基础CRUD代码。
|
||||
* 根据业务需求调整生成的代码,特别是表单验证规则和业务逻辑。
|
||||
* 生成对应的前端代码,包括API服务、视图组件和路由定义。
|
||||
|
||||
3. **业务逻辑实现**:
|
||||
* 依照分层架构实现业务逻辑,严格遵循前面章节的架构约定。
|
||||
* 实现个性化业务需求,如特殊查询、统计分析、报表导出等。
|
||||
* 添加单元测试,验证关键业务逻辑的正确性。
|
||||
|
||||
4. **接口测试与文档**:
|
||||
* 使用Swagger/Knife4j生成API文档,完善接口描述和参数说明。
|
||||
* 通过Postman或Knife4j界面测试接口功能,验证各种场景下的行为是否符合预期。
|
||||
* 编写接口测试脚本,便于后续持续集成和回归测试。
|
||||
|
||||
### 8.4 前端模块开发流程
|
||||
|
||||
前端模块开发应遵循RuoYi-Vue-Plus (Soybean Admin Pro) 的组件化开发理念:
|
||||
|
||||
1. **准备工作**:
|
||||
* 在前端项目中创建对应的目录结构,如`src/views/your-module/`。
|
||||
* 定义API服务和类型,放在`src/service/api/your-module/`和`src/typings/api/your-module/`。
|
||||
* 创建路由配置,放在`src/router/routes/modules/your-module.ts`。
|
||||
|
||||
2. **组件开发**:
|
||||
* 基于UI设计和业务需求,开发必要的页面组件和功能组件。
|
||||
* 实现数据加载、表单交互、列表展示等核心功能。
|
||||
* 确保所有组件支持国际化和主题切换。
|
||||
|
||||
3. **业务逻辑与状态管理**:
|
||||
* 根据需求实现前端业务逻辑,如数据处理、校验逻辑等。
|
||||
* 针对复杂状态,创建Pinia store进行集中管理。
|
||||
* 抽取通用逻辑到hooks,提高代码复用率。
|
||||
|
||||
4. **测试与优化**:
|
||||
* 在各种浏览器环境下测试功能,确保兼容性。
|
||||
* 进行性能优化,如组件懒加载、虚拟滚动等。
|
||||
* 使用Chrome DevTools分析渲染性能,优化瓶颈。
|
||||
|
||||
### 8.5 集成与部署
|
||||
|
||||
最后,将新模块集成到系统并准备部署:
|
||||
|
||||
1. **前后端集成**:
|
||||
* 进行前后端联调,解决接口对接问题。
|
||||
* 验证数据流转和业务流程的完整性。
|
||||
* 修复发现的Bug并优化用户体验。
|
||||
|
||||
2. **系统集成**:
|
||||
* 在系统菜单中添加新模块入口。
|
||||
* 配置模块权限,确保只有有权限的用户能访问。
|
||||
* 更新全局类型、工具类等共享资源。
|
||||
|
||||
3. **部署准备**:
|
||||
* 更新项目文档,包括README、用户手册等。
|
||||
* 准备部署脚本和配置文件。
|
||||
* 执行最终测试,确认所有功能正常运行。
|
||||
|
||||
4. **持续集成与部署**:
|
||||
* 将代码提交到版本控制系统,并触发CI/CD流程。
|
||||
* 执行自动化测试,验证新模块不会破坏现有功能。
|
||||
* 部署到测试环境或生产环境,并进行监控。
|
||||
|
||||
通过遵循这一完整的开发流程,并充分利用Cursor提供的AI辅助功能,可以高效地将新业务模块集成到RuoYi-Vue-Plus系统中,确保代码质量和功能稳定性。
|
||||
|
||||
## 9. 总结与最佳实践要点
|
||||
|
||||
作为总结,本文档提供了RuoYi-Vue-Plus项目二次开发的全面指南,特别强调了借助Cursor这一现代AI开发工具提升开发效率的方法。以下是关键最佳实践要点:
|
||||
|
||||
1. **架构理解与遵循**:
|
||||
* 深入理解RuoYi-Vue-Plus的分层架构和模块设计。
|
||||
* 严格遵循既定的架构约定,保持代码结构的一致性。
|
||||
* 使用Cursor快速分析和理解现有代码结构,加速学习曲线。
|
||||
|
||||
2. **代码规范与质量**:
|
||||
* 遵循统一的编码规范和风格指南。
|
||||
* 利用Cursor进行代码审查和质量提升。
|
||||
* 注重注释和文档,确保代码可维护性。
|
||||
|
||||
3. **模块设计与实现**:
|
||||
* 新模块应保持职责单一、边界清晰。
|
||||
* 充分利用代码生成器提高开发效率。
|
||||
* 确保前后端接口设计的一致性和规范性。
|
||||
|
||||
4. **前后端协作**:
|
||||
* 建立明确的接口契约和开发流程。
|
||||
* 保持数据结构和命名约定的一致性。
|
||||
* 利用Cursor辅助API调用和类型生成,减少协作成本。
|
||||
|
||||
5. **性能与安全考量**:
|
||||
* 在设计阶段考虑性能和安全因素。
|
||||
* 实施必要的安全措施,如输入验证、权限控制和数据脱敏。
|
||||
* 使用Cursor分析性能瓶颈并提供优化建议。
|
||||
|
||||
6. **测试与部署**:
|
||||
* 为核心业务逻辑编写单元测试。
|
||||
* 实施自动化测试和持续集成。
|
||||
* 建立规范的部署流程和回滚机制。
|
||||
|
||||
通过遵循这些最佳实践,结合Cursor的AI辅助能力,开发团队可以显著提高在RuoYi-Vue-Plus平台上的二次开发效率和代码质量,打造出高性能、高可靠性的企业级应用。
|
840
RuoYi-Vue-Plus项目分析报告.md
Normal file
840
RuoYi-Vue-Plus项目分析报告.md
Normal file
@ -0,0 +1,840 @@
|
||||
# RuoYi-Vue-Plus项目分析报告
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
RuoYi-Vue-Plus是在RuoYi-Vue基础上进行重构升级的开源项目,它专为分布式集群与多租户场景设计,采用插件化和扩展包的结构形式,极大提高了系统的解耦程度和扩展性。项目使用Spring Boot 3.4、JDK 17/21,基于Vue3+TS+ElementPlus重写前端,并使用Undertow替代Tomcat作为Web容器。
|
||||
|
||||
### 1.1 项目特点
|
||||
|
||||
- 采用插件化+扩展包形式,结构解耦,易于扩展
|
||||
- 严格遵守Alibaba规范,代码格式统一
|
||||
- 支持多种数据库(MySQL、Oracle、PostgreSQL、SQLServer等)
|
||||
- 支持多租户架构
|
||||
- 前端使用Vue3+TS+ElementPlus
|
||||
- 底层基于Redisson的分布式能力(分布式锁、限流等)
|
||||
- 使用Sa-Token+JWT实现权限认证
|
||||
- 使用Mybatis-Plus作为ORM框架
|
||||
- 内置丰富的功能模块和实用工具
|
||||
|
||||
## 2. 项目架构
|
||||
|
||||
### 2.1 整体架构
|
||||
|
||||
RuoYi-Vue-Plus采用前后端分离的架构模式:
|
||||
|
||||
- 后端:Spring Boot 3.4 + MyBatis-Plus + Sa-Token
|
||||
- 前端:Vue3 + TypeScript + ElementPlus
|
||||
|
||||
项目部署架构图:
|
||||

|
||||
|
||||
### 2.2 目录结构
|
||||
|
||||
项目主要分为以下几个部分:
|
||||
|
||||
#### 2.2.1 后端结构
|
||||
|
||||
```
|
||||
ruoyi-vue-plus
|
||||
├── ruoyi-admin -- 启动模块,项目入口
|
||||
├── ruoyi-common -- 通用模块,各种功能组件
|
||||
│ ├── ruoyi-common-bom -- 依赖版本管理
|
||||
│ ├── ruoyi-common-core -- 核心功能
|
||||
│ ├── ruoyi-common-doc -- 接口文档
|
||||
│ ├── ruoyi-common-encrypt -- 数据加解密
|
||||
│ ├── ruoyi-common-excel -- Excel处理
|
||||
│ ├── ruoyi-common-idempotent -- 幂等处理
|
||||
│ ├── ruoyi-common-job -- 定时任务
|
||||
│ ├── ruoyi-common-json -- JSON处理
|
||||
│ ├── ruoyi-common-log -- 日志处理
|
||||
│ ├── ruoyi-common-mail -- 邮件处理
|
||||
│ ├── ruoyi-common-mybatis -- ORM配置
|
||||
│ ├── ruoyi-common-oss -- 对象存储
|
||||
│ ├── ruoyi-common-ratelimiter -- 限流处理
|
||||
│ ├── ruoyi-common-redis -- Redis配置
|
||||
│ ├── ruoyi-common-satoken -- 认证鉴权
|
||||
│ ├── ruoyi-common-security -- 安全配置
|
||||
│ ├── ruoyi-common-sensitive -- 数据脱敏
|
||||
│ ├── ruoyi-common-sms -- 短信服务
|
||||
│ ├── ruoyi-common-social -- 社交登录
|
||||
│ ├── ruoyi-common-sse -- SSE推送
|
||||
│ ├── ruoyi-common-tenant -- 多租户
|
||||
│ ├── ruoyi-common-translation -- 数据翻译
|
||||
│ ├── ruoyi-common-web -- Web功能
|
||||
│ └── ruoyi-common-websocket -- WebSocket
|
||||
├── ruoyi-extend -- 扩展模块
|
||||
│ ├── ruoyi-monitor-admin -- 监控管理
|
||||
│ └── ruoyi-snailjob-server -- 任务调度
|
||||
├── ruoyi-modules -- 业务模块
|
||||
│ ├── ruoyi-demo -- 示例模块
|
||||
│ ├── ruoyi-generator -- 代码生成
|
||||
│ ├── ruoyi-job -- 定时任务
|
||||
│ ├── ruoyi-system -- 系统管理
|
||||
│ └── ruoyi-workflow -- 工作流
|
||||
└── ruoyi-plus-soybean -- 前端项目
|
||||
```
|
||||
|
||||
#### 2.2.2 前端结构
|
||||
|
||||
```
|
||||
ruoyi-plus-soybean
|
||||
├── docs -- 文档
|
||||
├── packages -- 功能包
|
||||
├── public -- 静态资源
|
||||
├── src -- 源代码目录
|
||||
│ ├── assets -- 静态资源
|
||||
│ ├── components -- 组件
|
||||
│ ├── constants -- 常量
|
||||
│ ├── enum -- 枚举
|
||||
│ ├── hooks -- 钩子函数
|
||||
│ ├── layouts -- 布局
|
||||
│ ├── locales -- 国际化
|
||||
│ ├── plugins -- 插件
|
||||
│ ├── router -- 路由
|
||||
│ ├── service -- 服务调用
|
||||
│ ├── store -- 状态管理
|
||||
│ ├── styles -- 样式
|
||||
│ ├── theme -- 主题
|
||||
│ ├── typings -- 类型定义
|
||||
│ ├── utils -- 工具类
|
||||
│ ├── views -- 视图
|
||||
│ ├── App.vue -- 主组件
|
||||
│ └── main.ts -- 入口文件
|
||||
```
|
||||
|
||||
## 3. 功能模块
|
||||
|
||||
### 3.1 核心功能
|
||||
|
||||
#### 基础功能
|
||||
- **多租户管理**:支持租户套餐、过期时间、用户数量等管理
|
||||
- **用户权限**:用户、角色、部门、菜单权限管理
|
||||
- **系统监控**:在线用户、操作日志、登录日志
|
||||
- **系统管理**:参数设置、字典管理、附件管理
|
||||
- **系统工具**:代码生成、表单设计、接口文档
|
||||
|
||||
#### 扩展功能
|
||||
- **工作流**:支持复杂审批流程
|
||||
- **在线构建器**:拖拽式表单生成
|
||||
- **定时任务**:任务调度管理
|
||||
- **系统接口**:API文档自动生成
|
||||
- **服务监控**:监控系统资源和性能
|
||||
- **缓存监控**:Redis监控
|
||||
|
||||
### 3.2 技术特性
|
||||
|
||||
- **数据权限**:基于Mybatis-Plus插件的无感式数据权限过滤
|
||||
- **数据脱敏**:支持注解+Jackson方式的数据脱敏
|
||||
- **数据加解密**:支持数据库字段级加解密
|
||||
- **接口加密**:动态AES+RSA加密请求体
|
||||
- **数据翻译**:注解+序列化期间自动翻译
|
||||
- **多数据源**:支持动态数据源配置和切换
|
||||
- **分布式锁**:基于Redisson的分布式锁
|
||||
- **分布式任务调度**:基于SnailJob的分布式任务调度
|
||||
- **文件存储**:支持MinIO和S3协议的对象存储
|
||||
|
||||
## 4. 代码规范
|
||||
|
||||
RuoYi-Vue-Plus项目严格遵循以下规范:
|
||||
|
||||
### 4.1 项目规范
|
||||
|
||||
- 严格遵守Alibaba编码规范
|
||||
- 使用统一的代码格式化配置
|
||||
- 采用插件化结构,功能模块独立封装
|
||||
- 按职责分离不同类型的代码(控制器、服务、实体等)
|
||||
|
||||
### 4.2 编码规范
|
||||
|
||||
#### 后端规范
|
||||
1. **命名规范**:
|
||||
- 类名:大驼峰命名(如:UserController)
|
||||
- 方法名/变量名:小驼峰命名(如:getUserInfo)
|
||||
- 常量:全大写下划线分隔(如:MAX_COUNT)
|
||||
|
||||
2. **包结构**:
|
||||
- controller:控制器
|
||||
- service:服务层
|
||||
- mapper:数据访问层
|
||||
- domain:实体类(entity、vo、bo、dto等)
|
||||
- util:工具类
|
||||
|
||||
3. **注释要求**:
|
||||
- 类注释:说明类的用途
|
||||
- 方法注释:说明方法功能、参数和返回值
|
||||
- 关键代码注释:解释复杂逻辑
|
||||
|
||||
#### 前端规范
|
||||
1. **命名规范**:
|
||||
- 组件名:大驼峰命名(如:UserForm)
|
||||
- 文件名:kebab-case(如:user-form.vue)
|
||||
- 变量/方法:小驼峰命名(如:getUserInfo)
|
||||
|
||||
2. **目录结构**:
|
||||
- components:组件
|
||||
- views:页面
|
||||
- api:接口定义
|
||||
- utils:工具函数
|
||||
- store:状态管理
|
||||
|
||||
3. **编码风格**:
|
||||
- 使用TypeScript进行类型检查
|
||||
- 组件使用组合式API (Composition API)
|
||||
- 样式使用SCSS并遵循BEM规范
|
||||
|
||||
### 4.3 常见代码示例
|
||||
|
||||
#### 4.3.1 控制器规范示例
|
||||
|
||||
```java
|
||||
/**
|
||||
* 用户信息控制器
|
||||
*
|
||||
* @author ruoyi-vue-plus
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/system/user")
|
||||
public class SysUserController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*
|
||||
* @param user 查询参数
|
||||
* @return 用户列表
|
||||
*/
|
||||
@SaCheckPermission("system:user:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<SysUserVo> list(SysUserBo user) {
|
||||
startPage();
|
||||
List<SysUserVo> list = userService.selectUserList(user);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 结果
|
||||
*/
|
||||
@SaCheckPermission("system:user:add")
|
||||
@Log(title = "用户管理", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<Void> add(@Validated @RequestBody SysUserBo user) {
|
||||
return toAjax(userService.insertUser(user));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3.2 服务接口和实现规范示例
|
||||
|
||||
```java
|
||||
/**
|
||||
* 用户服务接口
|
||||
*
|
||||
* @author ruoyi-vue-plus
|
||||
*/
|
||||
public interface ISysUserService {
|
||||
|
||||
/**
|
||||
* 查询用户列表
|
||||
*
|
||||
* @param user 查询参数
|
||||
* @return 用户列表
|
||||
*/
|
||||
List<SysUserVo> selectUserList(SysUserBo user);
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 结果
|
||||
*/
|
||||
int insertUser(SysUserBo user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户服务接口实现
|
||||
*
|
||||
* @author ruoyi-vue-plus
|
||||
*/
|
||||
@Service
|
||||
public class SysUserServiceImpl implements ISysUserService {
|
||||
|
||||
@Autowired
|
||||
private SysUserMapper userMapper;
|
||||
|
||||
/**
|
||||
* 查询用户列表
|
||||
*
|
||||
* @param user 查询参数
|
||||
* @return 用户列表
|
||||
*/
|
||||
@Override
|
||||
public List<SysUserVo> selectUserList(SysUserBo user) {
|
||||
return userMapper.selectUserList(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int insertUser(SysUserBo user) {
|
||||
// 业务逻辑实现
|
||||
return userMapper.insert(user.toEntity());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3.3 实体类规范示例
|
||||
|
||||
```java
|
||||
/**
|
||||
* 用户实体类
|
||||
*
|
||||
* @author ruoyi-vue-plus
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_user")
|
||||
@KeySequence("sys_user_seq")
|
||||
public class SysUser extends TenantEntity {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@TableId(value = "user_id", type = IdType.ASSIGN_ID)
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 用户类型(sys_user系统用户)
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.PHONE)
|
||||
private String phonenumber;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3.4 前端页面示例 (Vue3 + TS)
|
||||
|
||||
```typescript
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索表单 -->
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true">
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
v-hasPermi="['system:user:add']"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" :data="userList">
|
||||
<el-table-column label="用户编号" prop="userId" />
|
||||
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="手机号码" prop="phonenumber" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['system:user:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['system:user:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { listUser, delUser } from '@/api/system/user';
|
||||
|
||||
// 定义数据
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const userList = ref([]);
|
||||
const queryFormRef = ref<any>(null);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userName: undefined,
|
||||
phonenumber: undefined,
|
||||
});
|
||||
|
||||
/** 查询用户列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
listUser(queryParams.value).then(response => {
|
||||
userList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
// 实现新增逻辑
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
// 实现修改逻辑
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
// 实现删除逻辑
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## 5. 二次开发指南
|
||||
|
||||
### 5.1 开发环境准备
|
||||
|
||||
1. **基础环境**:
|
||||
- JDK 17/21
|
||||
- Maven 3.8+
|
||||
- Node.js 16+
|
||||
- MySQL 8.0+/Oracle/PostgreSQL/SQLServer
|
||||
- Redis 5.0+
|
||||
|
||||
2. **IDE推荐**:
|
||||
- IntelliJ IDEA(后端)
|
||||
- VSCode(前端)
|
||||
|
||||
### 5.2 开发规范与原则
|
||||
|
||||
1. **遵循现有架构**:
|
||||
- 保持与现有代码风格一致
|
||||
- 保持模块的独立性和可插拔性
|
||||
- 不随意修改核心模块代码
|
||||
|
||||
2. **扩展而非修改**:
|
||||
- 通过扩展现有组件实现功能
|
||||
- 避免直接修改框架核心代码
|
||||
|
||||
3. **注重代码质量**:
|
||||
- 编写单元测试
|
||||
- 遵循代码规范
|
||||
- 注释完善
|
||||
|
||||
### 5.3 增加PMS模块实施步骤
|
||||
|
||||
以下是添加一个产品管理系统(PMS)模块的具体步骤:
|
||||
|
||||
#### 5.3.1 后端开发
|
||||
|
||||
1. **创建模块结构**:
|
||||
```
|
||||
ruoyi-modules
|
||||
└── ruoyi-pms -- PMS模块
|
||||
├── src/main/java/org/dromara/pms
|
||||
│ ├── controller -- 控制器
|
||||
│ ├── domain -- 实体类
|
||||
│ │ ├── bo -- 业务对象
|
||||
│ │ ├── entity -- 数据库实体
|
||||
│ │ └── vo -- 视图对象
|
||||
│ ├── mapper -- MyBatis接口
|
||||
│ └── service -- 服务实现
|
||||
└── src/main/resources
|
||||
├── mapper -- MyBatis XML
|
||||
└── i18n -- 国际化资源
|
||||
```
|
||||
|
||||
2. **配置模块POM**:
|
||||
- 创建pom.xml,添加必要依赖
|
||||
- 在父模块中添加新模块引用
|
||||
|
||||
3. **创建数据库表**:
|
||||
- 设计表结构
|
||||
- 编写SQL脚本
|
||||
|
||||
4. **开发核心功能**:
|
||||
- 使用代码生成器生成基础CRUD代码
|
||||
- 扩展实现具体业务逻辑
|
||||
- 添加权限控制
|
||||
- 实现多租户和数据权限
|
||||
- 添加接口文档注释
|
||||
|
||||
#### 5.3.2 前端开发
|
||||
|
||||
1. **创建模块目录**:
|
||||
```
|
||||
ruoyi-plus-soybean/src/views/pms
|
||||
├── product -- 产品管理
|
||||
├── category -- 分类管理
|
||||
└── inventory -- 库存管理
|
||||
```
|
||||
|
||||
2. **API接口定义**:
|
||||
```
|
||||
ruoyi-plus-soybean/src/service/api/pms
|
||||
├── product.ts -- 产品API
|
||||
├── category.ts -- 分类API
|
||||
└── inventory.ts -- 库存API
|
||||
```
|
||||
|
||||
3. **配置路由**:
|
||||
- 在router/routes目录下创建pms.ts
|
||||
- 在router/index.ts中导入并注册路由
|
||||
|
||||
4. **开发页面组件**:
|
||||
- 列表页面
|
||||
- 表单页面
|
||||
- 详情页面
|
||||
|
||||
5. **添加权限控制**:
|
||||
- 配置菜单与按钮权限
|
||||
- 实现页面级与按钮级权限控制
|
||||
|
||||
#### 5.3.3 集成与测试
|
||||
|
||||
1. **数据库脚本集成**:
|
||||
- 将建表SQL添加到初始化脚本中
|
||||
|
||||
2. **菜单配置**:
|
||||
- 通过系统管理-菜单管理添加PMS模块菜单
|
||||
- 配置菜单权限
|
||||
|
||||
3. **角色授权**:
|
||||
- 为相关角色分配PMS模块权限
|
||||
|
||||
4. **单元测试**:
|
||||
- 编写API测试
|
||||
- 编写服务层测试
|
||||
|
||||
5. **集成测试**:
|
||||
- 测试完整业务流程
|
||||
- 测试与其他模块的交互
|
||||
|
||||
### 5.4 使用框架核心功能的最佳实践
|
||||
|
||||
#### 5.4.1 使用多租户
|
||||
|
||||
1. **实体类继承TenantEntity**:
|
||||
```java
|
||||
@Data
|
||||
@TableName("pms_product")
|
||||
public class PmsProduct extends TenantEntity {
|
||||
// 实体字段定义
|
||||
}
|
||||
```
|
||||
|
||||
2. **多租户过滤配置**:
|
||||
```java
|
||||
@Configuration
|
||||
public class TenantConfig {
|
||||
/**
|
||||
* 配置需要进行多租户过滤的表
|
||||
*/
|
||||
@Bean
|
||||
public TenantLineHandler tenantLineHandler() {
|
||||
return new PlusTenantLineHandler() {
|
||||
@Override
|
||||
public String getTenantIdColumn() {
|
||||
return "tenant_id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
// 配置不需要过滤的表
|
||||
return ArrayUtil.contains(IGNORE_TENANT_TABLES, tableName);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.4.2 使用数据权限
|
||||
|
||||
1. **在Mapper接口上使用注解**:
|
||||
```java
|
||||
@DataPermission({
|
||||
@DataColumn(key = "deptName", value = "d.dept_id"),
|
||||
@DataColumn(key = "userName", value = "u.user_id")
|
||||
})
|
||||
public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, SysUserVo> {
|
||||
// 方法定义
|
||||
}
|
||||
```
|
||||
|
||||
2. **在查询方法上使用注解**:
|
||||
```java
|
||||
@Override
|
||||
@DataScope(userAlias = "u", deptAlias = "d")
|
||||
public List<SysUserVo> selectUserList(SysUserBo user) {
|
||||
return baseMapper.selectUserList(user);
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.4.3 使用数据脱敏
|
||||
|
||||
```java
|
||||
public class UserVo {
|
||||
|
||||
// 手机号码脱敏
|
||||
@Sensitive(strategy = SensitiveStrategy.PHONE)
|
||||
private String phonenumber;
|
||||
|
||||
// 邮箱脱敏
|
||||
@Sensitive(strategy = SensitiveStrategy.EMAIL)
|
||||
private String email;
|
||||
|
||||
// 身份证脱敏
|
||||
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
|
||||
private String idCard;
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.4.4 使用Excel导入导出
|
||||
|
||||
```java
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class UserImportVo {
|
||||
|
||||
@ExcelProperty(value = "用户编号")
|
||||
private Long userId;
|
||||
|
||||
@ExcelProperty(value = "用户名称")
|
||||
@ExcelRequired
|
||||
private String userName;
|
||||
|
||||
@ExcelProperty(value = "用户昵称")
|
||||
@ExcelRequired
|
||||
private String nickName;
|
||||
|
||||
@ExcelProperty(value = "手机号码")
|
||||
@ExcelRequired
|
||||
private String phonenumber;
|
||||
|
||||
@ExcelProperty(value = "邮箱")
|
||||
private String email;
|
||||
}
|
||||
|
||||
@RestController
|
||||
public class UserController {
|
||||
|
||||
/**
|
||||
* 导出用户
|
||||
*/
|
||||
@SaCheckPermission("system:user:export")
|
||||
@Log(title = "用户管理", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, SysUserBo user) {
|
||||
List<SysUserVo> list = userService.selectUserList(user);
|
||||
ExcelUtil.exportExcel(list, "用户数据", SysUserVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入用户
|
||||
*/
|
||||
@SaCheckPermission("system:user:import")
|
||||
@Log(title = "用户管理", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
|
||||
ExcelResult<UserImportVo> result = ExcelUtil.importExcel(file, UserImportVo.class);
|
||||
userService.importUser(result.getList(), updateSupport);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.4.5 使用接口幂等性控制
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/system/user")
|
||||
public class SysUserController {
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*/
|
||||
@SaCheckPermission("system:user:add")
|
||||
@Log(title = "用户管理", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit(interval = 5000) // 5秒内不允许重复提交
|
||||
@PostMapping
|
||||
public R<Void> add(@Validated @RequestBody SysUserBo user) {
|
||||
return toAjax(userService.insertUser(user));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.4.6 使用Redis缓存
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class SysConfigServiceImpl implements ISysConfigService {
|
||||
|
||||
/**
|
||||
* 查询参数配置信息
|
||||
*
|
||||
* @param configId 参数配置ID
|
||||
* @return 参数配置信息
|
||||
*/
|
||||
@Override
|
||||
@Cacheable(cacheNames = CacheNames.SYS_CONFIG, key = "#configId")
|
||||
public SysConfig selectConfigById(Long configId) {
|
||||
return baseMapper.selectById(configId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增参数配置
|
||||
*
|
||||
* @param config 参数配置信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@CachePut(cacheNames = CacheNames.SYS_CONFIG, key = "#config.configId")
|
||||
public int insertConfig(SysConfig config) {
|
||||
return baseMapper.insert(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除参数配置
|
||||
*
|
||||
* @param configId 参数ID
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = CacheNames.SYS_CONFIG, key = "#configId")
|
||||
public int deleteConfigById(Long configId) {
|
||||
return baseMapper.deleteById(configId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 总结
|
||||
|
||||
RuoYi-Vue-Plus是一个功能完善、架构清晰的企业级应用开发框架,其插件化设计和模块化结构使其非常适合二次开发。在进行二次开发时,应当遵循项目的架构设计和编码规范,通过扩展而非修改的方式实现业务需求,确保系统的可维护性和可扩展性。
|
||||
|
||||
通过合理利用项目提供的代码生成、多租户、权限控制等特性,可以大幅提高开发效率,专注于业务逻辑的实现,而非底层架构的搭建。
|
||||
|
||||
## 7. 常见问题与解决方案
|
||||
|
||||
### 7.1 多租户问题
|
||||
|
||||
**问题**:如何对特定表或操作排除多租户过滤?
|
||||
|
||||
**解决方案**:
|
||||
- 全局排除:在TenantConfig中的ignoreTable方法中添加表名
|
||||
- 局部排除:使用@TenantIgnore注解标注在方法或类上
|
||||
|
||||
```java
|
||||
@TenantIgnore
|
||||
public List<SysTenant> selectTenantList(SysTenant tenant) {
|
||||
return baseMapper.selectList(buildQueryWrapper(tenant));
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 权限问题
|
||||
|
||||
**问题**:如何实现细粒度的数据权限控制?
|
||||
|
||||
**解决方案**:
|
||||
- 使用@DataScope注解并配置用户、部门别名
|
||||
- 在Mapper层使用@DataPermission定义权限字段
|
||||
- 在XML中引用权限过滤片段
|
||||
|
||||
### 7.3 分布式事务问题
|
||||
|
||||
**问题**:如何处理跨服务的分布式事务?
|
||||
|
||||
**解决方案**:
|
||||
- 对于强一致性需求,使用Seata进行分布式事务管理
|
||||
- 对于最终一致性需求,考虑使用本地消息表+定时任务或消息队列实现
|
||||
- 避免长事务,将业务拆分成多个小事务
|
||||
|
||||
### 7.4 性能优化问题
|
||||
|
||||
**问题**:系统运行缓慢,如何优化?
|
||||
|
||||
**解决方案**:
|
||||
- 使用Redis缓存热点数据,减少数据库访问
|
||||
- 优化SQL查询,避免全表扫描
|
||||
- 使用分页查询代替全量查询
|
||||
- 合理使用索引
|
||||
- 利用多级缓存(本地缓存+Redis缓存)
|
||||
- 考虑使用读写分离或分库分表
|
107
docs/GIT_MANUAL.md
Normal file
107
docs/GIT_MANUAL.md
Normal file
@ -0,0 +1,107 @@
|
||||
# RuoYi-Vue-Plus 多仓库 Git 管理手册
|
||||
|
||||
## 目录
|
||||
1. [项目架构说明](#项目架构说明)
|
||||
2. [初始化设置](#初始化设置)
|
||||
3. [日常操作流程](#日常操作流程)
|
||||
4. [冲突处理](#冲突处理)
|
||||
5. [命令速查表](#命令速查表)
|
||||
6. [注意事项](#注意事项)
|
||||
|
||||
## 项目架构说明
|
||||
- **主仓库**:`ruoyi-vue-plus`
|
||||
- 同步源:`https://github.com/dromara/RuoYi-Vue-Plus`
|
||||
- 推送目标:`https://github.com/figo990/RuoYi-Vue-Plus`
|
||||
- **子模块**:`ruoyi-plus-soybean`
|
||||
- 同步源:`https://gitee.com/xlsea/ruoyi-plus-soybean`
|
||||
- 与主仓库一起推送到 `figo990/RuoYi-Vue-Plus`
|
||||
|
||||
## 初始化设置
|
||||
```bash
|
||||
# 1. 克隆主仓库
|
||||
git clone https://github.com/figo990/RuoYi-Vue-Plus.git ruoyi-vue-plus
|
||||
cd ruoyi-vue-plus
|
||||
|
||||
# 2. 添加官方仓库为上游源
|
||||
git remote add upstream https://github.com/dromara/RuoYi-Vue-Plus.git
|
||||
|
||||
# 3. 初始化子模块
|
||||
git submodule add https://gitee.com/xlsea/ruoyi-plus-soybean.git ruoyi-plus-soybean
|
||||
```
|
||||
|
||||
## 日常操作流程
|
||||
### 同步主仓库更新
|
||||
```bash
|
||||
git fetch upstream
|
||||
git merge upstream/main
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 同步子模块更新
|
||||
```bash
|
||||
cd ruoyi-plus-soybean
|
||||
git pull origin main
|
||||
cd ..
|
||||
git add ruoyi-plus-soybean
|
||||
git commit -m "Update submodule"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 提交本地修改
|
||||
```bash
|
||||
# 修改主仓库
|
||||
git add .
|
||||
git commit -m "修改说明"
|
||||
git push origin main
|
||||
|
||||
# 修改子模块
|
||||
cd ruoyi-plus-soybean
|
||||
git add .
|
||||
git commit -m "子模块修改说明"
|
||||
git push origin main
|
||||
cd ..
|
||||
git add ruoyi-plus-soybean
|
||||
git commit -m "更新子模块引用"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## 冲突处理
|
||||
### 主仓库冲突
|
||||
```bash
|
||||
git fetch upstream
|
||||
git merge upstream/main
|
||||
# 手动解决冲突后
|
||||
git add .
|
||||
git commit -m "解决冲突"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 子模块冲突
|
||||
```bash
|
||||
cd ruoyi-plus-soybean
|
||||
git pull origin main
|
||||
# 手动解决冲突后
|
||||
git add .
|
||||
git commit -m "解决子模块冲突"
|
||||
git push origin main
|
||||
cd ..
|
||||
git add ruoyi-plus-soybean
|
||||
git commit -m "更新子模块引用"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## 命令速查表
|
||||
| 操作 | 命令 |
|
||||
| -------------- | ----------------------------------------------------------------------------------- |
|
||||
| 拉取官方更新 | `git fetch upstream && git merge upstream/main` |
|
||||
| 推送主仓库修改 | `git add . && git commit -m "msg" && git push origin main` |
|
||||
| 更新子模块 | `cd ruoyi-plus-soybean && git pull origin main` |
|
||||
| 提交子模块修改 | `cd ruoyi-plus-soybean && git add . && git commit -m "msg" && git push origin main` |
|
||||
|
||||
## 注意事项
|
||||
1. 首次克隆后需初始化子模块:
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
2. 子模块修改需单独提交到其仓库
|
||||
3. 保持主仓库和子模块分支一致(推荐使用`main`分支)
|
754
docs/basic/PMS数据模型.md
Normal file
754
docs/basic/PMS数据模型.md
Normal file
@ -0,0 +1,754 @@
|
||||
## 云宿居 PMS 系统 - PMS核心数据模型 (最终版 v_ry_final_4.0)
|
||||
|
||||
**快速导航:**
|
||||
- [1. 引言](#1-引言)
|
||||
- [2. PMS 核心E-R图 (最终版 - 简化)](#2-pms-核心er图-最终版---简化)
|
||||
- [3. 表结构定义](#3-表结构定义)
|
||||
- [3.1 SaaS平台与系统基础表 (源自若依)](#31-saas平台与系统基础表-源自若依)
|
||||
- [3.2 PMS核心业务表](#32-pms核心业务表)
|
||||
- [3.3 公共数据模型 (PMS相关)](#33-公共数据模型-pms相关)
|
||||
- [4. 主要业务实体状态流转](#4-主要业务实体状态流转)
|
||||
- [5. 核心业务枚举值定义 (PMS主要部分)](#5-核心业务枚举值定义-pms主要部分)
|
||||
- [6. 索引命名与规范建议](#6-索引命名与规范建议)
|
||||
|
||||
## 1. 引言
|
||||
|
||||
### 1.1 文档目的与范围
|
||||
本文档详细定义了"云宿居"民宿 Property Management System (PMS) 的数据库逻辑模型,并与其依赖的若依框架的基础表(如租户、用户、部门等)进行了整合。本文档旨在为数据库的物理实现和后端开发提供具体指导。
|
||||
|
||||
**核心原则:**
|
||||
1. 若依框架的基础实体(用户、租户、角色、部门等)及其字段规范固定不变。
|
||||
2. PMS新增的核心业务表主键统一采用 `BIGINT` 自增ID。
|
||||
3. **PMS业务表只关联部门(`dept_id`),不直接关联租户(`tenant_id`)。部门代表实际的门店/分店,通过部门可以查询到其所属租户信息。** 这使得"一个租户下有多个门店"的业务模型更加清晰,同时减少数据冗余。
|
||||
4. **特例:`pms_tenant_settings`和`pms_mp_settings`表保留tenant_id字段,通过dept_id为NULL来表示租户全局设置,通过具体的dept_id值表示部门/门店特定设置。**
|
||||
5. PMS业务表中的枚举字段,统一使用 `VARCHAR(50)` 存储描述性字符串以增强可读性和扩展性。这与若依常用的 `CHAR(1)` 枚举在交互时需注意API层面的适配。
|
||||
|
||||
### 1.2 命名与设计约定 (PMS新增表部分)
|
||||
* **表命名:** `pms_` (PMS核心业务表),`cmn_` (公共模块)。若依表使用其原生 `sys_` 前缀。
|
||||
* **字段命名:** `snake_case` (小写下划线)。
|
||||
* **主键 (PK):**
|
||||
* PMS核心业务实体: **`BIGINT` AUTO_INCREMENT**。
|
||||
* 日志类高频写入表: `BIGINT` AUTO_INCREMENT。
|
||||
* **部门ID `dept_id`:** `bigint(20)` (同 `sys_dept.dept_id`),在PMS业务表中非空,用于标识数据归属于哪个门店。
|
||||
* **外键 (FK):** 类型与关联表主键一致。`COMMENT '关联 table_name.column_name'`。
|
||||
* **通用审计字段 (对齐若依):**
|
||||
* `create_by` (BIGINT(20) NULLABLE, COMMENT '创建者,关联 sys_user.user_id')
|
||||
* `create_time` (DATETIME NULLABLE, COMMENT '创建时间')
|
||||
* `update_by` (BIGINT(20) NULLABLE, COMMENT '更新者,关联 sys_user.user_id')
|
||||
* `update_time` (DATETIME NULLABLE, COMMENT '更新时间')
|
||||
* `create_dept_id` (BIGINT(20) NULLABLE, COMMENT '创建记录的操作员所属部门,关联 sys_dept.dept_id')
|
||||
* **软删除策略 (对齐若依):**
|
||||
* `del_flag` (CHAR(1) DEFAULT '0' NOT NULL, COMMENT '删除标志(0代表存在 1代表删除)')。
|
||||
* **状态与枚举字段 (PMS优化):**
|
||||
* PMS业务表中的枚举字段,统一使用 `VARCHAR(50)` 存储描述性枚举字符串。
|
||||
|
||||
## 2. PMS 核心E-R图 (最终版 - 简化)
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
%% --- 若依基础表 (关键部分) ---
|
||||
sys_tenant {
|
||||
varchar(20) tenant_id PK "租户编号 (业务主键)"
|
||||
bigint id "物理主键, 若依内部使用"
|
||||
varchar(255) company_name "企业/民宿名称"
|
||||
}
|
||||
|
||||
sys_dept {
|
||||
bigint dept_id PK "部门ID"
|
||||
varchar(20) tenant_id FK "关联 sys_tenant.tenant_id"
|
||||
varchar(30) dept_name "部门名称"
|
||||
}
|
||||
|
||||
sys_user {
|
||||
bigint user_id PK "用户ID (PMS员工)"
|
||||
varchar(20) tenant_id FK "用户初始归属租户"
|
||||
bigint dept_id FK "用户所属部门"
|
||||
varchar(30) user_name "用户名, 全局唯一"
|
||||
}
|
||||
|
||||
sys_role {
|
||||
bigint role_id PK "角色ID"
|
||||
varchar(20) tenant_id FK "角色定义所属租户 (或平台)"
|
||||
varchar(30) role_name "角色名称"
|
||||
}
|
||||
|
||||
%% --- Common Contact Table ---
|
||||
cmn_contacts {
|
||||
BIGINT contact_id PK "联系人ID (自增)"
|
||||
BIGINT dept_id FK "所属部门ID"
|
||||
VARCHAR(255) full_name "姓名"
|
||||
}
|
||||
|
||||
%% --- PMS Core Business Tables (主键改为BIGINT) ---
|
||||
pms_room_types {
|
||||
BIGINT room_type_id PK "房型ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
VARCHAR(255) name "房型名称"
|
||||
}
|
||||
|
||||
pms_room_rooms {
|
||||
BIGINT room_id PK "房间ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
BIGINT room_type_id FK "房型ID"
|
||||
VARCHAR(255) room_number "房间号"
|
||||
VARCHAR(50) room_status "物理状态"
|
||||
VARCHAR(50) cleaning_status "清洁状态"
|
||||
}
|
||||
|
||||
pms_room_locks {
|
||||
BIGINT lock_id PK "锁定ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
BIGINT room_id FK "房间ID"
|
||||
}
|
||||
|
||||
pms_core_orders {
|
||||
BIGINT order_id PK "订单ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
BIGINT contact_id FK "联系人ID"
|
||||
BIGINT room_type_id FK "房型ID"
|
||||
BIGINT pms_room_id FK "房间ID(可空)"
|
||||
VARCHAR(50) order_status "订单状态"
|
||||
VARCHAR(50) order_source "订单来源"
|
||||
}
|
||||
|
||||
pms_core_order_items {
|
||||
BIGINT order_item_id PK "订单项ID (自增)"
|
||||
BIGINT order_id FK "订单ID"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
VARCHAR(50) product_type "产品类型"
|
||||
}
|
||||
|
||||
pms_core_channels {
|
||||
BIGINT channel_id PK "渠道ID (自增)"
|
||||
BIGINT dept_id FK "部门ID(可空)"
|
||||
VARCHAR(50) channel_type "渠道类型"
|
||||
}
|
||||
|
||||
pms_finance_folios {
|
||||
BIGINT folio_id PK "账单ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
BIGINT order_id FK "订单ID(UNIQUE)"
|
||||
VARCHAR(50) folio_status "账单状态"
|
||||
}
|
||||
|
||||
pms_finance_transactions {
|
||||
BIGINT transaction_id PK "交易ID (自增)"
|
||||
BIGINT folio_id FK "账单ID"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
VARCHAR(50) transaction_type "交易类型"
|
||||
}
|
||||
|
||||
pms_finance_payment_methods {
|
||||
BIGINT payment_method_id PK "支付方式ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
VARCHAR(50) method_type "支付方式类型"
|
||||
}
|
||||
|
||||
pms_finance_extra_charge_items {
|
||||
BIGINT item_id PK "附加费用项ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
}
|
||||
|
||||
pms_room_pricing_rules {
|
||||
BIGINT rule_id PK "价格规则ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
BIGINT room_type_id FK "房型ID(可空)"
|
||||
}
|
||||
|
||||
pms_tenant_settings {
|
||||
BIGINT setting_id PK "设置ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
VARCHAR(100) setting_group "设置分组"
|
||||
VARCHAR(100) setting_key "设置键名"
|
||||
}
|
||||
|
||||
pms_mp_settings {
|
||||
BIGINT setting_id PK "小程序设置ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
VARCHAR(100) setting_key "设置键名"
|
||||
}
|
||||
|
||||
pms_tenant_user_devices {
|
||||
BIGINT id PK "设备记录ID (自增)"
|
||||
BIGINT user_id FK "用户ID"
|
||||
}
|
||||
|
||||
cmn_contact_tags {
|
||||
BIGINT tag_id PK "标签ID (自增)"
|
||||
BIGINT dept_id FK "部门ID"
|
||||
}
|
||||
|
||||
cmn_contact_tag_relations {
|
||||
BIGINT relation_id PK "关联ID (自增)"
|
||||
BIGINT contact_id FK "联系人ID"
|
||||
BIGINT tag_id FK "标签ID"
|
||||
}
|
||||
|
||||
%% --- 若依基础表关联 ---
|
||||
sys_tenant ||--o{ sys_dept : "包含门店/部门"
|
||||
sys_tenant ||--o{ sys_user : "的主要租户"
|
||||
sys_tenant ||--o{ sys_role : "定义角色归属"
|
||||
|
||||
sys_dept ||--o{ sys_user : "员工属于门店"
|
||||
|
||||
%% --- PMS 表关联到若依基础表 ---
|
||||
sys_dept ||--o{ pms_room_types : "定义房型"
|
||||
sys_dept ||--o{ pms_room_rooms : "拥有房间"
|
||||
sys_dept ||--o{ pms_core_orders : "拥有订单"
|
||||
sys_dept ||--o{ cmn_contacts : "拥有联系人"
|
||||
sys_dept ||--o{ pms_tenant_settings : "拥有PMS配置"
|
||||
sys_dept ||--o{ pms_mp_settings : "拥有小程序配置"
|
||||
sys_dept ||--o{ cmn_contact_tags : "定义联系人标签"
|
||||
|
||||
sys_user ||--o{ pms_tenant_user_devices : "注册设备"
|
||||
|
||||
%% --- PMS 内部表关联 ---
|
||||
pms_room_types ||--o{ pms_room_rooms : "有具体房间"
|
||||
pms_room_types ||--o{ pms_core_orders : "被预订类型"
|
||||
pms_room_types o|--o{ pms_room_pricing_rules : "规则适用房型"
|
||||
|
||||
pms_room_rooms ||--o{ pms_room_locks : "可被锁定"
|
||||
pms_room_rooms o|--o{ pms_core_orders : "被分配房间"
|
||||
pms_room_rooms o|--o{ pms_core_order_items : "关联房晚项目"
|
||||
|
||||
pms_core_orders ||--o{ pms_core_order_items : "包含项目"
|
||||
pms_core_orders ||--|| pms_finance_folios : "生成账单"
|
||||
cmn_contacts o|--o{ pms_core_orders : "是主要联系人"
|
||||
pms_core_channels o|--o{ pms_core_orders : "是来源渠道"
|
||||
|
||||
pms_finance_folios ||--o{ pms_finance_transactions : "记录交易"
|
||||
pms_finance_payment_methods o|--o{ pms_finance_transactions : "使用支付方式"
|
||||
pms_finance_extra_charge_items o|--o{ pms_core_order_items : "可以是附加项"
|
||||
|
||||
cmn_contacts ||--o{ cmn_contact_tag_relations : "关联标签"
|
||||
cmn_contact_tags ||--o{ cmn_contact_tag_relations : "被应用于"
|
||||
```
|
||||
*注:E-R图中部分表的字段为简化表示,详细字段请参照下文。*
|
||||
|
||||
## 3. 表结构定义
|
||||
|
||||
### 3.1 SaaS平台与系统基础表 (源自若依)
|
||||
这些表结构直接采用或参考附件 `ry_vue_5.X - 副本.txt` 中的定义,关键表包括:
|
||||
* `sys_tenant`: 租户表 (业务主键 `tenant_id varchar(20)`)
|
||||
* `sys_dept`: 部门表 (主键 `dept_id bigint(20)`),在本系统中将作为门店/分店使用
|
||||
* `sys_user`: 用户信息表 (主键 `user_id bigint(20)`)
|
||||
* `sys_role`: 角色信息表 (主键 `role_id bigint(20)`)
|
||||
* `sys_user_role`: 用户角色关联表
|
||||
* `sys_config`: 参数配置表
|
||||
* `sys_logininfor`: 系统访问记录(登录日志)
|
||||
* `sys_dict_type`, `sys_dict_data`: 字典表 (PMS枚举值优先在PMS表中使用 `VARCHAR(50)`,但若依系统本身的字典可按其规范使用)
|
||||
|
||||
**PMS系统将直接使用或关联这些若依表中的记录,例如PMS订单的 `dept_id` 将关联到 `sys_dept.dept_id`,`create_by` 将关联到 `sys_user.user_id`。部门进一步通过 `sys_dept.tenant_id` 关联到租户。**
|
||||
|
||||
### 3.2 PMS核心业务表
|
||||
|
||||
#### 3.2.1 `pms_room_types` - 房型表
|
||||
* 业务描述: 存储各部门(门店)定义的房型信息。
|
||||
* 主键: `room_type_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `room_type_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '房型唯一ID'): 主键。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID(门店), 关联 sys_dept.dept_id')。
|
||||
* `name` (VARCHAR(255), NOT NULL, COMMENT '房型名称 (例如: 豪华大床房)')。
|
||||
* `default_price` (DECIMAL(10,2), NOT NULL, COMMENT '房型默认价格 (每晚)')。
|
||||
* `capacity` (INT, NOT NULL, COMMENT '标准入住人数')。
|
||||
* `amenities` (JSON, NULLABLE, COMMENT '房型设施标签。JSON数组,例如 [\"wifi\", \"空调\"]')。
|
||||
* `description` (TEXT, NULLABLE, COMMENT '房型描述')。
|
||||
* `images_json` (JSON, NULLABLE, COMMENT '房型图片。JSON数组')。
|
||||
* `sort_order` (INT, DEFAULT 0, COMMENT '显示排序值')。
|
||||
* `status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '状态。枚举: active (启用), inactive (禁用)')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_rt_dept_name` (dept_id, name), `idx_pms_rt_dept_status` (dept_id, status)
|
||||
|
||||
#### 3.2.2 `pms_room_rooms` - 房间表
|
||||
* 业务描述: 存储各部门(门店)的具体房间信息。
|
||||
* 主键: `room_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `room_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '房间唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID(门店), 关联 sys_dept.dept_id')。
|
||||
* `room_type_id` (BIGINT, FK, NOT NULL, COMMENT '所属房型ID, 关联 pms_room_types.room_type_id')。
|
||||
* `room_number` (VARCHAR(50), NOT NULL, COMMENT '房间号 (在门店内应唯一)')。
|
||||
* `floor` (VARCHAR(50), NULLABLE, COMMENT '楼层')。
|
||||
* `room_status` (VARCHAR(50), NOT NULL, DEFAULT 'available', COMMENT '物理状态。枚举: available (可用), occupied (占用中), maintenance (维护中), out_of_service (停用服务)')。
|
||||
* `cleaning_status` (VARCHAR(50), NOT NULL, DEFAULT 'clean', COMMENT '清洁状态。枚举: clean (已清洁), dirty (待清洁), cleaning_in_progress (清洁中), inspected (已查房)')。
|
||||
* `description` (TEXT, NULLABLE, COMMENT '房间描述或备注')。
|
||||
* `status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '记录状态。枚举: active (启用), inactive (禁用)')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 唯一约束: `unq_pms_r_dept_room_number` (dept_id, room_number, del_flag)
|
||||
* 索引: `idx_pms_r_dept_rt` (dept_id, room_type_id), `idx_pms_r_dept_room_status` (dept_id, room_status), `idx_pms_r_dept_cleaning_status` (dept_id, cleaning_status)
|
||||
|
||||
#### 3.2.3 `pms_room_locks` - 房间锁定记录表
|
||||
* 业务描述: 记录房间因维护、手动操作等原因被锁定的情况。
|
||||
* 主键: `lock_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `lock_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '锁定记录唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID, 关联 sys_dept.dept_id')。
|
||||
* `room_id` (BIGINT, FK, NOT NULL, COMMENT '被锁定的房间ID, 关联 pms_room_rooms.room_id')。
|
||||
* `start_datetime` (DATETIME, NOT NULL, COMMENT '锁定开始时间')。
|
||||
* `end_datetime` (DATETIME, NOT NULL, COMMENT '锁定结束时间')。
|
||||
* `reason` (TEXT, NULLABLE, COMMENT '锁定原因')。
|
||||
* `lock_type` (VARCHAR(50), NOT NULL, DEFAULT 'manual_lock', COMMENT '锁定类型。枚举: manual_lock (手动锁定), maintenance (维护), auto_block (自动锁定), staff_use (员工自用)')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_rl_tenant_dept_room_time` (tenant_id, dept_id, room_id, start_datetime, end_datetime), `idx_pms_rl_tenant_dept_lock_type` (tenant_id, dept_id, lock_type)
|
||||
|
||||
#### 3.2.4 `pms_core_orders` - 核心订单表
|
||||
* 业务描述: 存储所有来源的预订订单核心信息。
|
||||
* 主键: `order_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `order_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '订单唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID, 关联 sys_dept.dept_id')。
|
||||
* `contact_id` (BIGINT, FK, NULLABLE, COMMENT '主要联系人ID, 关联 cmn_contacts.contact_id')。
|
||||
* `primary_contact_name` (VARCHAR(100), NOT NULL, COMMENT '主要联系人姓名 (冗余)')。
|
||||
* `primary_contact_phone` (VARCHAR(50), NOT NULL, COMMENT '主要联系人电话 (冗余)')。
|
||||
* `pms_room_id` (BIGINT, FK, NULLABLE, COMMENT '分配的房间ID, 关联 pms_room_rooms.room_id, 入住时分配')。
|
||||
* `room_type_id` (BIGINT, FK, NOT NULL, COMMENT '预订的房型ID, 关联 pms_room_types.room_type_id')。
|
||||
* `channel_id` (BIGINT, FK, NULLABLE, COMMENT '订单来源渠道ID, 关联 pms_core_channels.channel_id')。
|
||||
* `check_in_date` (DATE, NOT NULL, COMMENT '计划入住日期')。
|
||||
* `check_out_date` (DATE, NOT NULL, COMMENT '计划离店日期')。
|
||||
* `num_adults` (INT, NOT NULL, DEFAULT 1, COMMENT '成人数')。
|
||||
* `num_children` (INT, DEFAULT 0, COMMENT '儿童数')。
|
||||
* `estimated_arrival_time` (TIME, NULLABLE, COMMENT '预计抵达时间')。
|
||||
* `total_amount` (DECIMAL(10,2), NOT NULL, COMMENT '订单总金额')。
|
||||
* `paid_amount` (DECIMAL(10,2), DEFAULT 0.00, COMMENT '已付金额')。
|
||||
* `due_amount` (DECIMAL(10,2), AS (`total_amount` - `paid_amount`)) STORED COMMENT '应付金额 (计算列)'。
|
||||
* `currency` (VARCHAR(3), NOT NULL, DEFAULT 'CNY', COMMENT '货币代码')。
|
||||
* `order_status` (VARCHAR(50), NOT NULL, COMMENT '订单状态。枚举: pending_confirmation, confirmed, checked_in, checked_out, cancelled, no_show, extended, waitlist')。
|
||||
* `order_source` (VARCHAR(50), NOT NULL, COMMENT '订单来源。枚举: direct_walk_in, direct_phone, direct_website, direct_mall_h5, ota_channel_manager, travel_agency, corporate, other')。
|
||||
* `notes` (TEXT, NULLABLE, COMMENT '订单备注')。
|
||||
* `cancelled_at` (DATETIME, NULLABLE, COMMENT '取消时间')。
|
||||
* `cancellation_reason` (TEXT, NULLABLE, COMMENT '取消原因')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_o_tenant_dept_status_dates` (tenant_id, dept_id, order_status, check_in_date, check_out_date), `idx_pms_o_tenant_dept_contact_phone` (tenant_id, dept_id, primary_contact_phone), `idx_pms_o_tenant_dept_source` (tenant_id, dept_id, order_source)
|
||||
|
||||
#### 3.2.5 `pms_core_order_items` - 核心订单项目表
|
||||
* 业务描述: 记录订单中包含的具体商品或服务项目。
|
||||
* 主键: `order_item_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `order_item_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '订单项目唯一ID')。
|
||||
* `order_id` (BIGINT, FK, NOT NULL, COMMENT '所属订单ID, 关联 pms_core_orders.order_id')。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID, 关联 sys_dept.dept_id (冗余)')。
|
||||
* `pms_room_id` (BIGINT, FK, NULLABLE, COMMENT '关联房间ID (如房晚项目), 关联 pms_room_rooms.room_id')。
|
||||
* `product_id` (BIGINT, FK, NULLABLE, COMMENT '关联产品ID (多态), 如 pms_finance_extra_charge_items.item_id')。
|
||||
* `product_type` (VARCHAR(50), NOT NULL, COMMENT '产品类型。枚举: room_night (房晚), extra_charge_item (附加收费项目), package_component (套餐子项), service_fee (服务费), discount_adjustment (折扣调整)')。
|
||||
* `description` (VARCHAR(255), NOT NULL, COMMENT '项目描述 (例如: 豪华大床房住宿, 额外早餐)')。
|
||||
* `quantity` (INT, NOT NULL, DEFAULT 1, COMMENT '数量')。
|
||||
* `unit_price` (DECIMAL(10,2), NOT NULL, COMMENT '单价')。
|
||||
* `total_price` (DECIMAL(10,2), AS (`quantity` * `unit_price`)) STORED COMMENT '总价 (计算列)'。
|
||||
* `service_date` (DATE, NULLABLE, COMMENT '服务发生日期 (例如房晚对应的日期)')。
|
||||
* `notes` (TEXT, NULLABLE, COMMENT '项目备注')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_oi_order_id` (order_id), `idx_pms_oi_tenant_dept_product_type` (tenant_id, dept_id, product_type)
|
||||
|
||||
#### 3.2.6 `pms_core_channels` - 订单来源渠道表
|
||||
* 业务描述: 管理订单的来源渠道。
|
||||
* 主键: `channel_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `channel_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '渠道唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, 若渠道归属特定部门')。
|
||||
* `name` (VARCHAR(100), NOT NULL, COMMENT '渠道名称 (例如: 携程, 官网直订)')。
|
||||
* `channel_type` (VARCHAR(50), NOT NULL, COMMENT '渠道类型。枚举: ota, direct_booking (官网/电话/前台), gds (全球分销系统), wholesaler (批发商), corporate (公司协议), internal_use (内部使用), other')。
|
||||
* `description` (TEXT, NULLABLE, COMMENT '渠道描述')。
|
||||
* `status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '状态。枚举: active (激活), inactive (停用), deprecated (弃用)')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_ch_tenant_dept_name` (tenant_id, dept_id, name), `idx_pms_ch_type` (channel_type), `idx_pms_ch_status` (status)
|
||||
|
||||
#### 3.2.7 `pms_finance_folios` - 客户账单(Folio)表
|
||||
* 业务描述: 记录与订单关联的客户账单核心信息。
|
||||
* 主键: `folio_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `folio_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '账单唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID, 关联 sys_dept.dept_id')。
|
||||
* `order_id` (BIGINT, UNIQUE, FK, NOT NULL, COMMENT '关联的订单ID, 关联 pms_core_orders.order_id')。
|
||||
* `total_charges` (DECIMAL(10,2), NOT NULL, DEFAULT 0.00, COMMENT '总应收费用')。
|
||||
* `total_payments` (DECIMAL(10,2), NOT NULL, DEFAULT 0.00, COMMENT '总已收付款')。
|
||||
* `total_refunds` (DECIMAL(10,2), NOT NULL, DEFAULT 0.00, COMMENT '总已退款')。
|
||||
* `balance` (DECIMAL(10,2), AS (`total_charges` - `total_payments` + `total_refunds`)) STORED COMMENT '当前余额 (计算列)'。
|
||||
* `folio_status` (VARCHAR(50), NOT NULL, DEFAULT 'open', COMMENT '账单状态。枚举: open (开放/未结清), closed (已结清/关闭), void (作废), pending_settlement (待结算)')。
|
||||
* `notes` (TEXT, NULLABLE, COMMENT '账单备注')。
|
||||
* `closed_at` (DATETIME, NULLABLE, COMMENT '账单关闭时间')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_f_tenant_dept_order_id` (tenant_id, dept_id, order_id), `idx_pms_f_tenant_dept_status` (tenant_id, dept_id, folio_status)
|
||||
|
||||
#### 3.2.8 `pms_finance_transactions` - 财务交易流水表
|
||||
* 业务描述: 记录在Folio中的每一笔财务交易。
|
||||
* 主键: `transaction_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `transaction_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '交易唯一ID')。
|
||||
* `folio_id` (BIGINT, FK, NOT NULL, COMMENT '所属账单ID, 关联 pms_finance_folios.folio_id')。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID, 关联 sys_dept.dept_id (冗余)')。
|
||||
* `transaction_type` (VARCHAR(50), NOT NULL, COMMENT '交易类型。枚举: charge (应收费用), payment (客人付款), refund (退款给客人), deposit (押金), deposit_refund (押金退还), adjustment_positive (正调整), adjustment_negative (负调整)')。
|
||||
* `amount` (DECIMAL(10,2), NOT NULL, COMMENT '交易金额')。
|
||||
* `description` (VARCHAR(255), NOT NULL, COMMENT '交易描述')。
|
||||
* `payment_method_id` (BIGINT, FK, NULLABLE, COMMENT '支付方式ID, 关联 pms_finance_payment_methods.payment_method_id')。
|
||||
* `payment_gateway_txn_id` (VARCHAR(255), NULLABLE, COMMENT '支付网关交易号')。
|
||||
* `transaction_time` (DATETIME, NOT NULL, COMMENT '交易实际发生时间')。
|
||||
* `related_order_item_id` (BIGINT, FK, NULLABLE, COMMENT '关联的订单项ID, 关联 pms_core_order_items.order_item_id')。
|
||||
* `notes` (TEXT, NULLABLE, COMMENT '交易备注')。
|
||||
* `is_void` (BOOLEAN, NOT NULL, DEFAULT FALSE, COMMENT '是否已作废冲销')。
|
||||
* `voided_at` (DATETIME, NULLABLE, COMMENT '作废时间')。
|
||||
* `voided_reason` (TEXT, NULLABLE, COMMENT '作废原因')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_ft_folio_id` (folio_id), `idx_pms_ft_tenant_dept_type_time` (tenant_id, dept_id, transaction_type, transaction_time), `idx_pms_ft_payment_gateway_txn_id` (payment_gateway_txn_id)
|
||||
|
||||
#### 3.2.9 `pms_finance_payment_methods` - 支付方式表
|
||||
* 业务描述: 存储租户可定义的支付方式。
|
||||
* 主键: `payment_method_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `payment_method_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '支付方式唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, 若支付方式归属特定部门')。
|
||||
* `name` (VARCHAR(100), NOT NULL, COMMENT '支付方式名称 (例如: 现金, 微信支付)')。
|
||||
* `method_type` (VARCHAR(50), NOT NULL, COMMENT '支付方式类型。枚举: cash, credit_card_visa, credit_card_mastercard, credit_card_amex, alipay, wechat_pay, bank_transfer, company_account, voucher, points, other_online_payment, mobile_payment, other')。
|
||||
* `details_json` (JSON, NULLABLE, COMMENT '支付方式附加配置。例如支付网关参数等')。
|
||||
* `status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '状态。枚举: active (激活), inactive (停用)')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_fpm_tenant_dept_name` (tenant_id, dept_id, name), `idx_pms_fpm_tenant_dept_type` (tenant_id, dept_id, method_type), `idx_pms_fpm_tenant_dept_status` (tenant_id, dept_id, status)
|
||||
|
||||
#### 3.2.10 `pms_finance_extra_charge_items` - 附加费用项目表
|
||||
* 业务描述: 存储租户可预定义的附加消费项目,如额外早餐、加床费等。
|
||||
* 主键: `item_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `item_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '附加费用项目唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, 若项目归属特定部门')。
|
||||
* `name` (VARCHAR(255), NOT NULL, COMMENT '项目名称')。
|
||||
* `default_price` (DECIMAL(10,2), NOT NULL, COMMENT '默认单价')。
|
||||
* `category` (VARCHAR(100), NULLABLE, COMMENT '费用类别,如餐饮、服务、商品等')。
|
||||
* `is_taxable` (BOOLEAN, NOT NULL, DEFAULT TRUE, COMMENT '是否应税')。
|
||||
* `description` (TEXT, NULLABLE, COMMENT '项目描述')。
|
||||
* `status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '状态。枚举: active (激活), inactive (停用)')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_feci_tenant_dept_name` (tenant_id, dept_id, name), `idx_pms_feci_tenant_dept_category` (tenant_id, dept_id, category), `idx_pms_feci_tenant_dept_status` (tenant_id, dept_id, status)
|
||||
|
||||
#### 3.2.11 `pms_room_pricing_rules` - 房间价格规则表
|
||||
* 业务描述: 为特定房型在特定日期范围或特定条件下设置价格调整规则。
|
||||
* 主键: `rule_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `rule_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '价格规则唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, 若规则归属特定部门')。
|
||||
* `name` (VARCHAR(255), NOT NULL, COMMENT '规则名称 (例如: 周末特价, 连住优惠)')。
|
||||
* `room_type_id` (BIGINT, FK, NULLABLE, COMMENT '适用的房型ID, 关联 pms_room_types.room_type_id, NULL表示适用于所有房型')。
|
||||
* `date_range_start` (DATE, NULLABLE, COMMENT '规则适用开始日期')。
|
||||
* `date_range_end` (DATE, NULLABLE, COMMENT '规则适用结束日期')。
|
||||
* `days_of_week_json` (JSON, NULLABLE, COMMENT 'JSON数组,数字代表星期几, e.g., [1,2,7] (1-Mon, 7-Sun)')。
|
||||
* `min_length_of_stay` (INT, NULLABLE, COMMENT '最小入住天数要求')。
|
||||
* `max_length_of_stay` (INT, NULLABLE, COMMENT '最大入住天数限制')。
|
||||
* `price_adjustment_type` (VARCHAR(50), NOT NULL, COMMENT '调整类型。枚举: fixed_amount_override (固定价格覆盖), percentage_discount_from_base (基价百分比折扣), fixed_amount_increase_on_base (基价固定金额上浮), fixed_amount_decrease_from_base (基价固定金额下调), set_to_value (设置为特定值)')。
|
||||
* `adjustment_value` (DECIMAL(10,2), NOT NULL, COMMENT '调整值 (具体金额或百分比,如50.00或0.1代表10%)')。
|
||||
* `priority` (INT, NOT NULL, DEFAULT 0, COMMENT '规则优先级,数字越大优先级越高')。
|
||||
* `status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '状态。枚举: active (激活), inactive (停用), scheduled (计划中)')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 索引: `idx_pms_rpr_tenant_dept_dates` (tenant_id, dept_id, date_range_start, date_range_end), `idx_pms_rpr_tenant_dept_status_priority` (tenant_id, dept_id, status, priority)
|
||||
|
||||
#### 3.2.12 `pms_tenant_user_devices` - 租户用户设备表
|
||||
* 业务描述: 用于存储租户用户(员工)登录过的设备信息,主要用于推送通知。
|
||||
* 主键: `id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '设备记录唯一ID')。
|
||||
* `user_id` (BIGINT(20), FK, NOT NULL, COMMENT '关联的用户ID, 关联 sys_user.user_id')。
|
||||
* `device_type` (VARCHAR(50), NOT NULL, COMMENT '设备类型,例如: ios_app, android_app, wechat_miniprogram, web_push_browser')。
|
||||
* `device_token` (VARCHAR(512), NOT NULL, COMMENT '设备推送令牌')。
|
||||
* `app_version` (VARCHAR(50), NULLABLE, COMMENT '客户端应用版本')。
|
||||
* `last_login_at` (DATETIME, NOT NULL, COMMENT '此设备最后登录时间')。
|
||||
* `status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '状态。枚举: active (激活), inactive (停用), token_expired (令牌过期)')。
|
||||
* `create_time` (DATETIME, NOT NULL, COMMENT '记录创建时间')。
|
||||
* `update_time` (DATETIME, NOT NULL, COMMENT '记录更新时间')。
|
||||
* 唯一约束: `unq_pms_ud_user_device_token_type` (user_id, device_token, device_type)
|
||||
* 索引: `idx_pms_ud_tenant_user_token` (tenant_id, user_id, device_token), `idx_pms_ud_user_status` (user_id, status)
|
||||
|
||||
#### 3.2.13 `pms_tenant_settings` - 租户级PMS特定设置表
|
||||
* 业务描述: 存储租户或部门级别的PMS特定业务配置项。与若依的 `sys_config` 互补。
|
||||
* 主键: `setting_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `setting_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '设置唯一ID')。
|
||||
* `tenant_id` (VARCHAR(20), NOT NULL, COMMENT '租户编号, 关联 sys_tenant.tenant_id')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, NULL表示租户全局PMS设置')。
|
||||
* `setting_group` (VARCHAR(100), NOT NULL, COMMENT '设置分组,例如: pms_booking_rules, pms_financial_params, pms_ui_appearance')。
|
||||
* `setting_key` (VARCHAR(100), NOT NULL, COMMENT '设置项的唯一键 (在tenant_id, dept_id, setting_group下唯一)')。
|
||||
* `setting_value` (TEXT, NOT NULL, COMMENT '设置项的值 (根据需要可存储JSON字符串)')。
|
||||
* `value_type` (VARCHAR(50), NOT NULL, DEFAULT 'string', COMMENT '值类型,如 string, integer, boolean, json,方便解析')。
|
||||
* `description` (TEXT, NULLABLE, COMMENT '设置项描述')。
|
||||
* `is_sensitive` (BOOLEAN, NOT NULL, DEFAULT FALSE, COMMENT '是否为敏感设置')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 唯一约束: `unq_pms_ts_tenant_dept_group_key` (tenant_id, dept_id, setting_group, setting_key, del_flag)
|
||||
* 索引: `idx_pms_ts_tenant_dept_group_key` (tenant_id, dept_id, setting_group, setting_key)
|
||||
|
||||
#### 3.2.14 `pms_mp_settings` - 民宿管理小程序配置表
|
||||
* 业务描述: 存储租户或部门级别针对民宿员工管理小程序的特定配置项。
|
||||
* 主键: `setting_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `setting_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '小程序设置唯一ID')。
|
||||
* `tenant_id` (VARCHAR(20), NOT NULL, COMMENT '租户编号, 关联 sys_tenant.tenant_id')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, NULL表示租户全局小程序设置')。
|
||||
* `setting_key` (VARCHAR(100), NOT NULL, COMMENT '小程序配置项的唯一键。例如: mp_theme_color, mp_enable_feature_xyz')。
|
||||
* `setting_value` (TEXT, NOT NULL, COMMENT '配置项的值 (可存储JSON字符串)')。
|
||||
* `value_type` (VARCHAR(50), NOT NULL, DEFAULT 'string', COMMENT '值类型,如 string, integer, boolean, json')。
|
||||
* `description` (TEXT, NULLABLE, COMMENT '配置项描述')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 唯一约束: `unq_pms_mps_tenant_dept_key` (tenant_id, dept_id, setting_key, del_flag)
|
||||
* 索引: `idx_pms_mps_tenant_dept_key` (tenant_id, dept_id, setting_key)
|
||||
|
||||
### 3.3 公共数据模型 (PMS相关)
|
||||
|
||||
#### 3.3.1 `cmn_contacts` - 联系人表
|
||||
* 业务描述: 存储系统中所有类型的联系人信息,主要是PMS的住客。
|
||||
* 主键: `contact_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `contact_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '联系人唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, 若联系人主要归属某部门')。
|
||||
* `contact_type` (VARCHAR(50), NOT NULL, COMMENT '联系人类型。枚举: guest_individual (散客), guest_group_contact (团队联系人), corporate_contact (公司协议联系人), travel_agent_contact (旅行社联系人), company_profile (公司档案), supplier_contact (供应商联系人), employee_profile (员工档案关联), other (其他)')。
|
||||
* `full_name` (VARCHAR(255), NULLABLE, COMMENT '姓名')。
|
||||
* `phone_number` (VARCHAR(50), NULLABLE, COMMENT '主要联系电话')。
|
||||
* `email` (VARCHAR(255), NULLABLE, COMMENT '电子邮件地址')。
|
||||
* `wechat_openid` (VARCHAR(100), NULLABLE, COMMENT '微信OpenID (特定于单个微信公众号或小程序)')。
|
||||
* `wechat_unionid` (VARCHAR(100), NULLABLE, COMMENT '微信UnionID (跨多个微信应用的用户唯一标识)')。
|
||||
* `related_user_id` (BIGINT(20), FK, NULLABLE, COMMENT '关联的系统用户ID, 关联 sys_user.user_id, 如员工档案关联')。
|
||||
* `gender` (VARCHAR(20), NULLABLE, COMMENT '性别。枚举: male (男), female (女), non_binary (非二元), prefer_not_to_say (不愿透露), unknown (未知)')。
|
||||
* `date_of_birth` (DATE, NULLABLE, COMMENT '出生日期')。
|
||||
* `id_type` (VARCHAR(50), NULLABLE, COMMENT '证件类型,如身份证、护照等。可关联sys_dict_data')。
|
||||
* `id_number_encrypted` (VARCHAR(255), NULLABLE, COMMENT '证件号码 (加密存储)')。
|
||||
* `nationality_country_code` (VARCHAR(10), NULLABLE, COMMENT '国籍代码 (ISO 3166-1 alpha-2)')。
|
||||
* `preferred_language` (VARCHAR(10), NULLABLE, COMMENT '偏好语言代码 (e.g., en, zh-CN)')。
|
||||
* `address_street` (VARCHAR(255), NULLABLE, COMMENT '街道地址')。
|
||||
* `address_city` (VARCHAR(100), NULLABLE, COMMENT '城市')。
|
||||
* `address_state_province` (VARCHAR(100), NULLABLE, COMMENT '省/州')。
|
||||
* `address_postal_code` (VARCHAR(50), NULLABLE, COMMENT '邮政编码')。
|
||||
* `address_country_code` (VARCHAR(10), NULLABLE, COMMENT '国家代码 (ISO 3166-1 alpha-2)')。
|
||||
* `contact_status` (VARCHAR(50), NOT NULL, DEFAULT 'active', COMMENT '联系人状态。枚举: active (活跃), inactive (不活跃), prospect (潜在客户), blacklisted (黑名单), pending_verification (待验证), merged_duplicate (已合并重复)')。
|
||||
* `remarks` (TEXT, NULLABLE, COMMENT '备注信息')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 唯一约束 (建议,考虑 `del_flag`):
|
||||
* `unq_cmn_c_tenant_dept_phone` (tenant_id, dept_id, phone_number, del_flag) (如果电话在部门内唯一)
|
||||
* `unq_cmn_c_tenant_dept_email` (tenant_id, dept_id, email, del_flag) (如果邮箱在部门内唯一)
|
||||
* 索引: `idx_cmn_c_tenant_dept_fname` (tenant_id, dept_id, full_name), `idx_cmn_c_tenant_dept_status_type` (tenant_id, dept_id, contact_status, contact_type), `idx_cmn_c_related_user` (related_user_id)
|
||||
|
||||
#### 3.3.2 `cmn_contact_tags` - 联系人标签表
|
||||
* 业务描述: 定义可用于分类和标记联系人的标签。
|
||||
* 主键: `tag_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `tag_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '标签唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NULLABLE, COMMENT '部门ID, 关联 sys_dept.dept_id, 若标签归属特定部门或全局')。
|
||||
* `name` (VARCHAR(100), NOT NULL, COMMENT '标签名称')。
|
||||
* `color` (VARCHAR(20), NULLABLE, COMMENT '标签显示颜色,如十六进制色值 #FF5733')。
|
||||
* `category` (VARCHAR(50), NULLABLE, COMMENT '标签分类,如兴趣、客户级别、来源等')。
|
||||
* `description` (TEXT, NULLABLE, COMMENT '标签描述')。
|
||||
* `is_system` (BOOLEAN, NOT NULL, DEFAULT FALSE, COMMENT '是否为系统预设标签 (不可删除)')。
|
||||
* `sort_order` (INT, NOT NULL, DEFAULT 0, COMMENT '排序值')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NULLABLE, COMMENT '创建时间')。
|
||||
* `update_by` (BIGINT(20), NULLABLE, COMMENT '更新者ID, 关联 sys_user.user_id')。
|
||||
* `update_time` (DATETIME, NULLABLE, COMMENT '更新时间')。
|
||||
* `create_dept_id` (BIGINT(20), NULLABLE, COMMENT '创建部门ID, 关联 sys_dept.dept_id')。
|
||||
* `del_flag` (CHAR(1), NOT NULL, DEFAULT '0', COMMENT '删除标志(0存在 1删除)')。
|
||||
* 唯一约束: `unq_cmn_ct_tenant_dept_name_category` (tenant_id, dept_id, name, category, del_flag)
|
||||
* 索引: `idx_cmn_ct_category` (category), `idx_cmn_ct_is_system` (is_system)
|
||||
|
||||
#### 3.3.3 `cmn_contact_tag_relations` - 联系人标签关联表
|
||||
* 业务描述: 实现联系人与标签的多对多关系。
|
||||
* 主键: `relation_id` (BIGINT AUTO_INCREMENT)
|
||||
* 字段定义:
|
||||
* `relation_id` (BIGINT, PK, AUTO_INCREMENT, NOT NULL, COMMENT '关联唯一ID')。
|
||||
* `dept_id` (BIGINT(20), NOT NULL, COMMENT '部门ID, 关联 sys_dept.dept_id (冗余, 以联系人所属部门为准)')。
|
||||
* `contact_id` (BIGINT, FK, NOT NULL, COMMENT '联系人ID, 关联 cmn_contacts.contact_id')。
|
||||
* `tag_id` (BIGINT, FK, NOT NULL, COMMENT '标签ID, 关联 cmn_contact_tags.tag_id')。
|
||||
* `create_by` (BIGINT(20), NULLABLE, COMMENT '创建者ID, 关联 sys_user.user_id')。
|
||||
* `create_time` (DATETIME, NOT NULL, COMMENT '创建时间')。
|
||||
* 唯一约束: `unq_cmn_ctr_tenant_dept_contact_tag` (tenant_id, dept_id, contact_id, tag_id)
|
||||
* 索引: `idx_cmn_ctr_contact_id` (contact_id), `idx_cmn_ctr_tag_id` (tag_id)
|
||||
|
||||
## 4. 主要业务实体状态流转
|
||||
|
||||
#### 4.1 订单 (`pms_core_orders`) 状态流转
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> pending_confirmation: 创建订单
|
||||
pending_confirmation --> confirmed: 确认订单
|
||||
pending_confirmation --> cancelled: 取消订单
|
||||
confirmed --> checked_in: 办理入住
|
||||
confirmed --> cancelled: 取消订单
|
||||
confirmed --> no_show: 未按时入住
|
||||
checked_in --> checked_out: 办理退房
|
||||
checked_in --> extended: 延长入住
|
||||
extended --> checked_out: 办理退房
|
||||
checked_out --> [*] : 订单完成
|
||||
cancelled --> [*] : 订单取消
|
||||
no_show --> [*] : 订单标记为No Show
|
||||
```
|
||||
* **订单状态 (`order_status` VARCHAR(50)) 枚举值示例:**
|
||||
* `pending_confirmation` (待确认)
|
||||
* `confirmed` (已确认)
|
||||
* `checked_in` (已入住)
|
||||
* `checked_out` (已退房)
|
||||
* `cancelled` (已取消)
|
||||
* `no_show` (未到店)
|
||||
* `extended` (已延期)
|
||||
* `waitlist` (等候名单)
|
||||
|
||||
#### 4.2 房间 (`pms_room_rooms`) 状态流转 (物理状态 & 清洁状态)
|
||||
|
||||
**物理状态 (`room_status` VARCHAR(50))**
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> available: 房间空闲可用
|
||||
available --> occupied: 客人入住 / 预留占用
|
||||
available --> maintenance: 安排维护 / 维修
|
||||
available --> out_of_service: 暂停服务 (其他原因)
|
||||
occupied --> available: 客人退房 (且房间已清洁完成)
|
||||
maintenance --> available: 维护完成 (房间恢复可用)
|
||||
out_of_service --> available: 恢复服务
|
||||
```
|
||||
* **物理状态枚举值示例:**
|
||||
* `available` (可用)
|
||||
* `occupied` (占用中)
|
||||
* `maintenance` (维护中)
|
||||
* `out_of_service` (暂停服务)
|
||||
|
||||
**清洁状态 (`cleaning_status` VARCHAR(50))**
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> clean: 初始状态 / 清洁完成
|
||||
clean --> dirty: 客人使用后 / 退房后标记
|
||||
dirty --> cleaning_in_progress: 客房服务员开始清洁
|
||||
cleaning_in_progress --> inspected: 清洁完成待查房
|
||||
inspected --> clean: 查房通过
|
||||
inspected --> cleaning_in_progress: 查房未通过,返工
|
||||
clean --> cleaning_in_progress: 客中打扫 (客人仍在住,但安排了打扫)
|
||||
```
|
||||
* **清洁状态枚举值示例:**
|
||||
* `clean` (已清洁)
|
||||
* `dirty` (待清洁)
|
||||
* `cleaning_in_progress` (清洁中)
|
||||
* `inspected` (已查房/待检)
|
||||
|
||||
#### 4.3 账单 (`pms_finance_folios`) 状态流转
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> open: 订单创建时自动生成 / 手动创建
|
||||
open --> closed: 账务结清 (所有费用付清)
|
||||
open --> void: 作废账单 (如错误创建)
|
||||
open --> pending_settlement: 等待结算 (如对公转账)
|
||||
pending_settlement --> closed: 结算完成
|
||||
pending_settlement --> open: 取消结算,返回开放
|
||||
closed --> open: 重新打开 (特殊情况,需权限,如补充入账或退款)
|
||||
void --> [*] : 账单作废
|
||||
closed --> [*] : 账单关闭
|
||||
```
|
||||
* **账单状态 (`folio_status` VARCHAR(50)) 枚举值示例:**
|
||||
* `open` (开放/未结清)
|
||||
* `closed` (已结清/关闭)
|
||||
* `void` (作废)
|
||||
* `pending_settlement` (待结算)
|
||||
|
||||
## 5. 核心业务枚举值定义 (PMS主要部分)
|
||||
|
||||
以下列出PMS核心业务表中 `VARCHAR(50)` 类型枚举字段的建议值,具体值可根据业务细化调整。
|
||||
|
||||
* **`pms_room_types.status`**: `active` (启用), `inactive` (禁用)
|
||||
* **`pms_room_rooms.room_status`**: `available` (可用), `occupied` (占用中), `maintenance` (维护中), `out_of_service` (暂停服务)
|
||||
* **`pms_room_rooms.cleaning_status`**: `clean` (已清洁), `dirty` (待清洁), `cleaning_in_progress` (清洁中), `inspected` (已查房/待检)
|
||||
* **`pms_room_rooms.status` (记录状态)**: `active` (启用), `inactive` (禁用)
|
||||
* **`pms_room_locks.lock_type`**: `manual_lock` (手动锁定), `maintenance` (维护), `auto_block` (自动锁定,如欠费), `staff_use` (员工自用), `cleaning_block` (清洁锁定)
|
||||
* **`pms_core_orders.order_status`**: `pending_confirmation` (待确认), `confirmed` (已确认), `checked_in` (已入住), `checked_out` (已退房), `cancelled` (已取消), `no_show` (未到店), `extended` (已延期), `waitlist` (等候名单), `provisional` (暂定预订)
|
||||
* **`pms_core_orders.order_source`**: `direct_walk_in` (现场步入), `direct_phone` (电话预订), `direct_website` (官网预订), `direct_mall_h5` (H5商城), `ota_channel_manager` (OTA渠道管理), `travel_agency` (旅行社), `corporate_account` (公司协议), `internal_booking` (内部预订), `other` (其他)
|
||||
* **`pms_core_order_items.product_type`**: `room_night` (房晚), `extra_charge_item` (附加收费项目), `package_component` (套餐子项), `service_fee` (服务费), `discount_adjustment` (折扣调整), `cancellation_fee` (取消费)
|
||||
* **`pms_core_channels.channel_type`**: `ota` (在线旅行社), `direct_booking` (直接预订-官网/电话/前台), `gds` (全球分销系统), `wholesaler` (批发商), `corporate` (公司协议), `internal_use` (内部使用), `meta_search` (元搜索), `other` (其他)
|
||||
* **`pms_core_channels.status`**: `active` (激活), `inactive` (停用), `deprecated` (弃用)
|
||||
* **`pms_finance_folios.folio_status`**: `open` (开放/未结清), `closed` (已结清/关闭), `void` (作废), `pending_settlement` (待结算)
|
||||
* **`pms_finance_transactions.transaction_type`**: `charge` (应收费用), `payment` (客人付款), `refund` (退款给客人), `deposit` (押金收款), `deposit_refund` (押金退还), `adjustment_positive` (正调整), `adjustment_negative` (负调整), `folio_transfer_out` (转出), `folio_transfer_in` (转入)
|
||||
* **`pms_finance_payment_methods.method_type`**: `cash` (现金), `credit_card_visa` (VISA卡), `credit_card_mastercard` (万事达卡), `credit_card_amex` (运通卡), `credit_card_unionpay` (银联卡), `alipay` (支付宝), `wechat_pay` (微信支付), `bank_transfer` (银行转账), `company_account` (公司账户/挂账), `voucher` (代金券), `points_redemption` (积分兑换), `other_online_payment` (其他在线支付), `mobile_payment` (其他移动支付), `other` (其他)
|
||||
* **`pms_finance_payment_methods.status`**: `active` (激活), `inactive` (停用)
|
||||
* **`pms_finance_extra_charge_items.status`**: `active` (激活), `inactive` (停用)
|
||||
* **`pms_room_pricing_rules.price_adjustment_type`**: `fixed_amount_override` (固定价格覆盖), `percentage_discount_from_base` (基价百分比折扣), `fixed_amount_increase_on_base` (基价固定金额上浮), `fixed_amount_decrease_from_base` (基价固定金额下调), `set_to_value` (设置为特定值)
|
||||
* **`pms_room_pricing_rules.status`**: `active` (激活), `inactive` (停用), `scheduled` (计划中/待生效)
|
||||
* **`pms_tenant_user_devices.status`**: `active` (激活), `inactive` (停用), `token_expired` (令牌过期)
|
||||
* **`cmn_contacts.contact_type`**: `guest_individual` (散客), `guest_group_contact` (团队联系人), `corporate_contact` (公司协议联系人), `travel_agent_contact` (旅行社联系人), `company_profile` (公司档案), `supplier_contact` (供应商联系人), `employee_profile` (员工档案关联), `other` (其他)
|
||||
* **`cmn_contacts.gender`**: `male` (男), `female` (女), `non_binary` (非二元性别), `prefer_not_to_say` (不愿透露), `unknown` (未知)
|
||||
* **`cmn_contacts.contact_status`**: `active` (活跃), `inactive` (不活跃), `prospect` (潜在客户), `blacklisted` (黑名单), `pending_verification` (待验证), `merged_duplicate` (已合并重复档案)
|
||||
|
||||
## 6. 索引命名与规范建议
|
||||
|
||||
* **唯一索引:** `unq_[子系统前缀]_[表名简写]_[字段1]_[字段2]`,例如:`unq_pms_r_tdrn` 代表 `pms_room_rooms` 表上 `tenant_id, dept_id, room_number` 的唯一索引。
|
||||
* **普通索引:** `idx_[子系统前缀]_[表名简写]_[字段1]_[字段2]`,例如:`idx_pms_o_tds` 代表 `pms_core_orders` 表上 `tenant_id, dept_id, order_status` 的普通索引。
|
||||
* **租户和部门优先:** 对于所有包含 `tenant_id` 和 `dept_id` 的PMS业务表,查询相关的索引应优先考虑这两个字段,顺序通常为 `(tenant_id, dept_id, ...其他条件字段)`。
|
||||
* **覆盖索引:** 尽量设计覆盖索引以减少回表,提高查询性能。
|
||||
* **避免过多索引:** 每个表的索引数量不宜过多,以免影响写入性能。
|
||||
|
||||
---
|
||||
**(文档末尾)**
|
||||
|
||||
**重要提示:**
|
||||
* 本文档中的主键类型已统一为 `BIGINT AUTO_INCREMENT` (用于PMS新增业务表) 或遵循若依原有的 `BIGINT` (用于若依系统表)。
|
||||
* PMS业务表中的枚举字段统一为 `VARCHAR(50)`,这与若依的 `CHAR(1)` 不同,在系统集成和前端交互时,可能需要在API层或逻辑层进行适配和转换。
|
||||
* 所有PMS业务表已添加 `dept_id` 字段,用于数据归属和统计。确保所有相关查询和业务逻辑都考虑到 `dept_id`。
|
||||
* 审计字段和软删除标记已尽量对齐若依规范。
|
||||
|
||||
请仔细审查此最终方案,确保其满足所有业务需求和技术约束。
|
328
docs/basic/PMS需求.md
Normal file
328
docs/basic/PMS需求.md
Normal file
@ -0,0 +1,328 @@
|
||||
# 云宿居 PMS 系统 - 核心功能需求 (PMS内部聚焦)
|
||||
|
||||
## 1. 引言
|
||||
|
||||
### 1.1 文档目的与范围
|
||||
|
||||
本文档定义了"云宿居"民宿 Property Management System (PMS) 的核心功能需求,**重点聚焦于PMS租户核心业务子系统(以下简称PMS核心模块)的内部功能**。它旨在明确该子系统的核心功能范围和关键业务流程。
|
||||
|
||||
本文档是PMS核心模块后续详细设计和开发工作的基础。
|
||||
|
||||
### 1.2 目标读者
|
||||
|
||||
产品经理、架构师、开发工程师、测试工程师。
|
||||
|
||||
### 1.4 术语表
|
||||
|
||||
| 术语 | 全称 | 描述 |
|
||||
| ------- | -------------------------- | ------------------------------------------------------------- |
|
||||
| PMS | Property Management System | 民宿/酒店属性管理系统,管理房间、预订、联系人、账务等核心业务 |
|
||||
| Folio | Folio | 客户账单,用于记录联系人在店期间所有费用和付款的详细清单 |
|
||||
| Tenant | Tenant | 租户,指使用本SaaS化PMS系统的单个民宿运营方 |
|
||||
| Dept | Department | 部门,在本系统中表示租户下的门店/分店 |
|
||||
| Contact | Contact | 联系人,指系统中统一管理的客户信息,包括预订人、实际入住人等 |
|
||||
|
||||
### 1.5 优先级定义
|
||||
|
||||
* **[P0]**: MVP 阶段必须实现的核心功能。
|
||||
* **[P1]**: 重要功能,MVP 后第一个主要版本包含。
|
||||
* **[P2]**: 次要或完善性功能,后续迭代实现。
|
||||
* **[P3]**: 未来规划或可选功能。
|
||||
|
||||
## 2. 核心设计原则 (适用于PMS内部)
|
||||
|
||||
1. **核心稳固**: 保持PMS核心功能(房态、订单、账务、联系人)的极简和稳定。
|
||||
2. **数据为基**: 确保PMS核心数据结构设计合理,易于内部功能扩展。
|
||||
3. **安全隐私**: PMS内部数据处理需考虑数据安全和隐私保护。
|
||||
4. **门店为中心**: 所有业务数据以门店(部门)为归属单位,支持一个租户下多个门店的业务模型。
|
||||
|
||||
## 3. 系统架构概览 (PMS视角)
|
||||
|
||||
PMS租户核心业务子系统是整个云宿居平台的核心,负责处理民宿日常运营的核心业务流程。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph PMSCore["PMS核心业务子系统"]
|
||||
PMS["PMS租户<br>核心业务子系统"]:::coreSystem
|
||||
end
|
||||
|
||||
subgraph ExternalSystems["外部关联系统"]
|
||||
SaaSPlat[SaaS平台管理]:::system
|
||||
WCDSMM[商城与商品管理]:::system
|
||||
end
|
||||
|
||||
PMSAdmin[PMS核心后台]:::app
|
||||
OwnerApp[民宿管理小程序]:::app
|
||||
|
||||
UserB([民宿主/前台]):::user
|
||||
|
||||
SaaSPlat -- "租户配置" --> PMS
|
||||
WCDSMM -- "预订数据" --> PMS
|
||||
PMS -- "房态/价格" --> WCDSMM
|
||||
|
||||
PMSAdmin --> PMS
|
||||
OwnerApp --> PMS
|
||||
|
||||
UserB -.-> PMSAdmin
|
||||
UserB -.-> OwnerApp
|
||||
|
||||
classDef system fill:#f0f8ff,stroke:#1890ff,stroke-width:1px
|
||||
classDef coreSystem fill:#f6ffed,stroke:#52c41a,stroke-width:3px,color:#135200,font-weight:bold
|
||||
classDef app fill:#d9f7be,stroke:#52c41a,stroke-width:1px
|
||||
classDef user fill:#fff7e6,stroke:#fa8c16,stroke-width:1px,shape:person
|
||||
```
|
||||
|
||||
## 4. PMS租户核心业务子系统 (PMSTenantCoreSubsystem)
|
||||
|
||||
**目标:** 为民宿租户提供稳定、高效的核心运营管理能力。
|
||||
**主要用户:** 民宿主/管理员、前台员工。
|
||||
|
||||
#### 4.2.1 房态管理 [P0]
|
||||
* **功能:**
|
||||
* **网格日历视图:** 直观展示房间占用情况(已预订、已入住、清洁中、维护锁定)。
|
||||
* **今日状态列表:** 按预抵、在住、预离分类展示当日订单/房间。
|
||||
* **清洁状态管理:** 手动标记房间清洁状态 (待清洁/清洁中/已干净)。
|
||||
* **快捷操作:** 在房态图上可快速创建预订、登记入住/退房、标记房间清洁状态、设置房间临时锁定/解锁。支持拖动修改预订的入住日期或房间。
|
||||
* **关键数据:**
|
||||
* `pms_room_types` (room_type_id:BIGINT:pk, dept_id:BIGINT, name:VARCHAR(255), default_price:DECIMAL(10,2), capacity:INT, amenities:JSON, description:TEXT, images_json:JSON, status:VARCHAR(50))
|
||||
* `pms_room_rooms` (room_id:BIGINT:pk, dept_id:BIGINT, room_type_id:BIGINT, room_number:VARCHAR(50), floor:VARCHAR(50), room_status:VARCHAR(50), cleaning_status:VARCHAR(50), description:TEXT, status:VARCHAR(50))
|
||||
* `pms_room_locks` (lock_id:BIGINT:pk, dept_id:BIGINT, room_id:BIGINT, start_datetime:DATETIME, end_datetime:DATETIME, reason:TEXT, lock_type:VARCHAR(50))
|
||||
|
||||
#### 4.2.2 订单管理 [P0, P1, P2]
|
||||
* **目标:** 高效、准确地管理所有来源的预订订单。
|
||||
* **[P0] 核心订单生命周期管理:**
|
||||
* 功能: 手动创建订单、接收外部系统订单、订单详情查看与编辑、核心状态管理(待确认、已确认、已入住、已退房、已取消、未入住)、关联联系人与房间、生成账单基础、房态联动。
|
||||
* 库存策略: `pending_confirmation` 状态不锁硬库存。`confirmed` 状态锁定库存。
|
||||
* 关键数据:
|
||||
* `pms_core_orders` (order_id:BIGINT:pk, dept_id:BIGINT, contact_id:BIGINT, primary_contact_name:VARCHAR(100), primary_contact_phone:VARCHAR(50), pms_room_id:BIGINT, room_type_id:BIGINT, channel_id:BIGINT, check_in_date:DATE, check_out_date:DATE, num_adults:INT, num_children:INT, estimated_arrival_time:TIME, total_amount:DECIMAL(10,2), paid_amount:DECIMAL(10,2), due_amount:DECIMAL(10,2), currency:VARCHAR(3), order_status:VARCHAR(50), order_source:VARCHAR(50), notes:TEXT, cancelled_at:DATETIME, cancellation_reason:TEXT)
|
||||
* `pms_core_order_items` (order_item_id:BIGINT:pk, order_id:BIGINT, dept_id:BIGINT, pms_room_id:BIGINT, product_id:BIGINT, product_type:VARCHAR(50), description:VARCHAR(255), quantity:INT, unit_price:DECIMAL(10,2), total_price:DECIMAL(10,2), service_date:DATE, notes:TEXT)
|
||||
* `pms_core_channels` (channel_id:BIGINT:pk, dept_id:BIGINT, name:VARCHAR(100), channel_type:VARCHAR(50), description:TEXT, status:VARCHAR(50))
|
||||
* 状态流转图:
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> pending_confirmation: 创建订单
|
||||
pending_confirmation --> confirmed: 确认订单
|
||||
pending_confirmation --> cancelled: 取消订单
|
||||
confirmed --> checked_in: 办理入住
|
||||
confirmed --> cancelled: 取消订单
|
||||
confirmed --> no_show: 未按时入住
|
||||
checked_in --> checked_out: 办理退房
|
||||
checked_in --> extended: 延长入住
|
||||
extended --> checked_out: 办理退房
|
||||
checked_out --> [*] : 订单完成
|
||||
cancelled --> [*] : 订单取消
|
||||
no_show --> [*] : 订单标记为No Show
|
||||
```
|
||||
* **[P1] 增强功能:** 订单修改(日期、房型等)、款项管理(预付款、押金)、取消与退款细则、基础支付记录(如手动录入)、自动化通知(预订成功、入住提醒)、订单备注与标签。
|
||||
* **[P2] 实用订单工具:** 简版团体预订、等候名单、订单报表、发票管理。
|
||||
|
||||
#### 4.2.3 财务与支付 (Folio Management) [P0]
|
||||
* **功能:** 账单管理(Folio)、交易流水、附加消费管理、收款方式与记录、退款处理、账单生成与查询。
|
||||
* **关键数据:**
|
||||
* `pms_finance_folios` (folio_id:BIGINT:pk, dept_id:BIGINT, order_id:BIGINT, total_charges:DECIMAL(10,2), total_payments:DECIMAL(10,2), total_refunds:DECIMAL(10,2), balance:DECIMAL(10,2), folio_status:VARCHAR(50), notes:TEXT, closed_at:DATETIME)
|
||||
* `pms_finance_transactions` (transaction_id:BIGINT:pk, folio_id:BIGINT, dept_id:BIGINT, transaction_type:VARCHAR(50), amount:DECIMAL(10,2), description:VARCHAR(255), payment_method_id:BIGINT, payment_gateway_txn_id:VARCHAR(255), transaction_time:DATETIME, related_order_item_id:BIGINT, notes:TEXT, is_void:BOOLEAN, voided_at:DATETIME, voided_reason:TEXT)
|
||||
* `pms_finance_payment_methods` (payment_method_id:BIGINT:pk, dept_id:BIGINT, name:VARCHAR(100), method_type:VARCHAR(50), details_json:JSON, status:VARCHAR(50))
|
||||
* `pms_finance_extra_charge_items` (item_id:BIGINT:pk, dept_id:BIGINT, name:VARCHAR(255), default_price:DECIMAL(10,2), category:VARCHAR(100), is_taxable:BOOLEAN, description:TEXT, status:VARCHAR(50))
|
||||
|
||||
#### 4.2.4 联系人与会员管理 [P1] (P0阶段依赖`cmn_contacts`部分字段)
|
||||
* **功能 (P0涉及):** 订单关联联系人时,使用/创建`cmn_contacts`记录。
|
||||
* **关键数据 (P0使用部分):**
|
||||
* `cmn_contacts` (contact_id:BIGINT:pk, dept_id:BIGINT, contact_type:VARCHAR(50), full_name:VARCHAR(255), phone_number:VARCHAR(50), email:VARCHAR(255), id_type:VARCHAR(50), id_number_encrypted:VARCHAR(255), gender:VARCHAR(20), contact_status:VARCHAR(50))
|
||||
* **[P1] 功能:** 统一联系人档案、证件管理、联系人标签[P2]、联系人偏好[P2]、会员信息管理。
|
||||
|
||||
#### 4.2.5 会员营销 (储值、等级、权益) [P2]
|
||||
* **功能:** 储值管理、会员等级定义与升级、会员权益定义与关联。
|
||||
|
||||
#### 4.2.6 租户级系统管理 [P0]
|
||||
* **功能:**
|
||||
* **员工管理:** 租户管理员管理内部员工账号。
|
||||
* **角色与权限:** 租户管理员定义内部角色 [MVP: 预设固定角色]。
|
||||
* **基础设置:** 民宿信息、Logo等;房型管理;房间管理;基础定价策略 (`pms_room_types.default_price`, `pms_room_pricing_rules`表);支付方式管理 (`pms_finance_payment_methods`);附加消费项目 (`pms_finance_extra_charge_items`)。
|
||||
* **门店管理:** 在一个租户下管理多个门店,每个门店对应一个sys_dept记录。
|
||||
* **租户特定配置:** 管理租户全局及各门店特定的配置。
|
||||
* **关键数据:**
|
||||
* `pms_tenant_settings` (setting_id:BIGINT:pk, tenant_id:VARCHAR(20), dept_id:BIGINT, setting_group:VARCHAR(100), setting_key:VARCHAR(100), setting_value:TEXT, value_type:VARCHAR(50), description:TEXT, is_sensitive:BOOLEAN)
|
||||
* 特殊说明: 保留tenant_id字段,当dept_id为NULL时表示租户全局配置,当dept_id有值时表示特定门店配置。主要存储PMS业务相关的配置项。
|
||||
* `pms_mp_settings` (setting_id:BIGINT:pk, tenant_id:VARCHAR(20), dept_id:BIGINT, setting_key:VARCHAR(100), setting_value:TEXT, value_type:VARCHAR(50), description:TEXT)
|
||||
* 特殊说明: 保留tenant_id字段,当dept_id为NULL时表示租户级小程序配置,当dept_id有值时表示特定门店的小程序配置。主要存储民宿管理小程序的界面和功能配置。
|
||||
* `pms_tenant_user_devices` (id:BIGINT:pk, user_id:BIGINT, device_type:VARCHAR(50), device_token:VARCHAR(512), app_version:VARCHAR(50), last_login_at:DATETIME, status:VARCHAR(50))
|
||||
* `pms_room_pricing_rules` (rule_id:BIGINT:pk, dept_id:BIGINT, name:VARCHAR(255), room_type_id:BIGINT, date_range_start:DATE, date_range_end:DATE, days_of_week_json:JSON, min_length_of_stay:INT, max_length_of_stay:INT, price_adjustment_type:VARCHAR(50), adjustment_value:DECIMAL(10,2), priority:INT, status:VARCHAR(50))
|
||||
|
||||
##### 4.2.6.1 员工管理
|
||||
* **功能:** 民宿租户管理员管理本民宿的员工账号、分配角色。用户登录后,若关联多个租户或部门,提供切换租户和部门(门店)功能。
|
||||
|
||||
#### 4.2.7 简易报表与分析 [P1]
|
||||
* **功能:** 核心运营报表 (总营收、入住率、ADR、RevPAR)、渠道来源分析、会员统计、消费分析。
|
||||
* **特点:** 支持门店级和租户级统计分析。
|
||||
|
||||
#### 4.2.8 民宿管理小程序 (Owner We App) [P0]
|
||||
* **技术:** 前端使用 Uni-App
|
||||
* **功能:** 房态查看、订单提醒、日程查看(今日预抵/预离)、状态处理 (简单入住/退房操作)、数据概览(核心收支数据)。
|
||||
* **特点:** 支持门店切换功能。
|
||||
|
||||
#### 4.2.9 PMS核心管理后台 [P0]
|
||||
* **描述:** 提供独立的Web管理后台界面,完成所有PMS核心业务操作。
|
||||
|
||||
## 5. 关键业务场景模型图 (PMS内部)
|
||||
|
||||
### 5.1 订单状态图 - 房间预订生命周期
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 待确认: 用户创建预订
|
||||
待确认 --> 已确认: 预付款成功/人工确认
|
||||
待确认 --> 已取消: 超时未确认/用户取消
|
||||
已确认 --> 已取消: 申请取消并退款
|
||||
已确认 --> 已入住: 办理入住手续
|
||||
已入住 --> 已退房: 办理退房手续
|
||||
已退房 --> 已完成: 完成结算
|
||||
已取消 --> [*]
|
||||
已完成 --> [*]
|
||||
```
|
||||
|
||||
### 5.2 PMS核心业务状态图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state 房间状态 {
|
||||
[*] --> 空闲
|
||||
空闲 --> 已预订: 创建预订
|
||||
空闲 --> 维护中: 设置维护
|
||||
已预订 --> 已入住: 办理入住
|
||||
已预订 --> 空闲: 取消预订
|
||||
已入住 --> 待清洁: 办理退房
|
||||
待清洁 --> 清洁中: 开始清洁
|
||||
清洁中 --> 空闲: 完成清洁
|
||||
维护中 --> 空闲: 完成维护
|
||||
}
|
||||
|
||||
state 财务状态 {
|
||||
[*] --> 未结账
|
||||
未结账 --> 部分支付: 收取定金/押金
|
||||
部分支付 --> 部分支付: 增加消费
|
||||
部分支付 --> 已结清: 支付尾款
|
||||
部分支付 --> 退款中: 申请退款
|
||||
退款中 --> 已退款: 确认退款
|
||||
已结清 --> [*]
|
||||
已退款 --> [*]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 PMS租户核心业务子系统组件图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph PMS["PMS租户核心业务子系统"]
|
||||
direction TB
|
||||
APILayer["API层 (内部)"]:::internal --> |请求分发| BusinessModules
|
||||
|
||||
subgraph BusinessModules["核心业务模块"]
|
||||
direction TB
|
||||
Room["房态管理"]:::internal
|
||||
Booking["订单管理"]:::internal
|
||||
Finance["财务与支付"]:::internal
|
||||
Guest["联系人与会员 (P1核心)"]:::internal
|
||||
Settings["系统管理 (含门店管理)"]:::internal
|
||||
Reports["报表分析 (P1+)"]:::internal
|
||||
|
||||
Room <--> Booking
|
||||
Booking <--> Finance
|
||||
Booking --> Guest
|
||||
end
|
||||
end
|
||||
|
||||
subgraph AccessLayer["应用访问层 (PMS用户)"]
|
||||
direction LR
|
||||
MiniApp["民宿管理小程序"]:::external
|
||||
WebAdmin["PMS Web管理后台"]:::external
|
||||
end
|
||||
|
||||
AccessLayer --> APILayer
|
||||
|
||||
classDef internal fill:#f0f8ff,stroke:#1890ff,stroke-width:1px
|
||||
classDef external fill:#f5f5f5,stroke:#d9d9d9,stroke-width:1px
|
||||
```
|
||||
|
||||
## 6. 系统角色与权限概述 (PMS相关用户)
|
||||
|
||||
1. **民宿租户管理员 (Tenant Admin):**
|
||||
* 用户实体: 关联到 `sys_user` 表。
|
||||
* 关联: 通过 `sys_user_role` 表关联到租户及相关角色。
|
||||
* 操作平台: PMS核心后台 (Web), 民宿管理小程序 (Uni-App)。
|
||||
* 核心职责: 管理其名下一个或多个民宿门店的完整运营,包括房型房间、订单、财务、员工、租户级系统设置等。
|
||||
2. **门店管理员 (Store Manager):**
|
||||
* 用户实体: 关联到 `sys_user` 表。
|
||||
* 关联: 通过 `sys_user_role` 表和 `sys_dept` 表关联到特定门店与角色。
|
||||
* 操作平台: PMS核心后台 (Web), 民宿管理小程序 (Uni-App)。
|
||||
* 核心职责: 管理特定门店的日常运营。
|
||||
3. **民宿前台/员工 (Tenant Staff):**
|
||||
* 用户实体: 关联到 `sys_user` 表。
|
||||
* 关联: 通过 `sys_user_role` 表和 `sys_dept` 表关联到门店及相关角色。
|
||||
* 操作平台: PMS核心后台 (Web), 民宿管理小程序 (Uni-App)。
|
||||
* 核心职责: 根据分配的权限执行日常操作,如订单处理、房态更新、联系人入住退房、账务录入等。
|
||||
|
||||
## 7. 实施策略与演进路径 (PMS P0重点)
|
||||
|
||||
### 7.1 MVP阶段 (PMS核心功能)
|
||||
- **房态管理:** 基础房型、房间定义和日历视图。
|
||||
- **订单管理:** 手动录单、接收外部订单(简化接口)、入住、退房基本流程。
|
||||
- **财务与支付:** 基础账单 (Folio)、交易流水、手动收款/支付记录。
|
||||
- **租户级系统管理:** 员工账号、门店管理、基础设置(房型、房间、基础价格、支付方式、附加消费项目)。
|
||||
- **民宿管理小程序:** 房态查看和简单操作(入住/退房)。
|
||||
|
||||
## 8. 版本历史
|
||||
- v2.8 (原始文档版本) - 本文档基于此裁剪和聚焦。
|
||||
- v3.0 (最新版本) - 修改为"部门作为门店"的数据模型,业务表只关联sys_dept。
|
||||
|
||||
## 附录 (PMS内部核心实体)
|
||||
|
||||
### 附录A 概念性 E-R 图 (PMS核心)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
sys_tenant(sys_tenant) -->|1..N| sys_dept(Department)
|
||||
sys_dept -->|1..N| pms_core_orders(Order)
|
||||
sys_dept -->|1..N| pms_room_rooms(Room)
|
||||
sys_dept -->|1..N| sys_user(User)
|
||||
sys_dept -->|1..N| pms_tenant_settings(Settings)
|
||||
|
||||
pms_core_orders -->|0..1| cmn_contacts(Contact)
|
||||
pms_core_orders -->|1..N| pms_core_order_items(OrderItem)
|
||||
pms_core_orders -->|1..1| pms_finance_folios(Folio)
|
||||
|
||||
pms_room_rooms -->|1..1| pms_room_types(RoomType)
|
||||
|
||||
pms_finance_folios -->|1..N| pms_finance_transactions(Transaction)
|
||||
```
|
||||
|
||||
### 附录B 实体关系图 (PMS核心及关联)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
sys_tenant("sys_tenant") -->|1..N| sys_dept("sys_dept (门店)")
|
||||
sys_dept -->|1..N| pms_core_orders
|
||||
sys_dept -->|1..N| pms_room_rooms
|
||||
sys_dept -->|1..N| sys_user
|
||||
|
||||
sys_user -->|1..N| pms_tenant_user_devices
|
||||
sys_user -->|1..N| sys_user_role
|
||||
|
||||
sys_dept -->|1..N| pms_tenant_settings
|
||||
sys_dept -->|1..N| pms_mp_settings
|
||||
sys_dept -->|1..N| cmn_contacts
|
||||
sys_dept -->|1..N| pms_room_types
|
||||
|
||||
pms_core_orders -->|0..1| cmn_contacts
|
||||
pms_core_orders -->|1..N| pms_core_order_items
|
||||
pms_core_orders -->|1..1| pms_finance_folios
|
||||
|
||||
pms_room_rooms -->|1..1| pms_room_types
|
||||
pms_room_types -->|1..N| pms_room_pricing_rules
|
||||
|
||||
pms_core_order_items -->|0..1| pms_finance_extra_charge_items
|
||||
pms_core_order_items -->|0..1| pms_room_rooms
|
||||
|
||||
pms_finance_folios -->|1..N| pms_finance_transactions
|
||||
|
||||
pms_finance_transactions -->|0..1| pms_finance_payment_methods
|
||||
```
|
288
docs/二开todolist.md
Normal file
288
docs/二开todolist.md
Normal file
@ -0,0 +1,288 @@
|
||||
# PMS 模块二次开发 ToDoList
|
||||
|
||||
## 阶段 0: 项目初始化与理解
|
||||
|
||||
- [x] **阅读核心文档:**
|
||||
- [x] 仔细阅读《PMS数据模型.md》,完全理解PMS核心表的结构、字段定义、关系以及主键/外键/索引规范。
|
||||
- [x] 仔细阅读《PMS需求.md》,明确PMS核心模块 [P0] 阶段必须实现的功能需求(房态、订单核心生命周期、基础财务支付、租户级系统管理基础)。
|
||||
- [x] 通读《RuoYi-Vue-Plus二次开发最佳实践.md》,重点关注以下章节:
|
||||
- Chapter 1: 引言 (文档目的, 模块化特性, **二次开发核心原则**, **Cursor使用优势**)
|
||||
- Chapter 2: 环境准备与项目结构 (前后端环境, **前后端项目结构**, **代码生成器与Cursor协同**)
|
||||
- Chapter 3: 模块深入分析 (用户管理模块的启示)
|
||||
- Chapter 4: 后端开发最佳实践 (Cursor辅助技巧, 模块设计, 分层约定, Domain对象规范)
|
||||
- Chapter 5: 前端开发最佳实践 (Cursor辅助技巧, 项目结构, 功能模块开发规范, 组件案例, 前后端交互)
|
||||
- Chapter 6: 前后端协作规范
|
||||
- Chapter 7: 代码规范与风格
|
||||
- Chapter 8: 新模块添加流程与实践
|
||||
- [x] **环境与工具准备:**
|
||||
- [x] 确保后端开发环境 (JDK, Maven, Redis, MySQL) 符合《最佳实践》Chapter 2.1 要求。
|
||||
- [x] 确保前端开发环境 (Node.js, pnpm) 符合《最佳实践》Chapter 2.1 要求。
|
||||
- [x] 安装并配置好 Cursor IDE,将整个 `ruoyi-vue-plus` 项目导入工作区。
|
||||
- [x] (可选)在 Cursor 中为项目设置特定上下文,如关键模块路径、技术栈等,以便更好地辅助开发。
|
||||
|
||||
## 阶段 1: 后端开发 - 模块搭建与核心表结构
|
||||
|
||||
- [ ] **创建后端Maven子模块 `ruoyi-pms`** (参考《最佳实践》Chapter 2.2, 4.2, 8.3):
|
||||
- [ ] 在 `ruoyi-vue-plus/ruoyi-modules` 目录下创建新的 Maven 子模块 `ruoyi-pms`。
|
||||
- [ ] 配置 `ruoyi-pms/pom.xml`:
|
||||
- [ ] 设置 `<parent>` 指向 `ruoyi-modules`。
|
||||
- [ ] 添加必要的公共模块依赖 (如: `ruoyi-common-core`, `ruoyi-common-mybatis`, `ruoyi-common-web`, `ruoyi-common-satoken`, `ruoyi-common-excel`, `ruoyi-common-translate`, `ruoyi-common-tenant`)。
|
||||
- [ ] 在项目根目录 `pom.xml` 和 `ruoyi-modules/pom.xml` 的 `<modules>` 部分注册 `ruoyi-pms`。
|
||||
- [ ] **创建后端基础包结构** (参考《最佳实践》Chapter 4.2):
|
||||
- [ ] 在 `ruoyi-pms/src/main/java/` 下创建基础包,例如 `org.dromara.pms`。
|
||||
- [ ] 在 `org.dromara.pms` 下创建标准分层包:`controller`, `service`, `service.impl`, `mapper`, `domain` (包含 `entity`, `bo`, `vo`)。
|
||||
- [ ] **数据库表创建与初始化**:
|
||||
- [ ] **执行SQL脚本**: 确保已在开发数据库中执行 `script/sql/pms_tables.sql` 文件,创建所有PMS相关的表。
|
||||
- [ ] **验证表结构**: 对照《PMS数据模型.md》仔细检查已创建的表结构、字段类型、约束、索引是否正确。
|
||||
- [ ] **验证基础数据**: 确认 `pms_core_channels` 和 `pms_finance_payment_methods` 表的基础数据已按 `pms_tables.sql` 中的`INSERT`语句正确插入。
|
||||
- [ ] **集成新模块到系统**:
|
||||
- [ ] (如果需要)在 `ruoyi-admin` 模块的 `application.yml` 中,确保新模块的包路径 (如 `org.dromara.pms`) 被扫描到。
|
||||
- [ ] (如果需要)配置MyBatis Plus扫描新模块的Mapper XML文件路径。
|
||||
- [ ] **后端国际化资源文件**:
|
||||
- [ ] 在 `ruoyi-pms/src/main/resources/` 下创建 `i18n` 目录。
|
||||
- [ ] 在 `i18n` 目录下创建基础的国际化属性文件,如 `messages_zh_CN.properties` 和 `messages_en_US.properties`。
|
||||
|
||||
## 阶段 2: 后端开发 - 核心业务功能 [P0]
|
||||
|
||||
**通用后端开发规范 (参考《最佳实践》Chapter 4 & 7.1):**
|
||||
- [ ] 遵循分层架构约定 (Controller -> Service -> Mapper)。
|
||||
- [ ] 严格区分 Entity, BO, VO 的职责和使用场景。
|
||||
- [ ] 使用 MapStruct Plus (`@AutoMapper`) 进行对象转换。
|
||||
- [ ] Service 层实现类的方法应添加 `@Transactional` 注解。
|
||||
- [ ] Controller 层方法应添加 `@Log` 操作日志注解和 `@SaCheckPermission` 权限注解。
|
||||
- [ ] BO 对象字段使用 JSR 303/380 注解进行参数校验。
|
||||
- [ ] VO 对象字段按需使用 `@Translation` 和 `@Sensitive` 注解。
|
||||
- [ ] 编写清晰的 Javadoc 注释和行内注释。
|
||||
- [ ] 使用 Cursor 辅助生成代码、分析逻辑、优化代码。
|
||||
|
||||
**具体功能模块开发:**
|
||||
|
||||
- [ ] **代码生成器应用 (可选但推荐)** (参考《最佳实践》Chapter 2.4):
|
||||
- [ ] 针对《PMS数据模型.md》中定义的核心表 (如 `pms_room_types`, `pms_room_rooms`, `pms_core_orders` 等),使用若依代码生成器生成初始的 Entity, Mapper, Service, Controller, BO, VO。
|
||||
- [ ] **Cursor辅助**: "请Cursor分析代码生成器为 `pms_room_types` 表生成的后端代码,并指出哪些部分需要根据《PMS需求.md》和《最佳实践》进行调整。"
|
||||
|
||||
- [ ] **1. 房型管理 (`pms_room_types`)**:
|
||||
- [ ] **Domain**: 创建/调整 `PmsRoomType`, `PmsRoomTypeBo`, `PmsRoomTypeVo`。
|
||||
- [ ] **Mapper**: 创建/调整 `PmsRoomTypeMapper.java` 和对应的 `PmsRoomTypeMapper.xml`。
|
||||
- [ ] **Service**: 创建/调整 `IPmsRoomTypeService.java` 和 `PmsRoomTypeServiceImpl.java` (实现CRUD逻辑)。
|
||||
- [ ] **Controller**: 创建/调整 `PmsRoomTypeController.java` (暴露RESTful API)。
|
||||
|
||||
- [ ] **2. 房间管理 (`pms_room_rooms`)**:
|
||||
- [ ] **Domain**: `PmsRoomRoom`, `PmsRoomRoomBo`, `PmsRoomRoomVo`.
|
||||
- [ ] **Mapper**: `PmsRoomRoomMapper.java` & XML.
|
||||
- [ ] **Service**: `IPmsRoomRoomService.java` & Impl (CRUD, 更新房间状态/清洁状态逻辑).
|
||||
- [ ] **Controller**: `PmsRoomRoomController.java`.
|
||||
|
||||
- [ ] **3. 房间锁定 (`pms_room_locks`)**:
|
||||
- [ ] **Domain**: `PmsRoomLock`, `PmsRoomLockBo`, `PmsRoomLockVo`.
|
||||
- [ ] **Mapper**: `PmsRoomLockMapper.java` & XML.
|
||||
- [ ] **Service**: `IPmsRoomLockService.java` & Impl (CRUD).
|
||||
- [ ] **Controller**: `PmsRoomLockController.java`.
|
||||
|
||||
- [ ] **4. 核心订单管理 (`pms_core_orders`, `pms_core_order_items`)** (参考《PMS需求.md》4.2.2 [P0]):
|
||||
- [ ] **Domain**:
|
||||
- `PmsCoreOrder`, `PmsCoreOrderBo`, `PmsCoreOrderVo` (应包含订单项列表).
|
||||
- `PmsCoreOrderItem`, `PmsCoreOrderItemBo`, `PmsCoreOrderItemVo`.
|
||||
- [ ] **Mapper**: `PmsCoreOrderMapper`, `PmsCoreOrderItemMapper` & XMLs.
|
||||
- [ ] **Service**: `IPmsCoreOrderService`, `IPmsCoreOrderItemService` & Impls (实现订单创建、查询、修改、取消、状态流转核心逻辑;订单项增删改查).
|
||||
- [ ] 实现订单状态转换逻辑 (pending_confirmation -> confirmed -> checked_in -> checked_out / cancelled / no_show).
|
||||
- [ ] 订单创建时关联/创建 `cmn_contacts` (基础字段)。
|
||||
- [ ] 订单创建时关联 `pms_core_channels`。
|
||||
- [ ] 订单创建/确认时,自动创建或关联 `pms_finance_folios`。
|
||||
- [ ] **Controller**: `PmsCoreOrderController`.
|
||||
|
||||
- [ ] **5. 订单来源渠道 (`pms_core_channels`)**:
|
||||
- [ ] **Domain**: `PmsCoreChannel`, `PmsCoreChannelBo`, `PmsCoreChannelVo`.
|
||||
- [ ] **Mapper**: `PmsCoreChannelMapper` & XML.
|
||||
- [ ] **Service**: `IPmsCoreChannelService` & Impl (CRUD).
|
||||
- [ ] **Controller**: `PmsCoreChannelController`.
|
||||
|
||||
- [ ] **6. 财务-账单管理 (`pms_finance_folios`)** (参考《PMS需求.md》4.2.3 [P0]):
|
||||
- [ ] **Domain**: `PmsFinanceFolio`, `PmsFinanceFolioBo`, `PmsFinanceFolioVo` (应包含交易流水列表).
|
||||
- [ ] **Mapper**: `PmsFinanceFolioMapper` & XML.
|
||||
- [ ] **Service**: `IPmsFinanceFolioService` & Impl (CRUD, 更新账单状态, 计算余额逻辑 - 应用层计算).
|
||||
- [ ] **Controller**: `PmsFinanceFolioController`.
|
||||
|
||||
- [ ] **7. 财务-交易流水 (`pms_finance_transactions`)**:
|
||||
- [ ] **Domain**: `PmsFinanceTransaction`, `PmsFinanceTransactionBo`, `PmsFinanceTransactionVo`.
|
||||
- [ ] **Mapper**: `PmsFinanceTransactionMapper` & XML.
|
||||
- [ ] **Service**: `IPmsFinanceTransactionService` & Impl (CRUD, 关联 `pms_finance_payment_methods`).
|
||||
- [ ] **Controller**: `PmsFinanceTransactionController`.
|
||||
|
||||
- [ ] **8. 财务-支付方式 (`pms_finance_payment_methods`)**:
|
||||
- [ ] **Domain**: `PmsFinancePaymentMethod`, `PmsFinancePaymentMethodBo`, `PmsFinancePaymentMethodVo`.
|
||||
- [ ] **Mapper**: `PmsFinancePaymentMethodMapper` & XML.
|
||||
- [ ] **Service**: `IPmsFinancePaymentMethodService` & Impl (CRUD).
|
||||
- [ ] **Controller**: `PmsFinancePaymentMethodController`.
|
||||
|
||||
- [ ] **9. 财务-附加费用项目 (`pms_finance_extra_charge_items`)**:
|
||||
- [ ] **Domain**: `PmsFinanceExtraChargeItem`, `PmsFinanceExtraChargeItemBo`, `PmsFinanceExtraChargeItemVo`.
|
||||
- [ ] **Mapper**: `PmsFinanceExtraChargeItemMapper` & XML.
|
||||
- [ ] **Service**: `IPmsFinanceExtraChargeItemService` & Impl (CRUD).
|
||||
- [ ] **Controller**: `PmsFinanceExtraChargeItemController`.
|
||||
|
||||
- [ ] **10. 价格规则 (`pms_room_pricing_rules`)**:
|
||||
- [ ] **Domain**: `PmsRoomPricingRule`, `PmsRoomPricingRuleBo`, `PmsRoomPricingRuleVo`.
|
||||
- [ ] **Mapper**: `PmsRoomPricingRuleMapper` & XML.
|
||||
- [ ] **Service**: `IPmsRoomPricingRuleService` & Impl (CRUD).
|
||||
- [ ] **Controller**: `PmsRoomPricingRuleController`.
|
||||
|
||||
- [ ] **11. 联系人基础 (`cmn_contacts`)** (P0阶段主要为订单关联所需字段):
|
||||
- [ ] **Domain**: `CmnContact`, `CmnContactBo`, `CmnContactVo` (仅包含P0阶段所需字段,如姓名、电话).
|
||||
- [ ] **Mapper**: `CmnContactMapper` & XML.
|
||||
- [ ] **Service**: `ICmnContactService` & Impl (提供基础的联系人查询、创建接口供订单模块调用).
|
||||
- [ ] **Controller**: (P0阶段可能不需要完整独立的Controller,主要通过订单业务间接操作).
|
||||
|
||||
- [ ] **12. 租户/部门特定配置 (`pms_tenant_settings`, `pms_mp_settings`)**:
|
||||
- [ ] **Domain**: `PmsTenantSetting`, `PmsTenantSettingBo`, `PmsTenantSettingVo` & 类似的 `PmsMpSetting` 对象.
|
||||
- [ ] **Mapper**: `PmsTenantSettingMapper`, `PmsMpSettingMapper` & XMLs.
|
||||
- [ ] **Service**: `IPmsTenantSettingService`, `IPmsMpSettingService` & Impls (CRUD, 按 group/key 查询).
|
||||
- [ ] **Controller**: `PmsTenantSettingController`, `PmsMpSettingController`.
|
||||
|
||||
- [ ] **13. 租户用户设备 (`pms_tenant_user_devices`)**: (用于民宿管理小程序推送等)
|
||||
- [ ] **Domain**: `PmsTenantUserDevice`, `PmsTenantUserDeviceBo`, `PmsTenantUserDeviceVo`.
|
||||
- [ ] **Mapper**: `PmsTenantUserDeviceMapper` & XML.
|
||||
- [ ] **Service**: `IPmsTenantUserDeviceService` & Impl (CRUD, 设备注册/更新逻辑).
|
||||
- [ ] **Controller**: `PmsTenantUserDeviceController`.
|
||||
|
||||
- [ ] **API文档**:
|
||||
- [ ] 为所有Controller和DTO添加清晰的Swagger/Knife4j注解 (参考《最佳实践》Chapter 8.3)。
|
||||
|
||||
## 阶段 3: 前端开发 - 核心管理界面 [P0] (Soybean Admin Pro)
|
||||
|
||||
**通用前端开发规范 (参考《最佳实践》Chapter 5 & 7.2):**
|
||||
- [ ] 遵循 Soybean Admin Pro 的项目结构和编码规范。
|
||||
- [ ] 使用 Vue 3 Composition API + `<script setup>`。
|
||||
- [ ] 为 props, emits, reactive state, methods 提供 TypeScript 类型。
|
||||
- [ ] 优先使用框架提供的 Hooks (`useTable`, `useForm` 等) 和 Naive UI 组件。
|
||||
- [ ] 组件样式使用 scoped CSS 或 CSS Modules。
|
||||
- [ ] 使用 Cursor 辅助生成组件、分析代码、实现逻辑。
|
||||
|
||||
**具体功能模块开发:**
|
||||
|
||||
- [ ] **前端项目结构搭建** (参考《最佳实践》Chapter 5.2):
|
||||
- [ ] 在 `src/views/`下创建 `pms` 目录,并为各管理功能创建子目录 (如 `room-type`, `room`, `order`, `folio` 等)。
|
||||
- [ ] 在 `src/service/api/` 下创建 `pms` 目录,并为各后端Controller创建对应的 `ts` 服务文件 (如 `roomType.ts`, `order.ts`)。
|
||||
- [ ] 在 `src/typings/api/` 下创建 `pms` 目录,定义与后端BO/VO对应的TypeScript接口。
|
||||
- [ ] 创建 `src/router/routes/modules/pms.ts` 路由配置文件。
|
||||
- [ ] (可选) 在 `src/store/modules/` 下创建 `pms` 目录,用于存放PMS相关的Pinia store。
|
||||
- [ ] **Cursor辅助**: "请Cursor根据《最佳实践》前端项目结构,为PMS模块生成基础的目录和文件占位符。"
|
||||
|
||||
- [ ] **1. 房型管理界面 (`src/views/pms/room-type/`)**:
|
||||
- [ ] **API服务**: `src/service/api/pms/roomType.ts` (实现对后端 `PmsRoomTypeController` 的调用)。
|
||||
- [ ] **类型定义**: `src/typings/api/pms/roomType.ts` (定义 `PmsRoomTypeVo` 等接口)。
|
||||
- [ ] **列表页**: `index.vue` (表格展示房型列表,支持搜索、分页、新增、编辑、删除按钮)。
|
||||
- [ ] **表单组件**: `components/RoomTypeForm.vue` (用于新增/编辑房型的弹窗或抽屉表单,包含字段校验)。
|
||||
|
||||
- [ ] **2. 房间管理界面 (`src/views/pms/room/`)**:
|
||||
- [ ] **API服务**: `src/service/api/pms/room.ts`.
|
||||
- [ ] **类型定义**: `src/typings/api/pms/room.ts`.
|
||||
- [ ] **列表页**: `index.vue` (展示房间列表,支持按房型、状态等搜索,操作按钮)。
|
||||
- [ ] **表单组件**: `components/RoomForm.vue`.
|
||||
- [ ] **房态图/日历视图基础**: (《PMS需求.md》4.2.1 [P0] 网格日历视图 - 此为复杂功能,P0阶段可能先实现基础列表和状态管理)
|
||||
|
||||
- [ ] **3. 订单管理界面 (`src/views/pms/order/`)**:
|
||||
- [ ] **API服务**: `src/service/api/pms/order.ts`.
|
||||
- [ ] **类型定义**: `src/typings/api/pms/order.ts`.
|
||||
- [ ] **列表页**: `index.vue` (展示订单列表,支持按日期、状态、联系人等搜索)。
|
||||
- [ ] **详情页/表单**: `detail.vue` 或 `components/OrderForm.vue` (用于创建/编辑/查看订单详情,包含订单项管理)。
|
||||
- [ ] 实现订单状态变更操作。
|
||||
- [ ] 关联联系人选择/创建。
|
||||
- [ ] 关联房型/房间选择。
|
||||
|
||||
- [ ] **4. 财务-账单管理界面 (`src/views/pms/folio/`)**:
|
||||
- [ ] **API服务**: `src/service/api/pms/folio.ts`.
|
||||
- [ ] **类型定义**: `src/typings/api/pms/folio.ts`.
|
||||
- [ ] **列表页/详情页**: (展示账单列表,点击可查看账单详情及交易流水)。
|
||||
|
||||
- [ ] **5. 财务-支付方式管理界面 (`src/views/pms/payment-method/`)**:
|
||||
- [ ] **API服务**: `src/service/api/pms/paymentMethod.ts`.
|
||||
- [ ] **类型定义**: `src/typings/api/pms/paymentMethod.ts`.
|
||||
- [ ] **列表页**: `index.vue` (管理支付方式)。
|
||||
- [ ] **表单组件**: `components/PaymentMethodForm.vue`.
|
||||
|
||||
- [ ] **6. 财务-附加费用项目管理界面 (`src/views/pms/extra-charge-item/`)**:
|
||||
- [ ] **API服务**: `src/service/api/pms/extraChargeItem.ts`.
|
||||
- [ ] **类型定义**: `src/typings/api/pms/extraChargeItem.ts`.
|
||||
- [ ] **列表页**: `index.vue`.
|
||||
- [ ] **表单组件**: `components/ExtraChargeItemForm.vue`.
|
||||
|
||||
- [ ] **7. 价格规则管理界面 (`src/views/pms/pricing-rule/`)**:
|
||||
- [ ] **API服务**: `src/service/api/pms/pricingRule.ts`.
|
||||
- [ ] **类型定义**: `src/typings/api/pms/pricingRule.ts`.
|
||||
- [ ] **列表页**: `index.vue`.
|
||||
- [ ] **表单组件**: `components/PricingRuleForm.vue`.
|
||||
|
||||
- [ ] **8. 租户/部门特定配置界面 (`src/views/pms/settings/`)**:
|
||||
- [ ] (按需实现,可能集成到系统现有配置管理或单独页面)
|
||||
|
||||
- [ ] **路由配置 (`src/router/routes/modules/pms.ts`)**:
|
||||
- [ ] 为以上所有PMS管理页面配置路由。
|
||||
- [ ] 配置菜单项 (名称、图标、排序、权限标识) (参考《最佳实践》Chapter 5.3)。
|
||||
- [ ] **Cursor辅助**: "请Cursor根据已创建的PMS前端页面,生成对应的 `pms.ts` 路由配置文件,并包含菜单定义。"
|
||||
|
||||
- [ ] **前端国际化**:
|
||||
- [ ] 将所有界面上的文本添加到 `src/locales/langs/zh-CN.ts` 和 `en-US.ts` 中。
|
||||
|
||||
- [ ] **权限控制**:
|
||||
- [ ] 使用 `useAuth` Hook 和权限指令,根据后端返回的权限标识控制按钮的显示/隐藏和页面的访问。
|
||||
|
||||
## 阶段 4: 民宿管理小程序 (Owner We App) [P0] - 基础功能
|
||||
|
||||
**技术栈**: Uni-App (参考《PMS需求.md》4.2.8)
|
||||
|
||||
- [ ] **项目搭建**: 创建 Uni-App 项目。
|
||||
- [ ] **API对接**:
|
||||
- [ ] 封装调用后端PMS接口的请求函数 (复用或参考前端Web的API服务)。
|
||||
- [ ] 处理用户认证和token。
|
||||
- [ ] **核心功能实现**:
|
||||
- [ ] **房态查看**:
|
||||
- [ ] 调用后端接口获取房态数据。
|
||||
- [ ] 以列表或简化日历形式展示房间状态。
|
||||
- [ ] **订单提醒**: (P0阶段可能为简单的订单列表,按预抵/预离排序)
|
||||
- [ ] 调用后端接口获取订单列表。
|
||||
- [ ] **日程查看 (今日预抵/预离)**:
|
||||
- [ ] 筛选并展示当日预抵和预离的订单。
|
||||
- [ ] **状态处理 (简单入住/退房操作)**:
|
||||
- [ ] 提供按钮或操作,调用后端接口更新订单状态为 `checked_in` 或 `checked_out`。
|
||||
- [ ] **数据概览 (核心收支数据)**: (P0阶段可简化,如当日收款总额)
|
||||
- [ ] **门店切换功能**: 如果用户关联多个门店,允许切换当前操作的门店上下文。
|
||||
- [ ] **UI/UX设计**: 简洁易用,符合移动端操作习惯。
|
||||
- [ ] **打包与测试**: 在微信开发者工具和真机上进行测试。
|
||||
|
||||
## 阶段 5: 集成、测试与部署
|
||||
|
||||
- [ ] **后端单元测试**:
|
||||
- [ ] 为核心Service层方法编写JUnit单元测试。
|
||||
- [ ] **前后端联调** (参考《最佳实践》Chapter 6, 8.5):
|
||||
- [ ] 逐个功能模块进行前后端接口联调,确保数据交互正确。
|
||||
- [ ] **Cursor辅助**: "后端 `PmsOrderController` 的 `getOrderById` 接口返回数据格式与前端期望不一致,请Cursor分析可能的原因。"
|
||||
- [ ] **系统集成测试**:
|
||||
- [ ] 测试PMS模块与若依基础模块 (用户、部门、租户、权限、字典等) 的集成情况。
|
||||
- [ ] 测试多租户、多部门数据隔离是否正确。
|
||||
- [ ] **[P0] 功能验收测试 (UAT)**:
|
||||
- [ ] 根据《PMS需求.md》中[P0]功能点,逐一进行业务场景测试。
|
||||
- [ ] **Bug修复与回归测试**:
|
||||
- [ ] **数据库菜单与权限SQL**:
|
||||
- [ ] 编写将PMS模块前端路由配置的菜单信息插入到 `sys_menu` 表的SQL脚本。
|
||||
- [ ] (如果需要新的权限标识) 编写插入到 `sys_permission` (或其他权限相关表) 的SQL脚本,并关联到角色。
|
||||
- [ ] **文档完善**:
|
||||
- [ ] 更新/创建PMS模块相关的开发文档、API文档 (确保Swagger/Knife4j是最新的)。
|
||||
- [ ] **部署准备** (参考《最佳实践》Chapter 8.5):
|
||||
- [ ] 后端打包 (如 `mvn package -DskipTests`)。
|
||||
- [ ] 前端打包 (`pnpm build`)。
|
||||
- [ ] 小程序打包。
|
||||
- [ ] **部署到测试/生产环境并验证**。
|
||||
|
||||
## 阶段 6: 持续迭代与优化 [P1+]
|
||||
|
||||
- [ ] 根据《PMS需求.md》中的 [P1], [P2] 优先级,规划后续迭代功能。
|
||||
- [ ] **订单管理[P1]**: 订单修改、款项管理、取消退款细则、自动化通知等。
|
||||
- [ ] **联系人与会员管理[P1]**: 统一联系人档案、证件管理。
|
||||
- [ ] **简易报表与分析[P1]**: 核心运营报表。
|
||||
- [ ] 性能优化。
|
||||
- [ ] 用户体验改进。
|
||||
- [ ] 安全加固。
|
||||
|
||||
---
|
||||
**注意**: 此ToDoList仅为初步计划,具体执行时需根据实际情况灵活调整。请频繁使用Cursor进行代码生成、分析、重构和问题排查,以提高开发效率和代码质量。
|
514
pom.xml
Normal file
514
pom.xml
Normal file
@ -0,0 +1,514 @@
|
||||
<?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>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
||||
|
||||
<properties>
|
||||
<revision>5.3.1</revision>
|
||||
<spring-boot.version>3.4.4</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.8.5</springdoc.version>
|
||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||
<easyexcel.version>4.0.3</easyexcel.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<satoken.version>1.40.0</satoken.version>
|
||||
<mybatis-plus.version>3.5.11</mybatis-plus.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<hutool.version>5.8.35</hutool.version>
|
||||
<spring-boot-admin.version>3.4.5</spring-boot-admin.version>
|
||||
<redisson.version>3.45.1</redisson.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
||||
<snailjob.version>1.4.0</snailjob.version>
|
||||
<mapstruct-plus.version>1.4.6</mapstruct-plus.version>
|
||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||
<lombok.version>1.18.36</lombok.version>
|
||||
<bouncycastle.version>1.76</bouncycastle.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
<!-- 离线IP地址定位库 -->
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
|
||||
<!-- OSS 配置 -->
|
||||
<aws.sdk.version>2.28.22</aws.sdk.version>
|
||||
<!-- SMS 配置 -->
|
||||
<sms4j.version>3.3.4</sms4j.version>
|
||||
<!-- 限制框架中的fastjson版本 -->
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<!-- 面向运行时的D-ORM依赖 -->
|
||||
<anyline.version>8.7.2-20250101</anyline.version>
|
||||
<!--工作流配置-->
|
||||
<warm-flow.version>1.6.8</warm-flow.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.version>3.11.0</maven-compiler-plugin.version>
|
||||
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
|
||||
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
|
||||
<!-- 打包默认跳过测试 -->
|
||||
<skipTests>true</skipTests>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>local</id>
|
||||
<properties>
|
||||
<!-- 环境标识,需要与配置文件的名称相对应 -->
|
||||
<profiles.active>local</profiles.active>
|
||||
<logging.level>info</logging.level>
|
||||
<monitor.username>ruoyi</monitor.username>
|
||||
<monitor.password>123456</monitor.password>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<properties>
|
||||
<!-- 环境标识,需要与配置文件的名称相对应 -->
|
||||
<profiles.active>dev</profiles.active>
|
||||
<logging.level>info</logging.level>
|
||||
<monitor.username>ruoyi</monitor.username>
|
||||
<monitor.password>123456</monitor.password>
|
||||
</properties>
|
||||
<activation>
|
||||
<!-- 默认环境 -->
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>prod</id>
|
||||
<properties>
|
||||
<profiles.active>prod</profiles.active>
|
||||
<logging.level>warn</logging.level>
|
||||
<monitor.username>ruoyi</monitor.username>
|
||||
<monitor.password>123456</monitor.password>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<!-- 依赖声明 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.warm</groupId>
|
||||
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
|
||||
<version>${warm-flow.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.warm</groupId>
|
||||
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
|
||||
<version>${warm-flow.version}</version>
|
||||
</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-jsqlparser</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>
|
||||
|
||||
<!-- AWS SDK for Java 2.x -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<version>${aws.sdk.version}</version>
|
||||
</dependency>
|
||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3-transfer-manager</artifactId>
|
||||
<version>${aws.sdk.version}</version>
|
||||
</dependency>
|
||||
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</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>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.15.0</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>
|
||||
|
||||
<!-- PMS民宿管理系统模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-pms</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.version}</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>
|
||||
</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>
|
||||
|
||||
|
30
ruoyi-admin/Dockerfile
Normal file
30
ruoyi-admin/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
||||
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
|
||||
#FROM bellsoft/liberica-openjdk-debian:21.0.5-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
|
||||
# 工作流字体文件
|
||||
ADD ./zhFonts/ /usr/share/fonts/zhFonts/
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
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
|
||||
|
159
ruoyi-admin/pom.xml
Normal file
159
ruoyi-admin/pom.xml
Normal file
@ -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>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>
|
||||
|
||||
<!-- PMS民宿管理系统模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-pms</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,239 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import me.zhyd.oauth.request.AuthRequest;
|
||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
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 (!SystemConstants.NORMAL.equals(client.getStatus())) {
|
||||
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
|
||||
}
|
||||
// 校验租户
|
||||
loginService.checkTenant(loginBody.getTenantId());
|
||||
// 登录
|
||||
LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
|
||||
|
||||
Long userId = LoginHelper.getUserId();
|
||||
scheduledExecutorService.schedule(() -> {
|
||||
SseMessageDto dto = new SseMessageDto();
|
||||
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
|
||||
dto.setUserIds(List.of(userId));
|
||||
SseMessageUtils.publishMessage(dto);
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取跳转URL
|
||||
*
|
||||
* @param source 登录来源
|
||||
* @return 结果
|
||||
*/
|
||||
@GetMapping("/binding/{source}")
|
||||
public R<String> authBinding(@PathVariable("source") String source,
|
||||
@RequestParam String tenantId, @RequestParam String domain) {
|
||||
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return R.fail(source + "平台账号暂不支持");
|
||||
}
|
||||
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("tenantId", tenantId);
|
||||
map.put("domain", domain);
|
||||
map.put("state", AuthStateUtils.createState());
|
||||
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
|
||||
return R.ok("操作成功", authorizeUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端回调绑定授权(需要token)
|
||||
*
|
||||
* @param loginBody 请求体
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/social/callback")
|
||||
public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
|
||||
// 校验token
|
||||
StpUtil.checkLogin();
|
||||
// 获取第三方登录信息
|
||||
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
|
||||
loginBody.getSource(), loginBody.getSocialCode(),
|
||||
loginBody.getSocialState(), socialProperties);
|
||||
AuthUser authUserData = response.getData();
|
||||
// 判断授权响应是否成功
|
||||
if (!response.ok()) {
|
||||
return R.fail(response.getMsg());
|
||||
}
|
||||
loginService.socialRegister(authUserData);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 取消授权(需要token)
|
||||
*
|
||||
* @param socialId socialId
|
||||
*/
|
||||
@DeleteMapping(value = "/unlock/{socialId}")
|
||||
public R<Void> unlockSocial(@PathVariable Long socialId) {
|
||||
// 校验token
|
||||
StpUtil.checkLogin();
|
||||
Boolean rows = socialUserService.deleteWithValidById(socialId);
|
||||
return rows ? R.ok() : R.fail("取消授权失败");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public R<Void> logout() {
|
||||
loginService.logout();
|
||||
return R.ok("退出成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@ApiEncrypt
|
||||
@PostMapping("/register")
|
||||
public R<Void> register(@Validated @RequestBody RegisterBody user) {
|
||||
if (!configService.selectRegisterEnabled(user.getTenantId())) {
|
||||
return R.fail("当前系统没有开启注册功能!");
|
||||
}
|
||||
registerService.register(user);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录页面租户下拉框
|
||||
*
|
||||
* @return 租户列表
|
||||
*/
|
||||
@GetMapping("/tenant/list")
|
||||
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
|
||||
// 返回对象
|
||||
LoginTenantVo result = new LoginTenantVo();
|
||||
boolean enable = TenantHelper.isEnable();
|
||||
result.setTenantEnabled(enable);
|
||||
// 如果未开启租户这直接返回
|
||||
if (!enable) {
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
|
||||
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
|
||||
try {
|
||||
// 如果只超管返回所有租户
|
||||
if (LoginHelper.isSuperAdmin()) {
|
||||
result.setVoList(voList);
|
||||
return R.ok(result);
|
||||
}
|
||||
} catch (NotLoginException ignored) {
|
||||
}
|
||||
|
||||
// 获取域名
|
||||
String host;
|
||||
String referer = request.getHeader("referer");
|
||||
if (StringUtils.isNotBlank(referer)) {
|
||||
// 这里从referer中取值是为了本地使用hosts添加虚拟域名,方便本地环境调试
|
||||
host = referer.split("//")[1].split("/")[0];
|
||||
} else {
|
||||
host = new URL(request.getRequestURL().toString()).getHost();
|
||||
}
|
||||
// 根据域名进行筛选
|
||||
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
|
||||
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
|
||||
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
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.exception.ServiceException;
|
||||
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 邮箱
|
||||
*/
|
||||
@GetMapping("/resource/email/code")
|
||||
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
|
||||
if (!mailProperties.getEnabled()) {
|
||||
return R.fail("当前系统没有开启邮箱功能!");
|
||||
}
|
||||
SpringUtils.getAopProxy(this).emailCodeImpl(email);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱验证码
|
||||
* 独立方法避免验证码关闭之后仍然走限流
|
||||
*/
|
||||
@RateLimiter(key = "#email", time = 60, count = 1)
|
||||
public void emailCodeImpl(String email) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
try {
|
||||
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
|
||||
} catch (Exception e) {
|
||||
log.error("验证码短信发送异常 => {}", e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
@GetMapping("/auth/code")
|
||||
public R<CaptchaVo> getCode() {
|
||||
boolean captchaEnabled = captchaProperties.getEnable();
|
||||
if (!captchaEnabled) {
|
||||
CaptchaVo captchaVo = new CaptchaVo();
|
||||
captchaVo.setCaptchaEnabled(false);
|
||||
return R.ok(captchaVo);
|
||||
}
|
||||
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
* 独立方法避免验证码关闭之后仍然走限流
|
||||
*/
|
||||
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
||||
public CaptchaVo getCodeImpl() {
|
||||
// 保存验证码信息
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
CaptchaType captchaType = captchaProperties.getType();
|
||||
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 captchaVo = new CaptchaVo();
|
||||
captchaVo.setUuid(uuid);
|
||||
captchaVo.setImg(captcha.getImageBase64());
|
||||
return captchaVo;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 首页
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@SaIgnore
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class IndexController {
|
||||
|
||||
/**
|
||||
* 访问首页,提示语
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName());
|
||||
}
|
||||
|
||||
}
|
@ -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,251 @@
|
||||
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.SystemConstants;
|
||||
import org.dromara.common.core.constant.TenantConstants;
|
||||
import org.dromara.common.core.domain.dto.PostDTO;
|
||||
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.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 ISysPostService postService;
|
||||
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();
|
||||
Long userId = user.getUserId();
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(userId);
|
||||
loginUser.setDeptId(user.getDeptId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setNickname(user.getNickName());
|
||||
loginUser.setUserType(user.getUserType());
|
||||
loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
|
||||
loginUser.setRolePermission(permissionService.getRolePermission(userId));
|
||||
if (ObjectUtil.isNotNull(user.getDeptId())) {
|
||||
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
||||
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
|
||||
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
|
||||
}
|
||||
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
|
||||
List<SysPostVo> posts = postService.selectPostsByUserId(userId);
|
||||
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
||||
loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void recordLoginInfo(Long userId, String ip) {
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserId(userId);
|
||||
sysUser.setLoginIp(ip);
|
||||
sysUser.setLoginDate(DateUtils.getNowDate());
|
||||
sysUser.setUpdateBy(userId);
|
||||
DataPermissionHelper.ignore(() -> userMapper.updateById(sysUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录校验
|
||||
*/
|
||||
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
|
||||
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
|
||||
String loginFail = Constants.LOGIN_FAIL;
|
||||
|
||||
// 获取用户登录错误次数,默认为0 (可自定义限制策略 例如: key + username + ip)
|
||||
int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
|
||||
// 锁定时间内登录 则踢出
|
||||
if (errorNumber >= maxRetryCount) {
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
if (supplier.get()) {
|
||||
// 错误次数递增
|
||||
errorNumber++;
|
||||
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
|
||||
// 达到规定错误次数 则锁定登录
|
||||
if (errorNumber >= maxRetryCount) {
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
} else {
|
||||
// 未达到规定错误次数
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
|
||||
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录成功 清空错误次数
|
||||
RedisUtils.deleteObject(errorKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验租户
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public void checkTenant(String tenantId) {
|
||||
if (!TenantHelper.isEnable()) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isBlank(tenantId)) {
|
||||
throw new TenantException("tenant.number.not.blank");
|
||||
}
|
||||
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
|
||||
return;
|
||||
}
|
||||
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
|
||||
if (ObjectUtil.isNull(tenant)) {
|
||||
log.info("登录租户:{} 不存在.", tenantId);
|
||||
throw new TenantException("tenant.not.exists");
|
||||
} else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
|
||||
log.info("登录租户:{} 已被停用.", tenantId);
|
||||
throw new TenantException("tenant.blocked");
|
||||
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
|
||||
&& new Date().after(tenant.getExpireTime())) {
|
||||
log.info("登录租户:{} 已超过有效期.", tenantId);
|
||||
throw new TenantException("tenant.expired");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param username 用户名
|
||||
* @param status 状态
|
||||
* @param message 消息内容
|
||||
* @return
|
||||
*/
|
||||
private void recordLogininfor(String tenantId, String username, String status, String message) {
|
||||
LogininforEvent logininforEvent = new LogininforEvent();
|
||||
logininforEvent.setTenantId(tenantId);
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
}
|
||||
|
||||
}
|
@ -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.constant.SystemConstants;
|
||||
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.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 (SystemConstants.DISABLE.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.constant.SystemConstants;
|
||||
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.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 (SystemConstants.DISABLE.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.constant.SystemConstants;
|
||||
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.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 (SystemConstants.DISABLE.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.constant.SystemConstants;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
import org.dromara.common.core.domain.model.SocialLoginBody;
|
||||
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 (SystemConstants.DISABLE.equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", "");
|
||||
throw new UserException("user.blocked", "");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
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 me.zhyd.oauth.config.AuthConfig;
|
||||
import me.zhyd.oauth.model.AuthCallback;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthToken;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import me.zhyd.oauth.request.AuthRequest;
|
||||
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.domain.model.XcxLoginBody;
|
||||
import org.dromara.common.core.domain.model.XcxLoginUser;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
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.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();
|
||||
|
||||
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
|
||||
AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
|
||||
.clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
|
||||
.ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
|
||||
AuthCallback authCallback = new AuthCallback();
|
||||
authCallback.setCode(xcxCode);
|
||||
AuthResponse<AuthUser> resp = authRequest.login(authCallback);
|
||||
String openid, unionId;
|
||||
if (resp.ok()) {
|
||||
AuthToken token = resp.getData().getToken();
|
||||
openid = token.getOpenId();
|
||||
// 微信小程序只有关联到微信开放平台下之后才能获取到 unionId,因此unionId不一定能返回。
|
||||
unionId = token.getUnionId();
|
||||
} else {
|
||||
throw new ServiceException(resp.getMsg());
|
||||
}
|
||||
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
|
||||
SysUserVo user = loadUserByOpenid(openid);
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
XcxLoginUser loginUser = new XcxLoginUser();
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(user.getUserId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setNickname(user.getNickName());
|
||||
loginUser.setUserType(user.getUserType());
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
loginUser.setOpenid(openid);
|
||||
|
||||
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 (SystemConstants.DISABLE.equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", openid);
|
||||
// todo 用户已被停用 业务逻辑自行实现
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
265
ruoyi-admin/src/main/resources/application-dev.yml
Normal file
265
ruoyi-admin/src/main/resources/application-dev.yml
Normal file
@ -0,0 +1,265 @@
|
||||
--- # 监控中心配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: false
|
||||
url: http://localhost:9090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
metadata:
|
||||
username: ${spring.boot.admin.client.username}
|
||||
userpassword: ${spring.boot.admin.client.password}
|
||||
username: @monitor.username@
|
||||
password: @monitor.password@
|
||||
|
||||
--- # snail-job 配置
|
||||
snail-job:
|
||||
enabled: false
|
||||
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
|
||||
group: "ruoyi_group"
|
||||
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表
|
||||
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 17888
|
||||
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
|
||||
namespace: ${spring.profiles.active}
|
||||
# 随主应用端口漂移
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
|
||||
dynamic:
|
||||
# 性能分析插件(有性能损耗 不建议生产环境使用)
|
||||
p6spy: true
|
||||
# 设置默认的数据源或者数据源组,默认值即为 master
|
||||
primary: master
|
||||
# 严格模式 匹配不到数据源则报错
|
||||
strict: true
|
||||
datasource:
|
||||
# 主库数据源
|
||||
master:
|
||||
type: ${spring.datasource.type}
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
|
||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||
url: jdbc:mysql://111.229.149.206:3306/ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: ruoyi
|
||||
password: 4yHmmhKyRYNWeRWk
|
||||
# # 从库数据源
|
||||
# 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: ''
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
# 是否开启ssl
|
||||
ssl.enabled: false
|
||||
|
||||
# redisson 配置
|
||||
redisson:
|
||||
# redis key前缀
|
||||
keyPrefix:
|
||||
# 线程池数量
|
||||
threads: 4
|
||||
# Netty线程池数量
|
||||
nettyThreads: 8
|
||||
# 单节点配置
|
||||
singleServerConfig:
|
||||
# 客户端名称 不能用中文
|
||||
clientName: RuoYi-Vue-Plus
|
||||
# 最小空闲连接数
|
||||
connectionMinimumIdleSize: 8
|
||||
# 连接池大小
|
||||
connectionPoolSize: 32
|
||||
# 连接空闲超时,单位:毫秒
|
||||
idleConnectionTimeout: 10000
|
||||
# 命令等待超时,单位:毫秒
|
||||
timeout: 3000
|
||||
# 发布和订阅连接池大小
|
||||
subscriptionConnectionPoolSize: 50
|
||||
|
||||
--- # mail 邮件发送
|
||||
mail:
|
||||
enabled: false
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
# 是否需要用户名密码验证
|
||||
auth: true
|
||||
# 发送方,遵循RFC-822标准
|
||||
from: xxx@163.com
|
||||
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
|
||||
user: xxx@163.com
|
||||
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
|
||||
pass: xxxxxxxxxx
|
||||
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
|
||||
starttlsEnable: true
|
||||
# 使用SSL安全连接
|
||||
sslEnable: true
|
||||
# SMTP超时时长,单位毫秒,缺省值不超时
|
||||
timeout: 0
|
||||
# Socket连接超时值,单位毫秒,缺省值不超时
|
||||
connectionTimeout: 0
|
||||
|
||||
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
|
||||
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
|
||||
sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
# 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
|
||||
restricted: true
|
||||
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
|
||||
minute-max: 1
|
||||
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
|
||||
account-max: 30
|
||||
# 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
|
||||
blends:
|
||||
# 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
|
||||
# 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
|
||||
config1:
|
||||
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: alibaba
|
||||
# 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。
|
||||
access-key-id: 您的accessKey
|
||||
# 称为accessSecret有些称之为apiSecret
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
config2:
|
||||
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: tencent
|
||||
access-key-id: 您的accessKey
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
|
||||
|
||||
--- # 三方授权
|
||||
justauth:
|
||||
# 前端外网访问地址
|
||||
address: http://localhost:80
|
||||
type:
|
||||
maxkey:
|
||||
# maxkey 服务器地址
|
||||
# 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
|
||||
server-url: http://sso.maxkey.top
|
||||
client-id: 876892492581044224
|
||||
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
|
||||
redirect-uri: ${justauth.address}/social-callback?source=maxkey
|
||||
topiam:
|
||||
# topiam 服务器地址
|
||||
server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
|
||||
client-id: 449c4*********937************759
|
||||
client-secret: ac7***********1e0************28d
|
||||
redirect-uri: ${justauth.address}/social-callback?source=topiam
|
||||
scopes: [openid, email, phone, profile]
|
||||
qq:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=qq
|
||||
union-id: false
|
||||
weibo:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=weibo
|
||||
gitee:
|
||||
client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
|
||||
client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitee
|
||||
dingtalk:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=dingtalk
|
||||
baidu:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=baidu
|
||||
csdn:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=csdn
|
||||
coding:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=coding
|
||||
coding-group-name: xx
|
||||
oschina:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=oschina
|
||||
alipay_wallet:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
|
||||
alipay-public-key: MIIB**************DAQAB
|
||||
wechat_open:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_open
|
||||
wechat_mp:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
|
||||
wechat_enterprise:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
|
||||
agent-id: 1000002
|
||||
gitlab:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitlab
|
267
ruoyi-admin/src/main/resources/application-prod.yml
Normal file
267
ruoyi-admin/src/main/resources/application-prod.yml
Normal file
@ -0,0 +1,267 @@
|
||||
--- # 临时文件存储位置 避免临时文件被系统清理报错
|
||||
spring.servlet.multipart.location: /ruoyi/server/temp
|
||||
|
||||
--- # 监控中心配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: true
|
||||
url: http://localhost:9090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
metadata:
|
||||
username: ${spring.boot.admin.client.username}
|
||||
userpassword: ${spring.boot.admin.client.password}
|
||||
username: @monitor.username@
|
||||
password: @monitor.password@
|
||||
|
||||
--- # snail-job 配置
|
||||
snail-job:
|
||||
enabled: true
|
||||
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
|
||||
group: "ruoyi_group"
|
||||
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表
|
||||
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 17888
|
||||
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
|
||||
namespace: ${spring.profiles.active}
|
||||
# 随主应用端口漂移
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
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-Vue-Plus
|
||||
# 最小空闲连接数
|
||||
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
|
283
ruoyi-admin/src/main/resources/application.yml
Normal file
283
ruoyi-admin/src/main/resources/application.yml
Normal file
@ -0,0 +1,283 @@
|
||||
# 开发环境配置
|
||||
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
|
||||
|
||||
captcha:
|
||||
enable: true
|
||||
# 页面 <参数设置> 可开启关闭 验证码校验
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
type: MATH
|
||||
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
||||
category: CIRCLE
|
||||
# 数字验证码位数
|
||||
numberLength: 1
|
||||
# 字符验证码长度
|
||||
charLength: 4
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
org.dromara: @logging.level@
|
||||
org.springframework: warn
|
||||
org.mybatis.spring.mapper: error
|
||||
org.apache.fury: warn
|
||||
config: classpath:logback-plus.xml
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
# 密码最大错误次数
|
||||
maxRetryCount: 5
|
||||
# 密码锁定时间(默认10分钟)
|
||||
lockTime: 10
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
application:
|
||||
name: RuoYi-Vue-Plus
|
||||
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
|
||||
- /*/api-docs
|
||||
- /*/api-docs/**
|
||||
- /warm-flow-ui/token-name
|
||||
|
||||
# 多租户配置
|
||||
tenant:
|
||||
# 是否开启
|
||||
enable: true
|
||||
# 排除表
|
||||
excludes:
|
||||
- sys_menu
|
||||
- sys_tenant
|
||||
- sys_tenant_package
|
||||
- sys_role_dept
|
||||
- sys_role_menu
|
||||
- sys_user_post
|
||||
- sys_user_role
|
||||
- sys_client
|
||||
- sys_oss_config
|
||||
|
||||
# MyBatisPlus配置
|
||||
# https://baomidou.com/config/
|
||||
mybatis-plus:
|
||||
# 自定义配置 是否全局开启逻辑删除 关闭后 所有逻辑删除功能将失效
|
||||
enableLogicDelete: true
|
||||
# 多包名使用 例如 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
|
||||
info:
|
||||
# 标题
|
||||
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
|
||||
# 描述
|
||||
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.workflow
|
||||
- group: 6.PMS民宿管理模块
|
||||
packages-to-scan: org.dromara.pms
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
# 排除链接(多个用逗号分隔)
|
||||
excludeUrls:
|
||||
- /system/notice
|
||||
|
||||
# 全局线程池相关配置
|
||||
# 如使用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: '*'
|
||||
|
||||
--- # warm-flow工作流配置
|
||||
warm-flow:
|
||||
# 是否开启工作流,默认true
|
||||
enabled: true
|
||||
# 是否开启设计器ui
|
||||
ui: true
|
||||
# 默认Authorization,如果有多个token,用逗号分隔
|
||||
token-name: ${sa-token.token-name},clientid
|
||||
# 流程状态对应的三元色
|
||||
chart-status-color:
|
||||
## 未办理
|
||||
- 62,62,62
|
||||
## 待办理
|
||||
- 255,205,23
|
||||
## 已办理
|
||||
- 157,255,0
|
8
ruoyi-admin/src/main/resources/banner.txt
Normal file
8
ruoyi-admin/src/main/resources/banner.txt
Normal file
@ -0,0 +1,8 @@
|
||||
Application Version: ${revision}
|
||||
Spring Boot Version: ${spring-boot.version}
|
||||
__________ _____.___.__ ____ ____ __________.__
|
||||
\______ \__ __ ____\__ | |__| \ \ / /_ __ ____ \______ \ | __ __ ______
|
||||
| _/ | \/ _ \/ | | | ______ \ Y / | \_/ __ \ ______ | ___/ | | | \/ ___/
|
||||
| | \ | ( <_> )____ | | /_____/ \ /| | /\ ___/ /_____/ | | | |_| | /\___ \
|
||||
|____|_ /____/ \____// ______|__| \___/ |____/ \___ > |____| |____/____//____ >
|
||||
\/ \/ \/ \/
|
61
ruoyi-admin/src/main/resources/i18n/messages.properties
Normal file
61
ruoyi-admin/src/main/resources/i18n/messages.properties
Normal file
@ -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=对不起,您的租户已过期,请联系管理员
|
BIN
ruoyi-admin/src/main/resources/ip2region.xdb
Normal file
BIN
ruoyi-admin/src/main/resources/ip2region.xdb
Normal file
Binary file not shown.
129
ruoyi-admin/src/main/resources/logback-plus.xml
Normal file
129
ruoyi-admin/src/main/resources/logback-plus.xml
Normal file
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="log.path" value="./logs"/>
|
||||
<property name="console.log.pattern"
|
||||
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
|
||||
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${console.log.pattern}</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/sys-console.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大 1天 -->
|
||||
<maxHistory>1</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/sys-info.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</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);
|
||||
}
|
||||
|
||||
}
|
70
ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java
Normal file
70
ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java
Normal file
@ -0,0 +1,70 @@
|
||||
package org.dromara.test;
|
||||
|
||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 单元测试案例
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@SpringBootTest // 此注解只能在 springboot 主包下使用 需包含 main 方法与 yml 配置文件
|
||||
@DisplayName("单元测试案例")
|
||||
public class DemoUnitTest {
|
||||
|
||||
@Autowired
|
||||
private CaptchaProperties captchaProperties;
|
||||
|
||||
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
|
||||
@Test
|
||||
public void testTest() {
|
||||
System.out.println(captchaProperties);
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@DisplayName("测试 @Disabled 注解")
|
||||
@Test
|
||||
public void testDisabled() {
|
||||
System.out.println(captchaProperties);
|
||||
}
|
||||
|
||||
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
|
||||
@DisplayName("测试 @Timeout 注解")
|
||||
@Test
|
||||
public void testTimeout() throws InterruptedException {
|
||||
Thread.sleep(3000);
|
||||
System.out.println(captchaProperties);
|
||||
}
|
||||
|
||||
|
||||
@DisplayName("测试 @RepeatedTest 注解")
|
||||
@RepeatedTest(3)
|
||||
public void testRepeatedTest() {
|
||||
System.out.println(666);
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void testBeforeAll() {
|
||||
System.out.println("@BeforeAll ==================");
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void testBeforeEach() {
|
||||
System.out.println("@BeforeEach ==================");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void testAfterEach() {
|
||||
System.out.println("@AfterEach ==================");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void testAfterAll() {
|
||||
System.out.println("@AfterAll ==================");
|
||||
}
|
||||
|
||||
}
|
@ -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 ==================");
|
||||
}
|
||||
|
||||
|
||||
}
|
54
ruoyi-admin/src/test/java/org/dromara/test/TagUnitTest.java
Normal file
54
ruoyi-admin/src/test/java/org/dromara/test/TagUnitTest.java
Normal file
@ -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 ==================");
|
||||
}
|
||||
|
||||
|
||||
}
|
1
ruoyi-admin/zhFonts/.uuid
Normal file
1
ruoyi-admin/zhFonts/.uuid
Normal file
@ -0,0 +1 @@
|
||||
3f2ee348-0303-40ca-bf03-03f48d2d2141
|
BIN
ruoyi-admin/zhFonts/SIMSUN.TTC
Normal file
BIN
ruoyi-admin/zhFonts/SIMSUN.TTC
Normal file
Binary file not shown.
4
ruoyi-admin/zhFonts/fonts.dir
Normal file
4
ruoyi-admin/zhFonts/fonts.dir
Normal file
@ -0,0 +1,4 @@
|
||||
3
|
||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
|
||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
|
||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
|
4
ruoyi-admin/zhFonts/fonts.scale
Normal file
4
ruoyi-admin/zhFonts/fonts.scale
Normal file
@ -0,0 +1,4 @@
|
||||
3
|
||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
|
||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
|
||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
|
46
ruoyi-common/pom.xml
Normal file
46
ruoyi-common/pom.xml
Normal file
@ -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>
|
185
ruoyi-common/ruoyi-common-bom/pom.xml
Normal file
185
ruoyi-common/ruoyi-common-bom/pom.xml
Normal file
@ -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.3.1</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>
|
99
ruoyi-common/ruoyi-common-core/pom.xml
Normal file
99
ruoyi-common/ruoyi-common-core/pom.xml
Normal file
@ -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,87 @@
|
||||
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.SpringUtils;
|
||||
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.core.task.VirtualThreadTaskExecutor;
|
||||
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() {
|
||||
// daemon 必须为 true
|
||||
BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
|
||||
if (SpringUtils.isVirtual()) {
|
||||
builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
|
||||
} else {
|
||||
builder.namingPattern("schedule-pool-%d");
|
||||
}
|
||||
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
|
||||
builder.build(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()) {
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
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,41 @@
|
||||
package org.dromara.common.core.config;
|
||||
|
||||
import jakarta.validation.Validator;
|
||||
import org.hibernate.validator.HibernateValidator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 校验框架配置类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@AutoConfiguration(before = ValidationAutoConfiguration.class)
|
||||
public class ValidatorConfig {
|
||||
|
||||
/**
|
||||
* 配置校验框架 快速失败模式
|
||||
*/
|
||||
@Bean
|
||||
public Validator validator(MessageSource messageSource) {
|
||||
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
|
||||
// 国际化
|
||||
factoryBean.setValidationMessageSource(messageSource);
|
||||
// 设置使用 HibernateValidator 校验器
|
||||
factoryBean.setProviderClass(HibernateValidator.class);
|
||||
Properties properties = new Properties();
|
||||
// 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
|
||||
properties.setProperty("hibernate.validator.fail_fast", "true");
|
||||
factoryBean.setValidationProperties(properties);
|
||||
// 加载配置
|
||||
factoryBean.afterPropertiesSet();
|
||||
return factoryBean.getValidator();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,88 @@
|
||||
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_DICT_TYPE = "sys_dict_type";
|
||||
|
||||
/**
|
||||
* 租户
|
||||
*/
|
||||
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
|
||||
|
||||
/**
|
||||
* 客户端
|
||||
*/
|
||||
String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
|
||||
|
||||
/**
|
||||
* 用户账户
|
||||
*/
|
||||
String SYS_USER_NAME = "sys_user_name#30d";
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
String SYS_NICKNAME = "sys_nickname#30d";
|
||||
|
||||
/**
|
||||
* 部门
|
||||
*/
|
||||
String SYS_DEPT = "sys_dept#30d";
|
||||
|
||||
/**
|
||||
* OSS内容
|
||||
*/
|
||||
String SYS_OSS = "sys_oss#30d";
|
||||
|
||||
/**
|
||||
* 角色自定义权限
|
||||
*/
|
||||
String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
|
||||
|
||||
/**
|
||||
* 部门及以下权限
|
||||
*/
|
||||
String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
|
||||
|
||||
/**
|
||||
* OSS配置
|
||||
*/
|
||||
String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
|
||||
|
||||
/**
|
||||
* 在线用户
|
||||
*/
|
||||
String ONLINE_TOKEN = "online_tokens";
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 顶级父级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,59 @@
|
||||
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_]*$";
|
||||
|
||||
/**
|
||||
* 权限标识必须符合以下格式:
|
||||
* 1. 标准格式:xxx:yyy:zzz
|
||||
* - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*`
|
||||
* - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*`
|
||||
* - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*`
|
||||
* 2. 允许空字符串(""),表示没有权限标识
|
||||
*/
|
||||
String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
|
||||
|
||||
/**
|
||||
* 身份证号码(后6位)
|
||||
*/
|
||||
String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
|
||||
|
||||
/**
|
||||
* QQ号码
|
||||
*/
|
||||
String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
|
||||
|
||||
/**
|
||||
* 邮政编码
|
||||
*/
|
||||
String POSTAL_CODE = "^[1-9]\\d{5}$";
|
||||
|
||||
/**
|
||||
* 注册账号
|
||||
*/
|
||||
String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
|
||||
|
||||
/**
|
||||
* 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
|
||||
*/
|
||||
String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
|
||||
|
||||
/**
|
||||
* 通用状态(0表示正常,1表示停用)
|
||||
*/
|
||||
String STATUS = "^[01]$";
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package org.dromara.common.core.constant;
|
||||
|
||||
/**
|
||||
* 系统常量信息
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface SystemConstants {
|
||||
|
||||
/**
|
||||
* 正常状态
|
||||
*/
|
||||
String NORMAL = "0";
|
||||
|
||||
/**
|
||||
* 异常状态
|
||||
*/
|
||||
String DISABLE = "1";
|
||||
|
||||
/**
|
||||
* 是否为系统默认(是)
|
||||
*/
|
||||
String YES = "Y";
|
||||
|
||||
/**
|
||||
* 是否为系统默认(否)
|
||||
*/
|
||||
String NO = "N";
|
||||
|
||||
/**
|
||||
* 是否菜单外链(是)
|
||||
*/
|
||||
String YES_FRAME = "0";
|
||||
|
||||
/**
|
||||
* 是否菜单外链(否)
|
||||
*/
|
||||
String NO_FRAME = "1";
|
||||
|
||||
/**
|
||||
* 菜单类型(目录)
|
||||
*/
|
||||
String TYPE_DIR = "M";
|
||||
|
||||
/**
|
||||
* 菜单类型(菜单)
|
||||
*/
|
||||
String TYPE_MENU = "C";
|
||||
|
||||
/**
|
||||
* 菜单类型(按钮)
|
||||
*/
|
||||
String TYPE_BUTTON = "F";
|
||||
|
||||
/**
|
||||
* Layout组件标识
|
||||
*/
|
||||
String LAYOUT = "Layout";
|
||||
|
||||
/**
|
||||
* ParentView组件标识
|
||||
*/
|
||||
String PARENT_VIEW = "ParentView";
|
||||
|
||||
/**
|
||||
* InnerLink组件标识
|
||||
*/
|
||||
String INNER_LINK = "InnerLink";
|
||||
|
||||
/**
|
||||
* 超级管理员ID
|
||||
*/
|
||||
Long SUPER_ADMIN_ID = 1L;
|
||||
|
||||
/**
|
||||
* 根部门祖级列表
|
||||
*/
|
||||
String ROOT_DEPT_ANCESTORS = "0";
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package org.dromara.common.core.constant;
|
||||
|
||||
/**
|
||||
* 租户常量信息
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface TenantConstants {
|
||||
|
||||
/**
|
||||
* 超级管理员ID
|
||||
*/
|
||||
Long SUPER_ADMIN_ID = 1L;
|
||||
|
||||
/**
|
||||
* 超级管理员角色 roleKey
|
||||
*/
|
||||
String SUPER_ADMIN_ROLE_KEY = "superadmin";
|
||||
|
||||
/**
|
||||
* 租户管理员角色 roleKey
|
||||
*/
|
||||
String TENANT_ADMIN_ROLE_KEY = "admin";
|
||||
|
||||
/**
|
||||
* 租户管理员角色名称
|
||||
*/
|
||||
String TENANT_ADMIN_ROLE_NAME = "管理员";
|
||||
|
||||
/**
|
||||
* 默认租户ID
|
||||
*/
|
||||
String DEFAULT_TENANT_ID = "000000";
|
||||
|
||||
}
|
@ -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,71 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 办理任务请求对象
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Data
|
||||
public class CompleteTaskDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 任务id
|
||||
*/
|
||||
private Long taskId;
|
||||
|
||||
/**
|
||||
* 附件id
|
||||
*/
|
||||
private String fileId;
|
||||
|
||||
/**
|
||||
* 抄送人员
|
||||
*/
|
||||
private List<FlowCopyDTO> flowCopyList;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private List<String> messageType;
|
||||
|
||||
/**
|
||||
* 办理意见
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 消息通知
|
||||
*/
|
||||
private String notice;
|
||||
|
||||
/**
|
||||
* 流程变量
|
||||
*/
|
||||
private Map<String, Object> variables;
|
||||
|
||||
/**
|
||||
* 扩展变量(此处为逗号分隔的ossId)
|
||||
*/
|
||||
private String ext;
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
}
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 部门
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class DeptDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 父部门ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 字典数据DTO
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class DictDataDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 字典标签
|
||||
*/
|
||||
private String dictLabel;
|
||||
|
||||
/**
|
||||
* 字典键值
|
||||
*/
|
||||
private String dictValue;
|
||||
|
||||
/**
|
||||
* 是否默认(Y是 N否)
|
||||
*/
|
||||
private String isDefault;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 字典类型DTO
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class DictTypeDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 字典主键
|
||||
*/
|
||||
private Long dictId;
|
||||
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
private String dictName;
|
||||
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
private String dictType;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* 抄送
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Data
|
||||
public class FlowCopyDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
}
|
@ -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,46 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 岗位
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class PostDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 岗位ID
|
||||
*/
|
||||
private Long postId;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 岗位编码
|
||||
*/
|
||||
private String postCode;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
private String postName;
|
||||
|
||||
/**
|
||||
* 岗位类别编码
|
||||
*/
|
||||
private String postCategory;
|
||||
|
||||
}
|
@ -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:仅本人数据权限 6:部门及以下或本人数据权限)
|
||||
*/
|
||||
private String dataScope;
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 启动流程对象
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Data
|
||||
public class StartProcessDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 业务唯一值id
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
|
||||
*/
|
||||
private Map<String, Object> variables;
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
}
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 启动流程返回对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
public class StartProcessReturnDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 流程实例id
|
||||
*/
|
||||
private Long processInstanceId;
|
||||
|
||||
/**
|
||||
* 任务id
|
||||
*/
|
||||
private Long taskId;
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 任务受让人
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class TaskAssigneeDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 总大小
|
||||
*/
|
||||
private Long total = 0L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private List<TaskHandler> list;
|
||||
|
||||
public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
|
||||
this.total = total;
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将源列表转换为 TaskHandler 列表
|
||||
*
|
||||
* @param <T> 通用类型
|
||||
* @param sourceList 待转换的源列表
|
||||
* @param storageId 提取 storageId 的函数
|
||||
* @param handlerCode 提取 handlerCode 的函数
|
||||
* @param handlerName 提取 handlerName 的函数
|
||||
* @param groupName 提取 groupName 的函数
|
||||
* @param createTimeMapper 提取 createTime 的函数
|
||||
* @return 转换后的 TaskHandler 列表
|
||||
*/
|
||||
public static <T> List<TaskHandler> convertToHandlerList(
|
||||
List<T> sourceList,
|
||||
Function<T, Long> storageId,
|
||||
Function<T, String> handlerCode,
|
||||
Function<T, String> handlerName,
|
||||
Function<T, Long> groupName,
|
||||
Function<T, Date> createTimeMapper) {
|
||||
return sourceList.stream()
|
||||
.map(item -> new TaskHandler(
|
||||
String.valueOf(storageId.apply(item)),
|
||||
handlerCode.apply(item),
|
||||
handlerName.apply(item),
|
||||
groupName != null ? String.valueOf(groupName.apply(item)) : null,
|
||||
createTimeMapper.apply(item)
|
||||
)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class TaskHandler {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
private String storageId;
|
||||
|
||||
/**
|
||||
* 权限编码
|
||||
*/
|
||||
private String handlerCode;
|
||||
|
||||
/**
|
||||
* 权限名称
|
||||
*/
|
||||
private String handlerName;
|
||||
|
||||
/**
|
||||
* 权限分组
|
||||
*/
|
||||
private String groupName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
|
||||
}
|
@ -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,44 @@
|
||||
package org.dromara.common.core.domain.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 流程创建任务监听
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Data
|
||||
public class ProcessCreateTaskEvent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 审批节点编码
|
||||
*/
|
||||
private String nodeCode;
|
||||
|
||||
/**
|
||||
* 任务id
|
||||
*/
|
||||
private Long taskId;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package org.dromara.common.core.domain.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 删除流程监听
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
public class ProcessDeleteEvent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package org.dromara.common.core.domain.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 总体流程监听
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Data
|
||||
public class ProcessEvent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 办理参数
|
||||
*/
|
||||
private Map<String, Object> params;
|
||||
|
||||
/**
|
||||
* 当为true时为申请人节点办理
|
||||
*/
|
||||
private boolean submit;
|
||||
|
||||
}
|
@ -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,148 @@
|
||||
package org.dromara.common.core.domain.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.domain.dto.PostDTO;
|
||||
import org.dromara.common.core.domain.dto.RoleDTO;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 岗位对象
|
||||
*/
|
||||
private List<PostDTO> posts;
|
||||
|
||||
/**
|
||||
* 数据权限 当前角色ID
|
||||
*/
|
||||
private Long roleId;
|
||||
|
||||
/**
|
||||
* 客户端
|
||||
*/
|
||||
private String clientKey;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
*/
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* 获取登录id
|
||||
*/
|
||||
public String getLoginId() {
|
||||
if (userType == null) {
|
||||
throw new IllegalArgumentException("用户类型不能为空");
|
||||
}
|
||||
if (userId == null) {
|
||||
throw new IllegalArgumentException("用户ID不能为空");
|
||||
}
|
||||
return userType + ":" + userId;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.dromara.common.core.domain.model;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
/**
|
||||
* 密码登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PasswordLoginBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "{user.username.not.blank}")
|
||||
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@NotBlank(message = "{user.password.not.blank}")
|
||||
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||
private String password;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* 用户注册对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RegisterBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "{user.username.not.blank}")
|
||||
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@NotBlank(message = "{user.password.not.blank}")
|
||||
@Length(min = 5, max = 20, 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,56 @@
|
||||
package org.dromara.common.core.domain.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 任务受让人
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class TaskAssigneeBody implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 权限编码
|
||||
*/
|
||||
private String handlerCode;
|
||||
|
||||
/**
|
||||
* 权限名称
|
||||
*/
|
||||
private String handlerName;
|
||||
|
||||
/**
|
||||
* 权限分组
|
||||
*/
|
||||
private String groupId;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private String beginTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private String endTime;
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页显示条数
|
||||
*/
|
||||
private Integer pageSize = 10;
|
||||
|
||||
}
|
@ -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,215 @@
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 业务状态枚举
|
||||
*
|
||||
* @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;
|
||||
|
||||
private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
|
||||
.collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
|
||||
|
||||
/**
|
||||
* 根据状态获取对应的 BusinessStatusEnum 枚举
|
||||
*
|
||||
* @param status 业务状态码
|
||||
* @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null
|
||||
*/
|
||||
public static BusinessStatusEnum getByStatus(String status) {
|
||||
// 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null
|
||||
return STATUS_MAP.get(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态获取对应的业务状态描述信息
|
||||
*
|
||||
* @param status 业务状态码
|
||||
* @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串
|
||||
*/
|
||||
public static String findByStatus(String status) {
|
||||
if (StringUtils.isBlank(status)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
|
||||
return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为指定的状态之一:草稿、已撤销或已退回
|
||||
*
|
||||
* @param status 要检查的状态
|
||||
* @return 如果状态为草稿、已撤销或已退回之一,则返回 true;否则返回 false
|
||||
*/
|
||||
public static boolean isDraftOrCancelOrBack(String status) {
|
||||
return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为撤销,退回,作废,终止
|
||||
*
|
||||
* @param status status
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean initialState(String status) {
|
||||
return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行中的实例状态列表
|
||||
*
|
||||
* @return 包含运行中实例状态的不可变列表
|
||||
* (包含 DRAFT、WAITING、BACK 和 CANCEL 状态)
|
||||
*/
|
||||
public static List<String> runningStatus() {
|
||||
return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结束实例的状态列表
|
||||
*
|
||||
* @return 包含结束实例状态的不可变列表
|
||||
* (包含 FINISH、INVALID 和 TERMINATION 状态)
|
||||
*/
|
||||
public static List<String> finishStatus() {
|
||||
return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动流程校验
|
||||
*
|
||||
* @param status 状态
|
||||
*/
|
||||
public static void checkStartStatus(String status) {
|
||||
if (WAITING.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已提交过申请,正在审批中!");
|
||||
} else if (FINISH.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已完成申请!");
|
||||
} else if (INVALID.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已作废!");
|
||||
} else if (TERMINATION.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已终止!");
|
||||
} else if (StringUtils.isBlank(status)) {
|
||||
throw new ServiceException("流程状态为空!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销流程校验
|
||||
*
|
||||
* @param status 状态
|
||||
*/
|
||||
public static void checkCancelStatus(String status) {
|
||||
if (CANCEL.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已撤销!");
|
||||
} else if (FINISH.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已完成申请!");
|
||||
} else if (INVALID.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已作废!");
|
||||
} else if (TERMINATION.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已终止!");
|
||||
} else if (BACK.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已退回!");
|
||||
} else if (StringUtils.isBlank(status)) {
|
||||
throw new ServiceException("流程状态为空!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回流程校验
|
||||
*
|
||||
* @param status 状态
|
||||
*/
|
||||
public static void checkBackStatus(String status) {
|
||||
if (BACK.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已退回!");
|
||||
} else if (FINISH.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已完成申请!");
|
||||
} else if (INVALID.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已作废!");
|
||||
} else if (TERMINATION.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已终止!");
|
||||
} else if (CANCEL.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已撤销!");
|
||||
} else if (StringUtils.isBlank(status)) {
|
||||
throw new ServiceException("流程状态为空!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 作废,终止流程校验
|
||||
*
|
||||
* @param status 状态
|
||||
*/
|
||||
public static void checkInvalidStatus(String status) {
|
||||
if (FINISH.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已完成申请!");
|
||||
} else if (INVALID.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已作废!");
|
||||
} else if (TERMINATION.getStatus().equals(status)) {
|
||||
throw new ServiceException("该单据已终止!");
|
||||
} else if (StringUtils.isBlank(status)) {
|
||||
throw new ServiceException("流程状态为空!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,146 @@
|
||||
package org.dromara.common.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
|
||||
/*
|
||||
* 日期格式
|
||||
* "yyyy":4位数的年份,例如:2023年表示为"2023"。
|
||||
* "yy":2位数的年份,例如:2023年表示为"23"。
|
||||
* "MM":2位数的月份,取值范围为01到12,例如:7月表示为"07"。
|
||||
* "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。
|
||||
* "dd":2位数的日期,取值范围为01到31,例如:22日表示为"22"。
|
||||
* "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。
|
||||
* "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
|
||||
* "E":星期的缩写,例如:星期三表示为"Wed"。
|
||||
* "DDD" 或 "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。
|
||||
* 时间格式
|
||||
* "HH":24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。
|
||||
* "hh":12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。
|
||||
* "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。
|
||||
* "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。
|
||||
* "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 日期格式与时间格式枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum FormatsType {
|
||||
|
||||
/**
|
||||
* 例如:2023年表示为"23"
|
||||
*/
|
||||
YY("yy"),
|
||||
|
||||
/**
|
||||
* 例如:2023年表示为"2023"
|
||||
*/
|
||||
YYYY("yyyy"),
|
||||
|
||||
/**
|
||||
* 例例如,2023年7月可以表示为 "2023-07"
|
||||
*/
|
||||
YYYY_MM("yyyy-MM"),
|
||||
|
||||
/**
|
||||
* 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22"
|
||||
*/
|
||||
YYYY_MM_DD("yyyy-MM-dd"),
|
||||
|
||||
/**
|
||||
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
|
||||
*/
|
||||
YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
|
||||
|
||||
/**
|
||||
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
|
||||
*/
|
||||
YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
|
||||
|
||||
/**
|
||||
* 例如:下午3点30分45秒,表示为 "15:30:45"
|
||||
*/
|
||||
HH_MM_SS("HH:mm:ss"),
|
||||
|
||||
/**
|
||||
* 例例如,2023年7月可以表示为 "2023/07"
|
||||
*/
|
||||
YYYY_MM_SLASH("yyyy/MM"),
|
||||
|
||||
/**
|
||||
* 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22"
|
||||
*/
|
||||
YYYY_MM_DD_SLASH("yyyy/MM/dd"),
|
||||
|
||||
/**
|
||||
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
|
||||
*/
|
||||
YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
|
||||
|
||||
/**
|
||||
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
|
||||
*/
|
||||
YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
|
||||
|
||||
/**
|
||||
* 例例如,2023年7月可以表示为 "2023.07"
|
||||
*/
|
||||
YYYY_MM_DOT("yyyy.MM"),
|
||||
|
||||
/**
|
||||
* 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22"
|
||||
*/
|
||||
YYYY_MM_DD_DOT("yyyy.MM.dd"),
|
||||
|
||||
/**
|
||||
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
|
||||
*/
|
||||
YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
|
||||
|
||||
/**
|
||||
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
|
||||
*/
|
||||
YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
|
||||
|
||||
/**
|
||||
* 例如,2023年7月可以表示为 "202307"
|
||||
*/
|
||||
YYYYMM("yyyyMM"),
|
||||
|
||||
/**
|
||||
* 例如,2023年7月22日可以表示为 "20230722"
|
||||
*/
|
||||
YYYYMMDD("yyyyMMdd"),
|
||||
|
||||
/**
|
||||
* 例如,2023年7月22日下午3点可以表示为 "2023072215"
|
||||
*/
|
||||
YYYYMMDDHH("yyyyMMddHH"),
|
||||
|
||||
/**
|
||||
* 例如,2023年7月22日下午3点30分可以表示为 "202307221530"
|
||||
*/
|
||||
YYYYMMDDHHMM("yyyyMMddHHmm"),
|
||||
|
||||
/**
|
||||
* 例如,2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
|
||||
*/
|
||||
YYYYMMDDHHMMSS("yyyyMMddHHmmss");
|
||||
|
||||
/**
|
||||
* 时间格式
|
||||
*/
|
||||
private final String timeFormat;
|
||||
|
||||
public static FormatsType getFormatsType(String str) {
|
||||
for (FormatsType value : values()) {
|
||||
if (StringUtils.contains(str, value.getTimeFormat())) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("'FormatsType' not found By " + str);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user