Commit 929ff4286afe6f42e1c6fa4785a34e3e9c9adb3c

Authored by owen
2 parents 171161e6 96b1662e

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

Showing 661 changed files with 3579 additions and 48251 deletions

Too many changes.

To preserve performance only 100 of 661 files are displayed.

README.md
... ... @@ -15,6 +15,8 @@
15 15 * 启动文档:<https://doc.iocoder.cn/quick-start/>
16 16 * 视频教程:<https://doc.iocoder.cn/video/>
17 17  
  18 +已支持 Spring Boot 3.X + JDK 17 版本,可见 [master-boot3](https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/README.md) 分支。
  19 +
18 20 ## 🐯 平台简介
19 21  
20 22 **芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
... ... @@ -62,18 +64,18 @@
62 64 | [yudao-ui-admin](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) | [![Gitee star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Stars)](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-admin) | 基于 Vue2 + element-ui 实现的管理后台 |
63 65 | [yudao-ui-admin-uniapp](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Stars)](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-admin-uniapp) | 基于 uni-app + uni-ui 实现的管理后台的小程序 |
64 66 | [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 |
65   -| [yudao-ui-app](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-app) | [![Gitee star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-app) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Stars)](https://github.com/YunaiV/ruoyi-vue-pro/tree/master/yudao-ui-app) | 基于 uni-app + uview 实现的用户 App |
  67 +| [yudao-mall-uniapp](https://gitee.com/yudaocode/yudao-mall-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-mall-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-mall-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-mall-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-mall-uniapp) | 基于 uni-app 实现的商城小程序 |
66 68  
67 69 ## 🐰 分支说明
68 70  
69 71 | | JDK 8 完整版 | JDK 8 精简版 | JDK 17 完整版 |
70 72 |-------|-----------------------------------------------------------|--------------------------------------------------------------------|-----------------------------------------------------------------------------|
71   -| 分支 | [`master`](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [`mini`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/mini/) | [`boot-dev`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/boot3-dev/) |
  73 +| 分支 | [`master`](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [`mini`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/mini/) | [`master-boot3`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master-boot3/) |
72 74 | 说明 | 包括所有功能 | 只保留核心功能 | 适配 Spring Boot 3.X |
73 75 | 系统功能 | √ | √ | √ |
74 76 | 基础设施 | √ | √ | √ |
75 77 | 会员中心 | √ | √ | √ |
76   -| 工作流程 | √ | x | 适配中 |
  78 +| 工作流程 | √ | x | √ |
77 79 | 数据报表 | √ | x | 适配中 |
78 80 | 商城系统 | √ | x | √ |
79 81 | 微信公众号 | √ | x | √ |
... ... @@ -218,6 +220,10 @@
218 220  
219 221 ![功能图](/.image/common/mall-preview.png)
220 222  
  223 +_前端基于 crmeb uniapp 经过授权重构,优化代码实现,接入芋道快速开发平台_
  224 +
  225 +演示地址:<https://doc.iocoder.cn/mall-preview/>
  226 +
221 227 ### 会员中心
222 228  
223 229 | | 功能 | 描述 |
... ... @@ -248,29 +254,29 @@
248 254  
249 255 ### 框架
250 256  
251   -| 框架 | 说明 | 版本 | 学习指南 |
252   -|---------------------------------------------------------------------------------------------|------------------|-------------|----------------------------------------------------------------|
253   -| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.14 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
254   -| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
255   -| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.18 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
256   -| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.3.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
257   -| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
258   -| [Redis](https://redis.io/) | key-value 数据库 | 5.0 / 6.0 | |
259   -| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.18.0 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
260   -| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.24 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
261   -| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.7.6 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
262   -| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.5 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
263   -| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.8.0 | [文档](https://doc.iocoder.cn/bpm/) |
264   -| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
265   -| [Springdoc](https://springdoc.org/) | Swagger 文档 | 1.6.15 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
266   -| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
267   -| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.12.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
268   -| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.7.10 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
269   -| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | |
270   -| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.5.5.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
271   -| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.28 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
272   -| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
273   -| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - |
  257 +| 框架 | 说明 | 版本 | 学习指南 |
  258 +|---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------|
  259 +| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.16 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
  260 +| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
  261 +| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.19 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
  262 +| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
  263 +| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
  264 +| [Redis](https://redis.io/) | key-value 数据库 | 5.0 / 6.0 /7.0 | |
  265 +| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.18.0 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
  266 +| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.24 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
  267 +| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.7.11 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
  268 +| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.5 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
  269 +| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.8.0 | [文档](https://doc.iocoder.cn/bpm/) |
  270 +| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
  271 +| [Springdoc](https://springdoc.org/) | Swagger 文档 | 1.6.15 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
  272 +| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
  273 +| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.12.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
  274 +| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.7.10 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
  275 +| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | |
  276 +| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.5.5.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
  277 +| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.30 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
  278 +| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
  279 +| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - |
274 280  
275 281 ## 🐷 演示图
276 282  
... ...
... ... @@ -30,7 +30,7 @@
30 30 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
31 31  
32 32 <properties>
33   - <revision>1.8.1-snapshot</revision>
  33 + <revision>1.8.2-snapshot</revision>
34 34 <!-- Maven 相关 -->
35 35 <java.version>1.8</java.version>
36 36 <maven.compiler.source>${java.version}</maven.compiler.source>
... ... @@ -39,8 +39,8 @@
39 39 <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
40 40 <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
41 41 <!-- 看看咋放到 bom 里 -->
42   - <lombok.version>1.18.28</lombok.version>
43   - <spring.boot.version>2.7.14</spring.boot.version>
  42 + <lombok.version>1.18.30</lombok.version>
  43 + <spring.boot.version>2.7.16</spring.boot.version>
44 44 <mapstruct.version>1.5.5.Final</mapstruct.version>
45 45 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
46 46 </properties>
... ...
sql/mysql/brokerage.sql deleted
1   --- 增加配置表
2   -CREATE TABLE trade_config
3   -(
4   - id BIGINT AUTO_INCREMENT COMMENT '自增主键' PRIMARY KEY,
5   - brokerage_enabled BIT DEFAULT 1 NOT NULL COMMENT '是否启用分佣',
6   - brokerage_enabled_condition TINYINT DEFAULT 1 NOT NULL COMMENT '分佣模式:1-人人分销 2-指定分销',
7   - brokerage_bind_mode TINYINT DEFAULT 1 NOT NULL COMMENT '分销关系绑定模式: 1-没有推广人,2-新用户, 3-扫码覆盖',
8   - brokerage_poster_urls VARCHAR(2000) DEFAULT '' NULL COMMENT '分销海报图地址数组',
9   - brokerage_first_percent INT DEFAULT 0 NOT NULL COMMENT '一级返佣比例',
10   - brokerage_second_percent INT DEFAULT 0 NOT NULL COMMENT '二级返佣比例',
11   - brokerage_withdraw_min_price INT DEFAULT 0 NOT NULL COMMENT '用户提现最低金额',
12   - brokerage_withdraw_fee_percent INT DEFAULT 0 NOT NULL COMMENT '提现手续费百分比',
13   - brokerage_bank_names VARCHAR(200) DEFAULT '' NOT NULL COMMENT '提现银行(字典类型=brokerage_bank_name)',
14   - brokerage_frozen_days INT DEFAULT 7 NOT NULL COMMENT '佣金冻结时间(天)',
15   - brokerage_withdraw_types VARCHAR(32) DEFAULT '1,2,3,4' NOT NULL COMMENT '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝',
16   - creator VARCHAR(64) DEFAULT '' NULL COMMENT '创建者',
17   - create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
18   - updater VARCHAR(64) DEFAULT '' NULL COMMENT '更新者',
19   - update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
20   - deleted BIT DEFAULT b'0' NOT NULL COMMENT '是否删除',
21   - tenant_id BIGINT DEFAULT 0 NOT NULL COMMENT '租户编号'
22   -) COMMENT '交易中心配置';
23   -
24   -# alter table trade_config
25   -# add brokerage_withdraw_fee_percent int default 0 not null comment '提现手续费百分比' after brokerage_withdraw_min_price;
26   -
27   -# alter table trade_brokerage_user
28   -# add level int not null default 1 comment '等级' after frozen_price;
29   -# alter table trade_brokerage_user
30   -# add path varchar(2000) null comment '路径' after level;
31   -
32   -
33   --- 增加分销用户扩展表
34   -create table trade_brokerage_user
35   -(
36   - id bigint auto_increment comment '用户编号' primary key,
37   - bind_user_id bigint null comment '推广员编号',
38   - bind_user_time datetime null comment '推广员绑定时间',
39   - brokerage_enabled bit default 1 not null comment '是否成为推广员',
40   - brokerage_time datetime null comment '成为分销员时间',
41   - price int default 0 not null comment '可用佣金',
42   - frozen_price int default 0 not null comment '冻结佣金',
43   - creator varchar(64) default '' null comment '创建者',
44   - create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
45   - updater varchar(64) default '' null comment '更新者',
46   - update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
47   - deleted bit default b'0' not null comment '是否删除',
48   - tenant_id bigint default 0 not null comment '租户编号'
49   -)
50   - comment '分销用户';
51   -
52   -create index idx_invite_user_id on trade_brokerage_user (bind_user_id) comment '推广员编号';
53   -create index idx_agent on trade_brokerage_user (brokerage_enabled) comment '是否成为推广员';
54   -
55   -
56   -create table trade_brokerage_record
57   -(
58   - id int auto_increment comment '编号'
59   - primary key,
60   - user_id bigint not null comment '用户编号',
61   - biz_id varchar(64) default '' not null comment '业务编号',
62   - biz_type tinyint default 0 not null comment '业务类型:1-订单,2-提现',
63   - title varchar(64) default '' not null comment '标题',
64   - price int default 0 not null comment '金额',
65   - total_price int default 0 not null comment '当前总佣金',
66   - description varchar(500) default '' not null comment '说明',
67   - status tinyint default 0 not null comment '状态:0-待结算,1-已结算,2-已取消',
68   - frozen_days int default 0 not null comment '冻结时间(天)',
69   - unfreeze_time datetime null comment '解冻时间',
70   - source_user_level int not null comment '来源用户等级',
71   - source_user_id bigint not null comment '来源用户编号',
72   - creator varchar(64) default '' null comment '创建者',
73   - create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
74   - updater varchar(64) default '' null comment '更新者',
75   - update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
76   - deleted bit default b'0' not null comment '是否删除',
77   - tenant_id bigint default 0 not null comment '租户编号'
78   -)
79   - comment '佣金记录';
80   -
81   -create index idx_user_id on trade_brokerage_record (user_id) comment '用户编号';
82   -create index idx_biz on trade_brokerage_record (biz_type, biz_id) comment '业务';
83   -create index idx_status on trade_brokerage_record (status) comment '状态';
84   -
85   -
86   -create table trade_brokerage_withdraw
87   -(
88   - id bigint auto_increment comment '编号'
89   - primary key,
90   - user_id bigint not null comment '用户编号',
91   - price int default 0 not null comment '提现金额',
92   - fee_price int default 0 not null comment '提现手续费',
93   - total_price int default 0 not null comment '当前总佣金',
94   - type tinyint default 0 not null comment '提现类型:1-钱包;2-银行卡;3-微信;4-支付宝',
95   - name varchar(64) null comment '真实姓名',
96   - account_no varchar(64) null comment '账号',
97   - bank_name varchar(100) null comment '银行名称',
98   - bank_address varchar(200) null comment '开户地址',
99   - account_qr_code_url varchar(512) null comment '收款码',
100   - status tinyint(2) default 0 not null comment '状态:0-审核中,10-审核通过 20-审核不通过;预留:11 - 提现成功;21-提现失败',
101   - audit_reason varchar(128) null comment '审核驳回原因',
102   - audit_time datetime null comment '审核时间',
103   - remark varchar(500) null comment '备注',
104   - creator varchar(64) default '' null comment '创建者',
105   - create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
106   - updater varchar(64) default '' null comment '更新者',
107   - update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
108   - deleted bit default b'0' not null comment '是否删除',
109   - tenant_id bigint default 0 not null comment '租户编号'
110   -)
111   - comment '佣金提现';
112   -
113   -create index idx_user_id on trade_brokerage_withdraw (user_id) comment '用户编号';
114   -create index idx_audit_status on trade_brokerage_withdraw (status) comment '状态';
115   -
116   --- 增加字典
117   -insert into system_dict_type(type, name)
118   -values ('brokerage_enabled_condition', '分佣模式');
119   -insert into system_dict_data(dict_type, label, value, sort, remark)
120   -values ('brokerage_enabled_condition', '人人分销', 1, 1, '所有用户都可以分销'),
121   - ('brokerage_enabled_condition', '指定分销', 2, 2, '仅可后台手动设置推广员');
122   -
123   -insert into system_dict_type(type, name)
124   -values ('brokerage_bind_mode', '分销关系绑定模式');
125   -insert into system_dict_data(dict_type, label, value, sort, remark)
126   -values ('brokerage_bind_mode', '没有推广人', 1, 1, '只要用户没有推广人,随时都可以绑定推广关系'),
127   - ('brokerage_bind_mode', '新用户', 2, 2, '仅新用户注册时才能绑定推广关系'),
128   - ('brokerage_bind_mode', '扫码覆盖', 3, 3, '如果用户已经有推广人,推广人会被变更');
129   -
130   -insert into system_dict_type(type, name)
131   -values ('brokerage_withdraw_type', '佣金提现类型');
132   -insert into system_dict_data(dict_type, label, value, sort)
133   -values ('brokerage_withdraw_type', '钱包', 1, 1),
134   - ('brokerage_withdraw_type', '银行卡', 2, 2),
135   - ('brokerage_withdraw_type', '微信', 3, 3),
136   - ('brokerage_withdraw_type', '支付宝', 4, 4);
137   -
138   -insert into system_dict_type(type, name)
139   -values ('brokerage_record_biz_type', '佣金记录业务类型');
140   -insert into system_dict_data(dict_type, label, value, sort)
141   -values ('brokerage_record_biz_type', '订单返佣', 1, 1),
142   - ('brokerage_record_biz_type', '申请提现', 2, 2),
143   - ('brokerage_record_biz_type', '申请提现驳回', 3, 3);
144   -
145   -insert into system_dict_type(type, name)
146   -values ('brokerage_record_status', '佣金记录状态');
147   -insert into system_dict_data(dict_type, label, value, sort)
148   -values ('brokerage_record_status', '待结算', 0, 0),
149   - ('brokerage_record_status', '已结算', 1, 1),
150   - ('brokerage_record_status', '已取消', 2, 2);
151   -
152   -insert into system_dict_type(type, name)
153   -values ('brokerage_withdraw_status', '佣金提现状态');
154   -insert into system_dict_data(dict_type, label, value, sort, color_type)
155   -values ('brokerage_withdraw_status', '审核中', 0, 0, ''),
156   - ('brokerage_withdraw_status', '审核通过', 10, 10, 'success'),
157   - ('brokerage_withdraw_status', '提现成功', 11, 11, 'success'),
158   - ('brokerage_withdraw_status', '审核不通过', 20, 20, 'danger'),
159   - ('brokerage_withdraw_status', '提现失败', 21, 21, 'danger');
160   -
161   -insert into system_dict_type(type, name)
162   -values ('brokerage_bank_name', '佣金提现银行');
163   -insert into system_dict_data(dict_type, label, value, sort)
164   -values ('brokerage_bank_name', '工商银行', 0, 0),
165   - ('brokerage_bank_name', '建设银行', 1, 1),
166   - ('brokerage_bank_name', '农业银行', 2, 2),
167   - ('brokerage_bank_name', '中国银行', 3, 3),
168   - ('brokerage_bank_name', '交通银行', 4, 4),
169   - ('brokerage_bank_name', '招商银行', 5, 5);
170   -
171   -
172   --- 交易中心配置:菜单 SQL
173   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
174   -VALUES ('交易中心配置', '', 2, 0, 2072, 'config', 'ep:setting', 'trade/config/index', 0, 'TradeConfig');
175   --- 按钮父菜单ID
176   --- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
177   -SELECT @parentId := LAST_INSERT_ID();
178   --- 按钮 SQL
179   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
180   -VALUES ('交易中心配置查询', 'trade:config:query', 3, 1, @parentId, '', '', '', 0);
181   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
182   -VALUES ('交易中心配置保存', 'trade:config:save', 3, 2, @parentId, '', '', '', 0);
183   -
184   -
185   --- 增加菜单:分销
186   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
187   -VALUES ('分销', '', 1, 5, 2072, 'brokerage', 'fa-solid:project-diagram', '', 0, '');
188   --- 按钮父菜单ID
189   -SELECT @brokerageMenuId := LAST_INSERT_ID();
190   -
191   --- 增加菜单:分销员
192   --- 菜单 SQL
193   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
194   -VALUES ('分销用户', '', 2, 0, @brokerageMenuId, 'brokerage-user', 'fa-solid:user-tie', 'trade/brokerage/user/index', 0,
195   - 'TradeBrokerageUser');
196   --- 按钮父菜单ID
197   --- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
198   -SELECT @parentId := LAST_INSERT_ID();
199   --- 按钮 SQL
200   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
201   -VALUES ('分销用户查询', 'trade:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
202   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
203   -VALUES ('分销用户推广人查询', 'trade:brokerage-user:user-query', 3, 2, @parentId, '', '', '', 0);
204   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
205   -VALUES ('分销用户推广订单查询', 'trade:brokerage-user:order-query', 3, 3, @parentId, '', '', '', 0);
206   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
207   -VALUES ('分销用户修改推广资格', 'trade:brokerage-user:update-brokerage-enable', 3, 4, @parentId, '', '', '', 0);
208   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
209   -VALUES ('分销用户修改推广员', 'trade:brokerage-user:update-bind-user', 3, 5, @parentId, '', '', '', 0);
210   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
211   -VALUES ('分销用户清除推广员', 'trade:brokerage-user:clear-bind-user', 3, 6, @parentId, '', '', '', 0);
212   -
213   --- 增加菜单:佣金记录
214   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
215   -VALUES ('佣金记录', '', 2, 1, @brokerageMenuId, 'brokerage-record', 'fa:money', 'trade/brokerage/record/index', 0,
216   - 'TradeBrokerageRecord');
217   --- 按钮父菜单ID
218   --- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
219   -SELECT @parentId := LAST_INSERT_ID();
220   --- 按钮 SQL
221   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
222   -VALUES ('佣金记录查询', 'trade:brokerage-record:query', 3, 1, @parentId, '', '', '', 0);
223   -
224   --- 增加菜单:佣金提现
225   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
226   -VALUES ('佣金提现', '', 2, 2, @brokerageMenuId, 'brokerage-withdraw', 'fa:credit-card',
227   - 'trade/brokerage/withdraw/index', 0, 'TradeBrokerageWithdraw');
228   --- 按钮父菜单ID
229   --- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
230   -SELECT @parentId := LAST_INSERT_ID();
231   --- 按钮 SQL
232   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
233   -VALUES ('佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
234   -INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
235   -VALUES ('佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);
236   -
237   --- 站内信模板
238   -INSERT INTO `ruoyi-vue-pro`.system_notify_template (name, code, nickname, content, type, params, status)
239   -VALUES
240   - ('佣金提现(审核通过)', 'brokerage_withdraw_audit_approve', 'system', '您在{createTime}提现¥{price}元的申请已通过审核', 2, '["createTime","price"]', 0),
241   - ('佣金提现(审核不通过)', 'brokerage_withdraw_audit_reject', 'system', '您在{createTime}提现¥{price}元的申请未通过审核,原因:{reason}', 2, '["createTime","price","reason"]', 0);
sql/mysql/optional/mall_trade_log.sql deleted
1   -ALTER TABLE `ruoyi-vue-pro`.`trade_after_sale_log`
2   - ADD COLUMN `before_status` int NOT NULL COMMENT '售前状态' AFTER `id`,
3   - ADD COLUMN `after_status` int NOT NULL COMMENT '售后状态' AFTER `before_status`;
sql/mysql/pay_wallet.sql
... ... @@ -73,3 +73,81 @@ CREATE TABLE `pay_wallet_recharge`
73 73 PRIMARY KEY (`id`) USING BTREE
74 74 ) ENGINE=InnoDB COMMENT='会员钱包充值';
75 75  
  76 +-- ----------------------------
  77 +-- 转账单表
  78 +-- ----------------------------
  79 +DROP TABLE IF EXISTS `pay_transfer`;
  80 +CREATE TABLE `pay_transfer`
  81 +(
  82 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  83 + `type` int NOT NULL COMMENT '类型',
  84 + `app_id` bigint NOT NULL COMMENT '应用编号',
  85 + `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单编号',
  86 + `price` int NOT NULL COMMENT '转账金额,单位:分',
  87 + `title` varchar(512) NOT NULL COMMENT '转账标题',
  88 + `payee_info` varchar(512) NOT NULL COMMENT '收款人信息,不同类型和渠道不同',
  89 + `status` tinyint NOT NULL COMMENT '转账状态',
  90 + `success_time` datetime NULL COMMENT '转账成功时间',
  91 + `extension_id` bigint NULL COMMENT '转账渠道编号',
  92 + `no` varchar(64) NULL COMMENT '转账单号',
  93 + `channel_id` bigint NULL COMMENT '转账渠道编号',
  94 + `channel_code` varchar(32) NULL COMMENT '转账渠道编码',
  95 + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  96 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  97 + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
  98 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  99 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  100 + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
  101 + PRIMARY KEY (`id`) USING BTREE
  102 +) ENGINE=InnoDB COMMENT='转账单表';
  103 +
  104 +-- ----------------------------
  105 +-- 转账扩展单
  106 +-- ----------------------------
  107 +DROP TABLE IF EXISTS `pay_transfer_extension`;
  108 +CREATE TABLE `pay_transfer_extension`
  109 +(
  110 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  111 + `no` varchar(64) NOT NULL COMMENT '转账单号',
  112 + `transfer_id` bigint NOT NULL COMMENT '转账单编号',
  113 + `channel_id` bigint NOT NULL COMMENT '转账渠道编号',
  114 + `channel_code` varchar(32) NOT NULL COMMENT '转账渠道编码',
  115 + `channel_extras` varchar(512) NULL DEFAULT NULL COMMENT '支付渠道的额外参数',
  116 + `status` tinyint NOT NULL COMMENT '转账状态',
  117 + `channel_notify_data` varchar(4096) NULL DEFAULT NULL COMMENT '支付渠道异步通知的内容',
  118 + `creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
  119 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  120 + `updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
  121 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  122 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  123 + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
  124 + PRIMARY KEY (`id`) USING BTREE
  125 +) ENGINE=InnoDB COMMENT='转账拓展单表';
  126 +
  127 +-- ----------------------------
  128 +-- Table structure for pay_demo_transfer
  129 +-- ----------------------------
  130 +DROP TABLE IF EXISTS `pay_demo_transfer`;
  131 +CREATE TABLE `pay_demo_transfer` (
  132 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  133 + `user_id` bigint UNSIGNED NOT NULL COMMENT '用户编号',
  134 + `price` int NOT NULL COMMENT '转账金额,单位:分',
  135 + `type` int NOT NULL COMMENT '转账类型',
  136 + `payee_info` varchar(512) NOT NULL COMMENT '收款人信息,不同类型和渠道不同',
  137 + `transfer_status` tinyint NOT NULL DEFAULT 0 COMMENT '转账状态',
  138 + `pay_transfer_id` bigint NULL DEFAULT NULL COMMENT '转账订单编号',
  139 + `pay_channel_code` varchar(16) NULL DEFAULT NULL COMMENT '转账支付成功渠道',
  140 + `transfer_time` datetime NULL DEFAULT NULL COMMENT '转账支付时间',
  141 + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '' COMMENT '创建者',
  142 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  143 + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '' COMMENT '更新者',
  144 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  145 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  146 + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
  147 + PRIMARY KEY (`id`) USING BTREE
  148 +) ENGINE = InnoDB COMMENT = '示例业务转账订单\n';
  149 +
  150 +
  151 +ALTER TABLE `pay_channel`
  152 + MODIFY COLUMN `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '支付渠道配置' AFTER `app_id`;
  153 +
... ...
sql/mysql/point.sql deleted
1   -ALTER TABLE trade_order ADD COLUMN use_point int NOT NULL DEFAULT 0 COMMENT '使用的积分' AFTER point_price;
2   -ALTER TABLE trade_order ADD COLUMN refund_point int NOT NULL DEFAULT 0 COMMENT '退还的使用积分' AFTER use_point;
3   -ALTER TABLE trade_order ADD COLUMN give_point int NOT NULL DEFAULT 0 COMMENT '赠送的积分' AFTER refund_point;
4   -
5   -ALTER TABLE trade_order_item ADD COLUMN use_point int NOT NULL DEFAULT 0 COMMENT '使用的积分' AFTER point_price;
6   -ALTER TABLE trade_order_item ADD COLUMN give_point int NOT NULL DEFAULT 0 COMMENT '赠送的积分' AFTER use_point;
sql/mysql/ruoyi-vue-pro.sql
... ... @@ -11,7 +11,7 @@
11 11 Target Server Version : 80034
12 12 File Encoding : 65001
13 13  
14   - Date: 03/09/2023 19:13:55
  14 + Date: 24/09/2023 23:23:06
15 15 */
16 16  
17 17 SET NAMES utf8mb4;
... ... @@ -72,6 +72,7 @@ CREATE TABLE `QRTZ_CRON_TRIGGERS` (
72 72 -- Records of QRTZ_CRON_TRIGGERS
73 73 -- ----------------------------
74 74 BEGIN;
  75 +INSERT INTO `QRTZ_CRON_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `CRON_EXPRESSION`, `TIME_ZONE_ID`) VALUES ('schedulerName', 'demoJob', 'DEFAULT', '0 0 0 * * ?', 'Asia/Shanghai');
75 76 INSERT INTO `QRTZ_CRON_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `CRON_EXPRESSION`, `TIME_ZONE_ID`) VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', '* * * * * ?', 'Asia/Shanghai');
76 77 INSERT INTO `QRTZ_CRON_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `CRON_EXPRESSION`, `TIME_ZONE_ID`) VALUES ('schedulerName', 'payOrderExpireJob', 'DEFAULT', '0 0/1 * * * ?', 'Asia/Shanghai');
77 78 INSERT INTO `QRTZ_CRON_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `CRON_EXPRESSION`, `TIME_ZONE_ID`) VALUES ('schedulerName', 'payOrderSyncJob', 'DEFAULT', '0 0/1 * * * ?', 'Asia/Shanghai');
... ... @@ -135,6 +136,7 @@ CREATE TABLE `QRTZ_JOB_DETAILS` (
135 136 -- Records of QRTZ_JOB_DETAILS
136 137 -- ----------------------------
137 138 BEGIN;
  139 +INSERT INTO `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `JOB_CLASS_NAME`, `IS_DURABLE`, `IS_NONCONCURRENT`, `IS_UPDATE_DATA`, `REQUESTS_RECOVERY`, `JOB_DATA`) VALUES ('schedulerName', 'demoJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000147400104A4F425F48414E444C45525F4E414D4574000764656D6F4A6F627800);
138 140 INSERT INTO `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `JOB_CLASS_NAME`, `IS_DURABLE`, `IS_NONCONCURRENT`, `IS_UPDATE_DATA`, `REQUESTS_RECOVERY`, `JOB_DATA`) VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000057400104A4F425F48414E444C45525F4E414D4574000C7061794E6F746966794A6F627800);
139 141 INSERT INTO `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `JOB_CLASS_NAME`, `IS_DURABLE`, `IS_NONCONCURRENT`, `IS_UPDATE_DATA`, `REQUESTS_RECOVERY`, `JOB_DATA`) VALUES ('schedulerName', 'payOrderExpireJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000127400104A4F425F48414E444C45525F4E414D457400117061794F726465724578706972654A6F627800);
140 142 INSERT INTO `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `JOB_CLASS_NAME`, `IS_DURABLE`, `IS_NONCONCURRENT`, `IS_UPDATE_DATA`, `REQUESTS_RECOVERY`, `JOB_DATA`) VALUES ('schedulerName', 'payOrderSyncJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000117400104A4F425F48414E444C45525F4E414D4574000F7061794F7264657253796E634A6F627800);
... ... @@ -191,7 +193,7 @@ CREATE TABLE `QRTZ_SCHEDULER_STATE` (
191 193 -- Records of QRTZ_SCHEDULER_STATE
192 194 -- ----------------------------
193 195 BEGIN;
194   -INSERT INTO `QRTZ_SCHEDULER_STATE` (`SCHED_NAME`, `INSTANCE_NAME`, `LAST_CHECKIN_TIME`, `CHECKIN_INTERVAL`) VALUES ('schedulerName', 'Yunai1690117495401', 1690119854263, 15000);
  196 +INSERT INTO `QRTZ_SCHEDULER_STATE` (`SCHED_NAME`, `INSTANCE_NAME`, `LAST_CHECKIN_TIME`, `CHECKIN_INTERVAL`) VALUES ('schedulerName', 'Yunai.local1694844151505', 1694844218609, 15000);
195 197 COMMIT;
196 198  
197 199 -- ----------------------------
... ... @@ -285,6 +287,7 @@ CREATE TABLE `QRTZ_TRIGGERS` (
285 287 -- Records of QRTZ_TRIGGERS
286 288 -- ----------------------------
287 289 BEGIN;
  290 +INSERT INTO `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `NEXT_FIRE_TIME`, `PREV_FIRE_TIME`, `PRIORITY`, `TRIGGER_STATE`, `TRIGGER_TYPE`, `START_TIME`, `END_TIME`, `CALENDAR_NAME`, `MISFIRE_INSTR`, `JOB_DATA`) VALUES ('schedulerName', 'demoJob', 'DEFAULT', 'demoJob', 'DEFAULT', NULL, 1694880000000, -1, 5, 'WAITING', 'CRON', 1694844083000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D7400007400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000A74000F4A4F425F52455452595F434F554E547371007E000A000000017800);
288 291 INSERT INTO `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `NEXT_FIRE_TIME`, `PREV_FIRE_TIME`, `PRIORITY`, `TRIGGER_STATE`, `TRIGGER_TYPE`, `START_TIME`, `END_TIME`, `CALENDAR_NAME`, `MISFIRE_INSTR`, `JOB_DATA`) VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', 'payNotifyJob', 'DEFAULT', NULL, 1688907102000, 1688907101000, 5, 'PAUSED', 'CRON', 1635294882000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800);
289 292 INSERT INTO `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `NEXT_FIRE_TIME`, `PREV_FIRE_TIME`, `PRIORITY`, `TRIGGER_STATE`, `TRIGGER_TYPE`, `START_TIME`, `END_TIME`, `CALENDAR_NAME`, `MISFIRE_INSTR`, `JOB_DATA`) VALUES ('schedulerName', 'payOrderExpireJob', 'DEFAULT', 'payOrderExpireJob', 'DEFAULT', NULL, 1690011600000, -1, 5, 'PAUSED', 'CRON', 1690011553000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800);
290 293 INSERT INTO `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`, `JOB_NAME`, `JOB_GROUP`, `DESCRIPTION`, `NEXT_FIRE_TIME`, `PREV_FIRE_TIME`, `PRIORITY`, `TRIGGER_STATE`, `TRIGGER_TYPE`, `START_TIME`, `END_TIME`, `CALENDAR_NAME`, `MISFIRE_INSTR`, `JOB_DATA`) VALUES ('schedulerName', 'payOrderSyncJob', 'DEFAULT', 'payOrderSyncJob', 'DEFAULT', NULL, 1690011600000, 1690011540000, 5, 'PAUSED', 'CRON', 1690007785000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800);
... ... @@ -360,7 +363,7 @@ CREATE TABLE `infra_api_error_log` (
360 363 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
361 364 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
362 365 PRIMARY KEY (`id`) USING BTREE
363   -) ENGINE = InnoDB AUTO_INCREMENT = 1497 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
  366 +) ENGINE = InnoDB AUTO_INCREMENT = 1544 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
364 367  
365 368 -- ----------------------------
366 369 -- Records of infra_api_error_log
... ... @@ -398,7 +401,7 @@ CREATE TABLE `infra_codegen_column` (
398 401 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
399 402 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
400 403 PRIMARY KEY (`id`) USING BTREE
401   -) ENGINE = InnoDB AUTO_INCREMENT = 1756 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
  404 +) ENGINE = InnoDB AUTO_INCREMENT = 1755 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
402 405  
403 406 -- ----------------------------
404 407 -- Records of infra_codegen_column
... ... @@ -514,7 +517,7 @@ CREATE TABLE `infra_file` (
514 517 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
515 518 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
516 519 PRIMARY KEY (`id`) USING BTREE
517   -) ENGINE = InnoDB AUTO_INCREMENT = 1054 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
  520 +) ENGINE = InnoDB AUTO_INCREMENT = 1055 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
518 521  
519 522 -- ----------------------------
520 523 -- Records of infra_file
... ... @@ -545,12 +548,7 @@ CREATE TABLE `infra_file_config` (
545 548 -- Records of infra_file_config
546 549 -- ----------------------------
547 550 BEGIN;
548   -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库', 1, '我是数据库', b'1', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2023-04-08 09:44:47', b'0');
549   -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5, '本地磁盘', 10, '测试下本地存储', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.local.LocalFileClientConfig\",\"basePath\":\"/Users/yunai/file_test\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:57:00', '1', '2023-04-08 09:44:47', b'0');
550   -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (11, 'S3 - 七牛云', 20, NULL, b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-03-19 18:00:03', '1', '2023-04-08 09:44:47', b'0');
551   -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (15, 'S3 - 七牛云', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-06-10 20:50:41', '1', '2023-04-08 09:44:47', b'0');
552   -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (16, 'S3 - 七牛云', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-06-11 20:32:08', '1', '2023-04-08 09:44:47', b'0');
553   -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (17, 'S3 - 七牛云', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3-cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8\",\"accessSecret\":\"kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP\"}', '1', '2022-06-11 20:32:47', '1', '2023-04-08 09:44:47', b'0');
  551 +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库', 1, '我是数据库', b'1', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2023-09-15 12:05:12', b'0');
554 552 COMMIT;
555 553  
556 554 -- ----------------------------
... ... @@ -568,7 +566,7 @@ CREATE TABLE `infra_file_content` (
568 566 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
569 567 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
570 568 PRIMARY KEY (`id`) USING BTREE
571   -) ENGINE = InnoDB AUTO_INCREMENT = 145 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
  569 +) ENGINE = InnoDB AUTO_INCREMENT = 149 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
572 570  
573 571 -- ----------------------------
574 572 -- Records of infra_file_content
... ... @@ -596,17 +594,17 @@ CREATE TABLE `infra_job` (
596 594 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
597 595 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
598 596 PRIMARY KEY (`id`) USING BTREE
599   -) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
  597 +) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
600 598  
601 599 -- ----------------------------
602 600 -- Records of infra_job
603 601 -- ----------------------------
604 602 BEGIN;
605 603 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5, '支付通知 Job', 2, 'payNotifyJob', NULL, '* * * * * ?', 0, 0, 0, '1', '2021-10-27 08:34:42', '1', '2023-07-09 20:51:41', b'0');
606   -INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (16, 'Job 示例', 1, 'demoJob', NULL, '* * * L * ?', 1, 1, 0, '1', '2022-09-24 22:31:41', '1', '2022-09-24 22:31:42', b'0');
607 604 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (17, '支付订单同步 Job', 2, 'payOrderSyncJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-22 14:36:26', '1', '2023-07-22 15:39:08', b'0');
608 605 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (18, '支付订单过期 Job', 2, 'payOrderExpireJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-22 15:36:23', '1', '2023-07-22 15:39:54', b'0');
609 606 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (19, '退款订单的同步 Job', 2, 'payRefundSyncJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-23 21:03:44', '1', '2023-07-23 21:09:00', b'0');
  607 +INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (20, 'Job 示例', 1, 'demoJob', '', '0 0 0 * * ?', 1, 10, 0, '1', '2023-09-16 14:01:23', '1', '2023-09-16 14:01:23', b'0');
610 608 COMMIT;
611 609  
612 610 -- ----------------------------
... ... @@ -630,7 +628,7 @@ CREATE TABLE `infra_job_log` (
630 628 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
631 629 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
632 630 PRIMARY KEY (`id`) USING BTREE
633   -) ENGINE = InnoDB AUTO_INCREMENT = 161 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
  631 +) ENGINE = InnoDB AUTO_INCREMENT = 163 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
634 632  
635 633 -- ----------------------------
636 634 -- Records of infra_job_log
... ... @@ -803,7 +801,7 @@ CREATE TABLE `member_level_record` (
803 801 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
804 802 PRIMARY KEY (`id`) USING BTREE,
805 803 INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员等级记录-用户编号'
806   -) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
  804 +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
807 805  
808 806 -- ----------------------------
809 807 -- Records of member_level_record
... ... @@ -828,7 +826,7 @@ CREATE TABLE `member_point_config` (
828 826 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
829 827 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
830 828 PRIMARY KEY (`id`) USING BTREE
831   -) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '会员积分配置表';
  829 +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员积分配置表';
832 830  
833 831 -- ----------------------------
834 832 -- Records of member_point_config
... ... @@ -844,10 +842,10 @@ DROP TABLE IF EXISTS `member_point_record`;
844 842 CREATE TABLE `member_point_record` (
845 843 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键',
846 844 `user_id` bigint NOT NULL COMMENT '用户编号',
847   - `biz_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '业务编码',
  845 + `biz_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务编码',
848 846 `biz_type` tinyint NOT NULL COMMENT '业务类型',
849   - `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '积分标题',
850   - `description` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '积分描述',
  847 + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '积分标题',
  848 + `description` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '积分描述',
851 849 `point` int NOT NULL COMMENT '积分',
852 850 `total_point` int NOT NULL COMMENT '变动后的积分',
853 851 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
... ... @@ -859,7 +857,7 @@ CREATE TABLE `member_point_record` (
859 857 PRIMARY KEY (`id`) USING BTREE,
860 858 INDEX `index_userId`(`user_id` ASC) USING BTREE,
861 859 INDEX `index_title`(`title` ASC) USING BTREE
862   -) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户积分记录';
  860 +) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
863 861  
864 862 -- ----------------------------
865 863 -- Records of member_point_record
... ... @@ -872,6 +870,15 @@ INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title
872 870 INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 247, 'null', 11, '订单取消', '退单获得 -18 积分', -18, 73, NULL, '2023-08-31 19:56:21', NULL, '2023-08-31 19:56:21', b'0', 1);
873 871 INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 247, '80', 10, '订单消费', '下单获得 2099718 积分', 2099718, 2099791, NULL, '2023-08-31 23:43:29', NULL, '2023-08-31 23:43:29', b'0', 1);
874 872 INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 247, '81', 10, '订单消费', '下单获得 8398818 积分', 8398818, 10498609, NULL, '2023-08-31 23:46:17', NULL, '2023-08-31 23:46:17', b'0', 1);
  873 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (8, 247, '85', 12, '订单使用', '下单使用 -30 积分', -30, 10498579, '247', '2023-09-20 17:11:36', '247', '2023-09-20 17:11:36', b'0', 1);
  874 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, 247, '86', 12, '订单使用', '下单使用 -30 积分', -30, 10498549, '247', '2023-09-20 19:32:59', '247', '2023-09-20 19:32:59', b'0', 1);
  875 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 247, '87', 12, '订单使用', '下单使用 -30 积分', -30, 10498519, '247', '2023-09-20 23:11:21', '247', '2023-09-20 23:11:21', b'0', 1);
  876 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 247, '88', 12, '订单使用', '下单使用 -30 积分', -30, 10498489, '247', '2023-09-20 23:20:21', '247', '2023-09-20 23:20:21', b'0', 1);
  877 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 247, '89', 12, '订单使用', '下单使用 -30 积分', -30, 10498459, '247', '2023-09-23 23:51:40', '247', '2023-09-23 23:51:40', b'0', 1);
  878 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (13, 247, '90', 12, '订单使用', '下单使用 -30 积分', -30, 10498429, '247', '2023-09-23 23:52:34', '247', '2023-09-23 23:52:34', b'0', 1);
  879 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (14, 247, '91', 12, '订单使用', '下单使用 -30 积分', -30, 10498399, '247', '2023-09-23 23:54:18', '247', '2023-09-23 23:54:18', b'0', 1);
  880 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (15, 247, '92', 12, '订单使用', '下单使用 -30 积分', -30, 10498369, '247', '2023-09-23 23:55:33', '247', '2023-09-23 23:55:33', b'0', 1);
  881 +INSERT INTO `member_point_record` (`id`, `user_id`, `biz_id`, `biz_type`, `title`, `description`, `point`, `total_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (16, 247, '93', 12, '订单使用', '下单使用 -30 积分', -30, 10498339, '247', '2023-09-23 23:56:53', '247', '2023-09-23 23:56:53', b'0', 1);
875 882 COMMIT;
876 883  
877 884 -- ----------------------------
... ... @@ -890,7 +897,7 @@ CREATE TABLE `member_sign_in_config` (
890 897 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
891 898 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
892 899 PRIMARY KEY (`id`) USING BTREE
893   -) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '签到规则';
  900 +) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '签到规则';
894 901  
895 902 -- ----------------------------
896 903 -- Records of member_sign_in_config
... ... @@ -919,20 +926,23 @@ CREATE TABLE `member_sign_in_record` (
919 926 `user_id` int NULL DEFAULT NULL COMMENT '签到用户',
920 927 `day` int NULL DEFAULT NULL COMMENT '第几天签到',
921 928 `point` int NULL DEFAULT NULL COMMENT '签到的分数',
922   - `create_time` datetime NULL DEFAULT NULL COMMENT '签到时间',
923   - `update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '变更时间',
924   - `tenant_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '租户id',
925   - `deleted` int NULL DEFAULT 0 COMMENT '是否删除',
926   - `creator` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',
927   - `updater` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新人',
  929 + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  930 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  931 + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
  932 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  933 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  934 + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
928 935 PRIMARY KEY (`id`) USING BTREE
929   -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '签到记录';
  936 +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '签到记录';
930 937  
931 938 -- ----------------------------
932 939 -- Records of member_sign_in_record
933 940 -- ----------------------------
934 941 BEGIN;
935   -INSERT INTO `member_sign_in_record` (`id`, `user_id`, `day`, `point`, `create_time`, `update_time`, `tenant_id`, `deleted`, `creator`, `updater`) VALUES (4, 247, 12, 1212, '2023-06-10 20:01:27', '2023-08-20 11:29:50', '1', 0, '1', '1');
  942 +INSERT INTO `member_sign_in_record` (`id`, `user_id`, `day`, `point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 247, 1, 123, '', '2023-09-24 01:23:47', '', '2023-09-24 01:23:47', b'0', 0);
  943 +INSERT INTO `member_sign_in_record` (`id`, `user_id`, `day`, `point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 247, 12, 12, '', '2023-09-24 01:23:47', '', '2023-09-24 01:23:47', b'0', 0);
  944 +INSERT INTO `member_sign_in_record` (`id`, `user_id`, `day`, `point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 247, 12, 1212, '', '2023-09-24 01:23:47', '', '2023-09-24 01:23:47', b'0', 0);
  945 +INSERT INTO `member_sign_in_record` (`id`, `user_id`, `day`, `point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 247, 12, 1212, '', '2023-09-24 01:23:47', '', '2023-09-24 01:23:47', b'0', 0);
936 946 COMMIT;
937 947  
938 948 -- ----------------------------
... ... @@ -972,7 +982,7 @@ CREATE TABLE `member_user` (
972 982 `login_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '最后登录IP',
973 983 `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
974 984 `nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称',
975   - `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像',
  985 + `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像',
976 986 `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '真实名字',
977 987 `sex` tinyint NULL DEFAULT 0 COMMENT '用户性别',
978 988 `area_id` bigint NULL DEFAULT NULL COMMENT '所在地',
... ... @@ -989,9 +999,8 @@ CREATE TABLE `member_user` (
989 999 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
990 1000 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
991 1001 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
992   - PRIMARY KEY (`id`) USING BTREE,
993   - UNIQUE INDEX `uk_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
994   -) ENGINE = InnoDB AUTO_INCREMENT = 248 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员用户';
  1002 + PRIMARY KEY (`id`) USING BTREE
  1003 +) ENGINE = InnoDB AUTO_INCREMENT = 249 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员用户';
995 1004  
996 1005 -- ----------------------------
997 1006 -- Records of member_user
... ... @@ -1305,7 +1314,7 @@ CREATE TABLE `system_dict_type` (
1305 1314 `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
1306 1315 PRIMARY KEY (`id`) USING BTREE,
1307 1316 UNIQUE INDEX `dict_type`(`type` ASC) USING BTREE
1308   -) ENGINE = InnoDB AUTO_INCREMENT = 176 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
  1317 +) ENGINE = InnoDB AUTO_INCREMENT = 175 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
1309 1318  
1310 1319 -- ----------------------------
1311 1320 -- Records of system_dict_type
... ... @@ -1389,7 +1398,7 @@ CREATE TABLE `system_error_code` (
1389 1398 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
1390 1399 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1391 1400 PRIMARY KEY (`id`) USING BTREE
1392   -) ENGINE = InnoDB AUTO_INCREMENT = 5933 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
  1401 +) ENGINE = InnoDB AUTO_INCREMENT = 5932 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
1393 1402  
1394 1403 -- ----------------------------
1395 1404 -- Records of system_error_code
... ... @@ -1418,7 +1427,7 @@ CREATE TABLE `system_login_log` (
1418 1427 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1419 1428 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
1420 1429 PRIMARY KEY (`id`) USING BTREE
1421   -) ENGINE = InnoDB AUTO_INCREMENT = 2375 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
  1430 +) ENGINE = InnoDB AUTO_INCREMENT = 2482 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
1422 1431  
1423 1432 -- ----------------------------
1424 1433 -- Records of system_login_log
... ... @@ -1483,7 +1492,7 @@ CREATE TABLE `system_mail_log` (
1483 1492 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
1484 1493 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1485 1494 PRIMARY KEY (`id`) USING BTREE
1486   -) ENGINE = InnoDB AUTO_INCREMENT = 354 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
  1495 +) ENGINE = InnoDB AUTO_INCREMENT = 355 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
1487 1496  
1488 1497 -- ----------------------------
1489 1498 -- Records of system_mail_log
... ... @@ -2116,7 +2125,7 @@ CREATE TABLE `system_oauth2_access_token` (
2116 2125 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2117 2126 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2118 2127 PRIMARY KEY (`id`) USING BTREE
2119   -) ENGINE = InnoDB AUTO_INCREMENT = 2597 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
  2128 +) ENGINE = InnoDB AUTO_INCREMENT = 2721 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
2120 2129  
2121 2130 -- ----------------------------
2122 2131 -- Records of system_oauth2_access_token
... ... @@ -2238,7 +2247,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
2238 2247 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2239 2248 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2240 2249 PRIMARY KEY (`id`) USING BTREE
2241   -) ENGINE = InnoDB AUTO_INCREMENT = 896 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
  2250 +) ENGINE = InnoDB AUTO_INCREMENT = 991 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
2242 2251  
2243 2252 -- ----------------------------
2244 2253 -- Records of system_oauth2_refresh_token
... ... @@ -2278,7 +2287,7 @@ CREATE TABLE `system_operate_log` (
2278 2287 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2279 2288 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2280 2289 PRIMARY KEY (`id`) USING BTREE
2281   -) ENGINE = InnoDB AUTO_INCREMENT = 8321 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
  2290 +) ENGINE = InnoDB AUTO_INCREMENT = 8338 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
2282 2291  
2283 2292 -- ----------------------------
2284 2293 -- Records of system_operate_log
... ... @@ -3286,7 +3295,7 @@ CREATE TABLE `system_sms_code` (
3286 3295 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3287 3296 PRIMARY KEY (`id`) USING BTREE,
3288 3297 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
3289   -) ENGINE = InnoDB AUTO_INCREMENT = 501 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
  3298 +) ENGINE = InnoDB AUTO_INCREMENT = 513 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
3290 3299  
3291 3300 -- ----------------------------
3292 3301 -- Records of system_sms_code
... ... @@ -3329,7 +3338,7 @@ CREATE TABLE `system_sms_log` (
3329 3338 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
3330 3339 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3331 3340 PRIMARY KEY (`id`) USING BTREE
3332   -) ENGINE = InnoDB AUTO_INCREMENT = 365 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
  3341 +) ENGINE = InnoDB AUTO_INCREMENT = 381 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
3333 3342  
3334 3343 -- ----------------------------
3335 3344 -- Records of system_sms_log
... ... @@ -3401,7 +3410,7 @@ CREATE TABLE `system_social_user` (
3401 3410 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3402 3411 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3403 3412 PRIMARY KEY (`id`) USING BTREE
3404   -) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
  3413 +) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
3405 3414  
3406 3415 -- ----------------------------
3407 3416 -- Records of system_social_user
... ... @@ -3426,7 +3435,7 @@ CREATE TABLE `system_social_user_bind` (
3426 3435 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3427 3436 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3428 3437 PRIMARY KEY (`id`) USING BTREE
3429   -) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
  3438 +) ENGINE = InnoDB AUTO_INCREMENT = 76 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
3430 3439  
3431 3440 -- ----------------------------
3432 3441 -- Records of system_social_user_bind
... ... @@ -3462,8 +3471,8 @@ CREATE TABLE `system_tenant` (
3462 3471 -- ----------------------------
3463 3472 BEGIN;
3464 3473 INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'https://www.iocoder.cn', 0, '2099-02-19 17:14:16', 9999, '1', '2021-01-05 17:03:47', '1', '2022-02-23 12:15:11', b'0');
3465   -INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'http://www.iocoder.cn', 111, '2024-03-11 00:00:00', 20, '1', '2022-02-22 00:56:14', '1', '2023-07-25 23:05:38', b'0');
3466   -INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'https://www.iocoder.cn', 111, '2022-04-30 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2023-07-25 23:53:16', b'0');
  3474 +INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'http://www.iocoder.cn', 111, '2024-03-11 00:00:00', 20, '1', '2022-02-22 00:56:14', '1', '2023-09-16 16:59:42', b'0');
  3475 +INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `domain`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'https://www.iocoder.cn', 111, '2022-04-30 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2023-09-16 16:59:27', b'0');
3467 3476 COMMIT;
3468 3477  
3469 3478 -- ----------------------------
... ... @@ -3602,10 +3611,10 @@ CREATE TABLE `system_users` (
3602 3611 -- Records of system_users
3603 3612 -- ----------------------------
3604 3613 BEGIN;
3605   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/e1fdd7271685ec143a0900681606406621717a666ad0b2798b096df41422b32f.png', 0, '0:0:0:0:0:0:0:1', '2023-09-02 00:03:37', 'admin', '2021-01-05 17:03:47', NULL, '2023-09-02 00:03:37', b'0', 1);
  3614 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/e1fdd7271685ec143a0900681606406621717a666ad0b2798b096df41422b32f.png', 0, '0:0:0:0:0:0:0:1', '2023-09-24 12:03:56', 'admin', '2021-01-05 17:03:47', NULL, '2023-09-24 12:03:56', b'0', 1);
3606 3615 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1);
3607 3616 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '127.0.0.1', '2022-07-08 01:26:27', '', '2021-01-13 23:50:35', NULL, '2022-07-08 01:26:27', b'0', 1);
3608   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '127.0.0.1', '2022-05-28 15:43:17', '', '2021-01-21 02:13:53', NULL, '2022-07-09 09:00:33', b'0', 1);
  3617 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2023-09-24 18:21:19', '', '2021-01-21 02:13:53', NULL, '2023-09-24 18:21:19', b'0', 1);
3609 3618 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2022-02-27 08:26:51', b'0', 118);
3610 3619 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119);
3611 3620 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120);
... ...
sql/sqlserver/ruoyi-vue-pro.sql
... ... @@ -5815,8 +5815,7 @@ GO
5815 5815  
5816 5816 CREATE TABLE [dbo].[system_dict_type]
5817 5817 (
5818   - [
5819   - id]
  5818 + [id]
5820 5819 bigint
5821 5820 IDENTITY
5822 5821 (
... ...
yudao-dependencies/pom.xml
... ... @@ -14,16 +14,16 @@
14 14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
15 15  
16 16 <properties>
17   - <revision>1.8.1-snapshot</revision>
  17 + <revision>1.8.2-snapshot</revision>
18 18 <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
19 19 <!-- 统一依赖管理 -->
20   - <spring.boot.version>2.7.14</spring.boot.version>
  20 + <spring.boot.version>2.7.16</spring.boot.version>
21 21 <!-- Web 相关 -->
22   - <springdoc.version>1.6.15</springdoc.version>
23   - <knife4j.version>4.1.0</knife4j.version>
  22 + <springdoc.version>1.7.0</springdoc.version>
  23 + <knife4j.version>4.3.0</knife4j.version>
24 24 <servlet.versoin>2.5</servlet.versoin>
25 25 <!-- DB 相关 -->
26   - <druid.version>1.2.18</druid.version>
  26 + <druid.version>1.2.19</druid.version>
27 27 <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
28 28 <mybatis-plus-generator.version>3.5.3.2</mybatis-plus-generator.version>
29 29 <dynamic-datasource.version>3.6.1</dynamic-datasource.version>
... ... @@ -44,33 +44,32 @@
44 44 <!-- Bpm 工作流相关 -->
45 45 <flowable.version>6.8.0</flowable.version>
46 46 <!-- 工具类相关 -->
47   - <captcha-plus.version>1.0.6</captcha-plus.version>
48   - <jsoup.version>1.15.4</jsoup.version>
49   - <lombok.version>1.18.28</lombok.version>
  47 + <captcha-plus.version>1.0.8</captcha-plus.version>
  48 + <jsoup.version>1.16.1</jsoup.version>
  49 + <lombok.version>1.18.30</lombok.version>
50 50 <mapstruct.version>1.5.5.Final</mapstruct.version>
51   - <hutool.version>5.8.20</hutool.version>
  51 + <hutool.version>5.8.22</hutool.version>
52 52 <easyexcel.verion>3.3.2</easyexcel.verion>
53 53 <velocity.version>2.3</velocity.version>
54 54 <screw.version>1.0.5</screw.version>
55 55 <fastjson.version>1.2.83</fastjson.version>
56   - <guava.version>32.0.1-jre</guava.version>
  56 + <guava.version>32.1.2-jre</guava.version>
57 57 <guice.version>5.1.0</guice.version>
58 58 <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
59 59 <commons-net.version>3.9.0</commons-net.version>
60 60 <jsch.version>0.1.55</jsch.version>
61 61 <tika-core.version>2.7.0</tika-core.version>
62   - <netty-all.version>4.1.90.Final</netty-all.version>
63 62 <ip2region.version>2.7.0</ip2region.version>
64 63 <!-- 三方云服务相关 -->
65   - <okio.version>3.0.0</okio.version>
66   - <okhttp3.version>4.10.0</okhttp3.version>
  64 + <okio.version>3.5.0</okio.version>
  65 + <okhttp3.version>4.11.0</okhttp3.version>
67 66 <commons-io.version>2.11.0</commons-io.version>
68   - <minio.version>8.5.4</minio.version>
69   - <aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
  67 + <minio.version>8.5.6</minio.version>
  68 + <aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>
70 69 <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
71   - <tencentcloud-sdk-java.version>3.1.758</tencentcloud-sdk-java.version>
72   - <justauth.version>1.0.3</justauth.version>
73   - <jimureport.version>1.5.8</jimureport.version>
  70 + <tencentcloud-sdk-java.version>3.1.853</tencentcloud-sdk-java.version>
  71 + <justauth.version>1.0.5</justauth.version>
  72 + <jimureport.version>1.6.1</jimureport.version>
74 73 <xercesImpl.version>2.12.2</xercesImpl.version>
75 74 <weixin-java.version>4.5.0</weixin-java.version>
76 75 </properties>
... ... @@ -233,6 +232,12 @@
233 232 <groupId>org.redisson</groupId>
234 233 <artifactId>redisson-spring-boot-starter</artifactId>
235 234 <version>${redisson.version}</version>
  235 + <exclusions>
  236 + <exclusion>
  237 + <groupId>org.springframework.boot</groupId>
  238 + <artifactId>spring-boot-starter-actuator</artifactId>
  239 + </exclusion>
  240 + </exclusions>
236 241 </dependency>
237 242  
238 243 <dependency>
... ... @@ -337,6 +342,12 @@
337 342 <groupId>de.codecentric</groupId>
338 343 <artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
339 344 <version>${spring-boot-admin.version}</version>
  345 + <exclusions>
  346 + <exclusion>
  347 + <groupId>de.codecentric</groupId>
  348 + <artifactId>spring-boot-admin-server-cloud</artifactId>
  349 + </exclusion>
  350 + </exclusions>
340 351 </dependency>
341 352 <dependency>
342 353 <groupId>de.codecentric</groupId>
... ... @@ -520,12 +531,6 @@
520 531 </dependency>
521 532  
522 533 <dependency>
523   - <groupId>io.netty</groupId>
524   - <artifactId>netty-all</artifactId>
525   - <version>${netty-all.version}</version>
526   - </dependency>
527   -
528   - <dependency>
529 534 <groupId>com.xingyuv</groupId>
530 535 <artifactId>spring-boot-starter-captcha-plus</artifactId>
531 536 <version>${captcha-plus.version}</version>
... ...
yudao-example/yudao-sso-demo-by-code/pom.xml
... ... @@ -21,7 +21,7 @@
21 21 <maven.compiler.target>8</maven.compiler.target>
22 22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23 23 <!-- 统一依赖管理 -->
24   - <spring.boot.version>2.7.14</spring.boot.version>
  24 + <spring.boot.version>2.7.16</spring.boot.version>
25 25 </properties>
26 26  
27 27 <dependencyManagement>
... ... @@ -52,7 +52,7 @@
52 52 <dependency>
53 53 <groupId>cn.hutool</groupId>
54 54 <artifactId>hutool-all</artifactId>
55   - <version>5.8.20</version>
  55 + <version>5.8.22</version>
56 56 </dependency>
57 57  
58 58 <dependency>
... ...
yudao-example/yudao-sso-demo-by-password/pom.xml
... ... @@ -21,7 +21,7 @@
21 21 <maven.compiler.target>8</maven.compiler.target>
22 22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23 23 <!-- 统一依赖管理 -->
24   - <spring.boot.version>2.7.14</spring.boot.version>
  24 + <spring.boot.version>2.7.16</spring.boot.version>
25 25 </properties>
26 26  
27 27 <dependencyManagement>
... ... @@ -52,7 +52,7 @@
52 52 <dependency>
53 53 <groupId>cn.hutool</groupId>
54 54 <artifactId>hutool-all</artifactId>
55   - <version>5.8.20</version>
  55 + <version>5.8.22</version>
56 56 </dependency>
57 57  
58 58 <dependency>
... ...
yudao-framework/yudao-common/pom.xml
... ... @@ -137,6 +137,7 @@
137 137 <dependency>
138 138 <groupId>org.springframework.boot</groupId>
139 139 <artifactId>spring-boot-starter-test</artifactId>
  140 + <scope>test</scope>
140 141 </dependency>
141 142 </dependencies>
142 143  
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
... ... @@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
10 10 import com.fasterxml.jackson.databind.SerializationFeature;
11 11 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
12 12 import lombok.SneakyThrows;
13   -import lombok.experimental.UtilityClass;
14 13 import lombok.extern.slf4j.Slf4j;
15 14  
16 15 import java.io.IOException;
... ... @@ -23,7 +22,6 @@ import java.util.List;
23 22 *
24 23 * @author 芋道源码
25 24 */
26   -@UtilityClass
27 25 @Slf4j
28 26 public class JsonUtils {
29 27  
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java
... ... @@ -64,12 +64,12 @@ public class PayTransferRespDTO {
64 64 }
65 65  
66 66 /**
67   - * 创建【FAILURE】状态的转账返回
  67 + * 创建【CLOSED】状态的转账返回
68 68 */
69   - public static PayTransferRespDTO failureOf(String channelErrorCode, String channelErrorMsg,
70   - String outTransferNo, Object rawData) {
  69 + public static PayTransferRespDTO closedOf(String channelErrorCode, String channelErrorMsg,
  70 + String outTransferNo, Object rawData) {
71 71 PayTransferRespDTO respDTO = new PayTransferRespDTO();
72   - respDTO.status = PayTransferStatusRespEnum.FAILURE.getStatus();
  72 + respDTO.status = PayTransferStatusRespEnum.CLOSED.getStatus();
73 73 respDTO.channelErrorCode = channelErrorCode;
74 74 respDTO.channelErrorMsg = channelErrorMsg;
75 75 // 相对通用的字段
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
... ... @@ -58,7 +58,6 @@ public class PayTransferUnifiedReqDTO {
58 58  
59 59 /**
60 60 * 支付渠道的额外参数
61   - *
62 61 */
63 62 private Map<String, String> channelExtras;
64 63  
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
... ... @@ -50,7 +50,7 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
50 50 */
51 51 public final void init() {
52 52 doInit();
53   - log.info("[init][客户端({}) 初始化完成]", getId());
  53 + log.debug("[init][客户端({}) 初始化完成]", getId());
54 54 }
55 55  
56 56 /**
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayTransferClient.java
... ... @@ -51,7 +51,7 @@ public class AlipayTransferClient extends AbstractAlipayPayClient {
51 51 model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD
52 52 model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER。
53 53 model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras()));
54   - PayTransferTypeEnum transferType = PayTransferTypeEnum.valueOf(reqDTO.getType());
  54 + PayTransferTypeEnum transferType = PayTransferTypeEnum.ofType(reqDTO.getType());
55 55 switch(transferType){
56 56 case WX_BALANCE :
57 57 case WALLET_BALANCE : {
... ... @@ -84,7 +84,7 @@ public class AlipayTransferClient extends AbstractAlipayPayClient {
84 84 if (ObjectUtils.equalsAny(response.getSubCode(), "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
85 85 return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);
86 86 }
87   - return PayTransferRespDTO.failureOf(response.getSubCode(), response.getSubMsg(),
  87 + return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
88 88 reqDTO.getOutTransferNo(), response);
89 89 }
90 90 return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java
... ... @@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.pay.core.enums.transfer;
3 3 import lombok.AllArgsConstructor;
4 4 import lombok.Getter;
5 5  
  6 +import java.util.Objects;
  7 +
6 8 /**
7 9 * 渠道的转账状态枚举
8 10 *
... ... @@ -12,10 +14,28 @@ import lombok.Getter;
12 14 @AllArgsConstructor
13 15 public enum PayTransferStatusRespEnum {
14 16  
15   - WAITING(0, "等待转账"),
16   - SUCCESS(10, "转账成功"),
17   - FAILURE(20, "转账失败");
  17 + WAITING(0, "转账中"),
  18 +
  19 + /**
  20 + * TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现
  21 + */
  22 + IN_PROGRESS(10, "转账进行中"),
  23 +
  24 +
  25 + SUCCESS(20, "转账成功"),
  26 + /**
  27 + * 转账关闭 (失败,或者其它情况)
  28 + */
  29 + CLOSED(30, "转账关闭");
18 30  
19 31 private final Integer status;
20 32 private final String name;
  33 +
  34 + public static boolean isSuccess(Integer status) {
  35 + return Objects.equals(status, SUCCESS.getStatus());
  36 + }
  37 +
  38 + public static boolean isClosed(Integer status) {
  39 + return Objects.equals(status, CLOSED.getStatus());
  40 + }
21 41 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferTypeEnum.java
... ... @@ -20,6 +20,9 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
20 20 BANK_CARD(3, "银行卡"),
21 21 WALLET_BALANCE(4, "钱包余额");
22 22  
  23 + public static final String ALIPAY_LOGON_ID = "ALIPAY_LOGON_ID";
  24 + public static final String ALIPAY_ACCOUNT_NAME = "ALIPAY_ACCOUNT_NAME";
  25 +
23 26 private final Integer type;
24 27 private final String name;
25 28  
... ... @@ -30,7 +33,7 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
30 33 return ARRAYS;
31 34 }
32 35  
33   - public static PayTransferTypeEnum valueOf(Integer type) {
  36 + public static PayTransferTypeEnum ofType(Integer type) {
34 37 return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
35 38 }
36 39 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/AbstractSmsClient.java
... ... @@ -40,7 +40,7 @@ public abstract class AbstractSmsClient implements SmsClient {
40 40 */
41 41 public final void init() {
42 42 doInit();
43   - log.info("[init][配置({}) 初始化完成]", properties);
  43 + log.debug("[init][配置({}) 初始化完成]", properties);
44 44 }
45 45  
46 46 /**
... ...
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java
1   -package cn.iocoder.yudao.framework.social.config;
2   -
3   -import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
4   -import com.xingyuv.http.HttpUtil;
5   -import com.xingyuv.http.support.hutool.HutoolImpl;
6   -import com.xingyuv.jushauth.cache.AuthStateCache;
7   -import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
8   -import lombok.extern.slf4j.Slf4j;
9   -import org.springframework.boot.autoconfigure.AutoConfiguration;
10   -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
11   -import org.springframework.boot.context.properties.EnableConfigurationProperties;
12   -import org.springframework.context.annotation.Bean;
13   -import org.springframework.context.annotation.Primary;
14   -
15   -/**
16   - * 社交自动装配类
17   - *
18   - * @author timfruit
19   - * @date 2021-10-30
20   - */
21   -@Slf4j
22   -@AutoConfiguration
23   -@EnableConfigurationProperties(JustAuthProperties.class)
24   -public class YudaoSocialAutoConfiguration {
25   -
26   - @Bean
27   - @Primary
28   - @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
29   - public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
30   - // 需要修改 HttpUtil 使用的实现,避免类报错
31   - HttpUtil.setHttp(new HutoolImpl());
32   - // 创建 YudaoAuthRequestFactory
33   - return new YudaoAuthRequestFactory(properties, authStateCache);
34   - }
35   -
36   -}
  1 +package cn.iocoder.yudao.framework.social.config;
  2 +
  3 +import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
  4 +import com.xingyuv.http.HttpUtil;
  5 +import com.xingyuv.http.support.hutool.HutoolImpl;
  6 +import com.xingyuv.jushauth.cache.AuthStateCache;
  7 +import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  10 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  11 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  12 +import org.springframework.context.annotation.Bean;
  13 +import org.springframework.context.annotation.Primary;
  14 +
  15 +/**
  16 + * 社交自动装配类
  17 + *
  18 + * @author timfruit
  19 + * @date 2021-10-30
  20 + */
  21 +@Slf4j
  22 +@AutoConfiguration
  23 +@EnableConfigurationProperties(JustAuthProperties.class)
  24 +public class YudaoSocialAutoConfiguration {
  25 +
  26 + @Bean
  27 + @Primary
  28 + @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
  29 + public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
  30 + // 需要修改 HttpUtil 使用的实现,避免类报错
  31 + HttpUtil.setHttp(new HutoolImpl());
  32 + // 创建 YudaoAuthRequestFactory
  33 + return new YudaoAuthRequestFactory(properties, authStateCache);
  34 + }
  35 +
  36 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.tenant.config;
2 2  
3   -import cn.hutool.core.annotation.AnnotationUtil;
4 3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
5 4 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
6   -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
7 5 import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
8 6 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
9 7 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
10   -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
11   -import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
  8 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
12 9 import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
13 10 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
14 11 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
... ... @@ -20,8 +17,6 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
20 17 import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
21 18 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
22 19 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
23   -import org.springframework.beans.BeansException;
24   -import org.springframework.beans.factory.config.BeanPostProcessor;
25 20 import org.springframework.boot.autoconfigure.AutoConfiguration;
26 21 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
27 22 import org.springframework.boot.context.properties.EnableConfigurationProperties;
... ... @@ -100,25 +95,8 @@ public class YudaoTenantAutoConfiguration {
100 95 // ========== Job ==========
101 96  
102 97 @Bean
103   - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
104   - public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
105   - return new BeanPostProcessor() {
106   -
107   - @Override
108   - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
109   - if (!(bean instanceof JobHandler)) {
110   - return bean;
111   - }
112   - // 有 TenantJob 注解的情况下,才会进行处理
113   - if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
114   - return bean;
115   - }
116   -
117   - // 使用 TenantJobHandlerDecorator 装饰
118   - return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
119   - }
120   -
121   - };
  98 + public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {
  99 + return new TenantJobAspect(tenantFrameworkService);
122 100 }
123 101  
124 102 // ========== Redis ==========
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJob.java
... ... @@ -8,7 +8,7 @@ import java.lang.annotation.Target;
8 8 /**
9 9 * 多租户 Job 注解
10 10 */
11   -@Target({ElementType.TYPE})
  11 +@Target({ElementType.METHOD})
12 12 @Retention(RetentionPolicy.RUNTIME)
13 13 public @interface TenantJob {
14 14 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerDecorator.java renamed to yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java
1 1 package cn.iocoder.yudao.framework.tenant.core.job;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.exceptions.ExceptionUtil;
4 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5   -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
6   -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 6 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
8   -import lombok.AllArgsConstructor;
  7 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  8 +import lombok.RequiredArgsConstructor;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.aspectj.lang.ProceedingJoinPoint;
  11 +import org.aspectj.lang.annotation.Around;
  12 +import org.aspectj.lang.annotation.Aspect;
9 13  
10 14 import java.util.List;
11 15 import java.util.Map;
12 16 import java.util.concurrent.ConcurrentHashMap;
13 17  
14 18 /**
15   - * 多租户 JobHandler 装饰器
  19 + * 多租户 JobHandler AOP
16 20 * 任务执行时,会按照租户逐个执行 Job 的逻辑
17 21 *
18 22 * 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。
19 23 *
20 24 * @author 芋道源码
21 25 */
22   -@AllArgsConstructor
23   -public class TenantJobHandlerDecorator implements JobHandler {
  26 +@Aspect
  27 +@RequiredArgsConstructor
  28 +@Slf4j
  29 +public class TenantJobAspect {
24 30  
25 31 private final TenantFrameworkService tenantFrameworkService;
26   - /**
27   - * 被装饰的 Job
28   - */
29   - private final JobHandler jobHandler;
30 32  
31   - @Override
32   - public final String execute(String param) throws Exception {
  33 + @Around("@annotation(tenantJob)")
  34 + public String around(ProceedingJoinPoint joinPoint, TenantJob tenantJob) {
33 35 // 获得租户列表
34 36 List<Long> tenantIds = tenantFrameworkService.getTenantIds();
35 37 if (CollUtil.isEmpty(tenantIds)) {
... ... @@ -38,19 +40,15 @@ public class TenantJobHandlerDecorator implements JobHandler {
38 40  
39 41 // 逐个租户,执行 Job
40 42 Map<Long, String> results = new ConcurrentHashMap<>();
41   - tenantIds.parallelStream().forEach(tenantId -> { // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
42   - try {
43   - // 设置租户
44   - TenantContextHolder.setTenantId(tenantId);
45   - // 执行 Job
46   - String result = jobHandler.execute(param);
47   - // 添加结果
48   - results.put(tenantId, result);
49   - } catch (Exception e) {
50   - throw new RuntimeException(e);
51   - } finally {
52   - TenantContextHolder.clear();
53   - }
  43 + tenantIds.parallelStream().forEach(tenantId -> {
  44 + // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
  45 + TenantUtils.execute(tenantId, () -> {
  46 + try {
  47 + joinPoint.proceed();
  48 + } catch (Throwable e) {
  49 + results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));
  50 + }
  51 + });
54 52 });
55 53 return JsonUtils.toJsonString(results);
56 54 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java
1 1 package cn.iocoder.yudao.framework.tenant.core.redis;
2 2  
  3 +import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
3 4 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
4 5 import lombok.extern.slf4j.Slf4j;
5 6 import org.springframework.cache.Cache;
... ... @@ -15,7 +16,7 @@ import org.springframework.data.redis.cache.RedisCacheWriter;
15 16 * @author airhead
16 17 */
17 18 @Slf4j
18   -public class TenantRedisCacheManager extends RedisCacheManager {
  19 +public class TenantRedisCacheManager extends TimeoutRedisCacheManager {
19 20  
20 21 public TenantRedisCacheManager(RedisCacheWriter cacheWriter,
21 22 RedisCacheConfiguration defaultCacheConfiguration) {
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobTest.java deleted
1   -package cn.iocoder.yudao.framework.tenant.core.job;
2   -
3   -import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
4   -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
5   -import com.google.common.collect.Lists;
6   -import org.junit.jupiter.api.Test;
7   -import org.mockito.Mock;
8   -import org.mockito.Mockito;
9   -
10   -import java.util.List;
11   -
12   -import static org.junit.jupiter.api.Assertions.assertEquals;
13   -
14   -/**
15   - * 验证 job 租户逻辑
16   - * {@link TenantJobHandlerDecorator}
17   - *
18   - * @author gaibu
19   - */
20   -public class TenantJobTest extends BaseMockitoUnitTest {
21   -
22   - @Mock
23   - TenantFrameworkService tenantFrameworkService;
24   -
25   - @Test
26   - public void test() throws Exception {
27   - // 准备测试租户 id
28   - List<Long> tenantIds = Lists.newArrayList(1L, 2L, 3L);
29   - // mock 数据
30   - Mockito.doReturn(tenantIds).when(tenantFrameworkService).getTenantIds();
31   - // 准备测试任务
32   - TestJob testJob = new TestJob();
33   - // 创建任务装饰器
34   - TenantJobHandlerDecorator tenantJobHandlerDecorator = new TenantJobHandlerDecorator(tenantFrameworkService, testJob);
35   -
36   - // 执行任务
37   - tenantJobHandlerDecorator.execute(null);
38   -
39   - // 断言返回值
40   - assertEquals(testJob.getTenantIds(), tenantIds);
41   - }
42   -}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/job/TestJob.java
... ... @@ -9,12 +9,12 @@ import java.util.List;
9 9 import java.util.concurrent.CopyOnWriteArrayList;
10 10  
11 11 @Component
12   -@TenantJob // 标记多租户
13 12 public class TestJob implements JobHandler {
14 13  
15 14 private final List<Long> tenantIds = new CopyOnWriteArrayList<>();
16 15  
17 16 @Override
  17 + @TenantJob // 标记多租户
18 18 public String execute(String param) throws Exception {
19 19 tenantIds.add(TenantContextHolder.getTenantId());
20 20 return "success";
... ... @@ -24,4 +24,5 @@ public class TestJob implements JobHandler {
24 24 CollUtil.sort(tenantIds, Long::compareTo);
25 25 return tenantIds;
26 26 }
  27 +
27 28 }
... ...
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java
... ... @@ -30,7 +30,7 @@ public abstract class AbstractFileClient&lt;Config extends FileClientConfig&gt; implem
30 30 */
31 31 public final void init() {
32 32 doInit();
33   - log.info("[init][配置({}) 初始化完成]", config);
  33 + log.debug("[init][配置({}) 初始化完成]", config);
34 34 }
35 35  
36 36 /**
... ...
yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.flowable.core.util;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import org.flowable.bpmn.converter.BpmnXMLConverter;
  5 +import org.flowable.bpmn.model.Process;
  6 +import org.flowable.bpmn.model.*;
  7 +
  8 +import java.util.*;
  9 +
  10 +/**
  11 + * 流程模型转操作工具类
  12 + */
  13 +public class BpmnModelUtils {
  14 +
  15 + /**
  16 + * 根据节点,获取入口连线
  17 + *
  18 + * @param source 起始节点
  19 + * @return 入口连线列表
  20 + */
  21 + public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
  22 + if (source instanceof FlowNode) {
  23 + return ((FlowNode) source).getIncomingFlows();
  24 + }
  25 + return new ArrayList<>();
  26 + }
  27 +
  28 + /**
  29 + * 根据节点,获取出口连线
  30 + *
  31 + * @param source 起始节点
  32 + * @return 出口连线列表
  33 + */
  34 + public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
  35 + if (source instanceof FlowNode) {
  36 + return ((FlowNode) source).getOutgoingFlows();
  37 + }
  38 + return new ArrayList<>();
  39 + }
  40 +
  41 + /**
  42 + * 获取流程元素信息
  43 + *
  44 + * @param model bpmnModel 对象
  45 + * @param flowElementId 元素 ID
  46 + * @return 元素信息
  47 + */
  48 + public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
  49 + Process process = model.getMainProcess();
  50 + return process.getFlowElement(flowElementId);
  51 + }
  52 +
  53 + /**
  54 + * 获得 BPMN 流程中,指定的元素们
  55 + *
  56 + * @param model
  57 + * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
  58 + * @return 元素们
  59 + */
  60 + public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
  61 + List<T> result = new ArrayList<>();
  62 + model.getProcesses().forEach(process -> {
  63 + process.getFlowElements().forEach(flowElement -> {
  64 + if (flowElement.getClass().isAssignableFrom(clazz)) {
  65 + result.add((T) flowElement);
  66 + }
  67 + });
  68 + });
  69 + return result;
  70 + }
  71 +
  72 + /**
  73 + * 比较 两个bpmnModel 是否相同
  74 + * @param oldModel 老的bpmn model
  75 + * @param newModel 新的bpmn model
  76 + */
  77 + public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
  78 + // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
  79 + return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
  80 + }
  81 +
  82 + /**
  83 + * 把 bpmnModel 转换成 byte[]
  84 + * @param model bpmnModel
  85 + */
  86 + public static byte[] getBpmnBytes(BpmnModel model) {
  87 + if (model == null) {
  88 + return new byte[0];
  89 + }
  90 + BpmnXMLConverter converter = new BpmnXMLConverter();
  91 + return converter.convertToXML(model);
  92 + }
  93 +
  94 + // ========== 遍历相关的方法 ==========
  95 +
  96 + /**
  97 + * 找到 source 节点之前的所有用户任务节点
  98 + *
  99 + * @param source 起始节点
  100 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
  101 + * @param userTaskList 已找到的用户任务节点
  102 + * @return 用户任务节点 数组
  103 + */
  104 + public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
  105 + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
  106 + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
  107 + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
  108 + if (source instanceof StartEvent && source.getSubProcess() != null) {
  109 + userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
  110 + }
  111 +
  112 + // 根据类型,获取入口连线
  113 + List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
  114 + if (sequenceFlows == null) {
  115 + return userTaskList;
  116 + }
  117 + // 循环找到目标元素
  118 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  119 + // 如果发现连线重复,说明循环了,跳过这个循环
  120 + if (hasSequenceFlow.contains(sequenceFlow.getId())) {
  121 + continue;
  122 + }
  123 + // 添加已经走过的连线
  124 + hasSequenceFlow.add(sequenceFlow.getId());
  125 + // 类型为用户节点,则新增父级节点
  126 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
  127 + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
  128 + }
  129 + // 类型为子流程,则添加子流程开始节点出口处相连的节点
  130 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
  131 + // 获取子流程用户任务节点
  132 + List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
  133 + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
  134 + if (CollUtil.isNotEmpty(childUserTaskList)) {
  135 + userTaskList.addAll(childUserTaskList);
  136 + }
  137 + }
  138 + // 继续迭代
  139 + userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
  140 + }
  141 + return userTaskList;
  142 + }
  143 +
  144 + /**
  145 + * 迭代获取子流程用户任务节点
  146 + *
  147 + * @param source 起始节点
  148 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
  149 + * @param userTaskList 需要撤回的用户任务列表
  150 + * @return 用户任务节点
  151 + */
  152 + public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
  153 + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
  154 + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
  155 +
  156 + // 根据类型,获取出口连线
  157 + List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
  158 + if (sequenceFlows == null) {
  159 + return userTaskList;
  160 + }
  161 + // 循环找到目标元素
  162 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  163 + // 如果发现连线重复,说明循环了,跳过这个循环
  164 + if (hasSequenceFlow.contains(sequenceFlow.getId())) {
  165 + continue;
  166 + }
  167 + // 添加已经走过的连线
  168 + hasSequenceFlow.add(sequenceFlow.getId());
  169 + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
  170 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
  171 + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
  172 + continue;
  173 + }
  174 + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
  175 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
  176 + List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
  177 + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
  178 + if (CollUtil.isNotEmpty(childUserTaskList)) {
  179 + userTaskList.addAll(childUserTaskList);
  180 + continue;
  181 + }
  182 + }
  183 + // 继续迭代
  184 + userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
  185 + }
  186 + return userTaskList;
  187 + }
  188 +
  189 +
  190 + /**
  191 + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
  192 + * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
  193 + *
  194 + * @param source 起始节点
  195 + * @param target 目标节点
  196 + * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
  197 + * @return 结果
  198 + */
  199 + public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
  200 + visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
  201 + // 不能是开始事件和子流程
  202 + if (source instanceof StartEvent && isInEventSubprocess(source)) {
  203 + return false;
  204 + }
  205 +
  206 + // 根据类型,获取入口连线
  207 + List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
  208 + if (CollUtil.isEmpty(sequenceFlows)) {
  209 + return true;
  210 + }
  211 + // 循环找到目标元素
  212 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  213 + // 如果发现连线重复,说明循环了,跳过这个循环
  214 + if (visitedElements.contains(sequenceFlow.getId())) {
  215 + continue;
  216 + }
  217 + // 添加已经走过的连线
  218 + visitedElements.add(sequenceFlow.getId());
  219 + // 这条线路存在目标节点,这条线路完成,进入下个线路
  220 + FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
  221 + if (target.getId().equals(sourceFlowElement.getId())) {
  222 + continue;
  223 + }
  224 + // 如果目标节点为并行网关,则不继续
  225 + if (sourceFlowElement instanceof ParallelGateway) {
  226 + return false;
  227 + }
  228 + // 否则就继续迭代
  229 + if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
  230 + return false;
  231 + }
  232 + }
  233 + return true;
  234 + }
  235 +
  236 + /**
  237 + * 判断当前节点是否属于不同的子流程
  238 + *
  239 + * @param flowElement 被判断的节点
  240 + * @return true 表示属于子流程
  241 + */
  242 + private static boolean isInEventSubprocess(FlowElement flowElement) {
  243 + FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
  244 + while (flowElementsContainer != null) {
  245 + if (flowElementsContainer instanceof EventSubProcess) {
  246 + return true;
  247 + }
  248 +
  249 + if (flowElementsContainer instanceof FlowElement) {
  250 + flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
  251 + } else {
  252 + flowElementsContainer = null;
  253 + }
  254 + }
  255 + return false;
  256 + }
  257 +
  258 + /**
  259 + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
  260 + *
  261 + * @param source 起始节点
  262 + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点
  263 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
  264 + * @param userTaskList 需要撤回的用户任务列表
  265 + * @return 子级任务节点列表
  266 + */
  267 + public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
  268 + Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
  269 + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
  270 + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
  271 + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
  272 + if (source instanceof StartEvent && source.getSubProcess() != null) {
  273 + userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
  274 + }
  275 +
  276 + // 根据类型,获取出口连线
  277 + List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
  278 + if (sequenceFlows == null) {
  279 + return userTaskList;
  280 + }
  281 + // 循环找到目标元素
  282 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  283 + // 如果发现连线重复,说明循环了,跳过这个循环
  284 + if (hasSequenceFlow.contains(sequenceFlow.getId())) {
  285 + continue;
  286 + }
  287 + // 添加已经走过的连线
  288 + hasSequenceFlow.add(sequenceFlow.getId());
  289 + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
  290 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
  291 + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
  292 + continue;
  293 + }
  294 + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
  295 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
  296 + List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
  297 + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
  298 + if (CollUtil.isNotEmpty(childUserTaskList)) {
  299 + userTaskList.addAll(childUserTaskList);
  300 + continue;
  301 + }
  302 + }
  303 + // 继续迭代
  304 + userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
  305 + }
  306 + return userTaskList;
  307 + }
  308 +
  309 +}
... ...
yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/FlowableUtils.java
1 1 package cn.iocoder.yudao.framework.flowable.core.util;
2 2  
3   -import org.flowable.bpmn.converter.BpmnXMLConverter;
4   -import org.flowable.bpmn.model.BpmnModel;
5   -import org.flowable.bpmn.model.FlowElement;
6 3 import org.flowable.common.engine.impl.identity.Authentication;
7 4  
8   -import java.util.ArrayList;
9   -import java.util.Arrays;
10   -import java.util.List;
11   -
12 5 /**
13 6 * Flowable 相关的工具方法
14 7 *
... ... @@ -26,49 +19,6 @@ public class FlowableUtils {
26 19 Authentication.setAuthenticatedUserId(null);
27 20 }
28 21  
29   - // ========== BPMN 相关的工具方法 ==========
30   -
31   - /**
32   - * 获得 BPMN 流程中,指定的元素们
33   - *
34   - * @param model
35   - * @param clazz 指定元素。例如说,{@link org.flowable.bpmn.model.UserTask}、{@link org.flowable.bpmn.model.Gateway} 等等
36   - * @return 元素们
37   - */
38   - public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
39   - List<T> result = new ArrayList<>();
40   - model.getProcesses().forEach(process -> {
41   - process.getFlowElements().forEach(flowElement -> {
42   - if (flowElement.getClass().isAssignableFrom(clazz)) {
43   - result.add((T) flowElement);
44   - }
45   - });
46   - });
47   - return result;
48   - }
49   -
50   - /**
51   - * 比较 两个bpmnModel 是否相同
52   - * @param oldModel 老的bpmn model
53   - * @param newModel 新的bpmn model
54   - */
55   - public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
56   - // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
57   - return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
58   - }
59   -
60   - /**
61   - * 把 bpmnModel 转换成 byte[]
62   - * @param model bpmnModel
63   - */
64   - public static byte[] getBpmnBytes(BpmnModel model) {
65   - if (model == null) {
66   - return new byte[0];
67   - }
68   - BpmnXMLConverter converter = new BpmnXMLConverter();
69   - return converter.convertToXML(model);
70   - }
71   -
72 22 // ========== Execution 相关的工具方法 ==========
73 23  
74 24 public static String formatCollectionVariable(String activityId) {
... ...
yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoQuartzAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.quartz.config;
2 2  
3 3 import cn.iocoder.yudao.framework.quartz.core.scheduler.SchedulerManager;
  4 +import lombok.extern.slf4j.Slf4j;
4 5 import org.quartz.Scheduler;
5 6 import org.springframework.boot.autoconfigure.AutoConfiguration;
6 7 import org.springframework.context.annotation.Bean;
7 8 import org.springframework.scheduling.annotation.EnableScheduling;
8 9  
  10 +import java.util.Optional;
  11 +
9 12 /**
10 13 * 定时任务 Configuration
11 14 */
12 15 @AutoConfiguration
13 16 @EnableScheduling // 开启 Spring 自带的定时任务
  17 +@Slf4j
14 18 public class YudaoQuartzAutoConfiguration {
15 19  
16 20 @Bean
17   - public SchedulerManager schedulerManager(Scheduler scheduler) {
18   - return new SchedulerManager(scheduler);
  21 + public SchedulerManager schedulerManager(Optional<Scheduler> scheduler) {
  22 + if (!scheduler.isPresent()) {
  23 + log.info("[定时任务 - 已禁用][参考 https://doc.iocoder.cn/job/ 开启]");
  24 + return new SchedulerManager(null);
  25 + }
  26 + return new SchedulerManager(scheduler.get());
19 27 }
20 28  
21 29 }
... ...
yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java
... ... @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum;
4 4 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker;
5 5 import org.quartz.*;
6 6  
  7 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
  8 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
  9 +
7 10 /**
8 11 * {@link org.quartz.Scheduler} 的管理器,负责创建任务
9 12 *
... ... @@ -37,6 +40,7 @@ public class SchedulerManager {
37 40 public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression,
38 41 Integer retryCount, Integer retryInterval)
39 42 throws SchedulerException {
  43 + validateScheduler();
40 44 // 创建 JobDetail 对象
41 45 JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class)
42 46 .usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId)
... ... @@ -61,6 +65,7 @@ public class SchedulerManager {
61 65 public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression,
62 66 Integer retryCount, Integer retryInterval)
63 67 throws SchedulerException {
  68 + validateScheduler();
64 69 // 创建新 Trigger 对象
65 70 Trigger newTrigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);
66 71 // 修改调度
... ... @@ -74,6 +79,7 @@ public class SchedulerManager {
74 79 * @throws SchedulerException 删除异常
75 80 */
76 81 public void deleteJob(String jobHandlerName) throws SchedulerException {
  82 + validateScheduler();
77 83 scheduler.deleteJob(new JobKey(jobHandlerName));
78 84 }
79 85  
... ... @@ -84,6 +90,7 @@ public class SchedulerManager {
84 90 * @throws SchedulerException 暂停异常
85 91 */
86 92 public void pauseJob(String jobHandlerName) throws SchedulerException {
  93 + validateScheduler();
87 94 scheduler.pauseJob(new JobKey(jobHandlerName));
88 95 }
89 96  
... ... @@ -94,6 +101,7 @@ public class SchedulerManager {
94 101 * @throws SchedulerException 启动异常
95 102 */
96 103 public void resumeJob(String jobHandlerName) throws SchedulerException {
  104 + validateScheduler();
97 105 scheduler.resumeJob(new JobKey(jobHandlerName));
98 106 scheduler.resumeTrigger(new TriggerKey(jobHandlerName));
99 107 }
... ... @@ -108,11 +116,12 @@ public class SchedulerManager {
108 116 */
109 117 public void triggerJob(Long jobId, String jobHandlerName, String jobHandlerParam)
110 118 throws SchedulerException {
  119 + validateScheduler();
  120 + // 触发任务
111 121 JobDataMap data = new JobDataMap(); // 无需重试,所以不设置 retryCount 和 retryInterval
112 122 data.put(JobDataKeyEnum.JOB_ID.name(), jobId);
113 123 data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName);
114 124 data.put(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam);
115   - // 触发任务
116 125 scheduler.triggerJob(new JobKey(jobHandlerName), data);
117 126 }
118 127  
... ... @@ -127,4 +136,11 @@ public class SchedulerManager {
127 136 .build();
128 137 }
129 138  
  139 + private void validateScheduler() {
  140 + if (scheduler == null) {
  141 + throw exception0(NOT_IMPLEMENTED.getCode(),
  142 + "[定时任务 - 已禁用][参考 https://doc.iocoder.cn/job/ 开启]");
  143 + }
  144 + }
  145 +
130 146 }
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
... ... @@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
7 7 import com.baomidou.mybatisplus.core.conditions.Wrapper;
8 8 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
9 9 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
10   -import com.baomidou.mybatisplus.core.mapper.BaseMapper;
11 10 import com.baomidou.mybatisplus.core.metadata.IPage;
12 11 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
13 12 import com.baomidou.mybatisplus.extension.toolkit.Db;
... ...
yudao-framework/yudao-spring-boot-starter-protection/pom.xml
... ... @@ -26,11 +26,13 @@
26 26 <dependency>
27 27 <groupId>com.baomidou</groupId>
28 28 <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
  29 + <optional>true</optional>
29 30 </dependency>
30 31  
31 32 <dependency>
32 33 <groupId>io.github.resilience4j</groupId>
33 34 <artifactId>resilience4j-spring-boot2</artifactId>
  35 + <optional>true</optional>
34 36 </dependency>
35 37 </dependencies>
36 38  
... ...
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/config/YudaoLock4jConfiguration.java
... ... @@ -3,9 +3,11 @@ package cn.iocoder.yudao.framework.lock4j.config;
3 3 import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
4 4 import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
5 5 import org.springframework.boot.autoconfigure.AutoConfiguration;
  6 +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
6 7 import org.springframework.context.annotation.Bean;
7 8  
8 9 @AutoConfiguration(before = LockAutoConfiguration.class)
  10 +@ConditionalOnClass(name = "com.baomidou.lock.annotation.Lock4j")
9 11 public class YudaoLock4jConfiguration {
10 12  
11 13 @Bean
... ...
yudao-framework/yudao-spring-boot-starter-redis/pom.xml
... ... @@ -33,12 +33,6 @@
33 33 </dependency>
34 34  
35 35 <dependency>
36   - <groupId>io.netty</groupId>
37   - <artifactId>netty-all</artifactId>
38   - </dependency>
39   -
40   -
41   - <dependency>
42 36 <groupId>com.fasterxml.jackson.datatype</groupId>
43 37 <artifactId>jackson-datatype-jsr310</artifactId>
44 38 </dependency>
... ...
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/TimeoutRedisCacheManager.java
... ... @@ -45,7 +45,7 @@ public class TimeoutRedisCacheManager extends RedisCacheManager {
45 45 Duration duration = parseDuration(names[1]);
46 46 cacheConfig = cacheConfig.entryTtl(duration);
47 47 }
48   - return super.createRedisCache(names[0], cacheConfig);
  48 + return super.createRedisCache(name, cacheConfig);
49 49 }
50 50  
51 51 /**
... ...
yudao-framework/yudao-spring-boot-starter-web/pom.xml
... ... @@ -55,13 +55,6 @@
55 55 <version>${revision}</version>
56 56 </dependency>
57 57  
58   - <!-- 服务保障相关 -->
59   - <dependency>
60   - <groupId>io.github.resilience4j</groupId>
61   - <artifactId>resilience4j-ratelimiter</artifactId>
62   - <scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
63   - </dependency>
64   -
65 58 <!-- xss -->
66 59 <dependency>
67 60 <groupId>org.jsoup</groupId>
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.jackson.config;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 5 import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeDeserializer;
5 6 import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeSerializer;
... ... @@ -11,51 +12,41 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
11 12 import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
12 13 import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
13 14 import lombok.extern.slf4j.Slf4j;
14   -import org.springframework.beans.BeansException;
15   -import org.springframework.beans.factory.config.BeanPostProcessor;
16 15 import org.springframework.boot.autoconfigure.AutoConfiguration;
17 16 import org.springframework.context.annotation.Bean;
18 17  
19 18 import java.time.LocalDate;
20 19 import java.time.LocalDateTime;
21 20 import java.time.LocalTime;
  21 +import java.util.List;
22 22  
23 23 @AutoConfiguration
24 24 @Slf4j
25 25 public class YudaoJacksonAutoConfiguration {
26 26  
27 27 @Bean
28   - public BeanPostProcessor objectMapperBeanPostProcessor() {
29   - return new BeanPostProcessor() {
30   -
31   - @Override
32   - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
33   - if (!(bean instanceof ObjectMapper)) {
34   - return bean;
35   - }
36   - ObjectMapper objectMapper = (ObjectMapper) bean;
37   - SimpleModule simpleModule = new SimpleModule();
38   - /*
39   - * 1. 新增Long类型序列化规则,数值超过2^53-1,在JS会出现精度丢失问题,因此Long自动序列化为字符串类型
40   - * 2. 新增LocalDateTime序列化、反序列化规则
41   - */
42   - simpleModule
43   - .addSerializer(Long.class, NumberSerializer.INSTANCE)
44   - .addSerializer(Long.TYPE, NumberSerializer.INSTANCE)
45   - .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)
46   - .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
47   - .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)
48   - .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
49   - .addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE)
50   - .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
51   -
52   - objectMapper.registerModules(simpleModule);
53   -
54   - JsonUtils.init(objectMapper);
55   - log.info("初始化 jackson 自动配置");
56   - return bean;
57   - }
58   - };
  28 + @SuppressWarnings("InstantiationOfUtilityClass")
  29 + public JsonUtils jsonUtils(List<ObjectMapper> objectMappers) {
  30 + // 1.1 创建 SimpleModule 对象
  31 + SimpleModule simpleModule = new SimpleModule();
  32 + simpleModule
  33 + // 新增 Long 类型序列化规则,数值超过 2^53-1,在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型
  34 + .addSerializer(Long.class, NumberSerializer.INSTANCE)
  35 + .addSerializer(Long.TYPE, NumberSerializer.INSTANCE)
  36 + .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)
  37 + .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
  38 + .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)
  39 + .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
  40 + // 新增 LocalDateTime 序列化、反序列化规则
  41 + .addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE)
  42 + .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
  43 + // 1.2 注册到 objectMapper
  44 + objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule));
  45 +
  46 + // 2. 设置 objectMapper 到 JsonUtils {
  47 + JsonUtils.init(CollUtil.getFirst(objectMappers));
  48 + log.info("[init][初始化 JsonUtils 成功]");
  49 + return new JsonUtils();
59 50 }
60 51  
61 52 }
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
... ... @@ -11,7 +11,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
11 11 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
12 12 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
13 13 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
14   -import io.github.resilience4j.ratelimiter.RequestNotPermitted;
15 14 import lombok.AllArgsConstructor;
16 15 import lombok.extern.slf4j.Slf4j;
17 16 import org.apache.commons.lang3.exception.ExceptionUtils;
... ... @@ -33,6 +32,7 @@ import javax.validation.ConstraintViolationException;
33 32 import javax.validation.ValidationException;
34 33 import java.time.LocalDateTime;
35 34 import java.util.Map;
  35 +import java.util.Objects;
36 36  
37 37 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
38 38  
... ... @@ -83,9 +83,6 @@ public class GlobalExceptionHandler {
83 83 if (ex instanceof HttpRequestMethodNotSupportedException) {
84 84 return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
85 85 }
86   - if (ex instanceof RequestNotPermitted) {
87   - return requestNotPermittedExceptionHandler(request, (RequestNotPermitted) ex);
88   - }
89 86 if (ex instanceof ServiceException) {
90 87 return serviceExceptionHandler((ServiceException) ex);
91 88 }
... ... @@ -186,8 +183,7 @@ public class GlobalExceptionHandler {
186 183 /**
187 184 * 处理 Resilience4j 限流抛出的异常
188 185 */
189   - @ExceptionHandler(value = RequestNotPermitted.class)
190   - public CommonResult<?> requestNotPermittedExceptionHandler(HttpServletRequest req, RequestNotPermitted ex) {
  186 + public CommonResult<?> requestNotPermittedExceptionHandler(HttpServletRequest req, Throwable ex) {
191 187 log.warn("[requestNotPermittedExceptionHandler][url({}) 访问过于频繁]", req.getRequestURL(), ex);
192 188 return CommonResult.error(TOO_MANY_REQUESTS);
193 189 }
... ... @@ -226,7 +222,12 @@ public class GlobalExceptionHandler {
226 222 return tableNotExistsResult;
227 223 }
228 224  
229   - // 情况二:处理异常
  225 + // 情况二:部分特殊的库的处理
  226 + if (Objects.equals("io.github.resilience4j.ratelimiter.RequestNotPermitted", ex.getClass().getName())) {
  227 + return requestNotPermittedExceptionHandler(req, ex);
  228 + }
  229 +
  230 + // 情况三:处理异常
230 231 log.error("[defaultExceptionHandler]", ex);
231 232 // 插入异常日志
232 233 this.createExceptionLog(req, ex);
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java
... ... @@ -21,6 +21,7 @@ import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.cr
21 21  
22 22 @AutoConfiguration
23 23 @EnableConfigurationProperties(XssProperties.class)
  24 +@ConditionalOnProperty(prefix = "yudao.xss", name = "enable", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
24 25 public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
25 26  
26 27 /**
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 1 cn.iocoder.yudao.framework.apilog.config.YudaoApiLogAutoConfiguration
2 2 cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
3   -com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration
4 3 cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
5 4 cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
6 5 cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
... ... @@ -4,61 +4,66 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
4 4  
5 5 /**
6 6 * Bpm 错误码枚举类
7   - *
  7 + * <p>
8 8 * bpm 系统,使用 1-009-000-000 段
9 9 */
10 10 public interface ErrorCodeConstants {
11 11  
12   - // ========== 通用流程处理 模块 1009000000 ==========
13   - ErrorCode HIGHLIGHT_IMG_ERROR = new ErrorCode(1009000002, "获取高亮流程图异常");
  12 + // ========== 通用流程处理 模块 1-009-000-000 ==========
  13 + ErrorCode HIGHLIGHT_IMG_ERROR = new ErrorCode(1_009_000_002, "获取高亮流程图异常");
14 14  
15   - // ========== OA 流程模块 1009001000 ==========
16   - ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1009001001, "请假申请不存在");
17   - ErrorCode OA_PM_POST_NOT_EXISTS = new ErrorCode(1009001002, "项目经理岗位未设置");
18   - ErrorCode OA_DEPART_PM_POST_NOT_EXISTS = new ErrorCode(1009001009, "部门的项目经理不存在");
19   - ErrorCode OA_BM_POST_NOT_EXISTS = new ErrorCode(1009001004, "部门经理岗位未设置");
20   - ErrorCode OA_DEPART_BM_POST_NOT_EXISTS = new ErrorCode(1009001005, "部门的部门经理不存在");
21   - ErrorCode OA_HR_POST_NOT_EXISTS = new ErrorCode(1009001006, "HR岗位未设置");
22   - ErrorCode OA_DAY_LEAVE_ERROR = new ErrorCode(1009001007, "请假天数必须>=1");
  15 + // ========== OA 流程模块 1-009-001-000 ==========
  16 + ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1_009_001_001, "请假申请不存在");
  17 + ErrorCode OA_PM_POST_NOT_EXISTS = new ErrorCode(1_009_001_002, "项目经理岗位未设置");
  18 + ErrorCode OA_DEPART_PM_POST_NOT_EXISTS = new ErrorCode(1_009_001_009, "部门的项目经理不存在");
  19 + ErrorCode OA_BM_POST_NOT_EXISTS = new ErrorCode(1_009_001_004, "部门经理岗位未设置");
  20 + ErrorCode OA_DEPART_BM_POST_NOT_EXISTS = new ErrorCode(1_009_001_005, "部门的部门经理不存在");
  21 + ErrorCode OA_HR_POST_NOT_EXISTS = new ErrorCode(1_009_001_006, "HR岗位未设置");
  22 + ErrorCode OA_DAY_LEAVE_ERROR = new ErrorCode(1_009_001_007, "请假天数必须>=1");
23 23  
24   - // ========== 流程模型 1009002000 ==========
25   - ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1009002000, "已经存在流程标识为【{}】的流程");
26   - ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1009002001, "流程模型不存在");
27   - ErrorCode MODEL_KEY_VALID = new ErrorCode(1009002002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!");
28   - ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1009002003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置");
29   - ErrorCode MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG = new ErrorCode(1009002004, "部署流程失败," +
  24 + // ========== 流程模型 1-009-002-000 ==========
  25 + ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1_009_002_000, "已经存在流程标识为【{}】的流程");
  26 + ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_009_002_001, "流程模型不存在");
  27 + ErrorCode MODEL_KEY_VALID = new ErrorCode(1_009_002_002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!");
  28 + ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1_009_002_003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置");
  29 + ErrorCode MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG = new ErrorCode(1_009_002_004, "部署流程失败," +
30 30 "原因:用户任务({})未配置分配规则,请点击【修改流程】按钮进行配置");
31   - ErrorCode MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS = new ErrorCode(1009003005, "流程定义部署失败,原因:信息未发生变化");
  31 + ErrorCode MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS = new ErrorCode(1_009_003_005, "流程定义部署失败,原因:信息未发生变化");
32 32  
33   - // ========== 流程定义 1009003000 ==========
34   - ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1009003000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
35   - ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1009003001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
36   - ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1009003002, "流程定义不存在");
37   - ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1009003003, "流程定义处于挂起状态");
38   - ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1009003004, "流程定义的模型不存在");
  33 + // ========== 流程定义 1-009-003-000 ==========
  34 + ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
  35 + ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1_009_003_001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
  36 + ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_009_003_002, "流程定义不存在");
  37 + ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1_009_003_003, "流程定义处于挂起状态");
  38 + ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1_009_003_004, "流程定义的模型不存在");
39 39  
40   - // ========== 流程实例 1009004000 ==========
41   - ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1009004000, "流程实例不存在");
42   - ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1009004001, "流程取消失败,流程不处于运行中");
43   - ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1009004002, "流程取消失败,该流程不是你发起的");
  40 + // ========== 流程实例 1-009-004-000 ==========
  41 + ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
  42 + ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
  43 + ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
44 44  
45   - // ========== 流程任务 1009005000 ==========
46   - ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009005000, "审批任务失败,原因:该任务不处于未审批");
47   - ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009005001, "审批任务失败,原因:该任务的审批人不是你");
  45 + // ========== 流程任务 1-009-005-000 ==========
  46 + ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
  47 + ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
  48 + ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
  49 + ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在");
  50 + ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
  51 + ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
  52 + ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
48 53  
49   - // ========== 流程任务分配规则 1009006000 ==========
50   - ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1009006000, "流程({}) 的任务({}) 已经存在分配规则");
51   - ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1009006001, "流程任务分配规则不存在");
52   - ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1009006002, "只有流程模型的任务分配规则,才允许被修改");
53   - ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1009006003, "操作失败,原因:找不到任务的审批人!");
54   - ErrorCode TASK_ASSIGN_SCRIPT_NOT_EXISTS = new ErrorCode(1009006004, "操作失败,原因:任务分配脚本({}) 不存在");
  54 + // ========== 流程任务分配规则 1-009-006-000 ==========
  55 + ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则");
  56 + ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在");
  57 + ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1_009_006_002, "只有流程模型的任务分配规则,才允许被修改");
  58 + ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
  59 + ErrorCode TASK_ASSIGN_SCRIPT_NOT_EXISTS = new ErrorCode(1_009_006_004, "操作失败,原因:任务分配脚本({}) 不存在");
55 60  
56   - // ========== 动态表单模块 1009010000 ==========
57   - ErrorCode FORM_NOT_EXISTS = new ErrorCode(1009010000, "动态表单不存在");
58   - ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
  61 + // ========== 动态表单模块 1-009-010-000 ==========
  62 + ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");
  63 + ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1_009_010_001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
59 64  
60   - // ========== 用户组模块 1009011000 ==========
61   - ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1009011000, "用户组不存在");
62   - ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1009011001, "名字为【{}】的用户组已被禁用");
  65 + // ========== 用户组模块 1-009-011-000 ==========
  66 + ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1_009_011_000, "用户组不存在");
  67 + ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1_009_011_001, "名字为【{}】的用户组已被禁用");
63 68  
64 69 }
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java
... ... @@ -20,7 +20,8 @@ public enum BpmProcessInstanceResultEnum {
20 20  
21 21 // ========== 流程任务独有的状态 ==========
22 22  
23   - BACK(5, "退回/驳回");
  23 + BACK(5, "驳回"), // 退回
  24 + DELEGATE(6, "委派");
24 25  
25 26 /**
26 27 * 结果
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
... ... @@ -75,4 +75,29 @@ public class BpmTaskController {
75 75 return success(true);
76 76 }
77 77  
  78 + @GetMapping("/get-return-list")
  79 + @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
  80 + @Parameter(name = "taskId", description = "当前任务ID", required = true)
  81 + @PreAuthorize("@ss.hasPermission('bpm:task:update')")
  82 + public CommonResult<List<BpmTaskSimpleRespVO>> getReturnList(@RequestParam("taskId") String taskId) {
  83 + return success(taskService.getReturnTaskList(taskId));
  84 + }
  85 +
  86 + @PutMapping("/return")
  87 + @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮")
  88 + @PreAuthorize("@ss.hasPermission('bpm:task:update')")
  89 + public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
  90 + taskService.returnTask(getLoginUserId(), reqVO);
  91 + return success(true);
  92 + }
  93 +
  94 + @PutMapping("/delegate")
  95 + @Operation(summary = "委派任务", description = "用于【流程详情】的【委派】按钮。和向前【加签】有点像,唯一区别是【委托】没有单独创立任务")
  96 + @PreAuthorize("@ss.hasPermission('bpm:task:update')")
  97 + public CommonResult<Boolean> delegateTask(@Valid @RequestBody BpmTaskDelegateReqVO reqVO) {
  98 + // TODO @海:, 后面要有空格
  99 + taskService.delegateTask(reqVO,getLoginUserId());
  100 + return success(true);
  101 + }
  102 +
78 103 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotEmpty;
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - 委派流程任务的 Request VO")
  10 +@Data
  11 +public class BpmTaskDelegateReqVO {
  12 +
  13 + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotEmpty(message = "任务编号不能为空")
  15 + private String id;
  16 +
  17 + @Schema(description = "被委派人 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + @NotNull(message = "被委派人 ID 不能为空")
  19 + private Long delegateUserId;
  20 +
  21 + @Schema(description = "委派原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "做不了决定,需要你先帮忙瞅瞅")
  22 + @NotEmpty(message = "委派原因不能为空")
  23 + private String reason;
  24 +
  25 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotEmpty;
  7 +
  8 +@Schema(description = "管理后台 - 回退流程任务的 Request VO")
  9 +@Data
  10 +public class BpmTaskReturnReqVO {
  11 +
  12 + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + @NotEmpty(message = "任务编号不能为空")
  14 + private String id;
  15 +
  16 + @Schema(description = "回退到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + @NotEmpty(message = "回退到的任务 Key 不能为空")
  18 + private String targetDefinitionKey;
  19 +
  20 + @Schema(description = "回退意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回")
  21 + @NotEmpty(message = "回退意见不能为空")
  22 + private String reason;
  23 +
  24 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSimpleRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - 流程任务的精简 Response VO")
  7 +@Data
  8 +public class BpmTaskSimpleRespVO {
  9 +
  10 + @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
  11 + private String definitionKey;
  12 +
  13 + @Schema(description = "任务名词", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批")
  14 + private String name;
  15 +
  16 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
... ... @@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.util.date.DateUtils;
6 6 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
7 7 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
8 8 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
  9 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskSimpleRespVO;
9 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
10 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
11 12 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
12 13 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
13 14 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  15 +import org.flowable.bpmn.model.FlowElement;
14 16 import org.flowable.common.engine.impl.db.SuspensionState;
15 17 import org.flowable.engine.history.HistoricProcessInstance;
16 18 import org.flowable.engine.runtime.ProcessInstance;
... ... @@ -55,8 +57,8 @@ public interface BpmTaskConvert {
55 57 }
56 58  
57 59 default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks,
58   - Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap,
59   - Map<Long, AdminUserRespDTO> userMap) {
  60 + Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap,
  61 + Map<Long, AdminUserRespDTO> userMap) {
60 62 return CollectionUtils.convertList(tasks, task -> {
61 63 BpmTaskDonePageItemRespVO respVO = convert2(task);
62 64 BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
... ... @@ -82,8 +84,8 @@ public interface BpmTaskConvert {
82 84 BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser);
83 85  
84 86 default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks,
85   - Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance,
86   - Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
  87 + Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance,
  88 + Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
87 89 return CollectionUtils.convertList(tasks, task -> {
88 90 BpmTaskRespVO respVO = convert3(task);
89 91 BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
... ... @@ -113,29 +115,34 @@ public interface BpmTaskConvert {
113 115 void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to);
114 116  
115 117 @Mappings({@Mapping(source = "processInstance.id", target = "id"),
116   - @Mapping(source = "processInstance.name", target = "name"),
117   - @Mapping(source = "processInstance.startUserId", target = "startUserId"),
118   - @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
119   - @Mapping(source = "startUser.nickname", target = "startUserNickname")})
  118 + @Mapping(source = "processInstance.name", target = "name"),
  119 + @Mapping(source = "processInstance.startUserId", target = "startUserId"),
  120 + @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
  121 + @Mapping(source = "startUser.nickname", target = "startUserNickname")})
120 122 BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance,
121   - AdminUserRespDTO startUser);
  123 + AdminUserRespDTO startUser);
122 124  
123 125 default BpmTaskExtDO convert2TaskExt(Task task) {
124 126 BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId())
125   - .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName())
126   - .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId());
  127 + .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName())
  128 + .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId());
127 129 taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime()));
128 130 return taskExtDO;
129 131 }
130 132  
131 133 default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
132   - Task task) {
  134 + Task task) {
133 135 BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
134 136 reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId())
135   - .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId())
136   - .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName())
137   - .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()));
  137 + .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId())
  138 + .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName())
  139 + .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()));
138 140 return reqDTO;
139 141 }
140 142  
  143 + default List<BpmTaskSimpleRespVO> convertList(List<? extends FlowElement> elementList) {
  144 + return CollectionUtils.convertList(elementList, element -> new BpmTaskSimpleRespVO()
  145 + .setName(element.getName())
  146 + .setDefinitionKey(element.getId()));
  147 + }
141 148 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
... ... @@ -74,4 +74,12 @@ public interface BpmModelService {
74 74 */
75 75 BpmnModel getBpmnModel(String id);
76 76  
  77 + /**
  78 + * 获得流程定义编号对应的 BPMN Model
  79 + *
  80 + * @param processDefinitionId 流程定义编号
  81 + * @return BPMN Model
  82 + */
  83 + BpmnModel getBpmnModelByDefinitionId(String processDefinitionId);
  84 +
77 85 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
... ... @@ -233,6 +233,11 @@ public class BpmModelServiceImpl implements BpmModelService {
233 233 return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
234 234 }
235 235  
  236 + @Override
  237 + public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
  238 + return repositoryService.getBpmnModel(processDefinitionId);
  239 + }
  240 +
236 241 private void checkKeyNCName(String key) {
237 242 if (!ValidationUtils.isXmlNCName(key)) {
238 243 throw exception(MODEL_KEY_VALID);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
... ... @@ -5,7 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
8   -import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
  8 +import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
9 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
10 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
11 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
... ... @@ -200,7 +200,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
200 200 BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes());
201 201 BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId());
202 202 // 对比字节变化
203   - if (!FlowableUtils.equals(oldModel, newModel)) {
  203 + if (!BpmnModelUtils.equals(oldModel, newModel)) {
204 204 return false;
205 205 }
206 206 // 最终发现都一致,则返回 true
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java
... ... @@ -7,7 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
9 9 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
10   -import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
  10 +import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
11 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
12 12 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
13 13 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
... ... @@ -114,7 +114,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
114 114 return Collections.emptyList();
115 115 }
116 116 // 获得用户任务,只有用户任务才可以设置分配规则
117   - List<UserTask> userTasks = FlowableUtils.getBpmnModelElements(model, UserTask.class);
  117 + List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(model, UserTask.class);
118 118 if (CollUtil.isEmpty(userTasks)) {
119 119 return Collections.emptyList();
120 120 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
1 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 5 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
... ... @@ -23,7 +22,6 @@ public interface BpmTaskService {
23 22 *
24 23 * @param userId 用户编号
25 24 * @param pageReqVO 分页请求
26   - *
27 25 * @return 流程任务分页
28 26 */
29 27 PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageReqVO);
... ... @@ -33,7 +31,6 @@ public interface BpmTaskService {
33 31 *
34 32 * @param userId 用户编号
35 33 * @param pageReqVO 分页请求
36   - *
37 34 * @return 流程任务分页
38 35 */
39 36 PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageReqVO);
... ... @@ -42,19 +39,17 @@ public interface BpmTaskService {
42 39 * 获得流程任务 Map
43 40 *
44 41 * @param processInstanceIds 流程实例的编号数组
45   - *
46 42 * @return 流程任务 Map
47 43 */
48 44 default Map<String, List<Task>> getTaskMapByProcessInstanceIds(List<String> processInstanceIds) {
49 45 return CollectionUtils.convertMultiMap(getTasksByProcessInstanceIds(processInstanceIds),
50   - Task::getProcessInstanceId);
  46 + Task::getProcessInstanceId);
51 47 }
52 48  
53 49 /**
54 50 * 获得流程任务列表
55 51 *
56 52 * @param processInstanceIds 流程实例的编号数组
57   - *
58 53 * @return 流程任务列表
59 54 */
60 55 List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds);
... ... @@ -63,7 +58,6 @@ public interface BpmTaskService {
63 58 * 获得指令流程实例的流程任务列表,包括所有状态的
64 59 *
65 60 * @param processInstanceId 流程实例的编号
66   - *
67 61 * @return 流程任务列表
68 62 */
69 63 List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId);
... ... @@ -128,4 +122,29 @@ public interface BpmTaskService {
128 122 */
129 123 void updateTaskExtAssign(Task task);
130 124  
  125 + /**
  126 + * 获取当前任务的可回退的流程集合
  127 + *
  128 + * @param taskId 当前的任务 ID
  129 + * @return 可以回退的节点列表
  130 + */
  131 + List<BpmTaskSimpleRespVO> getReturnTaskList(String taskId);
  132 +
  133 + /**
  134 + * 将任务回退到指定的 targetDefinitionKey 位置
  135 + *
  136 + * @param userId 用户编号
  137 + * @param reqVO 回退的任务key和当前所在的任务ID
  138 + */
  139 + void returnTask(Long userId, BpmTaskReturnReqVO reqVO);
  140 +
  141 + // TODO @海:userId 放前面
  142 + /**
  143 + * 将指定任务委派给其他人处理,等接收人处理后再回到原审批人手中审批
  144 + *
  145 + * @param reqVO 被委派人和被委派的任务编号理由参数
  146 + * @param userId 用户编号
  147 + */
  148 + void delegateTask(BpmTaskDelegateReqVO reqVO, Long userId);
  149 +
131 150 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
1 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.collection.ListUtil;
5 4 import cn.hutool.core.util.ArrayUtil;
6 5 import cn.hutool.core.util.StrUtil;
7 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 7 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
9 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
10 9 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
  10 +import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
  11 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
11 12 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
12 13 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
13 14 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
14 15 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
15 16 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
16 17 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
  18 +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
17 19 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
18 20 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
19 21 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
20 22 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 23 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 24 import lombok.extern.slf4j.Slf4j;
  25 +import org.flowable.bpmn.model.BpmnModel;
  26 +import org.flowable.bpmn.model.FlowElement;
  27 +import org.flowable.bpmn.model.UserTask;
23 28 import org.flowable.engine.HistoryService;
  29 +import org.flowable.engine.RuntimeService;
24 30 import org.flowable.engine.TaskService;
25 31 import org.flowable.engine.history.HistoricProcessInstance;
26 32 import org.flowable.engine.runtime.ProcessInstance;
  33 +import org.flowable.task.api.DelegationState;
27 34 import org.flowable.task.api.Task;
28 35 import org.flowable.task.api.TaskQuery;
29 36 import org.flowable.task.api.history.HistoricTaskInstance;
... ... @@ -32,6 +39,7 @@ import org.springframework.stereotype.Service;
32 39 import org.springframework.transaction.annotation.Transactional;
33 40 import org.springframework.transaction.support.TransactionSynchronization;
34 41 import org.springframework.transaction.support.TransactionSynchronizationManager;
  42 +import org.springframework.util.Assert;
35 43  
36 44 import javax.annotation.Resource;
37 45 import javax.validation.Valid;
... ... @@ -39,8 +47,7 @@ import java.time.LocalDateTime;
39 47 import java.util.*;
40 48  
41 49 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
42   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
43   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  50 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
44 51 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
45 52  
46 53 /**
... ... @@ -57,23 +64,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
57 64 private TaskService taskService;
58 65 @Resource
59 66 private HistoryService historyService;
  67 + @Resource
  68 + private RuntimeService runtimeService;
60 69  
61 70 @Resource
62 71 private BpmProcessInstanceService processInstanceService;
63 72 @Resource
  73 + private BpmModelService bpmModelService;
  74 + @Resource
  75 + private BpmMessageService messageService;
  76 +
  77 + @Resource
64 78 private AdminUserApi adminUserApi;
65 79 @Resource
66 80 private DeptApi deptApi;
  81 +
67 82 @Resource
68 83 private BpmTaskExtMapper taskExtMapper;
69   - @Resource
70   - private BpmMessageService messageService;
71 84  
72 85 @Override
73 86 public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
74 87 // 查询待办任务
75 88 TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己
76   - .orderByTaskCreateTime().desc(); // 创建时间倒序
  89 + .orderByTaskCreateTime().desc(); // 创建时间倒序
77 90 if (StrUtil.isNotBlank(pageVO.getName())) {
78 91 taskQuery.taskNameLike("%" + pageVO.getName() + "%");
79 92 }
... ... @@ -91,21 +104,21 @@ public class BpmTaskServiceImpl implements BpmTaskService {
91 104  
92 105 // 获得 ProcessInstance Map
93 106 Map<String, ProcessInstance> processInstanceMap =
94   - processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
  107 + processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
95 108 // 获得 User Map
96 109 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
97   - convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
  110 + convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
98 111 // 拼接结果
99 112 return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap),
100   - taskQuery.count());
  113 + taskQuery.count());
101 114 }
102 115  
103 116 @Override
104 117 public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) {
105 118 // 查询已办任务
106 119 HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
107   - .taskAssignee(String.valueOf(userId)) // 分配给自己
108   - .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
  120 + .taskAssignee(String.valueOf(userId)) // 分配给自己
  121 + .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
109 122 if (StrUtil.isNotBlank(pageVO.getName())) {
110 123 taskQuery.taskNameLike("%" + pageVO.getName() + "%");
111 124 }
... ... @@ -123,19 +136,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
123 136  
124 137 // 获得 TaskExtDO Map
125 138 List<BpmTaskExtDO> bpmTaskExtDOs =
126   - taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
  139 + taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
127 140 Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
128 141 // 获得 ProcessInstance Map
129 142 Map<String, HistoricProcessInstance> historicProcessInstanceMap =
130   - processInstanceService.getHistoricProcessInstanceMap(
131   - convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
  143 + processInstanceService.getHistoricProcessInstanceMap(
  144 + convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
132 145 // 获得 User Map
133 146 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
134   - convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
  147 + convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
135 148 // 拼接结果
136 149 return new PageResult<>(
137   - BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
138   - taskQuery.count());
  150 + BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
  151 + taskQuery.count());
139 152 }
140 153  
141 154 @Override
... ... @@ -176,27 +189,58 @@ public class BpmTaskServiceImpl implements BpmTaskService {
176 189 @Override
177 190 @Transactional(rollbackFor = Exception.class)
178 191 public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) {
179   - // 校验任务存在
180   - Task task = checkTask(userId, reqVO.getId());
181   - // 校验流程实例存在
  192 + // 1.1 校验任务存在
  193 + Task task = validateTask(userId, reqVO.getId());
  194 + // 1.2 校验流程实例存在
182 195 ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
183 196 if (instance == null) {
184 197 throw exception(PROCESS_INSTANCE_NOT_EXISTS);
185 198 }
186 199  
  200 + // 情况一:被委派的任务,不调用 complete 去完成任务
  201 + if (DelegationState.PENDING.equals(task.getDelegationState())) {
  202 + approveDelegateTask(reqVO, task);
  203 + return;
  204 + }
  205 +
  206 + // 情况二:自己审批的任务,调用 complete 去完成任务
187 207 // 完成任务,审批通过
188 208 taskService.complete(task.getId(), instance.getProcessVariables());
189   -
190 209 // 更新任务拓展表为通过
191 210 taskExtMapper.updateByTaskId(
192   - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
193   - .setReason(reqVO.getReason()));
  211 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
  212 + .setReason(reqVO.getReason()));
  213 + }
  214 +
  215 + /**
  216 + * 审批被委派的任务
  217 + *
  218 + * @param reqVO 前端请求参数,包含当前任务ID,审批意见等
  219 + * @param task 当前被审批的任务
  220 + */
  221 + private void approveDelegateTask(BpmTaskApproveReqVO reqVO, Task task) {
  222 + // 1. 添加审批意见
  223 + AdminUserRespDTO currentUser = adminUserApi.getUser(WebFrameworkUtils.getLoginUserId());
  224 + AdminUserRespDTO sourceApproveUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner()));
  225 + Assert.notNull(sourceApproveUser, "委派任务找不到原审批人,需要检查数据");
  226 + String comment = StrUtil.format("[{}]完成委派任务,任务重新回到[{}]手中,审批意见为:{}", currentUser.getNickname(),
  227 + sourceApproveUser.getNickname(), reqVO.getReason());
  228 + taskService.addComment(reqVO.getId(), task.getProcessInstanceId(),
  229 + BpmProcessInstanceResultEnum.DELEGATE.getResult().toString(), comment);
  230 +
  231 + // 2.1 调用 resolveTask 完成任务。
  232 + // 底层调用 TaskHelper.changeTaskAssignee(task, task.getOwner()):将 owner 设置为 assignee
  233 + taskService.resolveTask(task.getId());
  234 + // 2.2 更新任务拓展表为【处理中】
  235 + taskExtMapper.updateByTaskId(
  236 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult())
  237 + .setReason(reqVO.getReason()));
194 238 }
195 239  
196 240 @Override
197 241 @Transactional(rollbackFor = Exception.class)
198 242 public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) {
199   - Task task = checkTask(userId, reqVO.getId());
  243 + Task task = validateTask(userId, reqVO.getId());
200 244 // 校验流程实例存在
201 245 ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
202 246 if (instance == null) {
... ... @@ -208,14 +252,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
208 252  
209 253 // 更新任务拓展表为不通过
210 254 taskExtMapper.updateByTaskId(
211   - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
212   - .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
  255 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
  256 + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
213 257 }
214 258  
215 259 @Override
216 260 public void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO) {
217 261 // 校验任务存在
218   - Task task = checkTask(userId, reqVO.getId());
  262 + Task task = validateTask(userId, reqVO.getId());
219 263 // 更新负责人
220 264 updateTaskAssignee(task.getId(), reqVO.getAssigneeUserId());
221 265 }
... ... @@ -231,13 +275,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
231 275 * @param userId 用户 id
232 276 * @param taskId task id
233 277 */
234   - private Task checkTask(Long userId, String taskId) {
  278 + private Task validateTask(Long userId, String taskId) {
235 279 Task task = getTask(taskId);
236 280 if (task == null) {
237   - throw exception(TASK_COMPLETE_FAIL_NOT_EXISTS);
  281 + throw exception(TASK_NOT_EXISTS);
238 282 }
239 283 if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) {
240   - throw exception(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
  284 + throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
241 285 }
242 286 return task;
243 287 }
... ... @@ -245,7 +289,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
245 289 @Override
246 290 public void createTaskExt(Task task) {
247 291 BpmTaskExtDO taskExtDO =
248   - BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
  292 + BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
249 293 taskExtMapper.insert(taskExtDO);
250 294 }
251 295  
... ... @@ -293,27 +337,183 @@ public class BpmTaskServiceImpl implements BpmTaskService {
293 337 @Override
294 338 public void updateTaskExtAssign(Task task) {
295 339 BpmTaskExtDO taskExtDO =
296   - new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId());
  340 + new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId());
297 341 taskExtMapper.updateByTaskId(taskExtDO);
298 342 // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
299 343 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
300 344 @Override
301 345 public void afterCommit() {
302 346 ProcessInstance processInstance =
303   - processInstanceService.getProcessInstance(task.getProcessInstanceId());
  347 + processInstanceService.getProcessInstance(task.getProcessInstanceId());
304 348 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
305 349 messageService.sendMessageWhenTaskAssigned(
306   - BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
  350 + BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
307 351 }
308 352 });
309 353 }
310 354  
311 355 private Task getTask(String id) {
312   - return taskService.createTaskQuery().taskId(id).singleResult();
  356 + Task task = taskService.createTaskQuery().taskId(id).singleResult();
  357 + if (null == task) {
  358 + throw exception(TASK_NOT_EXISTS);
  359 + }
  360 + return task;
313 361 }
314 362  
315 363 private HistoricTaskInstance getHistoricTask(String id) {
316 364 return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult();
317 365 }
318 366  
  367 + @Override
  368 + public List<BpmTaskSimpleRespVO> getReturnTaskList(String taskId) {
  369 + // 1. 校验当前任务 task 存在
  370 + Task task = getTask(taskId);
  371 + if (task == null) {
  372 + throw exception(TASK_NOT_EXISTS);
  373 + }
  374 + // 根据流程定义获取流程模型信息
  375 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
  376 + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  377 + if (source == null) {
  378 + throw exception(TASK_NOT_EXISTS);
  379 + }
  380 +
  381 + // 2.1 查询该任务的前置任务节点的 key 集合
  382 + List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null);
  383 + if (CollUtil.isEmpty(previousUserList)) {
  384 + return Collections.emptyList();
  385 + }
  386 + // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
  387 + previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
  388 + return BpmTaskConvert.INSTANCE.convertList(previousUserList);
  389 + }
  390 +
  391 + @Override
  392 + @Transactional(rollbackFor = Exception.class)
  393 + public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) {
  394 + // 1.1 当前任务 task
  395 + Task task = validateTask(userId, reqVO.getId());
  396 + if (task.isSuspended()) {
  397 + throw exception(TASK_IS_PENDING);
  398 + }
  399 + // 1.2 校验源头和目标节点的关系,并返回目标元素
  400 + FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), reqVO.getTargetDefinitionKey(), task.getProcessDefinitionId());
  401 +
  402 + // 2. 调用 flowable 框架的回退逻辑
  403 + returnTask0(task, targetElement, reqVO);
  404 +
  405 + // 3. 更新任务扩展表
  406 + taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId())
  407 + .setResult(BpmProcessInstanceResultEnum.BACK.getResult())
  408 + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
  409 + }
  410 +
  411 + /**
  412 + * 回退流程节点时,校验目标任务节点是否可回退
  413 + *
  414 + * @param sourceKey 当前任务节点 Key
  415 + * @param targetKey 目标任务节点 key
  416 + * @param processDefinitionId 当前流程定义 ID
  417 + * @return 目标任务节点元素
  418 + */
  419 + private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
  420 + // 1.1 获取流程模型信息
  421 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
  422 + // 1.3 获取当前任务节点元素
  423 + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
  424 + // 1.3 获取跳转的节点元素
  425 + FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey);
  426 + if (target == null) {
  427 + throw exception(TASK_TARGET_NODE_NOT_EXISTS);
  428 + }
  429 +
  430 + // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
  431 + if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
  432 + throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
  433 + }
  434 + return target;
  435 + }
  436 +
  437 + /**
  438 + * 执行回退逻辑
  439 + *
  440 + * @param currentTask 当前回退的任务
  441 + * @param targetElement 需要回退到的目标任务
  442 + * @param reqVO 前端参数封装
  443 + */
  444 + public void returnTask0(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
  445 + // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤
  446 + // 1.1 获取所有正常进行的任务节点 Key
  447 + List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
  448 + List<String> runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey);
  449 + // 1.2 通过 targetElement 的出口连线,计算在 runTaskKeyList 有哪些 key 需要被撤回
  450 + // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F
  451 + List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
  452 + List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
  453 +
  454 + // 2. 给当前要被回退的 task 数组,设置回退意见
  455 + taskList.forEach(task -> {
  456 + // 需要排除掉,不需要设置回退意见的任务
  457 + if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
  458 + return;
  459 + }
  460 + taskService.addComment(task.getId(), currentTask.getProcessInstanceId(),
  461 + BpmProcessInstanceResultEnum.BACK.getResult().toString(), reqVO.getReason());
  462 + });
  463 +
  464 + // 3. 执行驳回
  465 + runtimeService.createChangeActivityStateBuilder()
  466 + .processInstanceId(currentTask.getProcessInstanceId())
  467 + .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
  468 + reqVO.getTargetDefinitionKey()) // targetKey 跳转到的节点(1)
  469 + .changeState();
  470 + }
  471 +
  472 + @Override
  473 + @Transactional(rollbackFor = Exception.class)
  474 + public void delegateTask(BpmTaskDelegateReqVO reqVO, Long userId) {
  475 + // 1.1 校验任务
  476 + Task task = validateTaskCanDelegate(userId, reqVO);
  477 + // 1.2 校验目标用户存在
  478 + AdminUserRespDTO delegateUser = adminUserApi.getUser(reqVO.getDelegateUserId());
  479 + if (delegateUser == null) {
  480 + throw exception(TASK_DELEGATE_FAIL_USER_NOT_EXISTS);
  481 + }
  482 +
  483 + // 2. 添加审批意见
  484 + AdminUserRespDTO currentUser = adminUserApi.getUser(userId);
  485 + String comment = StrUtil.format("[{}]将任务委派给[{}],委派理由为:{}", currentUser.getNickname(),
  486 + delegateUser.getNickname(), reqVO.getReason());
  487 + String taskId = reqVO.getId();
  488 + // TODO 海:后面改;感觉 comment 应该 type 做个枚举;不和 result 耦合在一起;
  489 + taskService.addComment(taskId, task.getProcessInstanceId(),
  490 + BpmProcessInstanceResultEnum.DELEGATE.getResult().toString(), comment);
  491 +
  492 + // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
  493 + taskService.setOwner(taskId, task.getAssignee());
  494 + // 3.2 执行委派,将任务委派给 receiveId
  495 + taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
  496 + // 3.3 更新任务拓展表为【委派】
  497 + taskExtMapper.updateByTaskId(
  498 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.DELEGATE.getResult())
  499 + .setReason(reqVO.getReason()));
  500 + }
  501 +
  502 + /**
  503 + * 校验任务委派参数
  504 + *
  505 + * @param userId 用户编号
  506 + * @param reqVO 任务编号,接收人ID
  507 + * @return 当前任务信息
  508 + */
  509 + private Task validateTaskCanDelegate(Long userId, BpmTaskDelegateReqVO reqVO) {
  510 + // 校验任务
  511 + Task task = validateTask(userId, reqVO.getId());
  512 + // 校验当前审批人和被委派人不是同一人
  513 + if (task.getAssignee().equals(reqVO.getDelegateUserId().toString())) {
  514 + throw exception(TASK_DELEGATE_FAIL_USER_REPEAT);
  515 + }
  516 + return task;
  517 + }
  518 +
319 519 }
... ...
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
... ... @@ -9,49 +9,49 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
9 9 */
10 10 public interface ErrorCodeConstants {
11 11  
12   - // ========== 参数配置 1001000000 ==========
13   - ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1001000001, "参数配置不存在");
14   - ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
15   - ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置");
16   - ErrorCode CONFIG_GET_VALUE_ERROR_IF_VISIBLE = new ErrorCode(1001000004, "获取参数配置失败,原因:不允许获取不可见配置");
17   -
18   - // ========== 定时任务 1001001000 ==========
19   - ErrorCode JOB_NOT_EXISTS = new ErrorCode(1001001000, "定时任务不存在");
20   - ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1001001001, "定时任务的处理器已经存在");
21   - ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1001001002, "只允许修改为开启或者关闭状态");
22   - ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1001001003, "定时任务已经处于该状态,无需修改");
23   - ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1001001004, "只有开启状态的任务,才可以修改");
24   - ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1001001005, "CRON 表达式不正确");
25   -
26   - // ========== API 错误日志 1001002000 ==========
27   - ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1001002000, "API 错误日志不存在");
28   - ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1001002001, "API 错误日志已处理");
29   -
30   - // ========= 文件相关 1001003000=================
31   - ErrorCode FILE_PATH_EXISTS = new ErrorCode(1001003000, "文件路径已存在");
32   - ErrorCode FILE_NOT_EXISTS = new ErrorCode(1001003001, "文件不存在");
33   - ErrorCode FILE_IS_EMPTY = new ErrorCode(1001003002, "文件为空");
34   -
35   - // ========== 代码生成器 1001004000 ==========
36   - ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1003001000, "表定义已经存在");
37   - ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1003001001, "导入的表不存在");
38   - ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1003001002, "导入的字段不存在");
39   - ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1003001004, "表定义不存在");
40   - ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1003001005, "字段义不存在");
41   - ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1003001006, "同步的字段不存在");
42   - ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1003001007, "同步失败,不存在改变");
43   - ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1003001008, "数据库的表注释未填写");
44   - ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1003001009, "数据库的表字段({})注释未填写");
45   -
46   - // ========== 字典类型(测试)1001005000 ==========
47   - ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1001005000, "测试示例不存在");
48   -
49   - // ========== 文件配置 1001006000 ==========
50   - ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1001006000, "文件配置不存在");
51   - ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1001006001, "该文件配置不允许删除,原因:它是主配置,删除会导致无法上传文件");
52   -
53   - // ========== 数据源配置 1001007000 ==========
54   - ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1001007000, "数据源配置不存在");
55   - ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1001007001, "数据源配置不正确,无法进行连接");
  12 + // ========== 参数配置 1-001-000-000 ==========
  13 + ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1_001_000_001, "参数配置不存在");
  14 + ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1_001_000_002, "参数配置 key 重复");
  15 + ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1_001_000_003, "不能删除类型为系统内置的参数配置");
  16 + ErrorCode CONFIG_GET_VALUE_ERROR_IF_VISIBLE = new ErrorCode(1_001_000_004, "获取参数配置失败,原因:不允许获取不可见配置");
  17 +
  18 + // ========== 定时任务 1-001-001-000 ==========
  19 + ErrorCode JOB_NOT_EXISTS = new ErrorCode(1_001_001_000, "定时任务不存在");
  20 + ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1_001_001_001, "定时任务的处理器已经存在");
  21 + ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1_001_001_002, "只允许修改为开启或者关闭状态");
  22 + ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1_001_001_003, "定时任务已经处于该状态,无需修改");
  23 + ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1_001_001_004, "只有开启状态的任务,才可以修改");
  24 + ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1_001_001_005, "CRON 表达式不正确");
  25 +
  26 + // ========== API 错误日志 1-001-002-000 ==========
  27 + ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1_001_002_000, "API 错误日志不存在");
  28 + ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1_001_002_001, "API 错误日志已处理");
  29 +
  30 + // ========= 文件相关 1-001-003-000 =================
  31 + ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在");
  32 + ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在");
  33 + ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空");
  34 +
  35 + // ========== 代码生成器 1-001-004-000 ==========
  36 + ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_003_001_000, "表定义已经存在");
  37 + ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1_003_001_001, "导入的表不存在");
  38 + ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1_003_001_002, "导入的字段不存在");
  39 + ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1_003_001_004, "表定义不存在");
  40 + ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1_003_001_005, "字段义不存在");
  41 + ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1_003_001_006, "同步的字段不存在");
  42 + ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1_003_001_007, "同步失败,不存在改变");
  43 + ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1_003_001_008, "数据库的表注释未填写");
  44 + ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1_003_001_009, "数据库的表字段({})注释未填写");
  45 +
  46 + // ========== 字典类型(测试)1-001-005-000 ==========
  47 + ErrorCode TEST_DEMO_NOT_EXISTS = new ErrorCode(1_001_005_000, "测试示例不存在");
  48 +
  49 + // ========== 文件配置 1-001-006-000 ==========
  50 + ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在");
  51 + ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1_001_006_001, "该文件配置不允许删除,原因:它是主配置,删除会导致无法上传文件");
  52 +
  53 + // ========== 数据源配置 1-001-007-000 ==========
  54 + ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_007_000, "数据源配置不存在");
  55 + ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1_001_007_001, "数据源配置不正确,无法进行连接");
56 56  
57 57 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
... ... @@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
7 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9   -import org.apache.ibatis.annotations.Select;
10   -
11   -import java.time.LocalDateTime;
12 9  
13 10 @Mapper
14 11 public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
... ... @@ -21,7 +18,8 @@ public interface FileConfigMapper extends BaseMapperX&lt;FileConfigDO&gt; {
21 18 .orderByDesc(FileConfigDO::getId));
22 19 }
23 20  
24   - @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
25   - Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
  21 + default FileConfigDO selectByMaster() {
  22 + return selectOne(FileConfigDO::getMaster, true);
  23 + }
26 24  
27 25 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
1 1 package cn.iocoder.yudao.module.infra.service.file;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.io.resource.ResourceUtil;
5 4 import cn.hutool.core.util.IdUtil;
6 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
9 7 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
10 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
... ... @@ -17,22 +15,22 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU
17 15 import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
18 16 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
19 17 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
  18 +import com.google.common.cache.CacheLoader;
  19 +import com.google.common.cache.LoadingCache;
20 20 import lombok.Getter;
21 21 import lombok.extern.slf4j.Slf4j;
22   -import org.springframework.scheduling.annotation.Scheduled;
23 22 import org.springframework.stereotype.Service;
24 23 import org.springframework.transaction.annotation.Transactional;
25 24 import org.springframework.validation.annotation.Validated;
26 25  
27   -import javax.annotation.PostConstruct;
28 26 import javax.annotation.Resource;
29 27 import javax.validation.Validator;
30   -import java.time.LocalDateTime;
31   -import java.util.List;
  28 +import java.time.Duration;
32 29 import java.util.Map;
33   -import java.util.concurrent.TimeUnit;
  30 +import java.util.Objects;
34 31  
35 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  33 +import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
36 34 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
37 35 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
38 36  
... ... @@ -46,19 +44,29 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG
46 44 @Slf4j
47 45 public class FileConfigServiceImpl implements FileConfigService {
48 46  
49   - @Resource
50   - private FileClientFactory fileClientFactory;
  47 + private static final Long CACHE_MASTER_ID = 0L;
51 48  
52 49 /**
53   - * 文件配置的缓存
54   - */
55   - @Getter
56   - private List<FileConfigDO> fileConfigCache;
57   - /**
58   - * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
  50 + * {@link FileClient} 缓存,通过它异步刷新 fileClientFactory
59 51 */
60 52 @Getter
61   - private FileClient masterFileClient;
  53 + private final LoadingCache<Long, FileClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
  54 + new CacheLoader<Long, FileClient>() {
  55 +
  56 + @Override
  57 + public FileClient load(Long id) {
  58 + FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ?
  59 + fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);
  60 + if (config != null) {
  61 + fileClientFactory.createOrUpdateFileClient(id, config.getStorage(), config.getConfig());
  62 + }
  63 + return fileClientFactory.getFileClient(id);
  64 + }
  65 +
  66 + });
  67 +
  68 + @Resource
  69 + private FileClientFactory fileClientFactory;
62 70  
63 71 @Resource
64 72 private FileConfigMapper fileConfigMapper;
... ... @@ -66,53 +74,12 @@ public class FileConfigServiceImpl implements FileConfigService {
66 74 @Resource
67 75 private Validator validator;
68 76  
69   - @PostConstruct
70   - public void initLocalCache() {
71   - // 第一步:查询数据
72   - List<FileConfigDO> configs = fileConfigMapper.selectList();
73   - log.info("[initLocalCache][缓存文件配置,数量为:{}]", configs.size());
74   -
75   - // 第二步:构建缓存:创建或更新文件 Client
76   - configs.forEach(config -> {
77   - fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
78   - // 如果是 master,进行设置
79   - if (Boolean.TRUE.equals(config.getMaster())) {
80   - masterFileClient = fileClientFactory.getFileClient(config.getId());
81   - }
82   - });
83   - this.fileConfigCache = configs;
84   - }
85   -
86   - /**
87   - * 通过定时任务轮询,刷新缓存
88   - *
89   - * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
90   - */
91   - @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
92   - public void refreshLocalCache() {
93   - // 情况一:如果缓存里没有数据,则直接刷新缓存
94   - if (CollUtil.isEmpty(fileConfigCache)) {
95   - initLocalCache();
96   - return;
97   - }
98   -
99   - // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
100   - LocalDateTime maxTime = CollectionUtils.getMaxValue(fileConfigCache, FileConfigDO::getUpdateTime);
101   - if (fileConfigMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
102   - initLocalCache();
103   - }
104   - }
105   -
106 77 @Override
107 78 public Long createFileConfig(FileConfigCreateReqVO createReqVO) {
108   - // 插入
109 79 FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO)
110 80 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
111 81 .setMaster(false); // 默认非 master
112 82 fileConfigMapper.insert(fileConfig);
113   -
114   - // 刷新缓存
115   - initLocalCache();
116 83 return fileConfig.getId();
117 84 }
118 85  
... ... @@ -125,8 +92,8 @@ public class FileConfigServiceImpl implements FileConfigService {
125 92 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
126 93 fileConfigMapper.updateById(updateObj);
127 94  
128   - // 刷新缓存
129   - initLocalCache();
  95 + // 清空缓存
  96 + clearCache(config.getId(), null);
130 97 }
131 98  
132 99 @Override
... ... @@ -139,8 +106,8 @@ public class FileConfigServiceImpl implements FileConfigService {
139 106 // 更新
140 107 fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
141 108  
142   - // 刷新缓存
143   - initLocalCache();
  109 + // 清空缓存
  110 + clearCache(null, true);
144 111 }
145 112  
146 113 private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
... ... @@ -164,8 +131,23 @@ public class FileConfigServiceImpl implements FileConfigService {
164 131 // 删除
165 132 fileConfigMapper.deleteById(id);
166 133  
167   - // 刷新缓存
168   - initLocalCache();
  134 + // 清空缓存
  135 + clearCache(id, null);
  136 + }
  137 +
  138 + /**
  139 + * 清空指定文件配置
  140 + *
  141 + * @param id 配置编号
  142 + * @param master 是否主配置
  143 + */
  144 + private void clearCache(Long id, Boolean master) {
  145 + if (id != null) {
  146 + clientCache.invalidate(id);
  147 + }
  148 + if (Boolean.TRUE.equals(master)) {
  149 + clientCache.invalidate(CACHE_MASTER_ID);
  150 + }
169 151 }
170 152  
171 153 private FileConfigDO validateFileConfigExists(Long id) {
... ... @@ -192,12 +174,17 @@ public class FileConfigServiceImpl implements FileConfigService {
192 174 validateFileConfigExists(id);
193 175 // 上传文件
194 176 byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
195   - return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
  177 + return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
196 178 }
197 179  
198 180 @Override
199 181 public FileClient getFileClient(Long id) {
200   - return fileClientFactory.getFileClient(id);
  182 + return clientCache.getUnchecked(id);
  183 + }
  184 +
  185 + @Override
  186 + public FileClient getMasterFileClient() {
  187 + return clientCache.getUnchecked(CACHE_MASTER_ID);
201 188 }
202 189  
203 190 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm
... ... @@ -3,27 +3,27 @@ import { defHttp } from &#39;@/utils/http/axios&#39;
3 3  
4 4 // 查询${table.classComment}列表
5 5 export function get${simpleClassName}Page(params) {
6   - return defHttp.get({ url: '${baseURL}/page', params })
  6 + return defHttp.get({ url: '${baseURL}/page', params })
7 7 }
8 8  
9 9 // 查询${table.classComment}详情
10 10 export function get${simpleClassName}(id: number) {
11   - return defHttp.get({ url: '${baseURL}/get?id=' + id })
  11 + return defHttp.get({ url: `${baseURL}/get?id=${id}` })
12 12 }
13 13  
14 14 // 新增${table.classComment}
15 15 export function create${simpleClassName}(data) {
16   - return defHttp.post({ url: '${baseURL}/create', data })
  16 + return defHttp.post({ url: '${baseURL}/create', data })
17 17 }
18 18  
19 19 // 修改${table.classComment}
20 20 export function update${simpleClassName}(data) {
21   - return defHttp.put({ url: '${baseURL}/update', data })
  21 + return defHttp.put({ url: '${baseURL}/update', data })
22 22 }
23 23  
24 24 // 删除${table.classComment}
25 25 export function delete${simpleClassName}(id: number) {
26   - return defHttp.delete({ url: '${baseURL}/delete?id=' + id })
  26 + return defHttp.delete({ url: `${baseURL}/delete?id=${id}` })
27 27 }
28 28  
29 29 // 导出${table.classComment} Excel
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm
1   -import { BasicColumn, FormSchema, useRender } from '@/components/Table'
  1 +import type { BasicColumn, FormSchema } from '@/components/Table'
  2 +import { useRender } from '@/components/Table'
2 3 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
3 4  
4 5 export const columns: BasicColumn[] = [
... ... @@ -92,13 +93,13 @@ export const createFormSchema: FormSchema[] = [
92 93 #elseif($column.htmlType == "imageUpload")## 图片上传
93 94 component: 'FileUpload',
94 95 componentProps: {
95   - fileType: 'file',
  96 + fileType: 'image',
96 97 maxCount: 1,
97 98 },
98 99 #elseif($column.htmlType == "fileUpload")## 文件上传
99 100 component: 'FileUpload',
100 101 componentProps: {
101   - fileType: 'image',
  102 + fileType: 'file',
102 103 maxCount: 1,
103 104 },
104 105 #elseif($column.htmlType == "editor")## 文本编辑器
... ... @@ -132,6 +133,11 @@ export const createFormSchema: FormSchema[] = [
132 133 },
133 134 #elseif($column.htmlType == "datetime")## 时间框
134 135 component: 'DatePicker',
  136 + componentProps: {
  137 + showTime: true,
  138 + format: 'YYYY-MM-DD HH:mm:ss',
  139 + valueFormat: 'x',
  140 + },
135 141 #elseif($column.htmlType == "textarea")## 文本域
136 142 component: 'InputTextArea',
137 143 #end
... ... @@ -154,7 +160,7 @@ export const updateFormSchema: FormSchema[] = [
154 160 #set ($javaField = $column.javaField)
155 161 #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
156 162 #set ($comment = $column.columnComment)
157   -#if (!$column.primaryKey)## 忽略主键,不用在表单里
  163 + #if (!$column.primaryKey)## 忽略主键,不用在表单里
158 164 {
159 165 label: '${comment}',
160 166 field: '${javaField}',
... ... @@ -164,45 +170,57 @@ export const updateFormSchema: FormSchema[] = [
164 170 #if ($column.htmlType == "input")
165 171 component: 'Input',
166 172 #elseif($column.htmlType == "imageUpload")## 图片上传
167   - component: 'Upload',
  173 + component: 'FileUpload',
  174 + componentProps: {
  175 + fileType: 'image',
  176 + maxCount: 1,
  177 + },
168 178 #elseif($column.htmlType == "fileUpload")## 文件上传
169   - component: 'Upload',
170   - #elseif($column.htmlType == "editor")## 文本编辑器
171   - component: 'Editor',
  179 + component: 'FileUpload',
  180 + componentProps: {
  181 + fileType: 'file',
  182 + maxCount: 1,
  183 + },
  184 + #elseif($column.htmlType == "editor")## 文本编辑器component: 'Editor',
172 185 #elseif($column.htmlType == "select")## 下拉框
173 186 component: 'Select',
174 187 componentProps: {
175 188 #if ("" != $dictType)## 有数据字典
176   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  189 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
177 190 #else##没数据字典
178   - options:[],
  191 + options:[],
179 192 #end
180 193 },
181 194 #elseif($column.htmlType == "checkbox")## 多选框
182 195 component: 'Checkbox',
183 196 componentProps: {
184 197 #if ("" != $dictType)## 有数据字典
185   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  198 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
186 199 #else##没数据字典
187   - options:[],
  200 + options:[],
188 201 #end
189 202 },
190 203 #elseif($column.htmlType == "radio")## 单选框
191 204 component: 'RadioButtonGroup',
192 205 componentProps: {
193 206 #if ("" != $dictType)## 有数据字典
194   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  207 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
195 208 #else##没数据字典
196   - options:[],
  209 + options:[],
197 210 #end
198 211 },
199 212 #elseif($column.htmlType == "datetime")## 时间框
200 213 component: 'DatePicker',
  214 + componentProps: {
  215 + showTime: true,
  216 + format: 'YYYY-MM-DD HH:mm:ss',
  217 + valueFormat: 'x',
  218 + },
201 219 #elseif($column.htmlType == "textarea")## 文本域
202 220 component: 'InputTextArea',
203 221 #end
204 222 },
205   -#end
  223 + #end
206 224 #end
207 225 #end
208 226 ]
209 227 \ No newline at end of file
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm
... ... @@ -9,9 +9,10 @@ import { create${simpleClassName}, get${simpleClassName}, update${simpleClassNam
9 9  
10 10 defineOptions({ name: '${table.className}Modal' })
11 11  
  12 +const emit = defineEmits(['success', 'register'])
  13 +
12 14 const { t } = useI18n()
13 15 const { createMessage } = useMessage()
14   -const emit = defineEmits(['success', 'register'])
15 16 const isUpdate = ref(true)
16 17  
17 18 const [registerForm, { setFieldsValue, resetFields, resetSchema, validate }] = useForm({
... ... @@ -37,11 +38,11 @@ async function handleSubmit() {
37 38 try {
38 39 const values = await validate()
39 40 setModalProps({ confirmLoading: true })
40   - if (unref(isUpdate)) {
  41 + if (unref(isUpdate))
41 42 await update${simpleClassName}(values)
42   - } else {
  43 + else
43 44 await create${simpleClassName}(values)
44   - }
  45 +
45 46 closeModal()
46 47 emit('success')
47 48 createMessage.success(t('common.saveSuccessText'))
... ... @@ -51,7 +52,7 @@ async function handleSubmit() {
51 52 }
52 53 </script>
53 54 <template>
54   - <BasicModal v-bind="$attrs" @register="registerModal" :title="isUpdate ? t('action.edit') : t('action.create')" @ok="handleSubmit">
  55 + <BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit">
55 56 <BasicForm @register="registerForm" />
56 57 </BasicModal>
57 58 </template>
58 59 \ No newline at end of file
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm
1 1 <script lang="ts" setup>
2   -import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
  2 +import ${ simpleClassName }Modal from './${simpleClassName}Modal.vue'
3 3 import { columns, searchFormSchema } from './${classNameVar}.data'
4 4 import { useI18n } from '@/hooks/web/useI18n'
5 5 import { useMessage } from '@/hooks/web/useMessage'
6 6 import { useModal } from '@/components/Modal'
7 7 import { IconEnum } from '@/enums/appEnum'
8 8 import { BasicTable, useTable, TableAction } from '@/components/Table'
9   -import { delete${simpleClassName}, export${simpleClassName}, get${simpleClassName}Page } from '@/api/${table.moduleName}/${classNameVar}'
  9 +import { delete${ simpleClassName }, export${ simpleClassName }, get${ simpleClassName } Page } from '@/api/${table.moduleName}/${classNameVar}'
10 10  
11 11 defineOptions({ name: '${table.className}' })
12 12  
... ... @@ -16,17 +16,17 @@ const [registerModal, { openModal }] = useModal()
16 16  
17 17 const [registerTable, { getForm, reload }] = useTable({
18 18 title: '${table.classComment}列表',
19   - api: get${simpleClassName}Page,
20   - columns,
21   - formConfig: { labelWidth: 120, schemas: searchFormSchema },
22   - useSearchForm: true,
23   - showTableSetting: true,
24   - actionColumn: {
25   - width: 140,
26   - title: t('common.action'),
27   - dataIndex: 'action',
28   - fixed: 'right',
29   - },
  19 + api: get${ simpleClassName }Page,
  20 + columns,
  21 + formConfig: { labelWidth: 120, schemas: searchFormSchema },
  22 + useSearchForm: true,
  23 + showTableSetting: true,
  24 + actionColumn: {
  25 + width: 140,
  26 + title: t('common.action'),
  27 + dataIndex: 'action',
  28 + fixed: 'right',
  29 + },
30 30 })
31 31  
32 32 function handleCreate() {
... ... @@ -43,14 +43,14 @@ async function handleExport() {
43 43 iconType: 'warning',
44 44 content: t('common.exportMessage'),
45 45 async onOk() {
46   - await export${simpleClassName}(getForm().getFieldsValue())
  46 + await export${ simpleClassName } (getForm().getFieldsValue())
47 47 createMessage.success(t('common.exportSuccessText'))
48 48 },
49 49 })
50 50 }
51 51  
52 52 async function handleDelete(record: Recordable) {
53   - await delete${simpleClassName}(record.id)
  53 + await delete${ simpleClassName } (record.id)
54 54 createMessage.success(t('common.delSuccessText'))
55 55 reload()
56 56 }
... ... @@ -89,4 +89,4 @@ async function handleDelete(record: Recordable) {
89 89 </BasicTable>
90 90 <${simpleClassName}Modal @register="registerModal" @success="reload()" />
91 91 </div>
92   -</template>
93 92 \ No newline at end of file
  93 +</template>
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
... ... @@ -60,31 +60,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
60 60 private FileClientFactory fileClientFactory;
61 61  
62 62 @Test
63   - public void testInitLocalCache() {
64   - // mock 数据
65   - FileConfigDO configDO1 = randomFileConfigDO().setId(1L).setMaster(true);
66   - fileConfigMapper.insert(configDO1);
67   - FileConfigDO configDO2 = randomFileConfigDO().setId(2L).setMaster(false);
68   - fileConfigMapper.insert(configDO2);
69   - // mock fileClientFactory 获得 master
70   - FileClient masterFileClient = mock(FileClient.class);
71   - when(fileClientFactory.getFileClient(eq(1L))).thenReturn(masterFileClient);
72   -
73   - // 调用
74   - fileConfigService.initLocalCache();
75   - // 断言 fileClientFactory 调用
76   - verify(fileClientFactory).createOrUpdateFileClient(eq(1L),
77   - eq(configDO1.getStorage()), eq(configDO1.getConfig()));
78   - verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
79   - eq(configDO2.getStorage()), eq(configDO2.getConfig()));
80   - assertSame(masterFileClient, fileConfigService.getMasterFileClient());
81   - // 断言 fileConfigCache 缓存
82   - assertEquals(2, fileConfigService.getFileConfigCache().size());
83   - assertEquals(configDO1, fileConfigService.getFileConfigCache().get(0));
84   - assertEquals(configDO2, fileConfigService.getFileConfigCache().get(1));
85   - }
86   -
87   - @Test
88 63 public void testCreateFileConfig_success() {
89 64 // 准备参数
90 65 Map<String, Object> config = MapUtil.<String, Object>builder().put("basePath", "/yunai")
... ... @@ -102,6 +77,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
102 77 assertFalse(fileConfig.getMaster());
103 78 assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
104 79 assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
  80 + // 验证 cache
  81 + assertNull(fileConfigService.getClientCache().getIfPresent(fileConfigId));
105 82 }
106 83  
107 84 @Test
... ... @@ -125,6 +102,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
125 102 assertPojoEquals(reqVO, fileConfig, "config");
126 103 assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
127 104 assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
  105 + // 验证 cache
  106 + assertNull(fileConfigService.getClientCache().getIfPresent(fileConfig.getId()));
128 107 }
129 108  
130 109 @Test
... ... @@ -149,6 +128,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
149 128 // 断言数据
150 129 assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
151 130 assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
  131 + // 验证 cache
  132 + assertNull(fileConfigService.getClientCache().getIfPresent(0L));
152 133 }
153 134  
154 135 @Test
... ... @@ -169,6 +150,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
169 150 fileConfigService.deleteFileConfig(id);
170 151 // 校验数据不存在了
171 152 assertNull(fileConfigMapper.selectById(id));
  153 + // 验证 cache
  154 + assertNull(fileConfigService.getClientCache().getIfPresent(id));
172 155 }
173 156  
174 157 @Test
... ... @@ -250,14 +233,38 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
250 233  
251 234 @Test
252 235 public void testGetFileClient() {
  236 + // mock 数据
  237 + FileConfigDO fileConfig = randomFileConfigDO().setMaster(false);
  238 + fileConfigMapper.insert(fileConfig);
253 239 // 准备参数
254   - Long id = randomLongId();
  240 + Long id = fileConfig.getId();
255 241 // mock 获得 Client
256 242 FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
257 243 when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient);
258 244  
259 245 // 调用,并断言
260 246 assertSame(fileClient, fileConfigService.getFileClient(id));
  247 + // 断言缓存
  248 + verify(fileClientFactory).createOrUpdateFileClient(eq(id), eq(fileConfig.getStorage()),
  249 + eq(fileConfig.getConfig()));
  250 + }
  251 +
  252 + @Test
  253 + public void testGetMasterFileClient() {
  254 + // mock 数据
  255 + FileConfigDO fileConfig = randomFileConfigDO().setMaster(true);
  256 + fileConfigMapper.insert(fileConfig);
  257 + // 准备参数
  258 + Long id = fileConfig.getId();
  259 + // mock 获得 Client
  260 + FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
  261 + when(fileClientFactory.getFileClient(eq(0L))).thenReturn(fileClient);
  262 +
  263 + // 调用,并断言
  264 + assertSame(fileClient, fileConfigService.getMasterFileClient());
  265 + // 断言缓存
  266 + verify(fileClientFactory).createOrUpdateFileClient(eq(0L), eq(fileConfig.getStorage()),
  267 + eq(fileConfig.getConfig()));
261 268 }
262 269  
263 270 private FileConfigDO randomFileConfigDO() {
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
... ... @@ -139,4 +139,5 @@ public class FileServiceImplTest extends BaseDbUnitTest {
139 139 // 断言
140 140 assertSame(result, content);
141 141 }
  142 +
142 143 }
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
... ... @@ -9,47 +9,47 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
9 9 */
10 10 public interface ErrorCodeConstants {
11 11  
12   - // ========== 商品分类相关 1008001000 ============
13   - ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1008001000, "商品分类不存在");
14   - ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1008001001, "父分类不存在");
15   - ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类");
16   - ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除");
17   - ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用");
18   - ErrorCode CATEGORY_HAVE_BIND_SPU = new ErrorCode(1008001005, "类别下存在商品,无法删除");
19   -
20   - // ========== 商品品牌相关编号 1008002000 ==========
21   - ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在");
22   - ErrorCode BRAND_DISABLED = new ErrorCode(1008002001, "品牌已禁用");
23   - ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1008002002, "品牌名称已存在");
24   -
25   - // ========== 商品属性项 1008003000 ==========
26   - ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "属性项不存在");
27   - ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "属性项的名称已存在");
28   - ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1008003002, "属性项下存在属性值,无法删除");
29   -
30   - // ========== 商品属性值 1008004000 ==========
31   - ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "属性值不存在");
32   - ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "属性值的名称已存在");
33   -
34   - // ========== 商品 SPU 1008005000 ==========
35   - ErrorCode SPU_NOT_EXISTS = new ErrorCode(1008005000, "商品 SPU 不存在");
36   - ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1008005001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
37   - ErrorCode SPU_NOT_ENABLE = new ErrorCode(1008005002, "商品 SPU 不处于上架状态");
38   - ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1008005003, "商品 SPU 不处于回收站状态");
39   -
40   - // ========== 商品 SKU 1008006000 ==========
41   - ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品 SKU 不存在");
42   - ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
43   - ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其属性项必须一致");
44   - ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU,必须不重复");
45   - ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
46   -
47   - // ========== 商品 评价 1008007000 ==========
48   - ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1008007000, "商品评价不存在");
49   - ErrorCode COMMENT_ORDER_EXISTS = new ErrorCode(1008007001, "订单的商品评价已存在");
50   -
51   - // ========== 商品 收藏 1008008000 ==========
52   - ErrorCode FAVORITE_EXISTS = new ErrorCode(1008008000, "该商品已经被收藏");
53   - ErrorCode FAVORITE_NOT_EXISTS = new ErrorCode(1008008001, "商品收藏不存在");
  12 + // ========== 商品分类相关 1-008-001-000 ============
  13 + ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_008_001_000, "商品分类不存在");
  14 + ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_008_001_001, "父分类不存在");
  15 + ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_008_001_002, "父分类不能是二级分类");
  16 + ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_008_001_003, "存在子分类,无法删除");
  17 + ErrorCode CATEGORY_DISABLED = new ErrorCode(1_008_001_004, "商品分类({})已禁用,无法使用");
  18 + ErrorCode CATEGORY_HAVE_BIND_SPU = new ErrorCode(1_008_001_005, "类别下存在商品,无法删除");
  19 +
  20 + // ========== 商品品牌相关编号 1-008-002-000 ==========
  21 + ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1_008_002_000, "品牌不存在");
  22 + ErrorCode BRAND_DISABLED = new ErrorCode(1_008_002_001, "品牌已禁用");
  23 + ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1_008_002_002, "品牌名称已存在");
  24 +
  25 + // ========== 商品属性项 1-008-003-000 ==========
  26 + ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1_008_003_000, "属性项不存在");
  27 + ErrorCode PROPERTY_EXISTS = new ErrorCode(1_008_003_001, "属性项的名称已存在");
  28 + ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1_008_003_002, "属性项下存在属性值,无法删除");
  29 +
  30 + // ========== 商品属性值 1-008-004-000 ==========
  31 + ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1_008_004_000, "属性值不存在");
  32 + ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1_008_004_001, "属性值的名称已存在");
  33 +
  34 + // ========== 商品 SPU 1-008-005-000 ==========
  35 + ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
  36 + ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
  37 + ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU 不处于上架状态");
  38 + ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_003, "商品 SPU 不处于回收站状态");
  39 +
  40 + // ========== 商品 SKU 1-008-006-000 ==========
  41 + ErrorCode SKU_NOT_EXISTS = new ErrorCode(1_008_006_000, "商品 SKU 不存在");
  42 + ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1_008_006_001, "商品 SKU 的属性组合存在重复");
  43 + ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1_008_006_002, "一个 SPU 下的每个 SKU,其属性项必须一致");
  44 + ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1_008_006_003, "一个 SPU 下的每个 SKU,必须不重复");
  45 + ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_008_006_004, "商品 SKU 库存不足");
  46 +
  47 + // ========== 商品 评价 1-008-007-000 ==========
  48 + ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1_008_007_000, "商品评价不存在");
  49 + ErrorCode COMMENT_ORDER_EXISTS = new ErrorCode(1_008_007_001, "订单的商品评价已存在");
  50 +
  51 + // ========== 商品 收藏 1-008-008-000 ==========
  52 + ErrorCode FAVORITE_EXISTS = new ErrorCode(1_008_008_000, "该商品已经被收藏");
  53 + ErrorCode FAVORITE_NOT_EXISTS = new ErrorCode(1_008_008_001, "商品收藏不存在");
54 54  
55 55 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
... ... @@ -140,4 +140,5 @@ public class AppProductSpuController {
140 140 return price - newPrice;
141 141 }
142 142  
  143 + // TODO 芋艿:商品的浏览记录;
143 144 }
... ...
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java
... ... @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
16 16 import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum;
17 17 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
18 18 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
19   -import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
20 19 import org.junit.jupiter.api.Test;
21 20 import org.springframework.boot.test.mock.mockito.MockBean;
22 21 import org.springframework.context.annotation.Import;
... ... @@ -49,8 +48,6 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
49 48 private ProductCommentServiceImpl productCommentService;
50 49  
51 50 @MockBean
52   - private TradeOrderApi tradeOrderApi;
53   - @MockBean
54 51 private ProductSpuService productSpuService;
55 52 @MockBean
56 53 private ProductSkuService productSkuService;
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -9,47 +9,47 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
9 9 */
10 10 public interface ErrorCodeConstants {
11 11  
12   - // ========== 促销活动相关 1013001000 ============
13   - ErrorCode DISCOUNT_ACTIVITY_NOT_EXISTS = new ErrorCode(1013001000, "限时折扣活动不存在");
14   - ErrorCode DISCOUNT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013001001, "存在商品参加了其它限时折扣活动");
15   - ErrorCode DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013001002, "限时折扣活动已关闭,不能修改");
16   - ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1013001003, "限时折扣活动未关闭,不能删除");
17   - ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013001004, "限时折扣活动已关闭,不能重复关闭");
18   - ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1013001005, "限时折扣活动已结束,不能关闭");
19   -
20   - // ========== Banner 相关 1013002000 ============
21   - ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1013002000, "Banner 不存在");
22   -
23   - // ========== Coupon 相关 1013003000 ============
24   - ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1013003000, "优惠劵没有可使用的商品!");
25   - ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1013003001, "所结算的商品中未满足使用的金额");
26   -
27   - // ========== 优惠劵模板 1013004000 ==========
28   - ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1013004000, "优惠劵模板不存在");
29   - ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1013004001, "发放数量不能小于已领取数量({})");
30   - ErrorCode COUPON_TEMPLATE_NOT_ENOUGH = new ErrorCode(1013004002, "当前剩余数量不够领取");
31   - ErrorCode COUPON_TEMPLATE_USER_ALREADY_TAKE = new ErrorCode(1013004003, "用户已领取过此优惠券");
32   - ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1013004004, "优惠券已过期");
33   - ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1013004005, "领取方式不正确");
34   -
35   - // ========== 优惠劵 1013005000 ==========
36   - ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1013005000, "优惠券不存在");
37   - ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1013005001, "回收优惠劵失败,优惠劵已被使用");
38   - ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1013005002, "优惠劵不处于待使用状态");
39   - ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1013005003, "优惠券不在使用时间范围内");
40   - ErrorCode COUPON_STATUS_NOT_USED = new ErrorCode(1013005004, "优惠劵不是已使用状态");
41   -
42   - // ========== 满减送活动 1013006000 ==========
43   - ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1013006000, "满减送活动不存在");
44   - ErrorCode REWARD_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013006001, "存在商品参加了其它满减送活动");
45   - ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013006002, "满减送活动已关闭,不能修改");
46   - ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1013006003, "满减送活动未关闭,不能删除");
47   - ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013006004, "满减送活动已关闭,不能重复关闭");
48   - ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1013006005, "满减送活动已结束,不能关闭");
49   -
50   - // ========== TODO 空着 1013007000 ============
51   -
52   - // ========== 秒杀活动 1013008000 ==========
  12 + // ========== 促销活动相关 1-013-001-000 ============
  13 + ErrorCode DISCOUNT_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_001_000, "限时折扣活动不存在");
  14 + ErrorCode DISCOUNT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_001_001, "存在商品参加了其它限时折扣活动");
  15 + ErrorCode DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_002, "限时折扣活动已关闭,不能修改");
  16 + ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_001_003, "限时折扣活动未关闭,不能删除");
  17 + ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_004, "限时折扣活动已关闭,不能重复关闭");
  18 + ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1_013_001_005, "限时折扣活动已结束,不能关闭");
  19 +
  20 + // ========== Banner 相关 1-013-002-000 ============
  21 + ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在");
  22 +
  23 + // ========== Coupon 相关 1-013-003-000 ============
  24 + ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1_013_003_000, "优惠劵没有可使用的商品!");
  25 + ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1_013_003_001, "所结算的商品中未满足使用的金额");
  26 +
  27 + // ========== 优惠劵模板 1-013-004-000 ==========
  28 + ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_013_004_000, "优惠劵模板不存在");
  29 + ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1_013_004_001, "发放数量不能小于已领取数量({})");
  30 + ErrorCode COUPON_TEMPLATE_NOT_ENOUGH = new ErrorCode(1_013_004_002, "当前剩余数量不够领取");
  31 + ErrorCode COUPON_TEMPLATE_USER_ALREADY_TAKE = new ErrorCode(1_013_004_003, "用户已领取过此优惠券");
  32 + ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1_013_004_004, "优惠券已过期");
  33 + ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1_013_004_005, "领取方式不正确");
  34 +
  35 + // ========== 优惠劵 1-013-005-000 ==========
  36 + ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1_013_005_000, "优惠券不存在");
  37 + ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1_013_005_001, "回收优惠劵失败,优惠劵已被使用");
  38 + ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1_013_005_002, "优惠劵不处于待使用状态");
  39 + ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1_013_005_003, "优惠券不在使用时间范围内");
  40 + ErrorCode COUPON_STATUS_NOT_USED = new ErrorCode(1_013_005_004, "优惠劵不是已使用状态");
  41 +
  42 + // ========== 满减送活动 1-013-006-000 ==========
  43 + ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_006_000, "满减送活动不存在");
  44 + ErrorCode REWARD_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_006_001, "存在商品参加了其它满减送活动");
  45 + ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_002, "满减送活动已关闭,不能修改");
  46 + ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_006_003, "满减送活动未关闭,不能删除");
  47 + ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_004, "满减送活动已关闭,不能重复关闭");
  48 + ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1_013_006_005, "满减送活动已结束,不能关闭");
  49 +
  50 + // ========== TODO 空着 1-013-007-000 ============
  51 +
  52 + // ========== 秒杀活动 1-013-008-000 ==========
53 53 ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1013008000, "秒杀活动不存在");
54 54 ErrorCode SECKILL_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013008002, "存在商品参加了其它秒杀活动,秒杀时段冲突");
55 55 ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013008003, "秒杀活动已关闭,不能修改");
... ... @@ -57,19 +57,19 @@ public interface ErrorCodeConstants {
57 57 ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013008005, "秒杀活动已关闭,不能重复关闭");
58 58 ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013008006, "秒杀失败,原因秒杀库存不足");
59 59  
60   - // ========== 秒杀时段 1013009000 ==========
61   - ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1013009000, "秒杀时段不存在");
62   - ErrorCode SECKILL_CONFIG_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
63   - ErrorCode SECKILL_CONFIG_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
  60 + // ========== 秒杀时段 1-013-009-000 ==========
  61 + ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");
  62 + ErrorCode SECKILL_CONFIG_TIME_CONFLICTS = new ErrorCode(1_013_009_001, "秒杀时段冲突");
  63 + ErrorCode SECKILL_CONFIG_DISABLE = new ErrorCode(1_013_009_004, "秒杀时段已关闭");
64 64  
65   - // ========== 拼团活动 1013010000 ==========
  65 + // ========== 拼团活动 1-013-010-000 ==========
66 66 ErrorCode COMBINATION_ACTIVITY_NOT_EXISTS = new ErrorCode(1013010000, "拼团活动不存在");
67 67 ErrorCode COMBINATION_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013010001, "存在商品参加了其它拼团活动");
68 68 ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE_NOT_UPDATE = new ErrorCode(1013010002, "拼团活动已关闭不能修改");
69 69 ErrorCode COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013010003, "拼团活动未关闭或未结束,不能删除");
70 70 ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013010004, "拼团失败,原因:拼团活动已关闭");
71 71  
72   - // ========== 拼团记录 1013011000 ==========
  72 + // ========== 拼团记录 1-013-011-000 ==========
73 73 ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1013011000, "拼团不存在");
74 74 ErrorCode COMBINATION_RECORD_EXISTS = new ErrorCode(1013011001, "拼团失败,已参与过该拼团");
75 75 ErrorCode COMBINATION_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013011002, "拼团失败,父拼团不存在");
... ... @@ -79,17 +79,17 @@ public interface ErrorCodeConstants {
79 79 ErrorCode COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1013011006, "拼团失败,原因:单次限购超出");
80 80 ErrorCode COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED = new ErrorCode(1013011007, "拼团失败,原因:超出总购买次数");
81 81  
82   - // ========== 砍价活动 1013012000 ==========
  82 + // ========== 砍价活动 1-013-012-000 ==========
83 83 ErrorCode BARGAIN_ACTIVITY_NOT_EXISTS = new ErrorCode(1013012000, "砍价活动不存在");
84 84 ErrorCode BARGAIN_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013012001, "存在商品参加了其它砍价活动");
85 85 ErrorCode BARGAIN_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013012002, "砍价活动已关闭不能修改");
86 86 ErrorCode BARGAIN_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013012003, "砍价活动未关闭或未结束,不能删除");
87 87 ErrorCode BARGAIN_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013012004, "砍价失败,原因:该砍价活动库存不足");
88 88  
89   - // ========== 砍价记录 1013013000 ==========
90   - ErrorCode BARGAIN_RECORD_NOT_EXISTS = new ErrorCode(1013013000, "砍价记录不存在");
91   - ErrorCode BARGAIN_RECORD_EXISTS = new ErrorCode(1013013001, "砍价失败,已参与过该砍价");
92   - ErrorCode BARGAIN_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013013002, "砍价失败,父砍价不存在");
93   - ErrorCode BARGAIN_RECORD_USER_FULL = new ErrorCode(1013013003, "砍价失败,砍价人数已满");
  89 + // ========== 砍价记录 1-013-013-000 ==========
  90 + ErrorCode BARGAIN_RECORD_NOT_EXISTS = new ErrorCode(1_013_013_000, "砍价记录不存在");
  91 + ErrorCode BARGAIN_RECORD_EXISTS = new ErrorCode(1_013_013_001, "砍价失败,已参与过该砍价");
  92 + ErrorCode BARGAIN_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1_013_013_002, "砍价失败,父砍价不存在");
  93 + ErrorCode BARGAIN_RECORD_USER_FULL = new ErrorCode(1_013_013_003, "砍价失败,砍价人数已满");
94 94  
95 95 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java
... ... @@ -37,15 +37,6 @@ public class CouponController {
37 37 @Resource
38 38 private MemberUserApi memberUserApi;
39 39  
40   -// @GetMapping("/get")
41   -// @Operation(summary = "获得优惠劵")
42   -// @Parameter(name = "id", description = "编号", required = true, example = "1024")
43   -// @PreAuthorize("@ss.hasPermission('promotion:coupon:query')")
44   -// public CommonResult<CouponRespVO> getCoupon(@RequestParam("id") Long id) {
45   -// CouponDO coupon = couponService.getCoupon(id);
46   -// return success(CouponConvert.INSTANCE.convert(coupon));
47   -// }
48   -
49 40 @DeleteMapping("/delete")
50 41 @Operation(summary = "回收优惠劵")
51 42 @Parameter(name = "id", description = "编号", required = true)
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
... ... @@ -44,4 +44,5 @@ public class CouponTemplatePageReqVO extends PageParam {
44 44  
45 45 @Schema(description = "商品范围编号", example = "1")
46 46 private Long productScopeValue;
  47 +
47 48 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/vo/article/AppArticleRespVO.java
... ... @@ -9,41 +9,41 @@ import java.time.LocalDateTime;
9 9 @Data
10 10 public class AppArticleRespVO {
11 11  
12   - @Schema(description = "文章编号", required = true, example = "1")
  12 + @Schema(description = "文章编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
13 13 private Long id;
14 14  
15   - @Schema(description = "文章标题", required = true, example = "芋道源码 - 促销模块")
  15 + @Schema(description = "文章标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码 - 促销模块")
16 16 private String title;
17 17  
18   - @Schema(description = "文章作者", required = true, example = "芋道源码")
  18 + @Schema(description = "文章作者", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
19 19 private String author;
20 20  
21   - @Schema(description = "分类编号", required = true, example = "2048")
  21 + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
22 22 private Long categoryId;
23 23  
24   - @Schema(description = "图文封面", required = true, example = "https://www.iocoder.cn/1.png")
  24 + @Schema(description = "图文封面", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
25 25 private String picUrl;
26 26  
27   - @Schema(description = "文章简介", required = true, example = "我是简介")
  27 + @Schema(description = "文章简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是简介")
28 28 private String introduction;
29 29  
30   - @Schema(description = "文章内容", required = true, example = "我是详细")
  30 + @Schema(description = "文章内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是详细")
31 31 private String description;
32 32  
33   - @Schema(description = "发布时间", required = true)
  33 + @Schema(description = "发布时间", requiredMode = Schema.RequiredMode.REQUIRED)
34 34 private LocalDateTime createTime;
35 35  
36   - @Schema(description = "浏览量", required = true, example = "1024")
  36 + @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
37 37 private Integer browseCount;
38 38  
39 39 @Schema(description = "关联的商品 SPU 编号", example = "1024")
40 40 private Long spuId;
41 41  
42 42 // TODO 芋艿:下面 2 个字段,后端要存储,前端不用返回;
43   -// @Schema(description = "是否热卖推荐", required = true, example = "true")
  43 +// @Schema(description = "是否热卖推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
44 44 // private Boolean recommendHot;
45 45 //
46   -// @Schema(description = "是否 Banner 推荐", required = true, example = "true")
  46 +// @Schema(description = "是否 Banner 推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
47 47 // private Boolean recommendBanner;
48 48  
49 49 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/vo/category/AppArticleCategoryRespVO.java
... ... @@ -7,20 +7,20 @@ import lombok.Data;
7 7 @Data
8 8 public class AppArticleCategoryRespVO {
9 9  
10   - @Schema(description = "分类编号", required = true, example = "1")
  10 + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
11 11 private Long id;
12 12  
13   - @Schema(description = "分类名称", required = true, example = "技术")
  13 + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术")
14 14 private String name;
15 15  
16   - @Schema(description = "分类图标", required = true, example = "https://www.iocoder.cn/1.png")
  16 + @Schema(description = "分类图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
17 17 private String picUrl;
18 18  
19 19 // TODO 芋艿:下面 2 个字段,后端要存储,前端不用返回;
20   -// @Schema(description = "状态", required = true, example = "1")
  20 +// @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
21 21 // private Integer status;
22 22 //
23   -// @Schema(description = "排序", required = true, example = "1024")
  23 +// @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
24 24 // private Integer sort;
25 25  
26 26 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityDetailRespVO.java
... ... @@ -9,46 +9,46 @@ import java.time.LocalDateTime;
9 9 @Data
10 10 public class AppBargainActivityDetailRespVO {
11 11  
12   - @Schema(description = "砍价活动编号", required = true, example = "1024")
  12 + @Schema(description = "砍价活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 13 private Long id;
14 14  
15   - @Schema(description = "砍价活动名称", required = true, example = "618 大砍价")
  15 + @Schema(description = "砍价活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618 大砍价")
16 16 private String name;
17 17  
18   - @Schema(description = "活动开始时间", required = true)
  18 + @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
19 19 private LocalDateTime startTime;
20 20  
21   - @Schema(description = "活动结束时间", required = true)
  21 + @Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
22 22 private LocalDateTime endTime;
23 23  
24   - @Schema(description = "商品 SPU 编号", required = true, example = "2048")
  24 + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
25 25 private Long spuId;
26 26  
27   - @Schema(description = "商品 SKU 编号", required = true, example = "1024")
  27 + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
28 28 private Long skuId;
29 29  
30   - @Schema(description = "商品价格,单位:分", required = true, example = "100")
  30 + @Schema(description = "商品价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
31 31 private Integer price;
32 32  
33   - @Schema(description = "商品描述", required = true, example = "我要吃西红柿")
  33 + @Schema(description = "商品描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "我要吃西红柿")
34 34 private String description;
35 35  
36   - @Schema(description = "砍价库存", required = true, example = "512")
  36 + @Schema(description = "砍价库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
37 37 private Integer stock;
38 38  
39   - @Schema(description = "商品图片", required = true, example = "4096") // 从 SPU 的 picUrl 读取
  39 + @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") // 从 SPU 的 picUrl 读取
40 40 private String picUrl;
41 41  
42   - @Schema(description = "商品市场价,单位:分", required = true, example = "50") // 从 SPU 的 marketPrice 读取
  42 + @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") // 从 SPU 的 marketPrice 读取
43 43 private Integer marketPrice;
44 44  
45   - @Schema(description = "商品单位", required = true, example = "个") // 从 SPU 的 unit 读取,然后转换
  45 + @Schema(description = "商品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") // 从 SPU 的 unit 读取,然后转换
46 46 private String unitName;
47 47  
48   - @Schema(description = "砍价最低金额,单位:分", required = true, example = "100")
  48 + @Schema(description = "砍价最低金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
49 49 private Integer bargainPrice;
50 50  
51   - @Schema(description = "砍价成功数量", required = true, example = "100")
  51 + @Schema(description = "砍价成功数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
52 52 private Integer successCount;
53 53  
54 54 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityRespVO.java
... ... @@ -9,34 +9,34 @@ import java.time.LocalDateTime;
9 9 @Data
10 10 public class AppBargainActivityRespVO {
11 11  
12   - @Schema(description = "砍价活动编号", required = true, example = "1024")
  12 + @Schema(description = "砍价活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 13 private Long id;
14 14  
15   - @Schema(description = "砍价活动名称", required = true, example = "618 大砍价")
  15 + @Schema(description = "砍价活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618 大砍价")
16 16 private String name;
17 17  
18   - @Schema(description = "活动开始时间", required = true)
  18 + @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
19 19 private LocalDateTime startTime;
20 20  
21   - @Schema(description = "活动结束时间", required = true)
  21 + @Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
22 22 private LocalDateTime endTime;
23 23  
24   - @Schema(description = "商品 SPU 编号", required = true, example = "2048")
  24 + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
25 25 private Long spuId;
26 26  
27   - @Schema(description = "商品 SKU 编号", required = true, example = "1024")
  27 + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
28 28 private Long skuId;
29 29  
30   - @Schema(description = "砍价库存", required = true, example = "512")
  30 + @Schema(description = "砍价库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
31 31 private Integer stock;
32 32  
33   - @Schema(description = "商品图片", required = true, example = "4096") // 从 SPU 的 picUrl 读取
  33 + @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") // 从 SPU 的 picUrl 读取
34 34 private String picUrl;
35 35  
36   - @Schema(description = "商品市场价,单位:分", required = true, example = "50") // 从 SPU 的 marketPrice 读取
  36 + @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") // 从 SPU 的 marketPrice 读取
37 37 private Integer marketPrice;
38 38  
39   - @Schema(description = "砍价最低金额,单位:分", required = true, example = "100")
  39 + @Schema(description = "砍价最低金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
40 40 private Integer bargainPrice;
41 41  
42 42 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/help/AppBargainHelpCreateReqVO.java
... ... @@ -9,7 +9,7 @@ import javax.validation.constraints.NotNull;
9 9 @Data
10 10 public class AppBargainHelpCreateReqVO {
11 11  
12   - @Schema(description = "砍价记录编号", required = true, example = "1024")
  12 + @Schema(description = "砍价记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 13 @NotNull(message = "砍价记录编号不能为空")
14 14 private Long recordId;
15 15  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/help/AppBargainHelpRespVO.java
... ... @@ -9,16 +9,16 @@ import java.time.LocalDateTime;
9 9 @Data
10 10 public class AppBargainHelpRespVO {
11 11  
12   - @Schema(description = "助力用户的昵称", required = true, example = "1024")
  12 + @Schema(description = "助力用户的昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 13 private String nickname;
14 14  
15   - @Schema(description = "助力用户的头像", required = true, example = "1024")
  15 + @Schema(description = "助力用户的头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
16 16 private String avatar;
17 17  
18   - @Schema(description = "助力用户的砍价金额", required = true, example = "1024")
  18 + @Schema(description = "助力用户的砍价金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
19 19 private Integer reducePrice;
20 20  
21   - @Schema(description = "创建时间", required = true, example = "1024")
  21 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
22 22 private LocalDateTime createTime;
23 23  
24 24 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/record/AppBargainRecordCreateReqVO.java
... ... @@ -9,7 +9,7 @@ import javax.validation.constraints.NotNull;
9 9 @Data
10 10 public class AppBargainRecordCreateReqVO {
11 11  
12   - @Schema(description = "砍价活动编号", required = true, example = "1024")
  12 + @Schema(description = "砍价活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 13 @NotNull(message = "砍价活动编号不能为空")
14 14 private Long activityId;
15 15  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/record/AppBargainRecordSummaryRespVO.java
... ... @@ -9,23 +9,23 @@ import java.util.List;
9 9 @Data
10 10 public class AppBargainRecordSummaryRespVO {
11 11  
12   - @Schema(description = "砍价用户数量", required = true, example = "1024")
  12 + @Schema(description = "砍价用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 13 private Integer userCount;
14 14  
15   - @Schema(description = "成功砍价的记录", required = true) // 只返回最近的 7 个
  15 + @Schema(description = "成功砍价的记录", requiredMode = Schema.RequiredMode.REQUIRED) // 只返回最近的 7 个
16 16 private List<Record> successRecords;
17 17  
18 18 @Schema(description = "成功砍价记录")
19 19 @Data
20 20 public static class Record {
21 21  
22   - @Schema(description = "用户昵称", required = true, example = "王**")
  22 + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王**")
23 23 private String nickname;
24 24  
25   - @Schema(description = "用户头像", required = true, example = "https://www.iocoder.cn/xxx.jpg")
  25 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
26 26 private String avatar;
27 27  
28   - @Schema(description = "活动名称", required = true, example = "天蚕土豆")
  28 + @Schema(description = "活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "天蚕土豆")
29 29 private String activityName;
30 30  
31 31 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
... ... @@ -40,14 +40,17 @@ public class AppCouponController {
40 40 @PostMapping("/take")
41 41 @Operation(summary = "领取优惠劵")
42 42 @Parameter(name = "templateId", description = "优惠券模板编号", required = true, example = "1024")
  43 + @PreAuthenticated
43 44 public CommonResult<Boolean> takeCoupon(@Valid @RequestBody AppCouponTakeReqVO reqVO) {
  45 + // 1. 领取优惠劵
44 46 Long userId = getLoginUserId();
45   - // 领取
46 47 couponService.takeCoupon(reqVO.getTemplateId(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.USER);
47   - // 检查是否可以继续领取
  48 +
  49 + // 2. 检查是否可以继续领取
48 50 CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(reqVO.getTemplateId());
49 51 boolean canTakeAgain = true;
50 52 if (couponTemplate.getTakeLimitCount() != null && couponTemplate.getTakeLimitCount() > 0) {
  53 + // TODO @疯狂:要不要搞个 getTakeCount 方法?
51 54 Integer takeCount = MapUtil.getInt(couponService.getTakeCountMapByTemplateIds(
52 55 Collections.singleton(reqVO.getTemplateId()), userId), reqVO.getTemplateId(), 0);
53 56 canTakeAgain = takeCount < couponTemplate.getTakeLimitCount();
... ... @@ -56,15 +59,16 @@ public class AppCouponController {
56 59 }
57 60  
58 61 @GetMapping("/match-list")
59   - @Operation(summary = "获得匹配指定商品的优惠劵列表")
  62 + @Operation(summary = "获得匹配指定商品的优惠劵列表", description = "用于下单页,展示优惠劵列表")
60 63 public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) {
61   - // todo: 优惠金额倒序
  64 + // todo: 优化:优惠金额倒序
62 65 return success(CouponConvert.INSTANCE.convertList(couponService.getMatchCouponList(getLoginUserId(), matchReqVO)));
63 66 }
64 67  
65 68 @GetMapping("/page")
66   - @Operation(summary = "优惠劵列表", description = "我的优惠劵")
67   - public CommonResult<PageResult<AppCouponRespVO>> takeCoupon(AppCouponPageReqVO pageReqVO) {
  69 + @Operation(summary = "我的优惠劵列表")
  70 + @PreAuthenticated
  71 + public CommonResult<PageResult<AppCouponRespVO>> getCouponPage(AppCouponPageReqVO pageReqVO) {
68 72 PageResult<CouponDO> pageResult = couponService.getCouponPage(
69 73 CouponConvert.INSTANCE.convert(pageReqVO, Collections.singleton(getLoginUserId())));
70 74 return success(CouponConvert.INSTANCE.convertAppPage(pageResult));
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.controller.app.coupon;
3 3 import cn.hutool.core.util.ObjUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
6 7 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
7 8 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
8 9 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
... ... @@ -14,13 +15,17 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
14 15 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
15 16 import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
16 17 import io.swagger.v3.oas.annotations.Operation;
  18 +import io.swagger.v3.oas.annotations.Parameter;
  19 +import io.swagger.v3.oas.annotations.Parameters;
17 20 import io.swagger.v3.oas.annotations.tags.Tag;
18 21 import org.springframework.validation.annotation.Validated;
19 22 import org.springframework.web.bind.annotation.GetMapping;
20 23 import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RequestParam;
21 25 import org.springframework.web.bind.annotation.RestController;
22 26  
23 27 import javax.annotation.Resource;
  28 +import java.time.LocalDateTime;
24 29 import java.util.*;
25 30  
26 31 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -41,16 +46,63 @@ public class AppCouponTemplateController {
41 46 @Resource
42 47 private ProductSpuApi productSpuApi;
43 48  
  49 + // TODO 疯狂:这里应该还有个 list 接口哈;获得优惠劵模版列表,用于首页、商品页的优惠劵
  50 + @GetMapping("/list")
  51 + @Operation(summary = "获得优惠劵模版列表")
  52 + @Parameters({
  53 + @Parameter(name = "spuId", description = "商品 SPU 编号"), // 目前主要给商品详情使用
  54 + @Parameter(name = "useType", description = "使用类型"),
  55 + @Parameter(name = "count", description = "数量", required = true)
  56 + })
  57 + public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(
  58 + @RequestParam(value = "spuId", required = false) Long spuId,
  59 + @RequestParam(value = "useType", required = false) Integer useType,
  60 + @RequestParam(value = "count", required = false, defaultValue = "10") Integer count) {
  61 + List<AppCouponTemplateRespVO> list = new ArrayList<>();
  62 + Random random = new Random();
  63 + for (int i = 0; i < 10; i++) {
  64 + AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
  65 + vo.setId(i + 1L);
  66 + vo.setName("优惠劵" + (i + 1));
  67 + vo.setTakeLimitCount(random.nextInt(10) + 1);
  68 + vo.setUsePrice(random.nextInt(100) * 100);
  69 + vo.setValidityType(random.nextInt(2) + 1);
  70 + if (vo.getValidityType() == 1) {
  71 + vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
  72 + vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
  73 + } else {
  74 + vo.setFixedStartTerm(random.nextInt(10));
  75 + vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
  76 + }
  77 + vo.setDiscountType(random.nextInt(2) + 1);
  78 + if (vo.getDiscountType() == 1) {
  79 + vo.setDiscountPercent(null);
  80 + vo.setDiscountPrice(random.nextInt(50) * 100);
  81 + vo.setDiscountLimitPrice(null);
  82 + } else {
  83 + vo.setDiscountPercent(random.nextInt(90) + 10);
  84 + vo.setDiscountPrice(null);
  85 + vo.setDiscountLimitPrice(random.nextInt(200) * 100);
  86 + }
  87 + // TODO @疯狂:是否已领取,要不在 TemplateService 搞个 static 方法,让它基于 countMap 这种去计算,这样好点?
  88 + vo.setTakeStatus(random.nextBoolean());
  89 + list.add(vo);
  90 + }
  91 + return success(list);
  92 + }
  93 +
44 94 @GetMapping("/page")
45 95 @Operation(summary = "获得优惠劵模版分页")
46 96 public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
47 97 // 1.1 处理查询条件:商品范围编号
48   - Long productScopeValue = getaProductScopeValue(pageReqVO);
49   - // 1.2 处理查询条件:领取方式=直接领取
  98 + Long productScopeValue = getProductScopeValue(pageReqVO);
  99 + // 1.2 处理查询条件:领取方式 = 直接领取
50 100 List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
  101 +
51 102 // 2. 分页查询
52 103 PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(
53 104 CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue));
  105 +
54 106 // 3.1 领取数量
55 107 Map<Long, Integer> couponTakeCountMap = new HashMap<>(0);
56 108 Long userId = getLoginUserId();
... ... @@ -63,17 +115,24 @@ public class AppCouponTemplateController {
63 115 return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap));
64 116 }
65 117  
66   - private Long getaProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
67   - Long productScopeValue = pageReqVO.getSpuId();
68   - if (pageReqVO.getProductScope() == null || Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope())) {
69   - // 通用券:清除商品范围
70   - productScopeValue = null;
71   - } else if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
72   - // 品类券:查询商品的品类
73   - productScopeValue = Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
  118 + /**
  119 + * 获得分页查询的商品范围
  120 + *
  121 + * @param pageReqVO 分页查询
  122 + * @return 商品范围
  123 + */
  124 + private Long getProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
  125 + // 通用券:清除商品范围
  126 + if (pageReqVO.getProductScope() == null || ObjectUtils.equalsAny(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope(), null)) {
  127 + return null;
  128 + }
  129 + // 品类券:查询商品的品类
  130 + if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
  131 + return Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
74 132 .map(ProductSpuRespDTO::getCategoryId).orElse(null);
75 133 }
76   - return productScopeValue;
  134 + // 商品卷:直接返回
  135 + return pageReqVO.getSpuId();
77 136 }
78 137  
79 138 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java
... ... @@ -20,4 +20,5 @@ public class AppCouponTemplatePageReqVO extends PageParam {
20 20  
21 21 @Schema(description = "商品标号", example = "1")
22 22 private Long spuId;
  23 +
23 24 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java
... ... @@ -23,14 +23,6 @@ public class AppCouponTemplateRespVO {
23 23 // 单位:分;0 - 不限制
24 24 private Integer usePrice;
25 25  
26   - // TODO 芋艿:这两要改的
27   -// @Schema(description = "商品范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
28   -// @InEnum(PromotionProductScopeEnum.class)
29   -// private Integer productScope;
30   -//
31   -// @Schema(description = "商品范围编号的数组", example = "1,3")
32   -// private List<Long> productScopeValues;
33   -
34 26 @Schema(description = "生效日期类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
35 27 private Integer validityType;
36 28  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
... ... @@ -61,4 +61,5 @@ public interface CouponConvert {
61 61 PageResult<AppCouponRespVO> convertAppPage(PageResult<CouponDO> pageResult);
62 62  
63 63 List<AppCouponMatchRespVO> convertList(List<CouponDO> list);
  64 +
64 65 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
... ... @@ -42,16 +42,16 @@ public interface CouponTemplateConvert {
42 42 if (MapUtil.isEmpty(couponTakeCountMap)) {
43 43 return result;
44 44 }
45   -
46   - for (AppCouponTemplateRespVO vo : result.getList()) {
  45 + for (AppCouponTemplateRespVO template : result.getList()) {
47 46 // 每人领取数量无限制
48   - if (vo.getTakeLimitCount() == -1) {
49   - vo.setTakeStatus(false);
  47 + if (template.getTakeLimitCount() == -1) {
  48 + template.setTakeStatus(false);
50 49 continue;
51 50 }
52 51 // 检查已领取数量是否超过限领数量
53   - vo.setTakeStatus(MapUtil.getInt(couponTakeCountMap, vo.getId(), 0) >= vo.getTakeLimitCount());
  52 + template.setTakeStatus(MapUtil.getInt(couponTakeCountMap, template.getId(), 0) >= template.getTakeLimitCount());
54 53 }
55 54 return result;
56 55 }
  56 +
57 57 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
... ... @@ -70,6 +70,7 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
70 70 );
71 71 }
72 72  
  73 + // TODO @疯狂:这个是不是搞个 Map 就可以呀?
73 74 default List<CouponTakeCountBO> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
74 75 return BeanUtil.copyToList(selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
75 76 .select(CouponDO::getTemplateId)
... ... @@ -81,18 +82,17 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
81 82  
82 83 default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(
83 84 Long userId, Integer status, Integer usePrice, List<Long> spuIds, List<Long> categoryIds) {
84   -
85 85 Function<List<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
86 86 .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
87 87 .collect(Collectors.joining(" OR "));
88 88 return selectList(new LambdaQueryWrapperX<CouponDO>()
89 89 .eq(CouponDO::getUserId, userId)
90 90 .eq(CouponDO::getStatus, status)
91   - .le(CouponDO::getUsePrice, usePrice)
92   - .and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope())
93   - .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope())
  91 + .le(CouponDO::getUsePrice, usePrice) // 价格小于等于,满足价格使用条件
  92 + .and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope()) // 商品范围一:全部
  93 + .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) // 商品范围二:满足指定商品
94 94 .apply(productScopeValuesFindInSetFunc.apply(spuIds)))
95   - .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope())
  95 + .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) // 商品范围三:满足指定分类
96 96 .apply(productScopeValuesFindInSetFunc.apply(categoryIds)))));
97 97 }
98 98  
... ... @@ -102,4 +102,5 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
102 102 .le(CouponDO::getValidEndTime, validEndTime)
103 103 );
104 104 }
  105 +
105 106 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/CouponExpireJob.java renamed to yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/coupon/CouponExpireJob.java
1   -package cn.iocoder.yudao.module.promotion.job;
  1 +package cn.iocoder.yudao.module.promotion.job.coupon;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
... ... @@ -8,19 +8,20 @@ import org.springframework.stereotype.Component;
8 8  
9 9 import javax.annotation.Resource;
10 10  
  11 +// TODO 芋艿:配置一个 Job
11 12 /**
12 13 * 优惠券过期 Job
13 14 *
14 15 * @author owen
15 16 */
16 17 @Component
17   -@TenantJob
18 18 public class CouponExpireJob implements JobHandler {
19 19  
20 20 @Resource
21 21 private CouponService couponService;
22 22  
23 23 @Override
  24 + @TenantJob
24 25 public String execute(String param) {
25 26 int count = couponService.expireCoupon();
26 27 return StrUtil.format("过期优惠券 {} 个", count);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/package-info.java renamed to yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/package-info.java
1 1 /**
2   - * 占位文件,无特殊用途
  2 + * TODO 占位,无具体含义
3 3 */
4   -package cn.iocoder.yudao.module.trade.job;
  4 +package cn.iocoder.yudao.module.promotion.job;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
... ... @@ -137,7 +137,8 @@ public interface CouponService {
137 137 * @return 领取优惠券的数量
138 138 */
139 139 default Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
140   - return convertMap(getTakeCountListByTemplateIds(templateIds, userId), CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount);
  140 + return convertMap(getTakeCountListByTemplateIds(templateIds, userId),
  141 + CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount);
141 142 }
142 143  
143 144 /**
... ... @@ -164,4 +165,5 @@ public interface CouponService {
164 165 * @return 过期数量
165 166 */
166 167 int expireCoupon();
  168 +
167 169 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
... ... @@ -40,6 +40,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
40 40 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
41 41 import static java.util.Arrays.asList;
42 42  
  43 +// TODO @疯狂:注册时,赠送用户优惠劵;为了解耦,可以考虑注册时发个 MQ 消息;然后营销这里监听后消费;
43 44 /**
44 45 * 优惠劵 Service 实现类
45 46 *
... ... @@ -192,7 +193,8 @@ public class CouponServiceImpl implements CouponService {
192 193  
193 194 @Override
194 195 public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
195   - return couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, CouponStatusEnum.UNUSED.getStatus(),
  196 + return couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
  197 + CouponStatusEnum.UNUSED.getStatus(),
196 198 matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
197 199 }
198 200  
... ... @@ -220,15 +222,20 @@ public class CouponServiceImpl implements CouponService {
220 222 return count;
221 223 }
222 224  
  225 + /**
  226 + * 过期单个优惠劵
  227 + *
  228 + * @param coupon 优惠劵
  229 + * @return 是否过期成功
  230 + */
223 231 private boolean expireCoupon(CouponDO coupon) {
224 232 // 更新记录状态
225   - CouponDO updateObj = new CouponDO().setStatus(CouponStatusEnum.EXPIRE.getStatus());
226   - int updateRows = couponMapper.updateByIdAndStatus(coupon.getId(), CouponStatusEnum.UNUSED.getStatus(), updateObj);
  233 + int updateRows = couponMapper.updateByIdAndStatus(coupon.getId(), CouponStatusEnum.UNUSED.getStatus(),
  234 + new CouponDO().setStatus(CouponStatusEnum.EXPIRE.getStatus()));
227 235 if (updateRows == 0) {
228 236 log.error("[expireCoupon][coupon({}) 更新为已过期失败]", coupon.getId());
229 237 return false;
230 238 }
231   -
232 239 log.info("[expireCoupon][coupon({}) 更新为已过期成功]", coupon.getId());
233 240 return true;
234 241 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
... ... @@ -29,6 +29,7 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
29 29 @Resource
30 30 private CouponTemplateMapper couponTemplateMapper;
31 31  
  32 + // TODO @疯狂:新增/修改时,需要校验对应的商品、分类是否存在
32 33 @Override
33 34 public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
34 35 // 插入
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java
... ... @@ -9,6 +9,7 @@ import lombok.Data;
9 9 */
10 10 @Data
11 11 public class CouponTakeCountBO {
  12 +
12 13 /**
13 14 * 优惠劵模板编号
14 15 */
... ... @@ -17,4 +18,5 @@ public class CouponTakeCountBO {
17 18 * 领取数量
18 19 */
19 20 private Integer count;
  21 +
20 22 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -11,7 +11,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
11 11 */
12 12 public interface ErrorCodeConstants {
13 13  
14   - // ========== Order 模块 1011000000 ==========
  14 + // ========== Order 模块 1-011-000-000 ==========
15 15 ErrorCode ORDER_ITEM_NOT_FOUND = new ErrorCode(1011000010, "交易订单项不存在");
16 16 ErrorCode ORDER_NOT_FOUND = new ErrorCode(1011000011, "交易订单不存在");
17 17 ErrorCode ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL = new ErrorCode(1011000012, "交易订单项更新售后状态失败,请重试");
... ... @@ -33,45 +33,44 @@ public interface ErrorCodeConstants {
33 33 ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1011000028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元");
34 34 ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1011000029, "交易订单删除失败,订单不是【已取消】状态");
35 35  
36   - // ========== After Sale 模块 1011000100 ==========
37   - ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在");
38   - ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1011000101, "申请退款金额错误");
39   - ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1011000102, "订单已关闭,无法申请售后");
40   - ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID = new ErrorCode(1011000103, "订单未支付,无法申请售后");
41   - ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED = new ErrorCode(1011000104, "订单未发货,无法申请【退货退款】售后");
42   - ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1011000105, "订单项已申请售后,无法重复申请");
43   - ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1011000106, "审批失败,售后状态不处于审批中");
44   - ErrorCode AFTER_SALE_UPDATE_STATUS_FAIL = new ErrorCode(1011000107, "操作售后单失败,请刷新后重试");
45   - ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1011000108, "退货失败,售后单状态不处于【待买家退货】");
46   - ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1011000109, "确认收货失败,售后单状态不处于【待确认收货】");
47   - ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1011000110, "退款失败,售后单状态不是【待退款】");
  36 + // ========== After Sale 模块 1-011-000-100 ==========
  37 + ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
  38 + ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1_011_000_101, "申请退款金额错误");
  39 + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1_011_000_102, "订单已关闭,无法申请售后");
  40 + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID = new ErrorCode(1_011_000_103, "订单未支付,无法申请售后");
  41 + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED = new ErrorCode(1_011_000_104, "订单未发货,无法申请【退货退款】售后");
  42 + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1_011_000_105, "订单项已申请售后,无法重复申请");
  43 + ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1_011_000_106, "审批失败,售后状态不处于审批中");
  44 + ErrorCode AFTER_SALE_UPDATE_STATUS_FAIL = new ErrorCode(1_011_000_107, "操作售后单失败,请刷新后重试");
  45 + ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1_011_000_108, "退货失败,售后单状态不处于【待买家退货】");
  46 + ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1_011_000_109, "确认收货失败,售后单状态不处于【待确认收货】");
  47 + ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】");
48 48 ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
49   - new ErrorCode(1011000111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
  49 + new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
50 50  
51   - // ========== Cart 模块 1011002000 ==========
52   - ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1011002000, "购物车项不存在");
  51 + // ========== Cart 模块 1-011-002-000 ==========
  52 + ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
53 53  
54   - // ========== Price 相关 1011003000 ============
  54 + // ========== Price 相关 1-011-003-000 ============
55 55 ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0");
56 56 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板");
57 57 ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1011003004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
58 58  
59   - // ========== 物流 Express 模块 1011004000 ==========
60   - ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在");
61   - ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1011004001, "已经存在该编码的快递公司");
62   - ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1011004002, "需要接入快递服务商,比如【快递100】");
63   - ErrorCode EXPRESS_STATUS_NOT_ENABLE = new ErrorCode(1011004003, "快递公司未启用");
  59 + // ========== 物流 Express 模块 1-011-004-000 ==========
  60 + ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
  61 + ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1_011_004_001, "已经存在该编码的快递公司");
  62 + ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1_011_004_002, "需要接入快递服务商,比如【快递100】");
  63 + ErrorCode EXPRESS_STATUS_NOT_ENABLE = new ErrorCode(1_011_004_003, "快递公司未启用");
64 64  
65   - ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1011004101, "快递查询接口异常");
66   - ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1011004102, "快递查询返回失败,原因:{}");
  65 + ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1_011_004_101, "快递查询接口异常");
  66 + ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1_011_004_102, "快递查询返回失败,原因:{}");
67 67  
68   - // ========== 物流 Template 模块 1011005000 ==========
69   - ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011005000, "已经存在该运费模板名");
70   - ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1011005001, "运费模板不存在");
71   -
72   - // ========== 物流 PICK_UP 模块 1011006000 ==========
73   - ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011006000, "自提门店不存在");
  68 + // ========== 物流 Template 模块 1-011-005-000 ==========
  69 + ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_011_005_000, "已经存在该运费模板名");
  70 + ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_011_005_001, "运费模板不存在");
74 71  
  72 + // ========== 物流 PICK_UP 模块 1-011-006-000 ==========
  73 + ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在");
75 74  
76 75 // ========== 分销用户 模块 1011007000 ==========
77 76 ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1011007000, "分销用户不存在");
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/AfterSaleOperateTypeEnum.java
1 1 package cn.iocoder.yudao.module.trade.enums.aftersale;
2 2  
  3 +import lombok.Getter;
  4 +import lombok.RequiredArgsConstructor;
  5 +
3 6 /**
4 7 * 售后操作类型的枚举
5 8 *
6 9 * @author 陈賝
7 10 * @since 2023/6/13 13:53
8 11 */
9   -// TODO @chenchen:可以 lombok 简化构造方法,和 get 方法
  12 +@RequiredArgsConstructor
  13 +@Getter
10 14 public enum AfterSaleOperateTypeEnum {
11 15  
12   - /**
13   - * 用户申请
14   - */
15   - APPLY("用户申请"),
  16 + MEMBER_CREATE(10, "会员申请退款"),
  17 + ADMIN_AGREE_APPLY(11, "商家同意退款"),
  18 + ADMIN_DISAGREE_APPLY(12, "商家拒绝退款"),
  19 + MEMBER_DELIVERY(20, "会员填写退货物流信息,快递公司:{deliveryName},快递单号:{logisticsNo}"),
  20 + ADMIN_AGREE_RECEIVE(21, "商家收货"),
  21 + ADMIN_DISAGREE_RECEIVE(22, "商家拒绝收货,原因:{reason}"),
  22 + ADMIN_REFUND(30, "商家退款"),
  23 + MEMBER_CANCEL(40, "会员取消退款"),
16 24 ;
17 25  
18   - private final String description;
19   -
20   - AfterSaleOperateTypeEnum(String description) {
21   - this.description = description;
22   - }
23   -
24   - public String description() {
25   - return description;
26   - }
  26 + /**
  27 + * 操作类型
  28 + */
  29 + private final Integer type;
  30 + /**
  31 + * 操作描述
  32 + */
  33 + private final String content;
27 34  
28 35 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageBindModeEnum.java
... ... @@ -18,15 +18,15 @@ public enum BrokerageBindModeEnum implements IntArrayValuable {
18 18 /**
19 19 * 只要用户没有推广人,随时都可以绑定分销关系
20 20 */
21   - ANYTIME(1, "没有推广人"),
  21 + ANYTIME(1, "首次绑定"),
22 22 /**
23 23 * 仅新用户注册时才能绑定推广关系
24 24 */
25   - REGISTER(2, "新用户"),
  25 + REGISTER(2, "注册绑定"),
26 26 /**
27 27 * 每次扫码都覆盖
28 28 */
29   - OVERRIDE(3, "扫码覆盖"),
  29 + OVERRIDE(3, "覆盖绑定"),
30 30 ;
31 31  
32 32 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageBindModeEnum::getMode).toArray();
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordBizTypeEnum.java
... ... @@ -17,7 +17,7 @@ public enum BrokerageRecordBizTypeEnum implements IntArrayValuable {
17 17  
18 18 ORDER(1, "获得推广佣金", "获得推广佣金 {}", true),
19 19 WITHDRAW(2, "提现申请", "提现申请扣除佣金 {}", false),
20   - WITHDRAW_REJECT(3, "提现申请驳回", "提现申请驳回返还佣金 {}", true),
  20 + WITHDRAW_REJECT(3, "提现申请驳回", "提现申请驳回,返还佣金 {}", true),
21 21 ;
22 22  
23 23 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordBizTypeEnum::getType).toArray();
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawStatusEnum.java
... ... @@ -6,6 +6,7 @@ import lombok.Getter;
6 6  
7 7 import java.util.Arrays;
8 8  
  9 +// TODO 芋艿:提现的打通,在纠结下;
9 10 /**
10 11 * 佣金提现状态枚举
11 12 *
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderOperateTypeEnum.java
... ... @@ -24,15 +24,16 @@ public enum TradeOrderOperateTypeEnum {
24 24 MEMBER_CANCEL(40, "取消订单"),
25 25 SYSTEM_CANCEL(41, "到期未支付,系统自动取消订单"),
26 26 // 42 预留:管理员取消订单
27   - MEMBER_DELETE(43, "删除订单"),
  27 + ADMIN_CANCEL_AFTER_SALE(43, "订单全部售后,管理员自动取消订单"),
  28 + MEMBER_DELETE(49, "删除订单"),
28 29 ;
29 30  
30 31 /**
31   - * 类型
  32 + * 操作类型
32 33 */
33 34 private final Integer type;
34 35 /**
35   - * 类型
  36 + * 操作描述
36 37 */
37 38 private final String content;
38 39  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java
... ... @@ -13,8 +13,8 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum
13 13 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
14 14 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
15 15 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
16   -import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
17   -import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
  16 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
  17 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
18 18 import io.swagger.v3.oas.annotations.Operation;
19 19 import io.swagger.v3.oas.annotations.Parameter;
20 20 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -88,24 +88,24 @@ public class BrokerageUserController {
88 88 // 分页查询
89 89 PageResult<BrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(pageVO);
90 90  
91   - // 涉及到的用户
92   - Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
93 91 // 查询用户信息
  92 + Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
94 93 Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
95   - // 合计分佣订单
96   - Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = brokerageRecordService.getUserBrokerageSummaryMapByUserId(
  94 + // 合计分佣的推广订单
  95 + Map<Long, UserBrokerageSummaryRespBO> brokerageOrderSummaryMap = brokerageRecordService.getUserBrokerageSummaryMapByUserId(
97 96 userIds, BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus());
98   - // TODO @芋艿:看看下面 getBrokerageUserCountByBindUserId 有没可能一次性出结果,不然 n 次有点太花性能了;
99   - // 合计推广用户数量
  97 + // 合计分佣的推广用户
  98 + // TODO @疯狂:转成 map 批量读取
100 99 Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
101 100 userId -> userId,
102 101 userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, null));
103   - // 合计提现
104   - Map<Long, UserWithdrawSummaryBO> withdrawMap = brokerageWithdrawService.getWithdrawSummaryMapByUserId(userIds,
105   - BrokerageWithdrawStatusEnum.AUDIT_SUCCESS);
  102 + // 合计分佣的提现
  103 + // TODO @疯狂:如果未来支持了打款这个动作,可能 status 会不对;
  104 + Map<Long, BrokerageWithdrawSummaryRespBO> withdrawMap = brokerageWithdrawService.getWithdrawSummaryMapByUserId(
  105 + userIds, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS);
106 106 // 拼接返回
107 107 return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap,
108   - userOrderSummaryMap, withdrawMap));
  108 + brokerageOrderSummaryMap, withdrawMap));
109 109 }
110 110  
111 111 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/user/BrokerageUserRespVO.java
... ... @@ -21,25 +21,25 @@ public class BrokerageUserRespVO extends BrokerageUserBaseVO {
21 21  
22 22 // ========== 用户信息 ==========
23 23  
24   - @Schema(description = "用户头像", example = "https://www.iocoder.cn/xxx.png")
  24 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.png")
25 25 private String avatar;
26   - @Schema(description = "用户昵称", example = "李四")
  26 + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
27 27 private String nickname;
28 28  
29 29 // ========== 推广信息 ==========
30 30  
31   - @Schema(description = "推广用户数量", example = "20019")
  31 + @Schema(description = "推广用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
32 32 private Integer brokerageUserCount;
33   - @Schema(description = "推广订单数量", example = "20019")
  33 + @Schema(description = "推广订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
34 34 private Integer brokerageOrderCount;
35   - @Schema(description = "推广订单金额", example = "20019")
  35 + @Schema(description = "推广订单金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
36 36 private Integer brokerageOrderPrice;
37 37  
38 38 // ========== 提现信息 ==========
39 39  
40   - @Schema(description = "已提现金额", example = "20019")
  40 + @Schema(description = "已提现金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
41 41 private Integer withdrawPrice;
42   - @Schema(description = "已提现次数", example = "20019")
  42 + @Schema(description = "已提现次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
43 43 private Integer withdrawCount;
44 44  
45 45 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigBaseVO.java
... ... @@ -19,6 +19,19 @@ import java.util.List;
19 19 */
20 20 @Data
21 21 public class TradeConfigBaseVO {
  22 +
  23 + // ========== 售后相关 ==========
  24 +
  25 + @Schema(description = "售后的退款理由", requiredMode = Schema.RequiredMode.REQUIRED)
  26 + @NotEmpty(message = "售后的退款理由不能为空")
  27 + private List<String> afterSaleRefundReasons;
  28 +
  29 + @Schema(description = "售后的退货理由", requiredMode = Schema.RequiredMode.REQUIRED)
  30 + @NotEmpty(message = "售后的退货理由不能为空")
  31 + private List<String> afterSaleReturnReasons;
  32 +
  33 + // ========== 配送相关 ==========
  34 +
22 35 /**
23 36 * 是否启用全场包邮
24 37 */
... ... @@ -80,7 +93,7 @@ public class TradeConfigBaseVO {
80 93 private Integer brokerageFrozenDays;
81 94  
82 95 @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
83   - @NotNull(message = "提现方式不能为空")
  96 + @NotEmpty(message = "提现方式不能为空")
84 97 @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
85 98 private List<Integer> brokerageWithdrawTypes;
86 99  
... ...