Commit 8b50cd9661113189dff6386276308fe7285d47ef

Authored by YunaiV
2 parents 2bbd86e2 314dee43

Merge branch 'feature/mall_product' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

 Conflicts:
	yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java
	yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
	yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
Showing 153 changed files with 4835 additions and 645 deletions
@@ -16,11 +16,11 @@ @@ -16,11 +16,11 @@
16 <module>yudao-module-member</module> 16 <module>yudao-module-member</module>
17 <module>yudao-module-system</module> 17 <module>yudao-module-system</module>
18 <module>yudao-module-infra</module> 18 <module>yudao-module-infra</module>
19 -<!-- <module>yudao-module-pay</module>-->  
20 -<!-- <module>yudao-module-bpm</module>--> 19 + <!-- <module>yudao-module-bpm</module>-->
21 <!-- <module>yudao-module-report</module>--> 20 <!-- <module>yudao-module-report</module>-->
22 <!-- <module>yudao-module-mp</module>--> 21 <!-- <module>yudao-module-mp</module>-->
23 -<!-- <module>yudao-module-mall</module>--> 22 + <module>yudao-module-pay</module>
  23 + <module>yudao-module-mall</module>
24 <!-- 示例项目 --> 24 <!-- 示例项目 -->
25 <module>yudao-example</module> 25 <module>yudao-example</module>
26 </modules> 26 </modules>
sql/mysql/brokerage.sql 0 → 100644
  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 0 not null comment '分佣模式:1-人人分销 2-指定分销',
  7 + brokerage_bind_mode tinyint default 0 not null comment '分销关系绑定模式: 1-没有推广人,2-新用户, 3-扫码覆盖',
  8 + brokerage_post_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_bank_names varchar(200) default '' not null comment '提现银行(字典类型=brokerage_bank_name)',
  13 + brokerage_frozen_days int default 7 not null comment '佣金冻结时间(天)',
  14 + brokerage_withdraw_type varchar(32) default '1,2,3,4' not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝',
  15 + creator varchar(64) collate utf8mb4_unicode_ci default '' null comment '创建者',
  16 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  17 + updater varchar(64) collate utf8mb4_unicode_ci default '' null comment '更新者',
  18 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
  19 + deleted bit default b'0' not null comment '是否删除',
  20 + tenant_id bigint default 0 not null comment '租户编号'
  21 +) comment '交易中心配置';
  22 +
  23 +-- 增加分销用户扩展表
  24 +create table trade_brokerage_user
  25 +(
  26 + id bigint auto_increment comment '用户编号' primary key,
  27 + bind_user_id bigint null comment '推广员编号',
  28 + bind_user_time datetime null comment '推广员绑定时间',
  29 + brokerage_enabled bit default 1 not null comment '是否成为推广员',
  30 + brokerage_time datetime null comment '成为分销员时间',
  31 + price int default 0 not null comment '可用佣金',
  32 + frozen_price int default 0 not null comment '冻结佣金',
  33 + creator varchar(64) collate utf8mb4_unicode_ci default '' null comment '创建者',
  34 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  35 + updater varchar(64) collate utf8mb4_unicode_ci default '' null comment '更新者',
  36 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
  37 + deleted bit default b'0' not null comment '是否删除',
  38 + tenant_id bigint default 0 not null comment '租户编号'
  39 +) comment '分销用户';
  40 +
  41 +create index idx_invite_user_id on trade_brokerage_user (bind_user_id) comment '推广员编号';
  42 +create index idx_agent on trade_brokerage_user (brokerage_enabled) comment '是否成为推广员';
  43 +
  44 +
  45 +create table trade_brokerage_record
  46 +(
  47 + id int auto_increment comment '编号'
  48 + primary key,
  49 + user_id bigint not null comment '用户编号',
  50 + biz_id varchar(64) default '' not null comment '业务编号',
  51 + biz_type tinyint default 0 not null comment '业务类型:0-订单,1-提现',
  52 + title varchar(64) default '' not null comment '标题',
  53 + price int default 0 not null comment '金额',
  54 + total_price int default 0 not null comment '当前总佣金',
  55 + description varchar(500) default '' not null comment '说明',
  56 + status tinyint default 0 not null comment '状态:0-待结算,1-已结算,2-已取消',
  57 + frozen_days int default 0 not null comment '冻结时间(天)',
  58 + unfreeze_time datetime null comment '解冻时间',
  59 + creator varchar(64) collate utf8mb4_general_ci default '' null comment '创建者',
  60 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  61 + updater varchar(64) collate utf8mb4_general_ci default '' null comment '更新者',
  62 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
  63 + deleted bit default b'0' not null comment '是否删除',
  64 + tenant_id bigint default 0 not null comment '租户编号'
  65 +)
  66 + comment '佣金记录';
  67 +
  68 +create index idx_user_id on trade_brokerage_record (user_id) comment '用户编号';
  69 +create index idx_biz on trade_brokerage_record (biz_type, biz_id) comment '业务';
  70 +create index idx_status on trade_brokerage_record (status) comment '状态';
  71 +
  72 +
  73 +create table trade_brokerage_withdraw
  74 +(
  75 + id int auto_increment comment '编号'
  76 + primary key,
  77 + user_id bigint not null comment '用户编号',
  78 + price int default 0 not null comment '提现金额',
  79 + fee_price int default 0 not null comment '提现手续费',
  80 + total_price int default 0 not null comment '当前总佣金',
  81 + type tinyint default 0 not null comment '提现类型:1-钱包;2-银行卡;3-微信;4-支付宝',
  82 + name varchar(64) null comment '真实姓名',
  83 + account_no varchar(64) null comment '账号',
  84 + bank_name varchar(100) null comment '银行名称',
  85 + bank_address varchar(200) null comment '开户地址',
  86 + account_qr_code_url varchar(512) null comment '收款码',
  87 + status tinyint(2) default 0 not null comment '状态:0-审核中,10-审核通过 20-审核不通过;预留:11 - 提现成功;21-提现失败',
  88 + audit_reason varchar(128) null comment '审核驳回原因',
  89 + audit_time datetime null comment '审核时间',
  90 + remark varchar(500) null comment '备注',
  91 + creator varchar(64) collate utf8mb4_general_ci default '' null comment '创建者',
  92 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  93 + updater varchar(64) collate utf8mb4_general_ci default '' null comment '更新者',
  94 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
  95 + deleted bit default b'0' not null comment '是否删除',
  96 + tenant_id bigint default 0 not null comment '租户编号'
  97 +)
  98 + comment '佣金提现';
  99 +
  100 +create index idx_user_id on trade_brokerage_withdraw (user_id) comment '用户编号';
  101 +create index idx_audit_status on trade_brokerage_withdraw (status) comment '状态';
  102 +
  103 +-- 增加字典
  104 +insert into system_dict_type(type, name)
  105 +values ('brokerage_enabled_condition', '分佣模式');
  106 +insert into system_dict_data(dict_type, label, value, sort, remark)
  107 +values ('brokerage_enabled_condition', '人人分销', 1, 1, '所有用户都可以分销'),
  108 + ('brokerage_enabled_condition', '指定分销', 2, 2, '仅可后台手动设置推广员');
  109 +
  110 +insert into system_dict_type(type, name)
  111 +values ('brokerage_bind_mode', '分销关系绑定模式');
  112 +insert into system_dict_data(dict_type, label, value, sort, remark)
  113 +values ('brokerage_bind_mode', '没有推广人', 1, 1, '只要用户没有推广人,随时都可以绑定推广关系'),
  114 + ('brokerage_bind_mode', '新用户', 2, 2, '仅新用户注册时才能绑定推广关系'),
  115 + ('brokerage_bind_mode', '扫码覆盖', 3, 3, '如果用户已经有推广人,推广人会被变更');
  116 +
  117 +insert into system_dict_type(type, name)
  118 +values ('brokerage_withdraw_type', '佣金提现类型');
  119 +insert into system_dict_data(dict_type, label, value, sort)
  120 +values ('brokerage_withdraw_type', '钱包', 1, 1),
  121 + ('brokerage_withdraw_type', '银行卡', 2, 2),
  122 + ('brokerage_withdraw_type', '微信', 3, 3),
  123 + ('brokerage_withdraw_type', '支付宝', 4, 4);
  124 +
  125 +insert into system_dict_type(type, name)
  126 +values ('brokerage_record_biz_type', '佣金记录业务类型');
  127 +insert into system_dict_data(dict_type, label, value, sort)
  128 +values ('brokerage_record_biz_type', '订单返佣', 1, 1),
  129 + ('brokerage_record_biz_type', '申请提现', 2, 2);
  130 +
  131 +insert into system_dict_type(type, name)
  132 +values ('brokerage_record_status', '佣金记录状态');
  133 +insert into system_dict_data(dict_type, label, value, sort)
  134 +values ('brokerage_record_status', '待结算', 0, 0),
  135 + ('brokerage_record_status', '已结算', 1, 1),
  136 + ('brokerage_record_status', '已取消', 2, 2);
  137 +
  138 +insert into system_dict_type(type, name)
  139 +values ('brokerage_withdraw_status', '佣金提现状态');
  140 +insert into system_dict_data(dict_type, label, value, sort)
  141 +values ('brokerage_withdraw_status', '审核中', 0, 0),
  142 + ('brokerage_withdraw_status', '审核通过', 10, 10),
  143 + ('brokerage_withdraw_status', '提现成功', 11, 11),
  144 + ('brokerage_withdraw_status', '审核不通过', 20, 20),
  145 + ('brokerage_withdraw_status', '提现失败', 21, 21);
  146 +
  147 +insert into system_dict_type(type, name)
  148 +values ('brokerage_bank_name', '佣金提现银行');
  149 +insert into system_dict_data(dict_type, label, value, sort)
  150 +values ('brokerage_bank_name', '工商银行', 0, 0),
  151 + ('brokerage_bank_name', '建设银行', 1, 1),
  152 + ('brokerage_bank_name', '农业银行', 2, 2),
  153 + ('brokerage_bank_name', '中国银行', 3, 3),
  154 + ('brokerage_bank_name', '交通银行', 4, 4),
  155 + ('brokerage_bank_name', '招商银行', 5, 5);
  156 +
  157 +
  158 +-- 交易中心配置:菜单 SQL
  159 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  160 +VALUES ('交易中心配置', '', 2, 0, 2072, 'config', 'ep:setting', 'trade/config/index', 0, 'TradeConfig');
  161 +-- 按钮父菜单ID
  162 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  163 +SELECT @parentId := LAST_INSERT_ID();
  164 +-- 按钮 SQL
  165 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  166 +VALUES ('交易中心配置查询', 'trade:config:query', 3, 1, @parentId, '', '', '', 0);
  167 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  168 +VALUES ('交易中心配置保存', 'trade:config:save', 3, 2, @parentId, '', '', '', 0);
  169 +
  170 +
  171 +-- 增加菜单:分销
  172 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  173 +VALUES ('分销', '', 1, 5, 2072, 'brokerage', 'fa-solid:project-diagram', '', 0, '');
  174 +-- 按钮父菜单ID
  175 +SELECT @brokerageMenuId := LAST_INSERT_ID();
  176 +
  177 +-- 增加菜单:分销员
  178 +-- 菜单 SQL
  179 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  180 +VALUES ('分销用户', '', 2, 0, @brokerageMenuId, 'brokerage-user', 'fa-solid:user-tie', 'trade/brokerage/user/index', 0,
  181 + 'TradeBrokerageUser');
  182 +-- 按钮父菜单ID
  183 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  184 +SELECT @parentId := LAST_INSERT_ID();
  185 +-- 按钮 SQL
  186 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  187 +VALUES ('分销用户查询', 'trade:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
  188 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  189 +VALUES ('分销用户推广人查询', 'trade:brokerage-user:user-query', 3, 2, @parentId, '', '', '', 0);
  190 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  191 +VALUES ('分销用户推广订单查询', 'trade:brokerage-user:order-query', 3, 3, @parentId, '', '', '', 0);
  192 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  193 +VALUES ('分销用户修改推广资格', 'trade:brokerage-user:update-brokerage-enable', 3, 4, @parentId, '', '', '', 0);
  194 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  195 +VALUES ('分销用户修改推广员', 'trade:brokerage-user:update-brokerage-user', 3, 5, @parentId, '', '', '', 0);
  196 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  197 +VALUES ('分销用户清除推广员', 'trade:brokerage-user:clear-brokerage-user', 3, 6, @parentId, '', '', '', 0);
  198 +
  199 +-- 增加菜单:佣金记录
  200 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  201 +VALUES ('佣金记录', '', 2, 1, @brokerageMenuId, 'brokerage-record', 'fa:money', 'trade/brokerage/record/index', 0,
  202 + 'TradeBrokerageRecord');
  203 +-- 按钮父菜单ID
  204 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  205 +SELECT @parentId := LAST_INSERT_ID();
  206 +-- 按钮 SQL
  207 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  208 +VALUES ('佣金记录查询', 'trade:brokerage-record:query', 3, 1, @parentId, '', '', '', 0);
  209 +
  210 +-- 增加菜单:佣金提现
  211 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  212 +VALUES ('佣金提现', '', 2, 2, @brokerageMenuId, 'brokerage-withdraw', 'fa:credit-card',
  213 + 'trade/brokerage/withdraw/index', 0, 'TradeBrokerageWithdraw');
  214 +-- 按钮父菜单ID
  215 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  216 +SELECT @parentId := LAST_INSERT_ID();
  217 +-- 按钮 SQL
  218 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  219 +VALUES ('佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
  220 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  221 +VALUES ('佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);
0 \ No newline at end of file 222 \ No newline at end of file
sql/mysql/pay_wallet.sql
1 -- ---------------------------- 1 -- ----------------------------
2 --- 支付-钱包表 2 +-- 会员钱包表
3 -- ---------------------------- 3 -- ----------------------------
4 DROP TABLE IF EXISTS `pay_wallet`; 4 DROP TABLE IF EXISTS `pay_wallet`;
5 CREATE TABLE `pay_wallet` 5 CREATE TABLE `pay_wallet`
@@ -8,8 +8,8 @@ CREATE TABLE `pay_wallet` @@ -8,8 +8,8 @@ CREATE TABLE `pay_wallet`
8 `user_id` bigint NOT NULL COMMENT '用户编号', 8 `user_id` bigint NOT NULL COMMENT '用户编号',
9 `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型', 9 `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型',
10 `balance` int NOT NULL DEFAULT 0 COMMENT '余额,单位分', 10 `balance` int NOT NULL DEFAULT 0 COMMENT '余额,单位分',
11 - `total_expense` bigint NOT NULL DEFAULT 0 COMMENT '累计支出,单位分',  
12 - `total_recharge` bigint NOT NULL DEFAULT 0 COMMENT '累计充值,单位分', 11 + `total_expense` int NOT NULL DEFAULT 0 COMMENT '累计支出,单位分',
  12 + `total_recharge` int NOT NULL DEFAULT 0 COMMENT '累计充值,单位分',
13 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', 13 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
14 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 14 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
15 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', 15 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -17,28 +17,27 @@ CREATE TABLE `pay_wallet` @@ -17,28 +17,27 @@ CREATE TABLE `pay_wallet`
17 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 17 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
18 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 18 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
19 PRIMARY KEY (`id`) USING BTREE 19 PRIMARY KEY (`id`) USING BTREE
20 -) ENGINE=InnoDB COMMENT='支付钱包表'; 20 +) ENGINE=InnoDB COMMENT='会员钱包表';
21 21
22 -- ---------------------------- 22 -- ----------------------------
23 --- 支付- 钱包余额明细表 23 +-- 会员钱包流水表
24 -- ---------------------------- 24 -- ----------------------------
25 DROP TABLE IF EXISTS `pay_wallet_transaction`; 25 DROP TABLE IF EXISTS `pay_wallet_transaction`;
26 CREATE TABLE `pay_wallet_transaction` 26 CREATE TABLE `pay_wallet_transaction`
27 ( 27 (
28 - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',  
29 - `wallet_id` bigint NOT NULL COMMENT '会员钱包 id',  
30 - `biz_type` tinyint NOT NULL COMMENT '关联类型',  
31 - `biz_id` bigint NOT NULL COMMENT '关联业务编号',  
32 - `no` varchar(64) NOT NULL COMMENT '流水号',  
33 - `description` varchar(255) COMMENT '操作说明',  
34 - `amount` int NOT NULL COMMENT '交易金额, 单位分',  
35 - `balance` int NOT NULL COMMENT '余额, 单位分',  
36 - `transaction_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '交易时间',  
37 - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',  
38 - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  
39 - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',  
40 - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',  
41 - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',  
42 - `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 28 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  29 + `wallet_id` bigint NOT NULL COMMENT '会员钱包 id',
  30 + `biz_type` tinyint NOT NULL COMMENT '关联类型',
  31 + `biz_id` varchar(64) NOT NULL COMMENT '关联业务编号',
  32 + `no` varchar(64) NOT NULL COMMENT '流水号',
  33 + `title` varchar(128) NOT NULL COMMENT '流水标题',
  34 + `price` int NOT NULL COMMENT '交易金额, 单位分',
  35 + `balance` int NOT NULL COMMENT '余额, 单位分',
  36 + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  37 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  38 + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
  39 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  40 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  41 + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
43 PRIMARY KEY (`id`) USING BTREE 42 PRIMARY KEY (`id`) USING BTREE
44 -) ENGINE=InnoDB COMMENT='支付钱包余额明细表'; 43 +) ENGINE=InnoDB COMMENT='会员钱包流水表';
yudao-dependencies/pom.xml
@@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
27 <mybatis-plus.version>3.5.3.2</mybatis-plus.version> 27 <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
28 <mybatis-plus-generator.version>3.5.3.2</mybatis-plus-generator.version> 28 <mybatis-plus-generator.version>3.5.3.2</mybatis-plus-generator.version>
29 <dynamic-datasource.version>3.6.1</dynamic-datasource.version> 29 <dynamic-datasource.version>3.6.1</dynamic-datasource.version>
30 - <mybatis-plus-join-boot-starter.version>1.4.5</mybatis-plus-join-boot-starter.version> 30 + <mybatis-plus-join.version>1.4.6</mybatis-plus-join.version>
31 <redisson.version>3.18.0</redisson.version> 31 <redisson.version>3.18.0</redisson.version>
32 <dm8.jdbc.version>8.1.2.141</dm8.jdbc.version> 32 <dm8.jdbc.version>8.1.2.141</dm8.jdbc.version>
33 <!-- 服务保障相关 --> 33 <!-- 服务保障相关 -->
@@ -220,7 +220,7 @@ @@ -220,7 +220,7 @@
220 <dependency> 220 <dependency>
221 <groupId>com.github.yulichang</groupId> 221 <groupId>com.github.yulichang</groupId>
222 <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 --> 222 <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
223 - <version>${mybatis-plus-join-boot-starter.version}</version> 223 + <version>${mybatis-plus-join.version}</version>
224 </dependency> 224 </dependency>
225 225
226 <dependency> 226 <dependency>
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java
@@ -41,7 +41,7 @@ public class CommonResult&lt;T&gt; implements Serializable { @@ -41,7 +41,7 @@ public class CommonResult&lt;T&gt; implements Serializable {
41 * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。 41 * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
42 * 42 *
43 * @param result 传入的 result 对象 43 * @param result 传入的 result 对象
44 - * @param <T> 返回的泛型 44 + * @param <T> 返回的泛型
45 * @return 新的 CommonResult 对象 45 * @return 新的 CommonResult 对象
46 */ 46 */
47 public static <T> CommonResult<T> error(CommonResult<?> result) { 47 public static <T> CommonResult<T> error(CommonResult<?> result) {
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/SortingField.java
1 package cn.iocoder.yudao.framework.common.pojo; 1 package cn.iocoder.yudao.framework.common.pojo;
2 2
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
3 import java.io.Serializable; 7 import java.io.Serializable;
4 8
5 /** 9 /**
@@ -7,6 +11,9 @@ import java.io.Serializable; @@ -7,6 +11,9 @@ import java.io.Serializable;
7 * 11 *
8 * 类名加了 ing 的原因是,避免和 ES SortField 重名。 12 * 类名加了 ing 的原因是,避免和 ES SortField 重名。
9 */ 13 */
  14 +@Data
  15 +@NoArgsConstructor
  16 +@AllArgsConstructor
10 public class SortingField implements Serializable { 17 public class SortingField implements Serializable {
11 18
12 /** 19 /**
@@ -27,30 +34,4 @@ public class SortingField implements Serializable { @@ -27,30 +34,4 @@ public class SortingField implements Serializable {
27 */ 34 */
28 private String order; 35 private String order;
29 36
30 - // 空构造方法,解决反序列化  
31 - public SortingField() {  
32 - }  
33 -  
34 - public SortingField(String field, String order) {  
35 - this.field = field;  
36 - this.order = order;  
37 - }  
38 -  
39 - public String getField() {  
40 - return field;  
41 - }  
42 -  
43 - public SortingField setField(String field) {  
44 - this.field = field;  
45 - return this;  
46 - }  
47 -  
48 - public String getOrder() {  
49 - return order;  
50 - }  
51 -  
52 - public SortingField setOrder(String order) {  
53 - this.order = order;  
54 - return this;  
55 - }  
56 } 37 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/util/MoneyUtils.java renamed to yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java
1 -package cn.iocoder.yudao.module.pay.util; 1 +package cn.iocoder.yudao.framework.common.util.number;
2 2
3 import cn.hutool.core.util.NumberUtil; 3 import cn.hutool.core.util.NumberUtil;
4 4
@@ -23,6 +23,17 @@ public class MoneyUtils { @@ -23,6 +23,17 @@ public class MoneyUtils {
23 return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue(); 23 return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue();
24 } 24 }
25 25
  26 + /**
  27 + * 计算百分比金额,向下传入
  28 + *
  29 + * @param price 金额
  30 + * @param rate 百分比,例如说 56.77% 则传入 56.77
  31 + * @return 百分比金额
  32 + */
  33 + public static Integer calculateRatePriceFloor(Integer price, Double rate) {
  34 + return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue();
  35 + }
  36 +
26 /** 37 /**
27 * 计算百分比金额 38 * 计算百分比金额
28 * 39 *
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java deleted
1 -package cn.iocoder.yudao.framework.pay.core.client.impl.delegate;  
2 -  
3 -import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;  
4 -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;  
5 -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;  
6 -import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;  
7 -import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;  
8 -import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;  
9 -  
10 -import java.util.Map;  
11 -  
12 -// TODO @jason:其它模块,主要是无法 pay client 初始化存在问题,所以我感觉,是不是可以搞个 PayClientInitializer 接口。这样,PayClientFactory 去 get 这个支付模式对应的 PayClientInitializer,通过它来创建。具体注入的地方,可以在 PayChannel init 方法那;  
13 -/**  
14 - * 代理支付 Client 的抽象类。  
15 - *  
16 - * 用于支付 Client 由其它模块实现,例如钱包支付  
17 - *  
18 - * @author jason  
19 - */  
20 -public abstract class DelegatePayClient<Config extends PayClientConfig> extends AbstractPayClient<PayClientConfig> {  
21 -  
22 - private final DelegatePayClient<Config> delegate;  
23 -  
24 - public DelegatePayClient(Long channelId, String channelCode, PayClientConfig config) {  
25 - super(channelId, channelCode, config);  
26 - delegate = this;  
27 - }  
28 -  
29 - @Override  
30 - protected void doInit() {  
31 - delegate.doInit();  
32 - }  
33 -  
34 - @Override  
35 - protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {  
36 - return delegate.doUnifiedOrder(reqDTO);  
37 - }  
38 -  
39 - @Override  
40 - protected PayOrderRespDTO doGetOrder(String outTradeNo) {  
41 - return delegate.doGetOrder(outTradeNo);  
42 - }  
43 -  
44 - @Override  
45 - protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {  
46 - return delegate.doUnifiedRefund(reqDTO);  
47 - }  
48 -  
49 - @Override  
50 - protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {  
51 - return delegate.doGetRefund(outTradeNo, outRefundNo);  
52 - }  
53 -  
54 - @Override  
55 - protected PayRefundRespDTO doParseRefundNotify(Map<String,String> params, String body) {  
56 - return delegate.doParseRefundNotify(params, body);  
57 - }  
58 -  
59 - @Override  
60 - protected PayOrderRespDTO doParseOrderNotify(Map<String,String> params, String body) {  
61 - return delegate.doParseOrderNotify(params, body);  
62 - }  
63 -}  
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDT @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDT
5 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; 5 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
6 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; 6 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
  8 +import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
8 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 9 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
9 10
10 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
@@ -17,11 +18,11 @@ import java.util.Map; @@ -17,11 +18,11 @@ import java.util.Map;
17 * 18 *
18 * @author jason 19 * @author jason
19 */ 20 */
20 -public class MockPayClient extends AbstractPayClient<MockPayClientConfig> { 21 +public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
21 22
22 private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS"; 23 private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
23 24
24 - public MockPayClient(Long channelId, MockPayClientConfig config) { 25 + public MockPayClient(Long channelId, NonePayClientConfig config) {
25 super(channelId, PayChannelEnum.MOCK.getCode(), config); 26 super(channelId, PayChannelEnum.MOCK.getCode(), config);
26 } 27 }
27 28
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClientConfig.java deleted
1 -package cn.iocoder.yudao.framework.pay.core.client.impl.mock;  
2 -  
3 -import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;  
4 -import lombok.Data;  
5 -  
6 -import javax.validation.Validator;  
7 -  
8 -/**  
9 - * 模拟支付的 PayClientConfig 实现类  
10 - *  
11 - * @author jason  
12 - */  
13 -@Data  
14 -public class MockPayClientConfig implements PayClientConfig {  
15 -  
16 - /**  
17 - * 配置名称  
18 - *  
19 - * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称  
20 - */  
21 - private String name;  
22 -  
23 - @Override  
24 - public void validate(Validator validator) {  
25 - // 模拟支付配置无需校验  
26 - }  
27 -  
28 -}  
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java
@@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil; @@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil;
4 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; 4 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
5 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; 5 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
6 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; 6 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
7 -import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;  
8 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; 7 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
9 import lombok.AllArgsConstructor; 8 import lombok.AllArgsConstructor;
10 import lombok.Getter; 9 import lombok.Getter;
@@ -30,7 +29,7 @@ public enum PayChannelEnum { @@ -30,7 +29,7 @@ public enum PayChannelEnum {
30 ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class), 29 ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
31 ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class), 30 ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
32 31
33 - MOCK("mock", "模拟支付", MockPayClientConfig.class), 32 + MOCK("mock", "模拟支付", NonePayClientConfig.class),
34 33
35 WALLET("wallet", "钱包支付", NonePayClientConfig.class); 34 WALLET("wallet", "钱包支付", NonePayClientConfig.class);
36 35
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java
@@ -4,6 +4,7 @@ import cn.hutool.core.util.EnumUtil; @@ -4,6 +4,7 @@ import cn.hutool.core.util.EnumUtil;
4 import cn.hutool.core.util.ReflectUtil; 4 import cn.hutool.core.util.ReflectUtil;
5 import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; 5 import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
6 import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest; 6 import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest;
  7 +import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMpRequest;
7 import com.xingyuv.jushauth.cache.AuthStateCache; 8 import com.xingyuv.jushauth.cache.AuthStateCache;
8 import com.xingyuv.jushauth.config.AuthConfig; 9 import com.xingyuv.jushauth.config.AuthConfig;
9 import com.xingyuv.jushauth.config.AuthSource; 10 import com.xingyuv.jushauth.config.AuthSource;
@@ -13,6 +14,8 @@ import com.xingyuv.justauth.autoconfigure.JustAuthProperties; @@ -13,6 +14,8 @@ import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
13 14
14 import java.lang.reflect.Method; 15 import java.lang.reflect.Method;
15 16
  17 +import static com.xingyuv.jushauth.config.AuthDefaultSource.WECHAT_MP;
  18 +
16 /** 19 /**
17 * 第三方授权拓展 request 工厂类 20 * 第三方授权拓展 request 工厂类
18 * 为使得拓展配置 {@link AuthConfig} 和默认配置齐平,所以自定义本工厂类 21 * 为使得拓展配置 {@link AuthConfig} 和默认配置齐平,所以自定义本工厂类
@@ -55,6 +58,12 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory { @@ -55,6 +58,12 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory {
55 } 58 }
56 59
57 protected AuthRequest getExtendRequest(String source) { 60 protected AuthRequest getExtendRequest(String source) {
  61 + // TODO 芋艿:临时兼容 justauth 迁移的类型不对问题;
  62 + if (WECHAT_MP.name().equalsIgnoreCase(source)) {
  63 + AuthConfig config = properties.getType().get(WECHAT_MP.name());
  64 + return new AuthWeChatMpRequest(config, authStateCache);
  65 + }
  66 +
58 AuthExtendSource authExtendSource; 67 AuthExtendSource authExtendSource;
59 try { 68 try {
60 authExtendSource = EnumUtil.fromString(AuthExtendSource.class, source.toUpperCase()); 69 authExtendSource = EnumUtil.fromString(AuthExtendSource.class, source.toUpperCase());
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMpRequest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.social.core.request;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.xingyuv.jushauth.cache.AuthStateCache;
  5 +import com.xingyuv.jushauth.config.AuthConfig;
  6 +import com.xingyuv.jushauth.config.AuthDefaultSource;
  7 +import com.xingyuv.jushauth.enums.AuthResponseStatus;
  8 +import com.xingyuv.jushauth.enums.AuthUserGender;
  9 +import com.xingyuv.jushauth.enums.scope.AuthWechatMpScope;
  10 +import com.xingyuv.jushauth.exception.AuthException;
  11 +import com.xingyuv.jushauth.model.AuthCallback;
  12 +import com.xingyuv.jushauth.model.AuthResponse;
  13 +import com.xingyuv.jushauth.model.AuthToken;
  14 +import com.xingyuv.jushauth.model.AuthUser;
  15 +import com.xingyuv.jushauth.request.AuthDefaultRequest;
  16 +import com.xingyuv.jushauth.utils.AuthScopeUtils;
  17 +import com.xingyuv.jushauth.utils.GlobalAuthUtils;
  18 +import com.xingyuv.jushauth.utils.HttpUtils;
  19 +import com.xingyuv.jushauth.utils.UrlBuilder;
  20 +
  21 +/**
  22 + * 微信公众平台登录
  23 + *
  24 + * @author yangkai.shen (https://xkcoding.com)
  25 + * @since 1.1.0
  26 + */
  27 +public class AuthWeChatMpRequest extends AuthDefaultRequest {
  28 + public AuthWeChatMpRequest(AuthConfig config) {
  29 + super(config, AuthDefaultSource.WECHAT_MP);
  30 + }
  31 +
  32 + public AuthWeChatMpRequest(AuthConfig config, AuthStateCache authStateCache) {
  33 + super(config, AuthDefaultSource.WECHAT_MP, authStateCache);
  34 + }
  35 +
  36 + /**
  37 + * 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
  38 + *
  39 + * @param authCallback 回调返回的参数
  40 + * @return 所有信息
  41 + */
  42 + @Override
  43 + protected AuthToken getAccessToken(AuthCallback authCallback) {
  44 + return this.getToken(accessTokenUrl(authCallback.getCode()));
  45 + }
  46 +
  47 + @Override
  48 + protected AuthUser getUserInfo(AuthToken authToken) {
  49 + String openId = authToken.getOpenId();
  50 +
  51 + String response = doGetUserInfo(authToken);
  52 + JSONObject object = JSONObject.parseObject(response);
  53 +
  54 + this.checkResponse(object);
  55 +
  56 + String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city"));
  57 +
  58 + if (object.containsKey("unionid")) {
  59 + authToken.setUnionId(object.getString("unionid"));
  60 + }
  61 +
  62 + return AuthUser.builder()
  63 + .rawUserInfo(object)
  64 + .username(object.getString("nickname"))
  65 + .nickname(object.getString("nickname"))
  66 + .avatar(object.getString("headimgurl"))
  67 + .location(location)
  68 + .uuid(openId)
  69 + .gender(AuthUserGender.getWechatRealGender(object.getString("sex")))
  70 + .token(authToken)
  71 + .source(source.toString())
  72 + .build();
  73 + }
  74 +
  75 + @Override
  76 + public AuthResponse refresh(AuthToken oldToken) {
  77 + return AuthResponse.builder()
  78 + .code(AuthResponseStatus.SUCCESS.getCode())
  79 + .data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken())))
  80 + .build();
  81 + }
  82 +
  83 + /**
  84 + * 检查响应内容是否正确
  85 + *
  86 + * @param object 请求响应内容
  87 + */
  88 + private void checkResponse(JSONObject object) {
  89 + if (object.containsKey("errcode")) {
  90 + throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg"));
  91 + }
  92 + }
  93 +
  94 + /**
  95 + * 获取token,适用于获取access_token和刷新token
  96 + *
  97 + * @param accessTokenUrl 实际请求token的地址
  98 + * @return token对象
  99 + */
  100 + private AuthToken getToken(String accessTokenUrl) {
  101 + String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl).getBody();
  102 + JSONObject accessTokenObject = JSONObject.parseObject(response);
  103 +
  104 + this.checkResponse(accessTokenObject);
  105 +
  106 + return AuthToken.builder()
  107 + .accessToken(accessTokenObject.getString("access_token"))
  108 + .refreshToken(accessTokenObject.getString("refresh_token"))
  109 + .expireIn(accessTokenObject.getIntValue("expires_in"))
  110 + .openId(accessTokenObject.getString("openid"))
  111 + .scope(accessTokenObject.getString("scope"))
  112 + .build();
  113 + }
  114 +
  115 + /**
  116 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
  117 + *
  118 + * @param state state 验证授权流程的参数,可以防止csrf
  119 + * @return 返回授权地址
  120 + * @since 1.9.3
  121 + */
  122 + @Override
  123 + public String authorize(String state) {
  124 + return UrlBuilder.fromBaseUrl(source.authorize())
  125 + .queryParam("appid", config.getClientId())
  126 + .queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
  127 + .queryParam("response_type", "code")
  128 + .queryParam("scope", this.getScopes(",", false, AuthScopeUtils.getDefaultScopes(AuthWechatMpScope.values())))
  129 + .queryParam("state", getRealState(state).concat("#wechat_redirect"))
  130 + .build();
  131 + }
  132 +
  133 + /**
  134 + * 返回获取accessToken的url
  135 + *
  136 + * @param code 授权码
  137 + * @return 返回获取accessToken的url
  138 + */
  139 + @Override
  140 + protected String accessTokenUrl(String code) {
  141 + return UrlBuilder.fromBaseUrl(source.accessToken())
  142 + .queryParam("appid", config.getClientId())
  143 + .queryParam("secret", config.getClientSecret())
  144 + .queryParam("code", code)
  145 + .queryParam("grant_type", "authorization_code")
  146 + .build();
  147 + }
  148 +
  149 + /**
  150 + * 返回获取userInfo的url
  151 + *
  152 + * @param authToken 用户授权后的token
  153 + * @return 返回获取userInfo的url
  154 + */
  155 + @Override
  156 + protected String userInfoUrl(AuthToken authToken) {
  157 + return UrlBuilder.fromBaseUrl(source.userInfo())
  158 + .queryParam("access_token", authToken.getAccessToken())
  159 + .queryParam("openid", authToken.getOpenId())
  160 + .queryParam("lang", "zh_CN")
  161 + .build();
  162 + }
  163 +
  164 + /**
  165 + * 返回获取userInfo的url
  166 + *
  167 + * @param refreshToken getAccessToken方法返回的refreshToken
  168 + * @return 返回获取userInfo的url
  169 + */
  170 + @Override
  171 + protected String refreshTokenUrl(String refreshToken) {
  172 + return UrlBuilder.fromBaseUrl(source.refresh())
  173 + .queryParam("appid", config.getClientId())
  174 + .queryParam("grant_type", "refresh_token")
  175 + .queryParam("refresh_token", refreshToken)
  176 + .build();
  177 + }
  178 +}
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
@@ -18,6 +18,9 @@ import java.util.List; @@ -18,6 +18,9 @@ import java.util.List;
18 18
19 /** 19 /**
20 * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 20 * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
  21 + *
  22 + * 1. {@link BaseMapper} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力
  23 + * 2. {@link MPJBaseMapper} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力
21 */ 24 */
22 public interface BaseMapperX<T> extends MPJBaseMapper<T> { 25 public interface BaseMapperX<T> extends MPJBaseMapper<T> {
23 26
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
@@ -60,4 +60,14 @@ public class ProductSkuRespDTO { @@ -60,4 +60,14 @@ public class ProductSkuRespDTO {
60 */ 60 */
61 private Double volume; 61 private Double volume;
62 62
  63 + // TODO @puhui:这 2 字段,需要改下;firstBrokerageRecord、secondBrokerageRecord;和分佣保持一致;
  64 + /**
  65 + * 一级分销的佣金,单位:分
  66 + */
  67 + private Integer subCommissionFirstPrice;
  68 + /**
  69 + * 二级分销的佣金,单位:分
  70 + */
  71 + private Integer subCommissionSecondPrice;
  72 +
63 } 73 }
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.bargain;
  2 +
  3 +/**
  4 + * 砍价活动 Api 接口
  5 + *
  6 + * @author HUIHUI
  7 + */
  8 +public interface BargainActivityApi {
  9 +
  10 + /**
  11 + * 更新砍价活动库存
  12 + *
  13 + * @param activityId 砍价活动编号
  14 + * @param count 购买数量
  15 + */
  16 + void updateBargainActivityStock(Long activityId, Integer count);
  17 +
  18 +}
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/dto/BargainRecordCreateReqDTO.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.bargain.dto; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.bargain.dto;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
5 -import javax.validation.constraints.NotEmpty;  
6 import javax.validation.constraints.NotNull; 5 import javax.validation.constraints.NotNull;
7 6
8 // TODO @芋艿:这块要在看看 7 // TODO @芋艿:这块要在看看
@@ -40,17 +39,7 @@ public class BargainRecordCreateReqDTO { @@ -40,17 +39,7 @@ public class BargainRecordCreateReqDTO {
40 */ 39 */
41 @NotNull(message = "订单编号不能为空") 40 @NotNull(message = "订单编号不能为空")
42 private Long orderId; 41 private Long orderId;
43 - // TODO @puhui999:spuName、picUrl、 之类字段不用传递;  
44 - /**  
45 - * 商品名字  
46 - */  
47 - @NotEmpty(message = "商品名字不能为空")  
48 - private String spuName;  
49 - /**  
50 - * 商品图片  
51 - */  
52 - @NotEmpty(message = "商品图片不能为空")  
53 - private String picUrl; 42 +
54 /** 43 /**
55 * 砍价商品单价 44 * 砍价商品单价
56 */ 45 */
@@ -61,17 +50,7 @@ public class BargainRecordCreateReqDTO { @@ -61,17 +50,7 @@ public class BargainRecordCreateReqDTO {
61 */ 50 */
62 @NotNull(message = "商品原价不能为空") 51 @NotNull(message = "商品原价不能为空")
63 private Integer price; 52 private Integer price;
64 - // TODO @puhui999:nickname、avatar 不用传递,去查询;  
65 - /**  
66 - * 用户昵称  
67 - */  
68 - @NotEmpty(message = "用户昵称不能为空")  
69 - private String nickname;  
70 - /**  
71 - * 用户头像  
72 - */  
73 - @NotEmpty(message = "用户头像不能为空")  
74 - private String avatar; 53 +
75 /** 54 /**
76 * 开团状态:进行中 砍价成功 砍价失败 55 * 开团状态:进行中 砍价成功 砍价失败
77 */ 56 */
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
@@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.promotion.api.combination; @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.promotion.api.combination;
2 2
3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO; 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
5 -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;  
6 5
7 import javax.validation.Valid; 6 import javax.validation.Valid;
  7 +import java.time.LocalDateTime;
8 import java.util.List; 8 import java.util.List;
9 9
10 // TODO @芋艿:后面也再撸撸这几个接口 10 // TODO @芋艿:后面也再撸撸这几个接口
@@ -51,13 +51,29 @@ public interface CombinationRecordApi { @@ -51,13 +51,29 @@ public interface CombinationRecordApi {
51 */ 51 */
52 void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount); 52 void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount);
53 53
54 - // TODO @puhui999:是不是搞成具体的方法,拼团成功,拼团失败,这种方法; 54 + /**
  55 + * 更新拼团状态为成功
  56 + *
  57 + * @param userId 用户编号
  58 + * @param orderId 订单编号
  59 + */
  60 + void updateRecordStatusToSuccess(Long userId, Long orderId);
55 61
56 /** 62 /**
57 - * 更新开团记录状态 63 + * 更新拼团状态为失败
58 * 64 *
59 - * @param reqDTO 请求 DTO 65 + * @param userId 用户编号
  66 + * @param orderId 订单编号
  67 + */
  68 + void updateRecordStatusToFailed(Long userId, Long orderId);
  69 +
  70 + /**
  71 + * 更新拼团状态为 进行中
  72 + *
  73 + * @param userId 用户编号
  74 + * @param orderId 订单编号
  75 + * @param startTime 开始时间
60 */ 76 */
61 - void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO); 77 + void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime);
62 78
63 } 79 }
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordUpdateStatusReqDTO.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.combination.dto;  
2 -  
3 -import lombok.Data;  
4 -  
5 -import javax.validation.constraints.NotNull;  
6 -import java.time.LocalDateTime;  
7 -  
8 -/**  
9 - * 拼团记录的更新状态 Request DTO  
10 - *  
11 - * @author HUIHUI  
12 - */  
13 -@Data  
14 -public class CombinationRecordUpdateStatusReqDTO {  
15 -  
16 - /**  
17 - * 用户编号  
18 - */  
19 - @NotNull(message = "用户编号不能为空")  
20 - private Long userId;  
21 -  
22 - /**  
23 - * 订单编号  
24 - */  
25 - @NotNull(message = "订单编号不能为空")  
26 - private Long orderId;  
27 -  
28 - /**  
29 - * 开团状态:正在开团 拼团成功 拼团失败  
30 - */  
31 - @NotNull(message = "开团状态不能为空")  
32 - private Integer status;  
33 -  
34 - /**  
35 - * 团开始时间  
36 - */  
37 - private LocalDateTime startTime;  
38 -  
39 -}  
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.seckill;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
  4 +
  5 +/**
  6 + * 秒杀活动 API 接口
  7 + *
  8 + * @author HUIHUI
  9 + */
  10 +public interface SeckillActivityApi {
  11 +
  12 + /**
  13 + * 更新秒杀库存
  14 + *
  15 + * @param updateStockReqDTO 请求
  16 + */
  17 + void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO);
  18 +
  19 +}
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityUpdateStockReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.seckill.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * 更新秒杀库存 request DTO
  9 + *
  10 + * @author HUIHUI
  11 + */
  12 +@Data
  13 +public class SeckillActivityUpdateStockReqDTO {
  14 +
  15 + // TODO @puhui999:参数校验
  16 +
  17 + // TODO @puhui999:秒杀的话,一次只能购买一种商品哈;不能多个哈;
  18 +
  19 + /**
  20 + * 活动编号
  21 + */
  22 + private Long activityId;
  23 + /**
  24 + * 总购买数量
  25 + */
  26 + private Integer count;
  27 + /**
  28 + * 活动商品
  29 + */
  30 + private List<Item> items;
  31 +
  32 + @Data
  33 + public static class Item {
  34 +
  35 + /**
  36 + * SPU 编号
  37 + */
  38 + private Long spuId;
  39 + /**
  40 + * SKU 编号
  41 + */
  42 + private Long skuId;
  43 + /**
  44 + * 购买数量
  45 + */
  46 + private Integer count;
  47 +
  48 + }
  49 +
  50 +}
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
@@ -55,6 +55,7 @@ public interface ErrorCodeConstants { @@ -55,6 +55,7 @@ public interface ErrorCodeConstants {
55 ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_003, "秒杀活动已关闭,不能修改"); 55 ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_003, "秒杀活动已关闭,不能修改");
56 ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除"); 56 ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除");
57 ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭"); 57 ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭");
  58 + ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013008006, "更新秒杀活动库存失败,原因秒杀库存不足");
58 59
59 // ========== 秒杀时段 1-013-009-000 ========== 60 // ========== 秒杀时段 1-013-009-000 ==========
60 ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在"); 61 ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.bargain;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
  4 +import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
  5 +import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
  6 +import org.springframework.stereotype.Service;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  11 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.BARGAIN_ACTIVITY_NOT_EXISTS;
  12 +
  13 +/**
  14 + * 砍价活动 Api 接口实现类
  15 + *
  16 + * @author HUIHUI
  17 + */
  18 +@Service
  19 +public class BargainActivityApiImpl implements BargainActivityApi {
  20 +
  21 + @Resource
  22 + private BargainActivityService bargainActivityService;
  23 +
  24 + @Override
  25 + public void updateBargainActivityStock(Long activityId, Integer count) {
  26 + // TODO @puhui999:可以整个实现到 bargainActivityService 中
  27 + // 查询砍价活动
  28 + BargainActivityDO activity = bargainActivityService.getBargainActivity(activityId);
  29 + if (activity == null) {
  30 + throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
  31 + }
  32 +
  33 + // 更新砍价库存
  34 + // TODO @puhui999:考虑下并发更新问题
  35 + BargainActivityUpdateReqVO reqVO = new BargainActivityUpdateReqVO();
  36 + reqVO.setId(activityId);
  37 + reqVO.setStock(activity.getStock() - count);
  38 + bargainActivityService.updateBargainActivity(reqVO);
  39 + }
  40 +
  41 +}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
@@ -2,13 +2,13 @@ package cn.iocoder.yudao.module.promotion.api.combination; @@ -2,13 +2,13 @@ package cn.iocoder.yudao.module.promotion.api.combination;
2 2
3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO; 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
5 -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;  
6 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert; 5 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
7 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; 6 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
8 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService; 7 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
9 import org.springframework.stereotype.Service; 8 import org.springframework.stereotype.Service;
10 9
11 import javax.annotation.Resource; 10 import javax.annotation.Resource;
  11 +import java.time.LocalDateTime;
12 import java.util.List; 12 import java.util.List;
13 13
14 /** 14 /**
@@ -43,12 +43,19 @@ public class CombinationRecordApiImpl implements CombinationRecordApi { @@ -43,12 +43,19 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
43 } 43 }
44 44
45 @Override 45 @Override
46 - public void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO) {  
47 - if (null == reqDTO.getStartTime()) {  
48 - recordService.updateCombinationRecordStatusByUserIdAndOrderId(reqDTO);  
49 - } else {  
50 - recordService.updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(reqDTO);  
51 - } 46 + public void updateRecordStatusToSuccess(Long userId, Long orderId) {
  47 + recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.SUCCESS.getStatus(), userId, orderId);
  48 + }
  49 +
  50 + @Override
  51 + public void updateRecordStatusToFailed(Long userId, Long orderId) {
  52 + recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.FAILED.getStatus(), userId, orderId);
  53 + }
  54 +
  55 + @Override
  56 + public void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime) {
  57 + recordService.updateRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordStatusEnum.IN_PROGRESS.getStatus(),
  58 + userId, orderId, startTime);
52 } 59 }
53 60
54 } 61 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.seckill;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  4 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
  5 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
  6 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
  7 +import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +import javax.annotation.Resource;
  11 +import java.util.ArrayList;
  12 +import java.util.HashMap;
  13 +import java.util.List;
  14 +import java.util.Map;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  17 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_ACTIVITY_UPDATE_STOCK_FAIL;
  18 +
  19 +/**
  20 + * 秒杀活动接口 Api 接口实现类
  21 + *
  22 + * @author HUIHUI
  23 + */
  24 +@Service
  25 +public class SeckillActivityApiImpl implements SeckillActivityApi {
  26 +
  27 + @Resource
  28 + private SeckillActivityService activityService;
  29 +
  30 + // TODO @puhui:建议这块弄到 activityService 实现哈;
  31 + // TODO @puhui:这个方法,要考虑事务性
  32 + @Override
  33 + public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
  34 + // TODO @puhui999:长方法,最好有 1.1 1.2 2.1 这种步骤哈;
  35 + SeckillActivityDO seckillActivity = activityService.getSeckillActivity(updateStockReqDTO.getActivityId());
  36 + if (seckillActivity.getStock() < updateStockReqDTO.getCount()) {
  37 + throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
  38 + }
  39 + // 获取活动商品
  40 + // TODO @puhui999:在一个方法里,dos 和 dolist 最好保持一致,要么用 s,要么用 list 哈;
  41 + List<SeckillProductDO> productDOs = activityService.getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
  42 + // TODO @puhui999:这个是不是搞成 CollectionUtils.convertMultiMap()
  43 + List<SeckillActivityUpdateStockReqDTO.Item> items = updateStockReqDTO.getItems();
  44 + Map<Long, List<Long>> map = new HashMap<>();
  45 + items.forEach(item -> {
  46 + if (map.containsKey(item.getSpuId())) {
  47 + List<Long> skuIds = map.get(item.getSpuId());
  48 + skuIds.add(item.getSkuId());
  49 + map.put(item.getSpuId(), skuIds);
  50 + } else {
  51 + List<Long> list = new ArrayList<>();
  52 + list.add(item.getSkuId());
  53 + map.put(item.getSpuId(), list);
  54 + }
  55 + });
  56 + // 过滤出购买的商品
  57 + // TODO @puhui999:productDOList 可以简化成 productList;一般来说,do 之类不用带着哈,在变量里;
  58 + List<SeckillProductDO> productDOList = CollectionUtils.filterList(productDOs, item -> map.get(item.getSpuId()).contains(item.getSkuId()));
  59 + Map<Long, SeckillActivityUpdateStockReqDTO.Item> productDOMap = CollectionUtils.convertMap(items, SeckillActivityUpdateStockReqDTO.Item::getSkuId, p -> p);
  60 + // 检查活动商品库存是否充足
  61 + // TODO @puhui999:避免 b 这种无业务含义的变量;
  62 + boolean b = CollectionUtils.anyMatch(productDOList, item -> {
  63 + SeckillActivityUpdateStockReqDTO.Item item1 = productDOMap.get(item.getSkuId());
  64 + return (item.getStock() < item1.getCount()) || (item.getStock() - item1.getCount()) < 0;
  65 + });
  66 + if (b) {
  67 + throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
  68 + }
  69 + // TODO @puhui999:类似 doList,应该和下面的 update 逻辑粘的更紧密一点;so 在空行的时候,应该挪到 74 之后里去;甚至更合理,应该是 79 之后;说白了,逻辑要分块,每个模块涉及的代码要紧密在一起;
  70 + List<SeckillProductDO> doList = CollectionUtils.convertList(productDOList, item -> {
  71 + item.setStock(item.getStock() - productDOMap.get(item.getSkuId()).getCount());
  72 + return item;
  73 + });
  74 +
  75 + // 更新活动库存
  76 + // TODO @puhui999:考虑下并发更新
  77 + seckillActivity.setStock(seckillActivity.getStock() + updateStockReqDTO.getCount());
  78 + seckillActivity.setTotalStock(seckillActivity.getTotalStock() - updateStockReqDTO.getCount());
  79 + activityService.updateSeckillActivity(seckillActivity);
  80 + // 更新活动商品库存
  81 + activityService.updateSeckillActivityProductList(doList);
  82 + }
  83 +
  84 +}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java
1 package cn.iocoder.yudao.module.promotion.controller.app.combination; 1 package cn.iocoder.yudao.module.promotion.controller.app.combination;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 -import cn.iocoder.yudao.framework.common.util.date.DateUtils; 4 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
5 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordDetailRespVO; 5 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordDetailRespVO;
6 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO; 6 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO;
7 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordSummaryRespVO; 7 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordSummaryRespVO;
@@ -16,8 +16,8 @@ import org.springframework.web.bind.annotation.RestController; @@ -16,8 +16,8 @@ import org.springframework.web.bind.annotation.RestController;
16 16
17 import javax.validation.constraints.Max; 17 import javax.validation.constraints.Max;
18 import java.time.Duration; 18 import java.time.Duration;
  19 +import java.time.LocalDateTime;
19 import java.util.ArrayList; 20 import java.util.ArrayList;
20 -import java.util.Date;  
21 import java.util.List; 21 import java.util.List;
22 22
23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -52,14 +52,13 @@ public class AppCombinationRecordController { @@ -52,14 +52,13 @@ public class AppCombinationRecordController {
52 @RequestParam(value = "activityId", required = false) Long activityId, 52 @RequestParam(value = "activityId", required = false) Long activityId,
53 @RequestParam("status") Integer status, 53 @RequestParam("status") Integer status,
54 @RequestParam(value = "count", defaultValue = "20") @Max(20) Integer count) { 54 @RequestParam(value = "count", defaultValue = "20") @Max(20) Integer count) {
55 - ZoneId zoneId = ZoneId.systemDefault();  
56 List<AppCombinationRecordRespVO> list = new ArrayList<>(); 55 List<AppCombinationRecordRespVO> list = new ArrayList<>();
57 for (int i = 1; i <= count; i++) { 56 for (int i = 1; i <= count; i++) {
58 AppCombinationRecordRespVO record = new AppCombinationRecordRespVO(); 57 AppCombinationRecordRespVO record = new AppCombinationRecordRespVO();
59 record.setId((long) i); 58 record.setId((long) i);
60 record.setNickname("用户" + i); 59 record.setNickname("用户" + i);
61 record.setAvatar("头像" + i); 60 record.setAvatar("头像" + i);
62 - record.setExpireTime(LocalDateTime.ofInstant(new Date().toInstant(), zoneId)); 61 + record.setExpireTime(LocalDateTime.now());
63 record.setUserSize(10); 62 record.setUserSize(10);
64 record.setUserCount(i); 63 record.setUserCount(i);
65 record.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg"); 64 record.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
@@ -74,14 +73,13 @@ public class AppCombinationRecordController { @@ -74,14 +73,13 @@ public class AppCombinationRecordController {
74 @Operation(summary = "获得拼团记录明细") 73 @Operation(summary = "获得拼团记录明细")
75 @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024") 74 @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024")
76 public CommonResult<AppCombinationRecordDetailRespVO> getCombinationRecordDetail(@RequestParam("id") Long id) { 75 public CommonResult<AppCombinationRecordDetailRespVO> getCombinationRecordDetail(@RequestParam("id") Long id) {
77 - ZoneId zoneId = ZoneId.systemDefault();  
78 AppCombinationRecordDetailRespVO detail = new AppCombinationRecordDetailRespVO(); 76 AppCombinationRecordDetailRespVO detail = new AppCombinationRecordDetailRespVO();
79 // 团长 77 // 团长
80 AppCombinationRecordRespVO headRecord = new AppCombinationRecordRespVO(); 78 AppCombinationRecordRespVO headRecord = new AppCombinationRecordRespVO();
81 headRecord.setId(1L); 79 headRecord.setId(1L);
82 headRecord.setNickname("用户" + 1); 80 headRecord.setNickname("用户" + 1);
83 headRecord.setAvatar("头像" + 1); 81 headRecord.setAvatar("头像" + 1);
84 - headRecord.setExpireTime((LocalDateTime.ofInstant(DateUtils.addTime(Duration.ofDays(1)).toInstant(), zoneId))); 82 + headRecord.setExpireTime(LocalDateTimeUtils.addTime(Duration.ofDays(1)));
85 headRecord.setUserSize(10); 83 headRecord.setUserSize(10);
86 headRecord.setUserCount(3); 84 headRecord.setUserCount(3);
87 headRecord.setStatus(1); 85 headRecord.setStatus(1);
@@ -96,7 +94,7 @@ public class AppCombinationRecordController { @@ -96,7 +94,7 @@ public class AppCombinationRecordController {
96 record.setId((long) i); 94 record.setId((long) i);
97 record.setNickname("用户" + i); 95 record.setNickname("用户" + i);
98 record.setAvatar("头像" + i); 96 record.setAvatar("头像" + i);
99 - record.setExpireTime(LocalDateTime.ofInstant(new Date().toInstant(), zoneId)); 97 + record.setExpireTime(LocalDateTime.now());
100 record.setUserSize(10); 98 record.setUserSize(10);
101 record.setUserCount(i); 99 record.setUserCount(i);
102 record.setStatus(1); 100 record.setStatus(1);
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationRecordDO.java
@@ -9,6 +9,7 @@ import lombok.*; @@ -9,6 +9,7 @@ import lombok.*;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 11
  12 +// TODO 芋艿:把字段的顺序,和 do 顺序对齐下
12 /** 13 /**
13 * 拼团记录 DO 14 * 拼团记录 DO
14 * 15 *
@@ -27,34 +28,28 @@ import java.time.LocalDateTime; @@ -27,34 +28,28 @@ import java.time.LocalDateTime;
27 @AllArgsConstructor 28 @AllArgsConstructor
28 public class CombinationRecordDO extends BaseDO { 29 public class CombinationRecordDO extends BaseDO {
29 30
  31 + /**
  32 + * 编号,主键自增
  33 + */
30 @TableId 34 @TableId
31 private Long id; 35 private Long id;
  36 +
32 /** 37 /**
33 * 拼团活动编号 38 * 拼团活动编号
  39 + *
  40 + * 关联 {@link CombinationActivityDO#getId()}
34 */ 41 */
35 private Long activityId; 42 private Long activityId;
36 /** 43 /**
37 - * SPU 编号  
38 - */  
39 - private Long spuId;  
40 - /**  
41 - * SKU 编号  
42 - */  
43 - private Long skuId;  
44 - /**  
45 - * 用户编号  
46 - */  
47 - private Long userId;  
48 - /**  
49 - * 订单编号 44 + * 拼团商品单价
  45 + *
  46 + * 冗余 {@link CombinationProductDO#getCombinationPrice()}
50 */ 47 */
51 - private Long orderId; 48 + private Integer combinationPrice;
52 /** 49 /**
53 - * 团长编号  
54 - *  
55 - * 关联 {@link CombinationRecordDO#getId()} 50 + * SPU 编号
56 */ 51 */
57 - private Long headId; 52 + private Long spuId;
58 /** 53 /**
59 * 商品名字 54 * 商品名字
60 */ 55 */
@@ -64,9 +59,14 @@ public class CombinationRecordDO extends BaseDO { @@ -64,9 +59,14 @@ public class CombinationRecordDO extends BaseDO {
64 */ 59 */
65 private String picUrl; 60 private String picUrl;
66 /** 61 /**
67 - * 拼团商品单价 62 + * SKU 编号
68 */ 63 */
69 - private Integer combinationPrice; 64 + private Long skuId;
  65 +
  66 + /**
  67 + * 用户编号
  68 + */
  69 + private Long userId;
70 /** 70 /**
71 * 用户昵称 71 * 用户昵称
72 */ 72 */
@@ -75,6 +75,13 @@ public class CombinationRecordDO extends BaseDO { @@ -75,6 +75,13 @@ public class CombinationRecordDO extends BaseDO {
75 * 用户头像 75 * 用户头像
76 */ 76 */
77 private String avatar; 77 private String avatar;
  78 +
  79 + /**
  80 + * 团长编号
  81 + *
  82 + * 关联 {@link CombinationRecordDO#getId()}
  83 + */
  84 + private Long headId;
78 /** 85 /**
79 * 开团状态 86 * 开团状态
80 * 87 *
@@ -82,15 +89,30 @@ public class CombinationRecordDO extends BaseDO { @@ -82,15 +89,30 @@ public class CombinationRecordDO extends BaseDO {
82 */ 89 */
83 private Integer status; 90 private Integer status;
84 /** 91 /**
  92 + * 订单编号
  93 + */
  94 + private Long orderId;
  95 + /**
  96 + * 开团需要人数
  97 + *
  98 + * 关联 {@link CombinationActivityDO#getUserSize()}
  99 + */
  100 + private Integer userSize;
  101 + /**
  102 + * 已加入拼团人数
  103 + */
  104 + private Integer userCount;
  105 + /**
85 * 是否虚拟成团 106 * 是否虚拟成团
86 */ 107 */
87 private Boolean virtualGroup; 108 private Boolean virtualGroup;
  109 +
88 /** 110 /**
89 - * 过期时间,单位:小时 111 + * 过期时间
90 * 112 *
91 - * 关联 {@link CombinationActivityDO#getLimitDuration()} 113 + * 基于 {@link CombinationRecordDO#getStartTime()} + {@link CombinationActivityDO#getLimitDuration()} 计算
92 */ 114 */
93 - private Integer expireTime; 115 + private LocalDateTime expireTime;
94 /** 116 /**
95 * 开始时间 (订单付款后开始的时间) 117 * 开始时间 (订单付款后开始的时间)
96 */ 118 */
@@ -99,15 +121,5 @@ public class CombinationRecordDO extends BaseDO { @@ -99,15 +121,5 @@ public class CombinationRecordDO extends BaseDO {
99 * 结束时间(成团时间/失败时间) 121 * 结束时间(成团时间/失败时间)
100 */ 122 */
101 private LocalDateTime endTime; 123 private LocalDateTime endTime;
102 - /**  
103 - * 开团需要人数  
104 - *  
105 - * 关联 {@link CombinationActivityDO#getUserSize()}  
106 - */  
107 - private Integer userSize;  
108 - /**  
109 - * 已加入拼团人数  
110 - */  
111 - private Integer userCount;  
112 124
113 } 125 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java
1 package cn.iocoder.yudao.module.promotion.service.combination; 1 package cn.iocoder.yudao.module.promotion.service.combination;
2 2
3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;  
5 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; 4 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
6 5
  6 +import java.time.LocalDateTime;
7 import java.util.List; 7 import java.util.List;
8 8
9 /** 9 /**
@@ -16,9 +16,11 @@ public interface CombinationRecordService { @@ -16,9 +16,11 @@ public interface CombinationRecordService {
16 /** 16 /**
17 * 更新拼团状态 17 * 更新拼团状态
18 * 18 *
19 - * @param reqDTO 请求 DTO 19 + * @param status 状态
  20 + * @param userId 用户编号
  21 + * @param orderId 订单编号
20 */ 22 */
21 - void updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO); 23 + void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId);
22 24
23 /** 25 /**
24 * 创建拼团记录 26 * 创建拼团记录
@@ -30,9 +32,12 @@ public interface CombinationRecordService { @@ -30,9 +32,12 @@ public interface CombinationRecordService {
30 /** 32 /**
31 * 更新拼团状态和开始时间 33 * 更新拼团状态和开始时间
32 * 34 *
33 - * @param reqDTO 请求 DTO 35 + * @param status 状态
  36 + * @param userId 用户编号
  37 + * @param orderId 订单编号
  38 + * @param startTime 开始时间
34 */ 39 */
35 - void updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO); 40 + void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime);
36 41
37 /** 42 /**
38 * 获得拼团状态 43 * 获得拼团状态
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.service.combination; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.service.combination;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
6 -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;  
7 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert; 6 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
9 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
@@ -21,6 +20,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @@ -21,6 +20,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
21 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; 20 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
22 21
23 // TODO 芋艿:等拼团记录做完,完整 review 下 22 // TODO 芋艿:等拼团记录做完,完整 review 下
  23 +
24 /** 24 /**
25 * 拼团记录 Service 实现类 25 * 拼团记录 Service 实现类
26 * 26 *
@@ -38,27 +38,27 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -38,27 +38,27 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
38 38
39 @Override 39 @Override
40 @Transactional(rollbackFor = Exception.class) 40 @Transactional(rollbackFor = Exception.class)
41 - public void updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO) { 41 + public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
42 // 校验拼团是否存在 42 // 校验拼团是否存在
43 - CombinationRecordDO recordDO = validateCombinationRecord(reqDTO.getUserId(), reqDTO.getOrderId()); 43 + CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
44 44
45 // 更新状态 45 // 更新状态
46 - recordDO.setStatus(reqDTO.getStatus()); 46 + recordDO.setStatus(status);
47 recordMapper.updateById(recordDO); 47 recordMapper.updateById(recordDO);
48 } 48 }
49 49
50 @Override 50 @Override
51 @Transactional(rollbackFor = Exception.class) 51 @Transactional(rollbackFor = Exception.class)
52 - public void updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordUpdateStatusReqDTO reqDTO) {  
53 - CombinationRecordDO recordDO = validateCombinationRecord(reqDTO.getUserId(), reqDTO.getOrderId()); 52 + public void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime) {
  53 + CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
54 // 更新状态 54 // 更新状态
55 - recordDO.setStatus(reqDTO.getStatus()); 55 + recordDO.setStatus(status);
56 // 更新开始时间 56 // 更新开始时间
57 - recordDO.setStartTime(reqDTO.getStartTime()); 57 + recordDO.setStartTime(startTime);
58 recordMapper.updateById(recordDO); 58 recordMapper.updateById(recordDO);
59 59
60 // 更新拼团参入人数 60 // 更新拼团参入人数
61 - List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), reqDTO.getStatus()); 61 + List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), status);
62 if (CollUtil.isNotEmpty(recordDOs)) { 62 if (CollUtil.isNotEmpty(recordDOs)) {
63 recordDOs.forEach(item -> { 63 recordDOs.forEach(item -> {
64 item.setUserCount(recordDOs.size()); 64 item.setUserCount(recordDOs.size());
@@ -115,8 +115,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -115,8 +115,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
115 // 2. 创建拼团记录 115 // 2. 创建拼团记录
116 CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO); 116 CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO);
117 record.setVirtualGroup(false); 117 record.setVirtualGroup(false);
118 - // TODO @puhui999:过期时间,应该是 Date 哈;  
119 - record.setExpireTime(activity.getLimitDuration()); 118 + record.setExpireTime(record.getStartTime().plusHours(activity.getLimitDuration()));
120 record.setUserSize(activity.getUserSize()); 119 record.setUserSize(activity.getUserSize());
121 recordMapper.insert(record); 120 recordMapper.insert(record);
122 } 121 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
@@ -34,6 +34,20 @@ public interface SeckillActivityService { @@ -34,6 +34,20 @@ public interface SeckillActivityService {
34 void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO); 34 void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
35 35
36 /** 36 /**
  37 + * 更新秒杀活动
  38 + *
  39 + * @param activityDO 秒杀活动
  40 + */
  41 + void updateSeckillActivity(SeckillActivityDO activityDO);
  42 +
  43 + /**
  44 + * 更新秒杀活动商品
  45 + *
  46 + * @param productList 活动商品列表
  47 + */
  48 + void updateSeckillActivityProductList(List<SeckillProductDO> productList);
  49 +
  50 + /**
37 * 关闭秒杀活动 51 * 关闭秒杀活动
38 * 52 *
39 * @param id 编号 53 * @param id 编号
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
@@ -79,8 +79,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -79,8 +79,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
79 * 1. 校验秒杀时段是否存在 79 * 1. 校验秒杀时段是否存在
80 * 2. 秒杀商品是否参加其它活动 80 * 2. 秒杀商品是否参加其它活动
81 * 81 *
82 - * @param configIds 秒杀时段数组  
83 - * @param spuId 商品 SPU 编号 82 + * @param configIds 秒杀时段数组
  83 + * @param spuId 商品 SPU 编号
84 * @param activityId 秒杀活动编号 84 * @param activityId 秒杀活动编号
85 */ 85 */
86 private void validateProductConflict(List<Long> configIds, Long spuId, Long activityId) { 86 private void validateProductConflict(List<Long> configIds, Long spuId, Long activityId) {
@@ -92,15 +92,9 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -92,15 +92,9 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
92 if (activityId != null) { // 排除自己 92 if (activityId != null) { // 排除自己
93 activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId)); 93 activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
94 } 94 }
95 - // TODO @puhui999:一个 spu,参与两个活动应该没关系,关键是活动时间不充能重叠;  
96 - // 2.2 过滤出所有 spuId 有交集的活动,判断是否存在重叠  
97 - List<SeckillActivityDO> activityDOs1 = filterList(activityList, s -> ObjectUtil.equal(s.getSpuId(), spuId));  
98 - if (isNotEmpty(activityDOs1)) {  
99 - throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);  
100 - }  
101 - // 2.3 过滤出所有 configIds 有交集的活动,判断是否存在重叠  
102 - List<SeckillActivityDO> activityDOs2 = filterList(activityList, s -> containsAny(s.getConfigIds(), configIds));  
103 - if (isNotEmpty(activityDOs2)) { 95 + // 2.2 过滤出所有 configIds 有交集的活动,判断是否存在重叠
  96 + List<SeckillActivityDO> conflictActivityList = filterList(activityList, s -> containsAny(s.getConfigIds(), configIds));
  97 + if (isNotEmpty(conflictActivityList)) {
104 throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS); 98 throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
105 } 99 }
106 } 100 }
@@ -108,7 +102,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -108,7 +102,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
108 /** 102 /**
109 * 校验秒杀商品是否都存在 103 * 校验秒杀商品是否都存在
110 * 104 *
111 - * @param spuId 商品 SPU 编号 105 + * @param spuId 商品 SPU 编号
112 * @param products 秒杀商品 106 * @param products 秒杀商品
113 */ 107 */
114 private void validateProductExists(Long spuId, List<SeckillProductBaseVO> products) { 108 private void validateProductExists(Long spuId, List<SeckillProductBaseVO> products) {
@@ -150,11 +144,21 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -150,11 +144,21 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
150 updateSeckillProduct(updateObj, updateReqVO.getProducts()); 144 updateSeckillProduct(updateObj, updateReqVO.getProducts());
151 } 145 }
152 146
  147 + @Override
  148 + public void updateSeckillActivity(SeckillActivityDO activityDO) {
  149 + seckillActivityMapper.updateById(activityDO);
  150 + }
  151 +
  152 + @Override
  153 + public void updateSeckillActivityProductList(List<SeckillProductDO> productList) {
  154 + seckillProductMapper.updateBatch(productList);
  155 + }
  156 +
153 /** 157 /**
154 * 更新秒杀商品 158 * 更新秒杀商品
155 * 159 *
156 * @param activity 秒杀活动 160 * @param activity 秒杀活动
157 - * @param products 该活动的最新商品配置 161 + * @param products 该活动的最新商品配置
158 */ 162 */
159 private void updateSeckillProduct(SeckillActivityDO activity, List<SeckillProductBaseVO> products) { 163 private void updateSeckillProduct(SeckillActivityDO activity, List<SeckillProductBaseVO> products) {
160 // 第一步,对比新老数据,获得添加、修改、删除的列表 164 // 第一步,对比新老数据,获得添加、修改、删除的列表
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/util/PromotionUtils.java
1 package cn.iocoder.yudao.module.promotion.util; 1 package cn.iocoder.yudao.module.promotion.util;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
5 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; 4 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
6 -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;  
7 5
8 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
9 -import java.util.List;  
10 -import java.util.Set;  
11 -import java.util.function.Function;  
12 -  
13 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
14 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;  
15 -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;  
16 7
17 /** 8 /**
18 * 活动工具类 9 * 活动工具类
@@ -31,21 +22,4 @@ public class PromotionUtils { @@ -31,21 +22,4 @@ public class PromotionUtils {
31 return LocalDateTimeUtils.beforeNow(endTime) ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus(); 22 return LocalDateTimeUtils.beforeNow(endTime) ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus();
32 } 23 }
33 24
34 - /**  
35 - * 校验商品 sku 是否都存在  
36 - *  
37 - * @param skus 数据库中的商品 skus  
38 - * @param products 需要校验的商品  
39 - * @param func 获取需要校验的商品的 skuId  
40 - */  
41 - public static <T> void validateProductSkuAllExists(List<ProductSkuRespDTO> skus, List<T> products, Function<T, Long> func) {  
42 - // 校验 sku 个数是否一致  
43 - Set<Long> skuIdsSet = CollectionUtils.convertSet(products, func);  
44 - Set<Long> skuIdsSet1 = CollectionUtils.convertSet(skus, ProductSkuRespDTO::getId);  
45 - // 校验 skuId 是否存在  
46 - if (anyMatch(skuIdsSet, s -> !skuIdsSet1.contains(s))) {  
47 - throw exception(SKU_NOT_EXISTS);  
48 - }  
49 - }  
50 -  
51 } 25 }
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.api.brokerage;
  2 +
  3 +import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
  4 +
  5 +/**
  6 + * 分销 API 接口
  7 + *
  8 + * @author owen
  9 + */
  10 +public interface BrokerageApi {
  11 +
  12 + /**
  13 + * 获得分销用户
  14 + *
  15 + * @param userId 用户编号
  16 + * @return 分销用户信息
  17 + */
  18 + BrokerageUserDTO getBrokerageUser(Long userId);
  19 +
  20 + /**
  21 + * 绑定推广员
  22 + *
  23 + * @param userId 用户编号
  24 + * @param bindUserId 推广员编号
  25 + * @param isNewUser 是否为新用户
  26 + * @return 是否绑定
  27 + */
  28 + boolean bindUser(Long userId, Long bindUserId, Boolean isNewUser);
  29 +}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/dto/BrokerageUserDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.api.brokerage.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.time.LocalDateTime;
  6 +
  7 +/**
  8 + * 分销用户 DTO
  9 + *
  10 + * @author owen
  11 + */
  12 +@Data
  13 +public class BrokerageUserDTO {
  14 +
  15 + /**
  16 + * 用户编号
  17 + * <p>
  18 + * 对应 MemberUserDO 的 id 字段
  19 + */
  20 + private Long id;
  21 +
  22 + /**
  23 + * 推广员编号
  24 + * <p>
  25 + * 关联 MemberUserDO 的 id 字段
  26 + */
  27 + private Long bindUserId;
  28 + /**
  29 + * 推广员绑定时间
  30 + */
  31 + private LocalDateTime bindUserTime;
  32 +
  33 + /**
  34 + * 推广资格
  35 + */
  36 + private Boolean brokerageEnabled;
  37 + /**
  38 + * 成为分销员时间
  39 + */
  40 + private LocalDateTime brokerageTime;
  41 +
  42 + /**
  43 + * 可用佣金
  44 + */
  45 + private Integer price;
  46 + /**
  47 + * 冻结佣金
  48 + */
  49 + private Integer frozenPrice;
  50 +
  51 +}
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,7 +11,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
11 */ 11 */
12 public interface ErrorCodeConstants { 12 public interface ErrorCodeConstants {
13 13
14 - // ========== Order 模块 1-011-000-000 ========== 14 + // ========== Order 模块 1-011-000-000 ==========
15 ErrorCode ORDER_CREATE_SKU_NOT_FOUND = new ErrorCode(1_011_000_001, "商品 SKU 不存在"); 15 ErrorCode ORDER_CREATE_SKU_NOT_FOUND = new ErrorCode(1_011_000_001, "商品 SKU 不存在");
16 ErrorCode ORDER_CREATE_SPU_NOT_SALE = new ErrorCode(1_011_000_002, "商品 SPU 不可售卖"); 16 ErrorCode ORDER_CREATE_SPU_NOT_SALE = new ErrorCode(1_011_000_002, "商品 SPU 不可售卖");
17 ErrorCode ORDER_CREATE_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_011_000_004, "商品 SKU 库存不足"); 17 ErrorCode ORDER_CREATE_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_011_000_004, "商品 SKU 库存不足");
@@ -35,7 +35,7 @@ public interface ErrorCodeConstants { @@ -35,7 +35,7 @@ public interface ErrorCodeConstants {
35 ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1_011_000_024, "交易订单发货失败,发货类型不是快递"); 35 ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1_011_000_024, "交易订单发货失败,发货类型不是快递");
36 ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1_011_000_025, "交易订单取消失败,订单不是【待支付】状态"); 36 ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1_011_000_025, "交易订单取消失败,订单不是【待支付】状态");
37 37
38 - // ========== After Sale 模块 1-011-000-100 ========== 38 + // ========== After Sale 模块 1-011-000-100 ==========
39 ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在"); 39 ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
40 ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1_011_000_101, "申请退款金额错误"); 40 ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1_011_000_101, "申请退款金额错误");
41 ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1_011_000_102, "订单已关闭,无法申请售后"); 41 ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1_011_000_102, "订单已关闭,无法申请售后");
@@ -50,7 +50,7 @@ public interface ErrorCodeConstants { @@ -50,7 +50,7 @@ public interface ErrorCodeConstants {
50 ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY = 50 ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
51 new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】"); 51 new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
52 52
53 - // ========== Cart 模块 1-011-002-000 ========== 53 + // ========== Cart 模块 1-011-002-000 ==========
54 ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在"); 54 ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
55 55
56 // ========== Price 相关 1-011-003-000 ============ 56 // ========== Price 相关 1-011-003-000 ============
@@ -58,7 +58,7 @@ public interface ErrorCodeConstants { @@ -58,7 +58,7 @@ public interface ErrorCodeConstants {
58 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1_011_003_001, "计算快递运费异常,收件人地址编号为空"); 58 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1_011_003_001, "计算快递运费异常,收件人地址编号为空");
59 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板"); 59 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板");
60 60
61 - // ========== 物流 Express 模块 1-011-004-000 ========== 61 + // ========== 物流 Express 模块 1-011-004-000 ==========
62 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); 62 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
63 ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1_011_004_001, "已经存在该编码的快递公司"); 63 ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1_011_004_001, "已经存在该编码的快递公司");
64 ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1_011_004_002, "需要接入快递服务商,比如【快递100】"); 64 ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1_011_004_002, "需要接入快递服务商,比如【快递100】");
@@ -67,11 +67,21 @@ public interface ErrorCodeConstants { @@ -67,11 +67,21 @@ public interface ErrorCodeConstants {
67 ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1_011_004_101, "快递查询接口异常"); 67 ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1_011_004_101, "快递查询接口异常");
68 ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1_011_004_102, "快递查询返回失败,原因:{}"); 68 ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1_011_004_102, "快递查询返回失败,原因:{}");
69 69
70 - // ========== 物流 Template 模块 1-011-005-000 ========== 70 + // ========== 物流 Template 模块 1-011-005-000 ==========
71 ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_011_005_000, "已经存在该运费模板名"); 71 ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_011_005_000, "已经存在该运费模板名");
72 ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_011_005_001, "运费模板不存在"); 72 ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_011_005_001, "运费模板不存在");
73 73
74 // ========== 物流 PICK_UP 模块 1-011-006-000 ========== 74 // ========== 物流 PICK_UP 模块 1-011-006-000 ==========
75 ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在"); 75 ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在");
76 76
  77 + // ========== 分销用户 模块 1011007000 ==========
  78 + ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1011007000, "分销用户不存在");
  79 + ErrorCode BROKERAGE_USER_FROZEN_PRICE_NOT_ENOUGH = new ErrorCode(1011007001, "用户冻结佣金({})数量不足");
  80 + ErrorCode BROKERAGE_BIND_SELF = new ErrorCode(1011007002, "不能绑定自己");
  81 + ErrorCode BROKERAGE_BIND_USER_NOT_ENABLED = new ErrorCode(1011007003, "绑定用户没有推广资格");
  82 + ErrorCode BROKERAGE_BIND_CONDITION_ADMIN = new ErrorCode(1011007004, "仅可在后台绑定推广员");
  83 + ErrorCode BROKERAGE_BIND_MODE_REGISTER = new ErrorCode(1011007005, "只有在注册时可以绑定");
  84 + ErrorCode BROKERAGE_BIND_OVERRIDE = new ErrorCode(1011007006, "已绑定了推广人");
  85 + ErrorCode BROKERAGE_BIND_LOOP = new ErrorCode(1011007007, "下级不能绑定自己的上级");
  86 +
77 } 87 }
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageBindModeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 分销关系绑定模式枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageBindModeEnum implements IntArrayValuable {
  17 +
  18 + /**
  19 + * 只要用户没有推广人,随时都可以绑定分销关系
  20 + */
  21 + ANYTIME(1, "没有推广人"),
  22 + /**
  23 + * 仅新用户注册时才能绑定推广关系
  24 + */
  25 + REGISTER(2, "新用户"),
  26 + /**
  27 + * 每次扫码都覆盖
  28 + */
  29 + OVERRIDE(3, "扫码覆盖"),
  30 + ;
  31 +
  32 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageBindModeEnum::getMode).toArray();
  33 +
  34 + /**
  35 + * 模式
  36 + */
  37 + private final Integer mode;
  38 + /**
  39 + * 名字
  40 + */
  41 + private final String name;
  42 +
  43 + @Override
  44 + public int[] array() {
  45 + return ARRAYS;
  46 + }
  47 +
  48 +}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageEnabledConditionEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 分佣模式枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageEnabledConditionEnum implements IntArrayValuable {
  17 +
  18 + /**
  19 + * 所有用户都可以分销
  20 + */
  21 + ALL(1, "人人分销"),
  22 + /**
  23 + * 仅可后台手动设置推广员
  24 + */
  25 + ADMIN(2, "指定分销"),
  26 + ;
  27 +
  28 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageEnabledConditionEnum::getCondition).toArray();
  29 +
  30 + /**
  31 + * 模式
  32 + */
  33 + private final Integer condition;
  34 + /**
  35 + * 名字
  36 + */
  37 + private final String name;
  38 +
  39 + @Override
  40 + public int[] array() {
  41 + return ARRAYS;
  42 + }
  43 +
  44 +}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordBizTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金记录业务类型枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageRecordBizTypeEnum implements IntArrayValuable {
  17 +
  18 + ORDER(1, "获得推广佣金", "获得推广佣金 {}", true),
  19 + WITHDRAW(2, "提现申请", "提现申请扣除佣金 {}", false),
  20 + ;
  21 +
  22 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordBizTypeEnum::getType).toArray();
  23 +
  24 + /**
  25 + * 类型
  26 + */
  27 + private final Integer type;
  28 + /**
  29 + * 标题
  30 + */
  31 + private final String title;
  32 + /**
  33 + * 描述
  34 + */
  35 + private final String description;
  36 + /**
  37 + * 是否为增加佣金
  38 + */
  39 + private final boolean add;
  40 +
  41 + @Override
  42 + public int[] array() {
  43 + return ARRAYS;
  44 + }
  45 +
  46 +}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金记录状态枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageRecordStatusEnum implements IntArrayValuable {
  17 +
  18 + WAIT_SETTLEMENT(0, "待结算"),
  19 + SETTLEMENT(1, "已结算"),
  20 + CANCEL(2, "已取消"),
  21 + ;
  22 +
  23 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordStatusEnum::getStatus).toArray();
  24 +
  25 + /**
  26 + * 状态
  27 + */
  28 + private final Integer status;
  29 + /**
  30 + * 名字
  31 + */
  32 + private final String name;
  33 +
  34 + @Override
  35 + public int[] array() {
  36 + return ARRAYS;
  37 + }
  38 +
  39 +}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金提现状态枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageWithdrawStatusEnum implements IntArrayValuable {
  17 +
  18 + AUDITING(0, "审核中"),
  19 + AUDIT_SUCCESS(10, "审核通过"),
  20 + WITHDRAW_SUCCESS(11, "提现成功"),
  21 + AUDIT_FAIL(20, "审核不通过"),
  22 + WITHDRAW_FAIL(21, "提现失败"),
  23 + ;
  24 +
  25 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawStatusEnum::getStatus).toArray();
  26 +
  27 + /**
  28 + * 状态
  29 + */
  30 + private final Integer status;
  31 + /**
  32 + * 名字
  33 + */
  34 + private final String name;
  35 +
  36 + @Override
  37 + public int[] array() {
  38 + return ARRAYS;
  39 + }
  40 +
  41 +}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金提现类型枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
  17 +
  18 + WALLET(1, "钱包"),
  19 + BANK(2, "银行卡"),
  20 + WECHAT(3, "微信"),
  21 + ALIPAY(4, "支付宝"),
  22 + ;
  23 +
  24 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawTypeEnum::getType).toArray();
  25 +
  26 + /**
  27 + * 类型
  28 + */
  29 + private final Integer type;
  30 + /**
  31 + * 名字
  32 + */
  33 + private final String name;
  34 +
  35 + @Override
  36 + public int[] array() {
  37 + return ARRAYS;
  38 + }
  39 +
  40 +}
yudao-module-mall/yudao-module-trade-biz/pom.xml
@@ -56,6 +56,10 @@ @@ -56,6 +56,10 @@
56 </dependency> 56 </dependency>
57 <dependency> 57 <dependency>
58 <groupId>cn.iocoder.boot</groupId> 58 <groupId>cn.iocoder.boot</groupId>
  59 + <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
  60 + </dependency>
  61 + <dependency>
  62 + <groupId>cn.iocoder.boot</groupId>
59 <artifactId>yudao-spring-boot-starter-biz-ip</artifactId> 63 <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
60 </dependency> 64 </dependency>
61 65
@@ -82,6 +86,11 @@ @@ -82,6 +86,11 @@
82 <artifactId>yudao-spring-boot-starter-mybatis</artifactId> 86 <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
83 </dependency> 87 </dependency>
84 88
  89 + <dependency>
  90 + <groupId>cn.iocoder.boot</groupId>
  91 + <artifactId>yudao-spring-boot-starter-redis</artifactId>
  92 + </dependency>
  93 +
85 <!-- Test 测试相关 --> 94 <!-- Test 测试相关 -->
86 <dependency> 95 <dependency>
87 <groupId>cn.iocoder.boot</groupId> 96 <groupId>cn.iocoder.boot</groupId>
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.api.brokerage;
  2 +
  3 +import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
  4 +import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert;
  5 +import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
  6 +import org.springframework.stereotype.Service;
  7 +import org.springframework.validation.annotation.Validated;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 分销 API 接口实现类
  13 + *
  14 + * @author owen
  15 + */
  16 +@Service
  17 +@Validated
  18 +public class BrokerageApiImpl implements BrokerageApi {
  19 +
  20 + @Resource
  21 + private BrokerageUserService brokerageUserService;
  22 +
  23 + @Override
  24 + public BrokerageUserDTO getBrokerageUser(Long userId) {
  25 + return BrokerageUserConvert.INSTANCE.convertDTO(brokerageUserService.getBrokerageUser(userId));
  26 + }
  27 +
  28 + @Override
  29 + public boolean bindUser(Long userId, Long bindUserId, Boolean isNewUser) {
  30 + return brokerageUserService.bindBrokerageUser(userId, bindUserId, isNewUser);
  31 + }
  32 +
  33 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/TradeAfterSaleController.java
@@ -76,6 +76,11 @@ public class TradeAfterSaleController { @@ -76,6 +76,11 @@ public class TradeAfterSaleController {
76 public CommonResult<TradeAfterSaleDetailRespVO> getOrderDetail(@RequestParam("id") Long id) { 76 public CommonResult<TradeAfterSaleDetailRespVO> getOrderDetail(@RequestParam("id") Long id) {
77 // 查询订单 77 // 查询订单
78 TradeAfterSaleDO afterSale = afterSaleService.getAfterSale(id); 78 TradeAfterSaleDO afterSale = afterSaleService.getAfterSale(id);
  79 + // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
  80 +// if (afterSale == null) {
  81 +// return success(null, AFTER_SALE_NOT_FOUND.getMsg());
  82 +// }
  83 +
79 // 查询订单 84 // 查询订单
80 TradeOrderDO order = tradeOrderQueryService.getOrder(afterSale.getOrderId()); 85 TradeOrderDO order = tradeOrderQueryService.getOrder(afterSale.getOrderId());
81 // 查询订单项 86 // 查询订单项
@@ -92,7 +97,11 @@ public class TradeAfterSaleController { @@ -92,7 +97,11 @@ public class TradeAfterSaleController {
92 TradeAfterSaleLogRespDTO respVO = new TradeAfterSaleLogRespDTO(); 97 TradeAfterSaleLogRespDTO respVO = new TradeAfterSaleLogRespDTO();
93 respVO.setId((long) i); 98 respVO.setId((long) i);
94 respVO.setUserId((long) i); 99 respVO.setUserId((long) i);
95 - respVO.setUserType(1); 100 + respVO.setUserType(i % 2 == 0 ? 2 : 1);
  101 + // 模拟系统操作
  102 + if (i == 2) {
  103 + respVO.setUserType(3);
  104 + }
96 respVO.setAfterSaleId(id); 105 respVO.setAfterSaleId(id);
97 respVO.setOrderId((long) i); 106 respVO.setOrderId((long) i);
98 respVO.setOrderItemId((long) i); 107 respVO.setOrderItemId((long) i);
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/TradeAfterSaleDetailRespVO.java
@@ -35,7 +35,7 @@ public class TradeAfterSaleDetailRespVO extends TradeAfterSaleBaseVO { @@ -35,7 +35,7 @@ public class TradeAfterSaleDetailRespVO extends TradeAfterSaleBaseVO {
35 /** 35 /**
36 * 售后日志 36 * 售后日志
37 */ 37 */
38 - private List<TradeAfterSaleLogRespVO> afterSaleLog; 38 + private List<TradeAfterSaleLogRespVO> logs;
39 39
40 @Schema(description = "管理后台 - 交易订单的详情的订单项目") 40 @Schema(description = "管理后台 - 交易订单的详情的订单项目")
41 @Data 41 @Data
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/BrokerageRecordController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordRespVO;
  7 +import cn.iocoder.yudao.module.trade.convert.brokerage.record.BrokerageRecordConvert;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
  9 +import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.Parameter;
  12 +import io.swagger.v3.oas.annotations.tags.Tag;
  13 +import org.springframework.security.access.prepost.PreAuthorize;
  14 +import org.springframework.validation.annotation.Validated;
  15 +import org.springframework.web.bind.annotation.GetMapping;
  16 +import org.springframework.web.bind.annotation.RequestMapping;
  17 +import org.springframework.web.bind.annotation.RequestParam;
  18 +import org.springframework.web.bind.annotation.RestController;
  19 +
  20 +import javax.annotation.Resource;
  21 +import javax.validation.Valid;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +
  25 +@Tag(name = "管理后台 - 佣金记录")
  26 +@RestController
  27 +@RequestMapping("/trade/brokerage-record")
  28 +@Validated
  29 +public class BrokerageRecordController {
  30 +
  31 + @Resource
  32 + private BrokerageRecordService brokerageRecordService;
  33 +
  34 + @GetMapping("/get")
  35 + @Operation(summary = "获得佣金记录")
  36 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  37 + @PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
  38 + public CommonResult<BrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Integer id) {
  39 + BrokerageRecordDO brokerageRecord = brokerageRecordService.getBrokerageRecord(id);
  40 + return success(BrokerageRecordConvert.INSTANCE.convert(brokerageRecord));
  41 + }
  42 +
  43 + @GetMapping("/page")
  44 + @Operation(summary = "获得佣金记录分页")
  45 + @PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
  46 + public CommonResult<PageResult<BrokerageRecordRespVO>> getBrokerageRecordPage(@Valid BrokerageRecordPageReqVO pageVO) {
  47 + PageResult<BrokerageRecordDO> pageResult = brokerageRecordService.getBrokerageRecordPage(pageVO);
  48 + return success(BrokerageRecordConvert.INSTANCE.convertPage(pageResult));
  49 + }
  50 +
  51 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import javax.validation.constraints.NotEmpty;
  8 +import javax.validation.constraints.NotNull;
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +/**
  14 + * 佣金记录 Base VO,提供给添加、修改、详细的子 VO 使用
  15 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  16 + */
  17 +@Data
  18 +public class BrokerageRecordBaseVO {
  19 +
  20 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25973")
  21 + @NotNull(message = "用户编号不能为空")
  22 + private Long userId;
  23 +
  24 + @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23353")
  25 + @NotEmpty(message = "业务编号不能为空")
  26 + private String bizId;
  27 +
  28 + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  29 + @NotNull(message = "业务类型不能为空")
  30 + private Integer bizType;
  31 +
  32 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
  33 + @NotEmpty(message = "标题不能为空")
  34 + private String title;
  35 +
  36 + @Schema(description = "金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "28731")
  37 + @NotNull(message = "金额不能为空")
  38 + private Integer price;
  39 +
  40 + @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "13226")
  41 + @NotNull(message = "当前总佣金不能为空")
  42 + private Integer totalPrice;
  43 +
  44 + @Schema(description = "说明", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
  45 + @NotNull(message = "说明不能为空")
  46 + private String description;
  47 +
  48 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  49 + @NotNull(message = "状态不能为空")
  50 + private Integer status;
  51 +
  52 + @Schema(description = "冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED)
  53 + @NotNull(message = "冻结时间(天)不能为空")
  54 + private Integer frozenDays;
  55 +
  56 + @Schema(description = "解冻时间")
  57 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  58 + private LocalDateTime unfreezeTime;
  59 +
  60 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - 佣金记录分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class BrokerageRecordPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "用户编号", example = "25973")
  21 + private Long userId;
  22 +
  23 + @Schema(description = "业务类型", example = "1")
  24 + private Integer bizType;
  25 +
  26 + @Schema(description = "状态", example = "1")
  27 + private Integer status;
  28 +
  29 + @Schema(description = "创建时间")
  30 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  31 + private LocalDateTime[] createTime;
  32 +
  33 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - 佣金记录 Response VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class BrokerageRecordRespVO extends BrokerageRecordBaseVO {
  15 +
  16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28896")
  17 + private Integer id;
  18 +
  19 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private LocalDateTime createTime;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/BrokerageUserController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
  6 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  7 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.*;
  8 +import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert;
  9 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  10 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  11 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
  12 +import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
  13 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
  14 +import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
  15 +import io.swagger.v3.oas.annotations.Operation;
  16 +import io.swagger.v3.oas.annotations.Parameter;
  17 +import io.swagger.v3.oas.annotations.tags.Tag;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.bind.annotation.*;
  21 +
  22 +import javax.annotation.Resource;
  23 +import javax.validation.Valid;
  24 +import java.util.Map;
  25 +import java.util.Set;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  30 +
  31 +@Tag(name = "管理后台 - 分销用户")
  32 +@RestController
  33 +@RequestMapping("/trade/brokerage-user")
  34 +@Validated
  35 +public class BrokerageUserController {
  36 +
  37 + @Resource
  38 + private BrokerageUserService brokerageUserService;
  39 + @Resource
  40 + private BrokerageRecordService brokerageRecordService;
  41 +
  42 + @Resource
  43 + private MemberUserApi memberUserApi;
  44 +
  45 + @PutMapping("/update-brokerage-user")
  46 + @Operation(summary = "修改推广员")
  47 + @PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-user')")
  48 + public CommonResult<Boolean> updateBrokerageUser(@Valid @RequestBody BrokerageUserUpdateBrokerageUserReqVO updateReqVO) {
  49 + brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), updateReqVO.getBindUserId());
  50 + return success(true);
  51 + }
  52 +
  53 + @PutMapping("/clear-brokerage-user")
  54 + @Operation(summary = "清除推广员")
  55 + @PreAuthorize("@ss.hasPermission('trade:brokerage-user:clear-brokerage-user')")
  56 + public CommonResult<Boolean> clearBrokerageUser(@Valid @RequestBody BrokerageUserClearBrokerageUserReqVO updateReqVO) {
  57 + brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), null);
  58 + return success(true);
  59 + }
  60 +
  61 + @PutMapping("/update-brokerage-enable")
  62 + @Operation(summary = "修改推广资格")
  63 + @PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-enable')")
  64 + public CommonResult<Boolean> updateBrokerageEnabled(@Valid @RequestBody BrokerageUserUpdateBrokerageEnabledReqVO updateReqVO) {
  65 + brokerageUserService.updateBrokerageUserEnabled(updateReqVO.getId(), updateReqVO.getEnabled());
  66 + return success(true);
  67 + }
  68 +
  69 + @GetMapping("/get")
  70 + @Operation(summary = "获得分销用户")
  71 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  72 + @PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
  73 + public CommonResult<BrokerageUserRespVO> getBrokerageUser(@RequestParam("id") Long id) {
  74 + BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(id);
  75 + return success(BrokerageUserConvert.INSTANCE.convert(brokerageUser));
  76 + }
  77 +
  78 + @GetMapping("/page")
  79 + @Operation(summary = "获得分销用户分页")
  80 + @PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
  81 + public CommonResult<PageResult<BrokerageUserRespVO>> getBrokerageUserPage(@Valid BrokerageUserPageReqVO pageVO) {
  82 + // 分页查询
  83 + PageResult<BrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(pageVO);
  84 +
  85 + // 涉及到的用户
  86 + Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
  87 + // 查询用户信息
  88 + Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
  89 + // 合计分佣订单
  90 + Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = convertMap(userIds,
  91 + userId -> userId,
  92 + userId -> brokerageRecordService.getUserBrokerageSummaryByUserId(userId,
  93 + BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus()));
  94 + // 合计推广用户数量
  95 + Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
  96 + userId -> userId,
  97 + userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId));
  98 +
  99 + // todo 合计提现
  100 +
  101 + return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap, userOrderSummaryMap));
  102 + }
  103 +
  104 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +/**
  13 + * 分销用户 Base VO,提供给添加、修改、详细的子 VO 使用
  14 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  15 + */
  16 +@Data
  17 +public class BrokerageUserBaseVO {
  18 +
  19 + @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
  20 + @NotNull(message = "推广员编号不能为空")
  21 + private Long bindUserId;
  22 +
  23 + @Schema(description = "推广员绑定时间")
  24 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  25 + private LocalDateTime bindUserTime;
  26 +
  27 + @Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
  28 + @NotNull(message = "推广资格不能为空")
  29 + private Boolean brokerageEnabled;
  30 +
  31 + @Schema(description = "成为分销员时间")
  32 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  33 + private LocalDateTime brokerageTime;
  34 +
  35 + @Schema(description = "可用佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "11089")
  36 + @NotNull(message = "可用佣金不能为空")
  37 + private Integer price;
  38 +
  39 + @Schema(description = "冻结佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "30916")
  40 + @NotNull(message = "冻结佣金不能为空")
  41 + private Integer frozenPrice;
  42 +
  43 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserClearBrokerageUserReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.ToString;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - 分销用户 - 清除推广员 Request VO")
  10 +@Data
  11 +@ToString(callSuper = true)
  12 +public class BrokerageUserClearBrokerageUserReqVO {
  13 +
  14 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
  15 + @NotNull(message = "用户编号不能为空")
  16 + private Long id;
  17 +
  18 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - 分销用户分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class BrokerageUserPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "推广员编号", example = "4587")
  21 + private Long bindUserId;
  22 +
  23 + @Schema(description = "推广资格")
  24 + private Boolean brokerageEnabled;
  25 +
  26 + @Schema(description = "创建时间")
  27 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  28 + private LocalDateTime[] createTime;
  29 +
  30 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - 分销用户 Response VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class BrokerageUserRespVO extends BrokerageUserBaseVO {
  15 +
  16 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
  17 + private Long id;
  18 +
  19 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private LocalDateTime createTime;
  21 +
  22 + // ========== 用户信息 ==========
  23 +
  24 + @Schema(description = "用户头像", example = "https://www.iocoder.cn/xxx.png")
  25 + private String avatar;
  26 + @Schema(description = "用户昵称", example = "李四")
  27 + private String nickname;
  28 +
  29 + // ========== 推广信息 ==========
  30 +
  31 + @Schema(description = "推广用户数量(一级)", example = "20019")
  32 + private Integer brokerageUserCount;
  33 + @Schema(description = "推广订单数量", example = "20019")
  34 + private Integer brokerageOrderCount;
  35 + @Schema(description = "推广订单金额", example = "20019")
  36 + private Integer brokerageOrderPrice;
  37 +
  38 + // ========== 提现信息 ==========
  39 +
  40 + @Schema(description = "已提现金额", example = "20019")
  41 + private Integer withdrawPrice;
  42 + @Schema(description = "已提现次数", example = "20019")
  43 + private Integer withdrawCount;
  44 +
  45 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserUpdateBrokerageEnabledReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.ToString;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
  10 +@Data
  11 +@ToString(callSuper = true)
  12 +public class BrokerageUserUpdateBrokerageEnabledReqVO {
  13 +
  14 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
  15 + @NotNull(message = "用户编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
  19 + @NotNull(message = "推广资格不能为空")
  20 + private Boolean enabled;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserUpdateBrokerageUserReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.ToString;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
  10 +@Data
  11 +@ToString(callSuper = true)
  12 +public class BrokerageUserUpdateBrokerageUserReqVO {
  13 +
  14 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
  15 + @NotNull(message = "用户编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
  19 + @NotNull(message = "推广员编号不能为空")
  20 + private Long bindUserId;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/TradeConfigController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.config;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
  5 +import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
  6 +import cn.iocoder.yudao.module.trade.convert.config.TradeConfigConvert;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  8 +import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import org.springframework.security.access.prepost.PreAuthorize;
  12 +import org.springframework.validation.annotation.Validated;
  13 +import org.springframework.web.bind.annotation.*;
  14 +
  15 +import javax.annotation.Resource;
  16 +import javax.validation.Valid;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "管理后台 - 交易中心配置")
  21 +@RestController
  22 +@RequestMapping("/trade/config")
  23 +@Validated
  24 +public class TradeConfigController {
  25 +
  26 + @Resource
  27 + private TradeConfigService tradeConfigService;
  28 +
  29 + @PutMapping("/save")
  30 + @Operation(summary = "更新交易中心配置")
  31 + @PreAuthorize("@ss.hasPermission('trade:config:save')")
  32 + public CommonResult<Boolean> updateConfig(@Valid @RequestBody TradeConfigSaveReqVO updateReqVO) {
  33 + tradeConfigService.saveTradeConfig(updateReqVO);
  34 + return success(true);
  35 + }
  36 +
  37 + @GetMapping("/get")
  38 + @Operation(summary = "获得交易中心配置")
  39 + @PreAuthorize("@ss.hasPermission('trade:config:query')")
  40 + public CommonResult<TradeConfigRespVO> getConfig() {
  41 + TradeConfigDO config = tradeConfigService.getTradeConfig();
  42 + return success(TradeConfigConvert.INSTANCE.convert(config));
  43 + }
  44 +
  45 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
  5 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
  6 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +import org.hibernate.validator.constraints.Range;
  10 +
  11 +import javax.validation.constraints.NotEmpty;
  12 +import javax.validation.constraints.NotNull;
  13 +import javax.validation.constraints.PositiveOrZero;
  14 +import java.util.List;
  15 +
  16 +/**
  17 + * 交易中心配置 Base VO,提供给添加、修改、详细的子 VO 使用
  18 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  19 + */
  20 +@Data
  21 +public class TradeConfigBaseVO {
  22 +
  23 + // ========== 分销相关 ==========
  24 +
  25 + @Schema(description = "是否启用分佣", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  26 + @NotNull(message = "是否启用分佣不能为空")
  27 + private Boolean brokerageEnabled;
  28 +
  29 + @Schema(description = "分佣模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  30 + @NotNull(message = "分佣模式不能为空")
  31 + @InEnum(value = BrokerageEnabledConditionEnum.class, message = "分佣模式必须是 {value}")
  32 + private Integer brokerageEnabledCondition;
  33 +
  34 + @Schema(description = "分销关系绑定模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  35 + @NotNull(message = "分销关系绑定模式不能为空")
  36 + @InEnum(value = BrokerageBindModeEnum.class, message = "分销关系绑定模式必须是 {value}")
  37 + private Integer brokerageBindMode;
  38 +
  39 + @Schema(description = "分销海报图地址数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/yudao.jpg]")
  40 + private List<String> brokeragePostUrls;
  41 +
  42 + @Schema(description = "一级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  43 + @NotNull(message = "一级返佣比例不能为空")
  44 + @Range(min = 0, max = 100, message = "一级返佣比例必须在 0 - 100 之间")
  45 + private Integer brokerageFirstPercent;
  46 +
  47 + @Schema(description = "二级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  48 + @NotNull(message = "二级返佣比例不能为空")
  49 + @Range(min = 0, max = 100, message = "二级返佣比例必须在 0 - 100 之间")
  50 + private Integer brokerageSecondPercent;
  51 +
  52 + @Schema(description = "用户提现最低金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
  53 + @NotNull(message = "用户提现最低金额不能为空")
  54 + @PositiveOrZero(message = "用户提现最低金额不能是负数")
  55 + private Integer brokerageWithdrawMinPrice;
  56 +
  57 + @Schema(description = "提现银行", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
  58 + @NotEmpty(message = "提现银行不能为空")
  59 + private List<Integer> brokerageBankNames;
  60 +
  61 + @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
  62 + @NotNull(message = "佣金冻结时间(天)不能为空")
  63 + @PositiveOrZero(message = "佣金冻结时间不能是负数")
  64 + private Integer brokerageFrozenDays;
  65 +
  66 + @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
  67 + @NotNull(message = "提现方式不能为空")
  68 + @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
  69 + private List<Integer> brokerageWithdrawType;
  70 +
  71 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@Schema(description = "管理后台 - 交易中心配置 Response VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class TradeConfigRespVO extends TradeConfigBaseVO {
  13 +
  14 + @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + private Long id;
  16 +
  17 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@Schema(description = "管理后台 - 交易中心配置更新 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class TradeConfigSaveReqVO extends TradeConfigBaseVO {
  13 +
  14 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java
@@ -67,6 +67,11 @@ public class TradeOrderController { @@ -67,6 +67,11 @@ public class TradeOrderController {
67 public CommonResult<TradeOrderDetailRespVO> getOrderDetail(@RequestParam("id") Long id) { 67 public CommonResult<TradeOrderDetailRespVO> getOrderDetail(@RequestParam("id") Long id) {
68 // 查询订单 68 // 查询订单
69 TradeOrderDO order = tradeOrderQueryService.getOrder(id); 69 TradeOrderDO order = tradeOrderQueryService.getOrder(id);
  70 + // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
  71 +// if (order == null) {
  72 +// return success(null, ORDER_NOT_FOUND.getMsg());
  73 +// }
  74 +
70 // 查询订单项 75 // 查询订单项
71 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id); 76 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id);
72 // orderLog 77 // orderLog
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderDetailRespVO.java
@@ -26,24 +26,23 @@ public class TradeOrderDetailRespVO extends TradeOrderBaseVO { @@ -26,24 +26,23 @@ public class TradeOrderDetailRespVO extends TradeOrderBaseVO {
26 private MemberUserRespVO user; 26 private MemberUserRespVO user;
27 27
28 /** 28 /**
29 - * TODO 订单操作日志, 先模拟一波;返回 logs,简洁,然后复数哈 29 + * TODO 订单操作日志, 先模拟一波
30 */ 30 */
31 - private List<OrderLog> orderLog; 31 + private List<OrderLog> logs;
32 32
33 - // TODO @puhui999:swagger 注解 33 + @Schema(description = "管理后台 - 交易订单的操作日志")
34 @Data 34 @Data
35 public static class OrderLog { 35 public static class OrderLog {
36 36
37 - /**  
38 - * 内容  
39 - */ 37 + @Schema(description = "操作详情", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单发货")
40 private String content; 38 private String content;
41 39
42 - /**  
43 - * 创建时间  
44 - */ 40 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-01 10:50:20")
45 private LocalDateTime createTime; 41 private LocalDateTime createTime;
46 42
  43 + @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  44 + private Integer userType;
  45 +
47 } 46 }
48 47
49 @Schema(description = "管理后台 - 交易订单的详情的订单项目") 48 @Schema(description = "管理后台 - 交易订单的详情的订单项目")
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageRecordController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  6 +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageProductPriceRespVO;
  7 +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordPageReqVO;
  8 +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordRespVO;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.validation.annotation.Validated;
  13 +import org.springframework.web.bind.annotation.GetMapping;
  14 +import org.springframework.web.bind.annotation.RequestMapping;
  15 +import org.springframework.web.bind.annotation.RequestParam;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import javax.validation.Valid;
  19 +import java.time.LocalDateTime;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +import static java.util.Arrays.asList;
  23 +
  24 +@Tag(name = "用户 APP - 分销用户")
  25 +@RestController
  26 +@RequestMapping("/trade/brokerage-record")
  27 +@Validated
  28 +@Slf4j
  29 +public class AppBrokerageRecordController {
  30 +
  31 + // TODO 芋艿:临时 mock =>
  32 + @GetMapping("/page")
  33 + @Operation(summary = "获得分销记录分页")
  34 + @PreAuthenticated
  35 + public CommonResult<PageResult<AppBrokerageRecordRespVO>> getBrokerageRecordPage(@Valid AppBrokerageRecordPageReqVO pageReqVO) {
  36 + AppBrokerageRecordRespVO vo1 = new AppBrokerageRecordRespVO()
  37 + .setId(1L).setPrice(10).setTitle("收到钱").setCreateTime(LocalDateTime.now())
  38 + .setFinishTime(LocalDateTime.now());
  39 + AppBrokerageRecordRespVO vo2 = new AppBrokerageRecordRespVO()
  40 + .setId(2L).setPrice(-20).setTitle("提现钱").setCreateTime(LocalDateTime.now())
  41 + .setFinishTime(LocalDateTime.now());
  42 + return success(new PageResult<>(asList(vo1, vo2), 10L));
  43 + }
  44 +
  45 + @GetMapping("/get-product-brokerage-price")
  46 + @Operation(summary = "获得商品的分销金额")
  47 + public CommonResult<AppBrokerageProductPriceRespVO> getProductBrokeragePrice(@RequestParam("spuId") Long spuId) {
  48 + AppBrokerageProductPriceRespVO respVO = new AppBrokerageProductPriceRespVO();
  49 + respVO.setEnabled(true); // TODO @疯狂:需要开启分销 + 人允许分销
  50 + respVO.setBrokerageMinPrice(1);
  51 + respVO.setBrokerageMaxPrice(2);
  52 + return success(respVO);
  53 + }
  54 +
  55 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  6 +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.*;
  7 +import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.Parameter;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.format.annotation.DateTimeFormat;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.*;
  15 +
  16 +import javax.annotation.Resource;
  17 +import javax.validation.Valid;
  18 +import java.time.LocalDateTime;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  22 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  23 +import static java.util.Arrays.asList;
  24 +
  25 +@Tag(name = "用户 APP - 分销用户")
  26 +@RestController
  27 +@RequestMapping("/trade/brokerage-user")
  28 +@Validated
  29 +@Slf4j
  30 +public class AppBrokerageUserController {
  31 + @Resource
  32 + private BrokerageUserService brokerageUserService;
  33 +
  34 + // TODO 芋艿:临时 mock =>
  35 + @GetMapping("/get")
  36 + @Operation(summary = "获得个人分销信息")
  37 + @PreAuthenticated
  38 + public CommonResult<AppBrokerageUserRespVO> getBrokerageUser() {
  39 + AppBrokerageUserRespVO respVO = new AppBrokerageUserRespVO()
  40 + .setBrokerageEnabled(true)
  41 + .setPrice(2000)
  42 + .setFrozenPrice(3000);
  43 + return success(respVO);
  44 + }
  45 +
  46 + @PutMapping("/bind")
  47 + @Operation(summary = "绑定推广员")
  48 + @PreAuthenticated
  49 + public CommonResult<Boolean> bindBrokerageUser(@Valid @RequestBody AppBrokerageUserBindReqVO reqVO) {
  50 + return success(brokerageUserService.bindBrokerageUser(getLoginUserId(), reqVO.getBindUserId(), false));
  51 + }
  52 +
  53 + // TODO 芋艿:临时 mock =>
  54 + @GetMapping("/get-summary")
  55 + @Operation(summary = "获得个人分销统计")
  56 + @PreAuthenticated
  57 + public CommonResult<AppBrokerageUserMySummaryRespVO> getBrokerageUserSummary() {
  58 + AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
  59 + .setYesterdayPrice(1)
  60 + .setBrokeragePrice(2)
  61 + .setFrozenPrice(3)
  62 + .setWithdrawPrice(4)
  63 + .setFirstBrokerageUserCount(166)
  64 + .setSecondBrokerageUserCount(233);
  65 + return success(respVO);
  66 + }
  67 +
  68 + // TODO 芋艿:临时 mock =>
  69 + @GetMapping("/rank-page-by-user-count")
  70 + @Operation(summary = "获得分销用户排行分页(基于用户量)")
  71 + @PreAuthenticated
  72 + public CommonResult<PageResult<AppBrokerageUserRankByUserCountRespVO>> getBrokerageUserRankPageByUserCount(AppBrokerageUserRankPageReqVO pageReqVO) {
  73 + AppBrokerageUserRankByUserCountRespVO vo1 = new AppBrokerageUserRankByUserCountRespVO()
  74 + .setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  75 + .setBrokerageUserCount(10);
  76 + AppBrokerageUserRankByUserCountRespVO vo2 = new AppBrokerageUserRankByUserCountRespVO()
  77 + .setId(2L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  78 + .setBrokerageUserCount(6);
  79 + AppBrokerageUserRankByUserCountRespVO vo3 = new AppBrokerageUserRankByUserCountRespVO()
  80 + .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  81 + .setBrokerageUserCount(4);
  82 + AppBrokerageUserRankByUserCountRespVO vo4 = new AppBrokerageUserRankByUserCountRespVO()
  83 + .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  84 + .setBrokerageUserCount(4);
  85 + return success(new PageResult<>(asList(vo1, vo2, vo3, vo4), 10L));
  86 + }
  87 +
  88 + // TODO 芋艿:临时 mock =>
  89 + @GetMapping("/rank-page-by-price")
  90 + @Operation(summary = "获得分销用户排行分页(基于佣金)")
  91 + @PreAuthenticated
  92 + public CommonResult<PageResult<AppBrokerageUserRankByPriceRespVO>> getBrokerageUserChildSummaryPageByPrice(AppBrokerageUserRankPageReqVO pageReqVO) {
  93 + AppBrokerageUserRankByPriceRespVO vo1 = new AppBrokerageUserRankByPriceRespVO()
  94 + .setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  95 + .setBrokeragePrice(10);
  96 + AppBrokerageUserRankByPriceRespVO vo2 = new AppBrokerageUserRankByPriceRespVO()
  97 + .setId(2L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  98 + .setBrokeragePrice(6);
  99 + AppBrokerageUserRankByPriceRespVO vo3 = new AppBrokerageUserRankByPriceRespVO()
  100 + .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  101 + .setBrokeragePrice(4);
  102 + AppBrokerageUserRankByPriceRespVO vo4 = new AppBrokerageUserRankByPriceRespVO()
  103 + .setId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  104 + .setBrokeragePrice(4);
  105 + return success(new PageResult<>(asList(vo1, vo2, vo3, vo4), 10L));
  106 + }
  107 +
  108 + // TODO 芋艿:临时 mock =>
  109 + @GetMapping("/child-summary-page")
  110 + @Operation(summary = "获得下级分销统计分页")
  111 + @PreAuthenticated
  112 + public CommonResult<PageResult<AppBrokerageUserChildSummaryRespVO>> getBrokerageUserChildSummaryPage(
  113 + AppBrokerageUserChildSummaryPageReqVO pageReqVO) {
  114 + AppBrokerageUserChildSummaryRespVO vo1 = new AppBrokerageUserChildSummaryRespVO()
  115 + .setId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  116 + .setBrokeragePrice(10).setBrokeragePrice(20).setBrokerageOrderCount(30)
  117 + .setBrokerageTime(LocalDateTime.now());
  118 + AppBrokerageUserChildSummaryRespVO vo2 = new AppBrokerageUserChildSummaryRespVO()
  119 + .setId(1L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
  120 + .setBrokeragePrice(20).setBrokeragePrice(30).setBrokerageOrderCount(40)
  121 + .setBrokerageTime(LocalDateTime.now());
  122 + return success(new PageResult<>(asList(vo1, vo2), 10L));
  123 + }
  124 +
  125 + // TODO 芋艿:临时 mock =>
  126 + @GetMapping("/get-rank-by-price")
  127 + @Operation(summary = "获得分销用户排行(基于佣金)")
  128 + @Parameter(name = "times", description = "时间段", required = true)
  129 + public CommonResult<Integer> bindBrokerageUser(
  130 + @RequestParam("times") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime[] times) {
  131 + return success(1);
  132 + }
  133 +
  134 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  6 +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
  7 +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.*;
  13 +
  14 +import javax.validation.Valid;
  15 +import java.time.LocalDateTime;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  18 +import static java.util.Arrays.asList;
  19 +
  20 +@Tag(name = "用户 APP - 分销提现")
  21 +@RestController
  22 +@RequestMapping("/trade/brokerage-withdraw")
  23 +@Validated
  24 +@Slf4j
  25 +public class AppBrokerageWithdrawController {
  26 +
  27 + // TODO 芋艿:临时 mock =>
  28 + @GetMapping("/page")
  29 + @Operation(summary = "获得分销提现分页")
  30 + @PreAuthenticated
  31 + public CommonResult<PageResult<AppBrokerageWithdrawRespVO>> getBrokerageWithdrawPage() {
  32 + AppBrokerageWithdrawRespVO vo1 = new AppBrokerageWithdrawRespVO()
  33 + .setId(1L).setStatus(10).setPrice(10).setStatusName("审批通过").setCreateTime(LocalDateTime.now());
  34 + AppBrokerageWithdrawRespVO vo2 = new AppBrokerageWithdrawRespVO()
  35 + .setId(2L).setStatus(0).setPrice(20).setStatusName("审批中").setCreateTime(LocalDateTime.now());
  36 + return success(new PageResult<>(asList(vo1, vo2), 10L));
  37 + }
  38 +
  39 + // TODO 芋艿:临时 mock =>
  40 + @PostMapping("/create")
  41 + @Operation(summary = "创建分销提现")
  42 + @PreAuthenticated
  43 + public CommonResult<Long> createBrokerageWithdraw(@RequestBody @Valid AppBrokerageWithdrawCreateReqVO createReqVO) {
  44 + return success(1L);
  45 + }
  46 +
  47 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageProductPriceRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "用户 App - 商品的分销金额 Response VO")
  7 +@Data
  8 +public class AppBrokerageProductPriceRespVO {
  9 +
  10 + @Schema(description = "是否开启", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  11 + private Boolean enabled;
  12 +
  13 + @Schema(description = "分销最小金额,单位:分", example = "100")
  14 + private Integer brokerageMinPrice;
  15 +
  16 + @Schema(description = "分销最大金额,单位:分", example = "100")
  17 + private Integer brokerageMaxPrice;
  18 +
  19 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  6 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "应用 App - 分销记录分页 Request VO")
  11 +@Data
  12 +public class AppBrokerageRecordPageReqVO extends PageParam {
  13 +
  14 + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  15 + @InEnum(value = BrokerageRecordBizTypeEnum.class, message = "业务类型必须是 {value}")
  16 + private Integer bizType;
  17 +
  18 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + @InEnum(value = BrokerageRecordStatusEnum.class, message = "状态必须是 {value}")
  20 + private Integer status;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户 App - 分销记录 Response VO")
  9 +@Data
  10 +public class AppBrokerageRecordRespVO {
  11 +
  12 + @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  13 + private Long id;
  14 +
  15 + @Schema(description = "分销标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户下单")
  16 + private String title;
  17 +
  18 + @Schema(description = "分销金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
  19 + private Integer price;
  20 +
  21 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  22 + private LocalDateTime createTime;
  23 +
  24 + @Schema(description = "完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + private LocalDateTime finishTime;
  26 +
  27 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserBindReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "应用 App - 绑定推广员 Request VO")
  10 +@Data
  11 +public class AppBrokerageUserBindReqVO extends PageParam {
  12 +
  13 + @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "推广员编号不能为空")
  15 + private Long bindUserId;
  16 +
  17 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserChildSummaryPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.pojo.SortingField;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "用户 App - 下级分销统计分页 Request VO")
  9 +@Data
  10 +public class AppBrokerageUserChildSummaryPageReqVO extends PageParam {
  11 +
  12 + public static final String SORT_FIELD_USER_COUNT = "userCount";
  13 + public static final String SORT_FIELD_ORDER_COUNT = "orderCount";
  14 + public static final String SORT_FIELD_PRICE = "price";
  15 +
  16 + @Schema(description = "用户昵称", example = "李") // 模糊匹配
  17 + private String nickname;
  18 +
  19 + @Schema(description = "排序字段", example = "userCount")
  20 + private SortingField sortingField;
  21 +
  22 + @Schema(description = "下级的级别", example = "1") // 1 - 直接下级;2 - 间接下级
  23 + private Integer level;
  24 +
  25 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserChildSummaryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户 App - 下级分销统计 Response VO")
  9 +@Data
  10 +public class AppBrokerageUserChildSummaryRespVO {
  11 +
  12 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  13 + private Long id;
  14 +
  15 + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
  16 + private String nickname;
  17 +
  18 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
  19 + private String avatar;
  20 +
  21 + @Schema(description = "佣金金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  22 + private Integer brokeragePrice;
  23 +
  24 + @Schema(description = "分销订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
  25 + private Integer brokerageOrderCount;
  26 +
  27 + @Schema(description = "分销用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
  28 + private Integer brokerageUserCount;
  29 +
  30 + @Schema(description = "成为分销员时间", requiredMode = Schema.RequiredMode.REQUIRED)
  31 + private LocalDateTime brokerageTime;
  32 +
  33 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserMySummaryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "用户 App - 个人分销统计 Response VO")
  7 +@Data
  8 +public class AppBrokerageUserMySummaryRespVO {
  9 +
  10 + @Schema(description = "昨天的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  11 + private Integer yesterdayPrice;
  12 +
  13 + @Schema(description = "提现的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Integer withdrawPrice;
  15 +
  16 + @Schema(description = "可用的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2408")
  17 + private Integer brokeragePrice;
  18 +
  19 + @Schema(description = "冻结的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "234")
  20 + private Integer frozenPrice;
  21 +
  22 + @Schema(description = "分销用户数量(一级)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  23 + private Integer firstBrokerageUserCount;
  24 +
  25 + @Schema(description = "分销用户数量(二级)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  26 + private Integer secondBrokerageUserCount;
  27 +
  28 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankByPriceRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "用户 App - 分销排行用户(基于用户量) Response VO")
  7 +@Data
  8 +public class AppBrokerageUserRankByPriceRespVO {
  9 +
  10 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  11 + private Long id;
  12 +
  13 + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
  14 + private String nickname;
  15 +
  16 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
  17 + private String avatar;
  18 +
  19 + @Schema(description = "佣金金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  20 + private Integer brokeragePrice;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankByUserCountRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "用户 App - 分销排行用户(基于用户量) Response VO")
  7 +@Data
  8 +public class AppBrokerageUserRankByUserCountRespVO {
  9 +
  10 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  11 + private Long id;
  12 +
  13 + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
  14 + private String nickname;
  15 +
  16 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
  17 + private String avatar;
  18 +
  19 + @Schema(description = "邀请用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  20 + private Integer brokerageUserCount;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRankPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import javax.validation.constraints.NotEmpty;
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "应用 App - 分销用户排行 Request VO")
  14 +@Data
  15 +public class AppBrokerageUserRankPageReqVO extends PageParam {
  16 +
  17 + @Schema(description = "开始 + 结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
  18 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  19 + @NotEmpty(message = "时间不能为空")
  20 + private LocalDateTime[] times;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "用户 App - 分销用户信息 Response VO")
  7 +@Data
  8 +public class AppBrokerageUserRespVO {
  9 +
  10 + @Schema(description = "是否有分销资格", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  11 + private Boolean brokerageEnabled;
  12 +
  13 + @Schema(description = "可用的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2408")
  14 + private Integer price;
  15 +
  16 + @Schema(description = "冻结的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "234")
  17 + private Integer frozenPrice;
  18 +
  19 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import org.hibernate.validator.constraints.URL;
  9 +
  10 +import javax.validation.Validator;
  11 +import javax.validation.constraints.Min;
  12 +import javax.validation.constraints.NotBlank;
  13 +
  14 +@Schema(description = "用户 App - 分销提现创建 Request VO")
  15 +@Data
  16 +public class AppBrokerageWithdrawCreateReqVO {
  17 +
  18 + @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
  20 + private Integer type;
  21 +
  22 + @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
  23 + @Min(value = 1, message = "提现金额不能小于 1")
  24 + private Integer price;
  25 +
  26 + // ========== 银行卡、微信、支付宝 提现相关字段 ==========
  27 +
  28 + @Schema(description = "提现账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456789")
  29 + @NotBlank(message = "提现账号不能为空", groups = {Bank.class, Wechat.class, Alipay.class})
  30 + private String accountNo;
  31 +
  32 + // ========== 微信、支付宝 提现相关字段 ==========
  33 +
  34 + @Schema(description = "收款码的图片", example = "https://www.iocoder.cn/1.png")
  35 + @URL(message = "收款码的图片,必须是一个 URL")
  36 + private String accountQrCodeUrl;
  37 +
  38 + // ========== 银行卡 提现相关字段 ==========
  39 +
  40 + @Schema(description = "持卡人姓名", example = "张三")
  41 + @NotBlank(message = "持卡人姓名不能为空", groups = {Bank.class})
  42 + private String name;
  43 + @Schema(description = "提现银行", example = "1")
  44 + @NotBlank(message = "提现银行不能为空", groups = {Bank.class})
  45 + private Integer bankName;
  46 + @Schema(description = "开户地址", example = "海淀支行")
  47 + private String bankAddress;
  48 +
  49 + public interface Wallet {
  50 + }
  51 +
  52 + public interface Bank {
  53 + }
  54 +
  55 + public interface Wechat {
  56 + }
  57 +
  58 + public interface Alipay {
  59 + }
  60 +
  61 + public void validate(Validator validator) {
  62 + if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(type)) {
  63 + ValidationUtils.validate(validator, this, Wallet.class);
  64 + } else if (BrokerageWithdrawTypeEnum.BANK.getType().equals(type)) {
  65 + ValidationUtils.validate(validator, this, Bank.class);
  66 + } else if (BrokerageWithdrawTypeEnum.WECHAT.getType().equals(type)) {
  67 + ValidationUtils.validate(validator, this, Wechat.class);
  68 + } else if (BrokerageWithdrawTypeEnum.ALIPAY.getType().equals(type)) {
  69 + ValidationUtils.validate(validator, this, Alipay.class);
  70 + }
  71 + }
  72 +
  73 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户 App - 分销提现 Response VO")
  9 +@Data
  10 +public class AppBrokerageWithdrawRespVO {
  11 +
  12 + @Schema(description = "提现编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  13 + private Long id;
  14 +
  15 + @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  16 + private Integer status;
  17 +
  18 + @Schema(description = "提现状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "审核中")
  19 + private String statusName;
  20 +
  21 + @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
  22 + private Integer price;
  23 +
  24 + @Schema(description = "提现时间", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + private LocalDateTime createTime;
  26 +
  27 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/config/AppTradeConfigController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.config;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.trade.controller.app.config.vo.AppTradeConfigRespVO;
  5 +import io.swagger.v3.oas.annotations.tags.Tag;
  6 +import lombok.RequiredArgsConstructor;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.validation.annotation.Validated;
  9 +import org.springframework.web.bind.annotation.GetMapping;
  10 +import org.springframework.web.bind.annotation.RequestMapping;
  11 +import org.springframework.web.bind.annotation.RestController;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  14 +import static java.util.Arrays.asList;
  15 +
  16 +@Tag(name = "用户 App - 交易配置")
  17 +@RestController
  18 +@RequestMapping("/trade/config")
  19 +@RequiredArgsConstructor
  20 +@Validated
  21 +@Slf4j
  22 +public class AppTradeConfigController {
  23 +
  24 + @GetMapping("/get")
  25 + public CommonResult<AppTradeConfigRespVO> getTradeConfig() {
  26 + AppTradeConfigRespVO respVO = new AppTradeConfigRespVO();
  27 + respVO.setBrokeragePosterUrls(asList(
  28 + "https://api.java.crmeb.net/crmebimage/product/2020/08/03/755bf516b1ca4b6db3bfeaa4dd5901cdh71kob20re.jpg",
  29 + "https://api.java.crmeb.net/crmebimage/maintain/2021/03/01/406d729b84ed4ec9a2171bfcf6fd0634ughzbz9kfi.jpg",
  30 + "https://api.java.crmeb.net/crmebimage/maintain/2021/03/01/efb1e4e7fe604fe1988b4213ce08cb11tdsyijtd2r.jpg"
  31 + ));
  32 + respVO.setBrokerageFrozenDays(10);
  33 + respVO.setBrokerageWithdrawMinPrice(100);
  34 + return success(respVO);
  35 + }
  36 +
  37 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/config/vo/AppTradeConfigRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.config.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +@Schema(description = "用户 App - 交易配置 Response VO")
  9 +@Data
  10 +public class AppTradeConfigRespVO {
  11 +
  12 + @Schema(description = "分销海报地址数组", requiredMode = Schema.RequiredMode.REQUIRED)
  13 + private List<String> brokeragePosterUrls;
  14 +
  15 + @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  16 + private Integer brokerageFrozenDays;
  17 +
  18 + @Schema(description = "佣金提现最小金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  19 + private Integer brokerageWithdrawMinPrice;
  20 +
  21 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.config; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.config;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -// TODO 芋艿:后续要实现下,配送配置 6 +// TODO 芋艿:后续要实现下,配送配置;后续融合到 AppTradeConfigRespVO 中
7 @Schema(description = "用户 App - 配送配置 Response VO") 7 @Schema(description = "用户 App - 配送配置 Response VO")
8 @Data 8 @Data
9 public class AppDeliveryConfigRespVO { 9 public class AppDeliveryConfigRespVO {
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; 5 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
6 import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; 6 import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
7 -import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;  
8 import cn.iocoder.yudao.module.trade.controller.app.order.vo.*; 7 import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
9 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; 8 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
10 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; 9 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
@@ -82,9 +81,10 @@ public class AppTradeOrderController { @@ -82,9 +81,10 @@ public class AppTradeOrderController {
82 public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) { 81 public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
83 // 查询订单 82 // 查询订单
84 TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id); 83 TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id);
85 - if (order == null) {  
86 - return success(null);  
87 - } 84 + // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
  85 +// if (order == null) {
  86 +// return success(null, ORDER_NOT_FOUND.getMsg());
  87 +// }
88 88
89 // 查询订单项 89 // 查询订单项
90 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId()); 90 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId());
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
@@ -50,12 +50,18 @@ public class AppTradeOrderSettlementReqVO { @@ -50,12 +50,18 @@ public class AppTradeOrderSettlementReqVO {
50 private Long seckillActivityId; 50 private Long seckillActivityId;
51 51
52 // ========== 拼团活动相关字段 ========== 52 // ========== 拼团活动相关字段 ==========
  53 + // TODO @puhui999:是不是拼团记录的编号哈?
53 @Schema(description = "拼团活动编号", example = "1024") 54 @Schema(description = "拼团活动编号", example = "1024")
54 private Long combinationActivityId; 55 private Long combinationActivityId;
55 56
56 @Schema(description = "拼团团长编号", example = "2048") 57 @Schema(description = "拼团团长编号", example = "2048")
57 private Long combinationHeadId; 58 private Long combinationHeadId;
58 59
  60 + // ========== 砍价活动相关字段 ==========
  61 + // TODO @puhui999:是不是砍价记录的编号哈?
  62 + @Schema(description = "砍价活动编号", example = "123")
  63 + private Long bargainActivityId;
  64 +
59 @Data 65 @Data
60 @Schema(description = "用户 App - 商品项") 66 @Schema(description = "用户 App - 商品项")
61 @Valid 67 @Valid
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java
@@ -78,7 +78,7 @@ public interface TradeAfterSaleConvert { @@ -78,7 +78,7 @@ public interface TradeAfterSaleConvert {
78 // 处理订单信息 78 // 处理订单信息
79 respVO.setOrder(convert(order)); 79 respVO.setOrder(convert(order));
80 // 处理售后日志 80 // 处理售后日志
81 - respVO.setAfterSaleLog(convertList1(logs)); 81 + respVO.setLogs(convertList1(logs));
82 return respVO; 82 return respVO;
83 } 83 }
84 List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list); 84 List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list);
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/record/BrokerageRecordConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.brokerage.record;
  2 +
  3 +import cn.hutool.core.util.ObjectUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordRespVO;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  9 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  10 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
  11 +import org.mapstruct.Mapper;
  12 +import org.mapstruct.factory.Mappers;
  13 +
  14 +import java.time.LocalDateTime;
  15 +import java.util.List;
  16 +
  17 +/**
  18 + * 佣金记录 Convert
  19 + *
  20 + * @author owen
  21 + */
  22 +@Mapper
  23 +public interface BrokerageRecordConvert {
  24 +
  25 + BrokerageRecordConvert INSTANCE = Mappers.getMapper(BrokerageRecordConvert.class);
  26 +
  27 + BrokerageRecordRespVO convert(BrokerageRecordDO bean);
  28 +
  29 + List<BrokerageRecordRespVO> convertList(List<BrokerageRecordDO> list);
  30 +
  31 + PageResult<BrokerageRecordRespVO> convertPage(PageResult<BrokerageRecordDO> page);
  32 +
  33 + // TODO @疯狂:可能 title 不是很固化,会存在类似:沐晴成功购买《XXX JVM 实战》
  34 + default BrokerageRecordDO convert(BrokerageUserDO user, BrokerageRecordBizTypeEnum bizType, String bizId,
  35 + Integer brokerageFrozenDays, int brokeragePrice, LocalDateTime unfreezeTime,
  36 + String title) {
  37 + brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
  38 + // 不冻结时,佣金直接就是结算状态
  39 + Integer status = brokerageFrozenDays > 0
  40 + ? BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus()
  41 + : BrokerageRecordStatusEnum.SETTLEMENT.getStatus();
  42 + return new BrokerageRecordDO().setUserId(user.getId())
  43 + .setBizType(bizType.getType()).setBizId(bizId)
  44 + .setPrice(brokeragePrice).setTotalPrice(user.getBrokeragePrice())
  45 + .setTitle(title)
  46 + .setDescription(StrUtil.format(bizType.getDescription(), String.valueOf(brokeragePrice / 100.0)))
  47 + .setStatus(status).setFrozenDays(brokerageFrozenDays).setUnfreezeTime(unfreezeTime);
  48 + }
  49 +
  50 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/user/BrokerageUserConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.brokerage.user;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  6 +import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
  7 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserRespVO;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  9 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
  10 +import org.mapstruct.Mapper;
  11 +import org.mapstruct.factory.Mappers;
  12 +
  13 +import java.util.List;
  14 +import java.util.Map;
  15 +import java.util.Optional;
  16 +
  17 +/**
  18 + * 分销用户 Convert
  19 + *
  20 + * @author owen
  21 + */
  22 +@Mapper
  23 +public interface BrokerageUserConvert {
  24 +
  25 + BrokerageUserConvert INSTANCE = Mappers.getMapper(BrokerageUserConvert.class);
  26 +
  27 + BrokerageUserRespVO convert(BrokerageUserDO bean);
  28 +
  29 + List<BrokerageUserRespVO> convertList(List<BrokerageUserDO> list);
  30 +
  31 + PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> page);
  32 +
  33 + default PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> pageResult,
  34 + Map<Long, MemberUserRespDTO> userMap,
  35 + Map<Long, Long> brokerageUserCountMap,
  36 + Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap) {
  37 + PageResult<BrokerageUserRespVO> result = convertPage(pageResult);
  38 + for (BrokerageUserRespVO vo : result.getList()) {
  39 + // 用户信息
  40 + Optional.ofNullable(userMap.get(vo.getId())).ifPresent(
  41 + user -> vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
  42 + // 推广用户数量(一级)
  43 + vo.setBrokerageUserCount(MapUtil.getInt(brokerageUserCountMap, vo.getId(), 0));
  44 + // 推广订单数量、推广订单金额
  45 + Optional<UserBrokerageSummaryBO> orderSummaryOptional = Optional.ofNullable(userOrderSummaryMap.get(vo.getId()));
  46 + vo.setBrokerageOrderCount(orderSummaryOptional.map(UserBrokerageSummaryBO::getCount).orElse(0))
  47 + .setBrokerageOrderPrice(orderSummaryOptional.map(UserBrokerageSummaryBO::getPrice).orElse(0));
  48 + // todo 已提现次数、已提现金额
  49 + vo.setWithdrawCount(0);
  50 + vo.setWithdrawPrice(0);
  51 + }
  52 + return result;
  53 + }
  54 +
  55 + BrokerageUserDTO convertDTO(BrokerageUserDO brokerageUser);
  56 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/config/TradeConfigConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.config;
  2 +
  3 +import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
  4 +import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
  5 +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  6 +import org.mapstruct.Mapper;
  7 +import org.mapstruct.factory.Mappers;
  8 +
  9 +/**
  10 + * 交易中心配置 Convert
  11 + *
  12 + * @author owen
  13 + */
  14 +@Mapper
  15 +public interface TradeConfigConvert {
  16 +
  17 + TradeConfigConvert INSTANCE = Mappers.getMapper(TradeConfigConvert.class);
  18 +
  19 + TradeConfigDO convert(TradeConfigSaveReqVO bean);
  20 +
  21 + TradeConfigRespVO convert(TradeConfigDO bean);
  22 +
  23 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
@@ -7,11 +7,13 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils; @@ -7,11 +7,13 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils;
7 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; 7 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
8 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; 8 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
9 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; 9 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
  10 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
10 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; 11 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
11 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; 12 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
12 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; 13 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
13 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; 14 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
14 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; 15 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
  16 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
15 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; 17 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
16 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 18 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
17 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO; 19 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
@@ -93,6 +95,7 @@ public interface TradeOrderConvert { @@ -93,6 +95,7 @@ public interface TradeOrderConvert {
93 items.forEach(item -> item.setIncrCount(-item.getIncrCount())); 95 items.forEach(item -> item.setIncrCount(-item.getIncrCount()));
94 return new ProductSkuUpdateStockReqDTO(items); 96 return new ProductSkuUpdateStockReqDTO(items);
95 } 97 }
  98 +
96 List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list); 99 List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
97 100
98 @Mappings({ 101 @Mappings({
@@ -151,9 +154,10 @@ public interface TradeOrderConvert { @@ -151,9 +154,10 @@ public interface TradeOrderConvert {
151 TradeOrderDetailRespVO.OrderLog orderLog = new TradeOrderDetailRespVO.OrderLog(); 154 TradeOrderDetailRespVO.OrderLog orderLog = new TradeOrderDetailRespVO.OrderLog();
152 orderLog.setContent("订单操作" + i); 155 orderLog.setContent("订单操作" + i);
153 orderLog.setCreateTime(LocalDateTime.now()); 156 orderLog.setCreateTime(LocalDateTime.now());
  157 + orderLog.setUserType(i % 2 == 0 ? 2 : 1);
154 orderLogs.add(orderLog); 158 orderLogs.add(orderLog);
155 } 159 }
156 - orderVO.setOrderLog(orderLogs); 160 + orderVO.setLogs(orderLogs);
157 return orderVO; 161 return orderVO;
158 } 162 }
159 163
@@ -273,4 +277,10 @@ public interface TradeOrderConvert { @@ -273,4 +277,10 @@ public interface TradeOrderConvert {
273 277
274 TradeOrderDO convert(TradeOrderRemarkReqVO reqVO); 278 TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
275 279
  280 + default BrokerageAddReqBO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
  281 + return new BrokerageAddReqBO().setBizId(String.valueOf(item.getId()))
  282 + .setBasePrice(item.getPayPrice() * item.getCount())
  283 + .setFirstFixedPrice(sku.getSubCommissionFirstPrice())
  284 + .setSecondFixedPrice(sku.getSubCommissionSecondPrice());
  285 + }
276 } 286 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  5 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
  6 +import com.baomidou.mybatisplus.annotation.KeySequence;
  7 +import com.baomidou.mybatisplus.annotation.TableId;
  8 +import com.baomidou.mybatisplus.annotation.TableName;
  9 +import lombok.*;
  10 +
  11 +import java.time.LocalDateTime;
  12 +
  13 +/**
  14 + * 佣金记录 DO
  15 + *
  16 + * @author owen
  17 + */
  18 +@TableName("trade_brokerage_record")
  19 +@KeySequence("trade_brokerage_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  20 +@Data
  21 +@EqualsAndHashCode(callSuper = true)
  22 +@ToString(callSuper = true)
  23 +@Builder
  24 +@NoArgsConstructor
  25 +@AllArgsConstructor
  26 +public class BrokerageRecordDO extends BaseDO {
  27 +
  28 + /**
  29 + * 编号
  30 + */
  31 + @TableId
  32 + private Integer id;
  33 + /**
  34 + * 用户编号
  35 + */
  36 + private Long userId;
  37 + /**
  38 + * 业务编号
  39 + */
  40 + private String bizId;
  41 + /**
  42 + * 业务类型
  43 + * <p>
  44 + * 枚举 {@link BrokerageRecordBizTypeEnum}
  45 + */
  46 + private Integer bizType;
  47 +
  48 + /**
  49 + * 标题
  50 + */
  51 + private String title;
  52 + /**
  53 + * 说明
  54 + */
  55 + private String description;
  56 +
  57 + /**
  58 + * 金额
  59 + */
  60 + private Integer price;
  61 + /**
  62 + * 当前总佣金
  63 + */
  64 + private Integer totalPrice;
  65 +
  66 + /**
  67 + * 状态
  68 + * <p>
  69 + * 枚举 {@link BrokerageRecordStatusEnum}
  70 + */
  71 + private Integer status;
  72 +
  73 + /**
  74 + * 冻结时间(天)
  75 + */
  76 + private Integer frozenDays;
  77 + /**
  78 + * 解冻时间
  79 + */
  80 + private LocalDateTime unfreezeTime;
  81 +
  82 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import com.baomidou.mybatisplus.annotation.KeySequence;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.*;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +/**
  12 + * 分销用户 DO
  13 + *
  14 + * @author owen
  15 + */
  16 +@TableName("trade_brokerage_user")
  17 +@KeySequence("trade_brokerage_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  18 +@Data
  19 +@EqualsAndHashCode(callSuper = true)
  20 +@ToString(callSuper = true)
  21 +@Builder
  22 +@NoArgsConstructor
  23 +@AllArgsConstructor
  24 +public class BrokerageUserDO extends BaseDO {
  25 +
  26 + /**
  27 + * 用户编号
  28 + * <p>
  29 + * 对应 MemberUserDO 的 id 字段
  30 + */
  31 + @TableId
  32 + private Long id;
  33 +
  34 + /**
  35 + * 推广员编号
  36 + * <p>
  37 + * 关联 MemberUserDO 的 id 字段
  38 + */
  39 + private Long bindUserId;
  40 + /**
  41 + * 推广员绑定时间
  42 + */
  43 + private LocalDateTime bindUserTime;
  44 +
  45 + /**
  46 + * 是否有分销资格
  47 + */
  48 + private Boolean brokerageEnabled;
  49 + /**
  50 + * 成为分销员时间
  51 + */
  52 + private LocalDateTime brokerageTime;
  53 +
  54 + /**
  55 + * 可用佣金
  56 + */
  57 + private Integer brokeragePrice;
  58 + /**
  59 + * 冻结佣金
  60 + */
  61 + private Integer frozenPrice;
  62 +
  63 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.dataobject.config;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
  5 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
  6 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
  7 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
  8 +import com.baomidou.mybatisplus.annotation.KeySequence;
  9 +import com.baomidou.mybatisplus.annotation.TableField;
  10 +import com.baomidou.mybatisplus.annotation.TableId;
  11 +import com.baomidou.mybatisplus.annotation.TableName;
  12 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  13 +import lombok.*;
  14 +
  15 +import java.util.List;
  16 +
  17 +/**
  18 + * 交易中心配置 DO
  19 + *
  20 + * @author owen
  21 + */
  22 +@TableName(value = "trade_config", autoResultMap = true)
  23 +@KeySequence("trade_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  24 +@Data
  25 +@EqualsAndHashCode(callSuper = true)
  26 +@ToString(callSuper = true)
  27 +@Builder
  28 +@NoArgsConstructor
  29 +@AllArgsConstructor
  30 +public class TradeConfigDO extends BaseDO {
  31 +
  32 + /**
  33 + * 自增主键
  34 + */
  35 + @TableId
  36 + private Long id;
  37 +
  38 + // ========== 分销相关 ==========
  39 +
  40 + /**
  41 + * 是否启用分佣
  42 + */
  43 + private Boolean brokerageEnabled;
  44 + /**
  45 + * 分佣模式
  46 + * <p>
  47 + * 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
  48 + */
  49 + private Integer brokerageEnabledCondition;
  50 + /**
  51 + * 分销关系绑定模式
  52 + * <p>
  53 + * 枚举 {@link BrokerageBindModeEnum 对应的类}
  54 + */
  55 + private Integer brokerageBindMode;
  56 + /**
  57 + * 分销海报图地址数组
  58 + */
  59 + @TableField(typeHandler = JacksonTypeHandler.class)
  60 + private List<String> brokeragePostUrls;
  61 + /**
  62 + * 一级返佣比例
  63 + */
  64 + private Integer brokerageFirstPercent;
  65 + /**
  66 + * 二级返佣比例
  67 + */
  68 + private Integer brokerageSecondPercent;
  69 + /**
  70 + * 用户提现最低金额
  71 + */
  72 + private Integer brokerageWithdrawMinPrice;
  73 + /**
  74 + * 提现银行
  75 + */
  76 + @TableField(typeHandler = IntegerListTypeHandler.class)
  77 + private List<Integer> brokerageBankNames;
  78 + /**
  79 + * 佣金冻结时间(天)
  80 + */
  81 + private Integer brokerageFrozenDays;
  82 + /**
  83 + * 提现方式
  84 + * <p>
  85 + * 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
  86 + */
  87 + @TableField(typeHandler = IntegerListTypeHandler.class)
  88 + private List<Integer> brokerageWithdrawType;
  89 +
  90 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
  8 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  10 +import org.apache.ibatis.annotations.Mapper;
  11 +import org.apache.ibatis.annotations.Param;
  12 +import org.apache.ibatis.annotations.Select;
  13 +
  14 +import java.time.LocalDateTime;
  15 +import java.util.List;
  16 +
  17 +/**
  18 + * 佣金记录 Mapper
  19 + *
  20 + * @author owen
  21 + */
  22 +@Mapper
  23 +public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
  24 +
  25 + default PageResult<BrokerageRecordDO> selectPage(BrokerageRecordPageReqVO reqVO) {
  26 + return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageRecordDO>()
  27 + .eqIfPresent(BrokerageRecordDO::getUserId, reqVO.getUserId())
  28 + .eqIfPresent(BrokerageRecordDO::getBizType, reqVO.getBizType())
  29 + .eqIfPresent(BrokerageRecordDO::getStatus, reqVO.getStatus())
  30 + .betweenIfPresent(BrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
  31 + .orderByDesc(BrokerageRecordDO::getId));
  32 + }
  33 +
  34 + default List<BrokerageRecordDO> selectListByStatusAndUnfreezeTimeLt(Integer status, LocalDateTime unfreezeTime) {
  35 + return selectList(new LambdaQueryWrapper<BrokerageRecordDO>()
  36 + .eq(BrokerageRecordDO::getStatus, status)
  37 + .lt(BrokerageRecordDO::getUnfreezeTime, unfreezeTime));
  38 + }
  39 +
  40 + default int updateByIdAndStatus(Integer id, Integer status, BrokerageRecordDO updateObj) {
  41 + return update(updateObj, new LambdaQueryWrapper<BrokerageRecordDO>()
  42 + .eq(BrokerageRecordDO::getId, id)
  43 + .eq(BrokerageRecordDO::getStatus, status));
  44 + }
  45 +
  46 + default BrokerageRecordDO selectByBizTypeAndBizId(Integer bizType, String bizId) {
  47 + return selectOne(BrokerageRecordDO::getBizType, bizType,
  48 + BrokerageRecordDO::getBizId, bizId);
  49 + }
  50 +
  51 + // TODO @疯狂:mysql 关键字,大写哈;这样看起来清晰点;例如说 SELECT COUNT(1)
  52 + @Select("select count(1), sum(price) from trade_brokerage_record where user_id = #{userId} and biz_type = #{bizType} and status = #{status}")
  53 + UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
  54 + @Param("bizType") Integer bizType,
  55 + @Param("status") Integer status);
  56 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  7 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  9 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  10 +import org.apache.ibatis.annotations.Mapper;
  11 +
  12 +/**
  13 + * 分销用户 Mapper
  14 + *
  15 + * @author owen
  16 + */
  17 +@Mapper
  18 +public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
  19 +
  20 + default PageResult<BrokerageUserDO> selectPage(BrokerageUserPageReqVO reqVO) {
  21 + return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageUserDO>()
  22 + .eqIfPresent(BrokerageUserDO::getBindUserId, reqVO.getBindUserId())
  23 + .eqIfPresent(BrokerageUserDO::getBrokerageEnabled, reqVO.getBrokerageEnabled())
  24 + .betweenIfPresent(BrokerageUserDO::getCreateTime, reqVO.getCreateTime())
  25 + .orderByDesc(BrokerageUserDO::getId));
  26 + }
  27 +
  28 + /**
  29 + * 更新用户可用佣金(增加)
  30 + *
  31 + * @param id 用户编号
  32 + * @param incrCount 增加佣金(正数)
  33 + */
  34 + default void updatePriceIncr(Long id, Integer incrCount) {
  35 + Assert.isTrue(incrCount > 0);
  36 + LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
  37 + .setSql(" price = price + " + incrCount)
  38 + .eq(BrokerageUserDO::getId, id);
  39 + update(null, lambdaUpdateWrapper);
  40 + }
  41 +
  42 + /**
  43 + * 更新用户可用佣金(减少)
  44 + * 注意:理论上佣金可能已经提现,这时会扣出负数,确保平台不会造成损失
  45 + *
  46 + * @param id 用户编号
  47 + * @param incrCount 增加佣金(负数)
  48 + */
  49 + default void updatePriceDecr(Long id, Integer incrCount) {
  50 + Assert.isTrue(incrCount < 0);
  51 + LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
  52 + .setSql(" price = price + " + incrCount) // 负数,所以使用 + 号
  53 + .eq(BrokerageUserDO::getId, id);
  54 + update(null, lambdaUpdateWrapper);
  55 + }
  56 +
  57 + /**
  58 + * 更新用户冻结佣金(增加)
  59 + *
  60 + * @param id 用户编号
  61 + * @param incrCount 增加冻结佣金(正数)
  62 + */
  63 + default void updateFrozenPriceIncr(Long id, Integer incrCount) {
  64 + Assert.isTrue(incrCount > 0);
  65 + LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
  66 + .setSql(" frozen_price = frozen_price + " + incrCount)
  67 + .eq(BrokerageUserDO::getId, id);
  68 + update(null, lambdaUpdateWrapper);
  69 + }
  70 +
  71 + /**
  72 + * 更新用户冻结佣金(减少)
  73 + * 注意:理论上冻结佣金可能已经解冻,这时会扣出负数,确保平台不会造成损失
  74 + *
  75 + * @param id 用户编号
  76 + * @param incrCount 减少冻结佣金(负数)
  77 + */
  78 + default void updateFrozenPriceDecr(Long id, Integer incrCount) {
  79 + Assert.isTrue(incrCount < 0);
  80 + LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
  81 + .setSql(" frozen_price = frozen_price + " + incrCount) // 负数,所以使用 + 号
  82 + .eq(BrokerageUserDO::getId, id);
  83 + update(null, lambdaUpdateWrapper);
  84 + }
  85 +
  86 + /**
  87 + * 更新用户冻结佣金(减少), 更新用户佣金(增加)
  88 + *
  89 + * @param id 用户编号
  90 + * @param incrCount 减少冻结佣金(负数)
  91 + * @return 更新条数
  92 + */
  93 + default int updateFrozenPriceDecrAndPriceIncr(Long id, Integer incrCount) {
  94 + Assert.isTrue(incrCount < 0);
  95 + LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
  96 + .setSql(" frozen_price = frozen_price + " + incrCount + // 负数,所以使用 + 号
  97 + ", price = price + " + -incrCount) // 负数,所以使用 - 号
  98 + .eq(BrokerageUserDO::getId, id)
  99 + .ge(BrokerageUserDO::getFrozenPrice, -incrCount); // cas 逻辑
  100 + return update(null, lambdaUpdateWrapper);
  101 + }
  102 +
  103 + default void updateBindUserIdAndBindUserTimeToNull(Long id) {
  104 + update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
  105 + .eq(BrokerageUserDO::getId, id)
  106 + .set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null));
  107 + }
  108 +
  109 + default void updateEnabledFalseAndBrokerageTimeToNull(Long id) {
  110 + update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
  111 + .eq(BrokerageUserDO::getId, id)
  112 + .set(BrokerageUserDO::getBrokerageEnabled, false).set(BrokerageUserDO::getBrokerageTime, null));
  113 + }
  114 +
  115 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.config;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +/**
  8 + * 交易中心配置 Mapper
  9 + *
  10 + * @author owen
  11 + */
  12 +@Mapper
  13 +public interface TradeConfigMapper extends BaseMapperX<TradeConfigDO> {
  14 +
  15 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/no/TradeOrderNoRedisDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.redis.no;
  2 +
  3 +import cn.hutool.core.date.DatePattern;
  4 +import cn.hutool.core.date.DateUtil;
  5 +import org.springframework.data.redis.core.StringRedisTemplate;
  6 +import org.springframework.stereotype.Repository;
  7 +
  8 +import javax.annotation.Resource;
  9 +import java.time.LocalDateTime;
  10 +
  11 +/**
  12 + * 订单序号的 Redis DAO
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Repository
  17 +public class TradeOrderNoRedisDAO {
  18 +
  19 + public static final String TRADE_ORDER_NO_PREFIX = "O";
  20 +
  21 + @Resource
  22 + private StringRedisTemplate stringRedisTemplate;
  23 +
  24 + /**
  25 + * 生成序号
  26 + *
  27 + * @param prefix 前缀
  28 + * @return 序号
  29 + */
  30 + public String generate(String prefix) {
  31 + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
  32 + Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
  33 + return noPrefix + no;
  34 + }
  35 +
  36 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/BrokerageRecordUnfreezeJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.job.brokerage;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  5 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
  6 +import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 佣金解冻 Job
  13 + *
  14 + * @author owen
  15 + */
  16 +@Component
  17 +@TenantJob
  18 +public class BrokerageRecordUnfreezeJob implements JobHandler {
  19 +
  20 + @Resource
  21 + private BrokerageRecordService brokerageRecordService;
  22 +
  23 + @Override
  24 + public String execute(String param) {
  25 + int count = brokerageRecordService.unfreezeRecord();
  26 + return StrUtil.format("解冻佣金 {} 个", count);
  27 + }
  28 +
  29 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/package-info.java 0 → 100644
  1 +/**
  2 + * 占位文件,无特殊用途
  3 + */
  4 +package cn.iocoder.yudao.module.trade.job;
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
@@ -90,12 +90,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa @@ -90,12 +90,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
90 90
91 @Override 91 @Override
92 public TradeAfterSaleDO getAfterSale(Long id) { 92 public TradeAfterSaleDO getAfterSale(Long id) {
93 - TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);  
94 - // TODO @puhui999;读不到,不要这里报错哈;交给前端报错;一般是读取信息不到,message 提示,然后 close tab;  
95 - if (afterSale == null) {  
96 - throw exception(AFTER_SALE_NOT_FOUND);  
97 - }  
98 - return afterSale; 93 + return tradeAfterSaleMapper.selectById(id);
99 } 94 }
100 95
101 // TODO 芋艿:拼团失败,要不要发起售后的方式退款?还是走取消逻辑? 96 // TODO 芋艿:拼团失败,要不要发起售后的方式退款?还是走取消逻辑?
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/BrokerageAddReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.bo;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +import javax.validation.constraints.NotBlank;
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +/**
  11 + * 佣金 增加 Request BO
  12 + *
  13 + * @author owen
  14 + */
  15 +@Data
  16 +@NoArgsConstructor
  17 +@AllArgsConstructor
  18 +public class BrokerageAddReqBO {
  19 +
  20 + /**
  21 + * 业务编号
  22 + */
  23 + @NotBlank(message = "业务编号不能为空")
  24 + private String bizId;
  25 + /**
  26 + * 佣金基数
  27 + */
  28 + @NotNull(message = "佣金基数不能为空")
  29 + private Integer basePrice;
  30 + /**
  31 + * 一级佣金(固定)
  32 + */
  33 + private Integer firstFixedPrice;
  34 + /**
  35 + * 二级佣金(固定)
  36 + */
  37 + private Integer secondFixedPrice;
  38 +
  39 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.bo;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * 用户佣金合计 BO
  9 + *
  10 + * @author owen
  11 + */
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class UserBrokerageSummaryBO {
  16 +
  17 + /**
  18 + * 佣金数量
  19 + */
  20 + private Integer count;
  21 + /**
  22 + * 佣金总额
  23 + */
  24 + private Integer price;
  25 +
  26 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
  5 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
  6 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  7 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
  8 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
  9 +
  10 +import javax.validation.Valid;
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * 佣金记录 Service 接口
  15 + *
  16 + * @author owen
  17 + */
  18 +public interface BrokerageRecordService {
  19 +
  20 + /**
  21 + * 获得佣金记录
  22 + *
  23 + * @param id 编号
  24 + * @return 佣金记录
  25 + */
  26 + BrokerageRecordDO getBrokerageRecord(Integer id);
  27 +
  28 + /**
  29 + * 获得佣金记录分页
  30 + *
  31 + * @param pageReqVO 分页查询
  32 + * @return 佣金记录分页
  33 + */
  34 + PageResult<BrokerageRecordDO> getBrokerageRecordPage(BrokerageRecordPageReqVO pageReqVO);
  35 +
  36 + /**
  37 + * 增加佣金
  38 + *
  39 + * @param userId 会员编号
  40 + * @param bizType 业务类型
  41 + * @param list 请求参数列表
  42 + */
  43 + void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, @Valid List<BrokerageAddReqBO> list);
  44 +
  45 + /**
  46 + * 取消佣金:将佣金记录,状态修改为已失效
  47 + *
  48 + * @param userId 会员编号
  49 + * @param bizType 业务类型
  50 + * @param bizId 业务编号
  51 + */
  52 + void cancelBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId);
  53 +
  54 + /**
  55 + * 解冻佣金:将待结算的佣金记录,状态修改为已结算
  56 + *
  57 + * @return 解冻佣金的数量
  58 + */
  59 + int unfreezeRecord();
  60 +
  61 + /**
  62 + * 汇总用户佣金
  63 + *
  64 + * @param userId 用户编号
  65 + * @param bizType 业务类型
  66 + * @param status 佣金状态
  67 + * @return 用户佣金汇总
  68 + */
  69 + UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status);
  70 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.record;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.BooleanUtil;
  5 +import cn.hutool.core.util.ObjectUtil;
  6 +import cn.hutool.extra.spring.SpringUtil;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
  9 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
  10 +import cn.iocoder.yudao.module.trade.convert.brokerage.record.BrokerageRecordConvert;
  11 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
  12 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  13 +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  14 +import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.BrokerageRecordMapper;
  15 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  16 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
  17 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
  18 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
  19 +import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
  20 +import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.stereotype.Service;
  23 +import org.springframework.transaction.annotation.Transactional;
  24 +import org.springframework.validation.annotation.Validated;
  25 +
  26 +import javax.annotation.Resource;
  27 +import java.time.LocalDateTime;
  28 +import java.util.ArrayList;
  29 +import java.util.List;
  30 +import java.util.function.Function;
  31 +
  32 +/**
  33 + * 佣金记录 Service 实现类
  34 + *
  35 + * @author owen
  36 + */
  37 +@Slf4j
  38 +@Service
  39 +@Validated
  40 +public class BrokerageRecordServiceImpl implements BrokerageRecordService {
  41 +
  42 + @Resource
  43 + private BrokerageRecordMapper brokerageRecordMapper;
  44 + @Resource
  45 + private TradeConfigService tradeConfigService;
  46 + @Resource
  47 + private BrokerageUserService brokerageUserService;
  48 +
  49 + @Override
  50 + public BrokerageRecordDO getBrokerageRecord(Integer id) {
  51 + return brokerageRecordMapper.selectById(id);
  52 + }
  53 +
  54 + @Override
  55 + public PageResult<BrokerageRecordDO> getBrokerageRecordPage(BrokerageRecordPageReqVO pageReqVO) {
  56 + return brokerageRecordMapper.selectPage(pageReqVO);
  57 + }
  58 +
  59 + @Override
  60 + @Transactional(rollbackFor = Exception.class)
  61 + public void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, List<BrokerageAddReqBO> list) {
  62 + TradeConfigDO memberConfig = tradeConfigService.getTradeConfig();
  63 + // 0 未启用分销功能
  64 + if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
  65 + log.warn("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,userId({})", userId);
  66 + return;
  67 + }
  68 +
  69 + // 1.1 获得一级推广人
  70 + BrokerageUserDO firstUser = brokerageUserService.getBindBrokerageUser(userId);
  71 + if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
  72 + return;
  73 + }
  74 + // 1.2 计算一级分佣
  75 + addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(), BrokerageAddReqBO::getFirstFixedPrice, bizType);
  76 +
  77 + // 2.1 获得二级推广员
  78 + if (firstUser.getBindUserId() == null) {
  79 + return;
  80 + }
  81 + BrokerageUserDO secondUser = brokerageUserService.getBrokerageUser(firstUser.getBindUserId());
  82 + if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
  83 + return;
  84 + }
  85 + // 2.2 计算二级分佣
  86 + addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(), BrokerageAddReqBO::getSecondFixedPrice, bizType);
  87 + }
  88 +
  89 + @Override
  90 + @Transactional(rollbackFor = Exception.class)
  91 + public void cancelBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId) {
  92 + // TODO @疯狂:userId 加进去查询,会不会更好一点?万一穿错参数;
  93 + BrokerageRecordDO record = brokerageRecordMapper.selectByBizTypeAndBizId(bizType.getType(), bizId);
  94 + if (record == null || ObjectUtil.notEqual(record.getUserId(), userId)) {
  95 + log.error("[cancelBrokerage][userId({})][bizId({}) 更新为已失效失败:记录不存在]", userId, bizId);
  96 + return;
  97 + }
  98 +
  99 + // 1. 更新佣金记录为已失效
  100 + BrokerageRecordDO updateObj = new BrokerageRecordDO().setStatus(BrokerageRecordStatusEnum.CANCEL.getStatus());
  101 + int updateRows = brokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
  102 + if (updateRows == 0) {
  103 + log.error("[cancelBrokerage][record({}) 更新为已失效失败]", record.getId());
  104 + return;
  105 + }
  106 +
  107 + // 2. 更新用户的佣金
  108 + if (BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus().equals(record.getStatus())) {
  109 + brokerageUserService.updateUserFrozenPrice(userId, -record.getPrice());
  110 + } else if (BrokerageRecordStatusEnum.SETTLEMENT.getStatus().equals(record.getStatus())) {
  111 + brokerageUserService.updateUserPrice(userId, -record.getPrice());
  112 + }
  113 + }
  114 +
  115 + /**
  116 + * 计算佣金
  117 + *
  118 + * @param basePrice 佣金基数
  119 + * @param percent 佣金比例
  120 + * @param fixedPrice 固定佣金
  121 + * @return 佣金
  122 + */
  123 + int calculatePrice(Integer basePrice, Integer percent, Integer fixedPrice) {
  124 + // 1. 优先使用固定佣金
  125 + if (fixedPrice != null && fixedPrice > 0) {
  126 + return ObjectUtil.defaultIfNull(fixedPrice, 0);
  127 + }
  128 + // 2. 根据比例计算佣金
  129 + if (basePrice != null && basePrice > 0 && percent != null && percent > 0) {
  130 + return MoneyUtils.calculateRatePriceFloor(basePrice, Double.valueOf(percent));
  131 + }
  132 + return 0;
  133 + }
  134 +
  135 + /**
  136 + * 增加用户佣金
  137 + *
  138 + * @param user 用户
  139 + * @param list 佣金增加参数列表
  140 + * @param brokerageFrozenDays 冻结天数
  141 + * @param brokeragePercent 佣金比例
  142 + * @param fixedPriceFun 固定佣金 // TODO 疯狂:这里是不是可以直接传递 fixedPrice 呀?
  143 + * @param bizType 业务类型
  144 + */
  145 + private void addBrokerage(BrokerageUserDO user, List<BrokerageAddReqBO> list, Integer brokerageFrozenDays,
  146 + Integer brokeragePercent, Function<BrokerageAddReqBO, Integer> fixedPriceFun,
  147 + BrokerageRecordBizTypeEnum bizType) {
  148 + // 1.1 处理冻结时间
  149 + LocalDateTime unfreezeTime = null;
  150 + if (brokerageFrozenDays != null && brokerageFrozenDays > 0) {
  151 + unfreezeTime = LocalDateTime.now().plusDays(brokerageFrozenDays);
  152 + }
  153 + // 1.2 计算分佣
  154 + int totalBrokerage = 0;
  155 + List<BrokerageRecordDO> records = new ArrayList<>();
  156 + for (BrokerageAddReqBO item : list) {
  157 + int brokeragePerItem = calculatePrice(item.getBasePrice(), brokeragePercent, fixedPriceFun.apply(item));
  158 + if (brokeragePerItem <= 0) {
  159 + continue;
  160 + }
  161 + records.add(BrokerageRecordConvert.INSTANCE.convert(user, bizType, item.getBizId(),
  162 + brokerageFrozenDays, brokeragePerItem, unfreezeTime, bizType.getTitle()));
  163 + totalBrokerage += brokeragePerItem;
  164 + }
  165 + if (CollUtil.isEmpty(records)) {
  166 + return;
  167 + }
  168 + // 1.3 保存佣金记录
  169 + brokerageRecordMapper.insertBatch(records);
  170 +
  171 + // 2. 更新用户佣金
  172 + if (brokerageFrozenDays != null && brokerageFrozenDays > 0) { // 更新用户冻结佣金
  173 + brokerageUserService.updateUserFrozenPrice(user.getId(), totalBrokerage);
  174 + } else { // 更新用户可用佣金
  175 + brokerageUserService.updateUserPrice(user.getId(), totalBrokerage);
  176 + }
  177 + }
  178 +
  179 + @Override
  180 + public int unfreezeRecord() {
  181 + // 1. 查询待结算的佣金记录
  182 + List<BrokerageRecordDO> records = brokerageRecordMapper.selectListByStatusAndUnfreezeTimeLt(
  183 + BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus(), LocalDateTime.now());
  184 + if (CollUtil.isEmpty(records)) {
  185 + return 0;
  186 + }
  187 +
  188 + // 2. 遍历执行
  189 + int count = 0;
  190 + for (BrokerageRecordDO record : records) {
  191 + try {
  192 + boolean success = getSelf().unfreezeRecord(record);
  193 + if (success) {
  194 + count++;
  195 + }
  196 + } catch (Exception e) {
  197 + log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId(), e);
  198 + }
  199 + }
  200 + return count;
  201 + }
  202 +
  203 + @Override
  204 + public UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status) {
  205 + UserBrokerageSummaryBO summaryBO = brokerageRecordMapper.selectCountAndSumPriceByUserIdAndBizTypeAndStatus(userId, bizType, status);
  206 + return summaryBO != null ? summaryBO : new UserBrokerageSummaryBO(0, 0);
  207 + }
  208 +
  209 + @Transactional(rollbackFor = Exception.class)
  210 + public boolean unfreezeRecord(BrokerageRecordDO record) {
  211 + // 更新记录状态
  212 + BrokerageRecordDO updateObj = new BrokerageRecordDO()
  213 + .setStatus(BrokerageRecordStatusEnum.SETTLEMENT.getStatus())
  214 + .setUnfreezeTime(LocalDateTime.now());
  215 + int updateRows = brokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
  216 + if (updateRows == 0) {
  217 + log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId());
  218 + return false;
  219 + }
  220 +
  221 + // 更新用户冻结佣金
  222 + brokerageUserService.updateFrozenPriceDecrAndPriceIncr(record.getUserId(), -record.getPrice());
  223 + log.info("[unfreezeRecord][record({}) 更新为已结算成功]", record.getId());
  224 + return true;
  225 + }
  226 +
  227 + /**
  228 + * 获得自身的代理对象,解决 AOP 生效问题
  229 + *
  230 + * @return 自己
  231 + */
  232 + private BrokerageRecordServiceImpl getSelf() {
  233 + return SpringUtil.getBean(getClass());
  234 + }
  235 +
  236 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.user;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
  5 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  6 +
  7 +import java.util.Collection;
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 分销用户 Service 接口
  12 + *
  13 + * @author owen
  14 + */
  15 +public interface BrokerageUserService {
  16 +
  17 + /**
  18 + * 获得分销用户
  19 + *
  20 + * @param id 编号
  21 + * @return 分销用户
  22 + */
  23 + BrokerageUserDO getBrokerageUser(Long id);
  24 +
  25 + /**
  26 + * 获得分销用户列表
  27 + *
  28 + * @param ids 编号
  29 + * @return 分销用户列表
  30 + */
  31 + List<BrokerageUserDO> getBrokerageUserList(Collection<Long> ids);
  32 +
  33 + /**
  34 + * 获得分销用户分页
  35 + *
  36 + * @param pageReqVO 分页查询
  37 + * @return 分销用户分页
  38 + */
  39 + PageResult<BrokerageUserDO> getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO);
  40 +
  41 + /**
  42 + * 修改推广员编号
  43 + *
  44 + * @param id 用户编号
  45 + * @param bindUserId 推广员编号
  46 + */
  47 + void updateBrokerageUserId(Long id, Long bindUserId);
  48 +
  49 + /**
  50 + * 修改推广资格
  51 + *
  52 + * @param id 用户编号
  53 + * @param enabled 推广资格
  54 + */
  55 + void updateBrokerageUserEnabled(Long id, Boolean enabled);
  56 +
  57 + /**
  58 + * 获得用户的推广人
  59 + *
  60 + * @param id 用户编号
  61 + * @return 用户的推广人
  62 + */
  63 + BrokerageUserDO getBindBrokerageUser(Long id);
  64 +
  65 + /**
  66 + * 更新用户佣金
  67 + *
  68 + * @param id 用户编号
  69 + * @param price 用户可用佣金
  70 + */
  71 + void updateUserPrice(Long id, Integer price);
  72 +
  73 + /**
  74 + * 更新用户冻结佣金
  75 + *
  76 + * @param id 用户编号
  77 + * @param frozenPrice 用户冻结佣金
  78 + */
  79 + void updateUserFrozenPrice(Long id, Integer frozenPrice);
  80 +
  81 + /**
  82 + * 更新用户冻结佣金(减少),更新用户佣金(增加)
  83 + *
  84 + * @param id 用户编号
  85 + * @param frozenPrice 减少冻结佣金(负数)
  86 + */
  87 + void updateFrozenPriceDecrAndPriceIncr(Long id, Integer frozenPrice);
  88 +
  89 + // TODO @疯狂:这个后面可能要支持下,二级
  90 + /**
  91 + * 获得推广用户数量(一级)
  92 + *
  93 + * @param bindUserId 绑定的推广员编号
  94 + * @return 推广用户数量
  95 + */
  96 + Long getBrokerageUserCountByBindUserId(Long bindUserId);
  97 +
  98 + /**
  99 + * 【会员】绑定推广员
  100 + *
  101 + * @param userId 用户编号
  102 + * @param bindUserId 推广员编号
  103 + * @param isNewUser 是否为新用户
  104 + * @return 是否绑定
  105 + */
  106 + boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser);
  107 +
  108 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.user;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.BooleanUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  9 +import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.BrokerageUserMapper;
  10 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
  11 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
  12 +import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
  13 +import org.springframework.stereotype.Service;
  14 +import org.springframework.validation.annotation.Validated;
  15 +
  16 +import javax.annotation.Resource;
  17 +import java.time.LocalDateTime;
  18 +import java.util.Collection;
  19 +import java.util.List;
  20 +import java.util.Objects;
  21 +import java.util.Optional;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  24 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
  25 +
  26 +/**
  27 + * 分销用户 Service 实现类
  28 + *
  29 + * @author owen
  30 + */
  31 +@Service
  32 +@Validated
  33 +public class BrokerageUserServiceImpl implements BrokerageUserService {
  34 +
  35 + @Resource
  36 + private BrokerageUserMapper brokerageUserMapper;
  37 +
  38 + @Resource
  39 + private TradeConfigService tradeConfigService;
  40 +
  41 + @Override
  42 + public BrokerageUserDO getBrokerageUser(Long id) {
  43 + return brokerageUserMapper.selectById(id);
  44 + }
  45 +
  46 + @Override
  47 + public List<BrokerageUserDO> getBrokerageUserList(Collection<Long> ids) {
  48 + return brokerageUserMapper.selectBatchIds(ids);
  49 + }
  50 +
  51 + @Override
  52 + public PageResult<BrokerageUserDO> getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO) {
  53 + return brokerageUserMapper.selectPage(pageReqVO);
  54 + }
  55 +
  56 + @Override
  57 + public void updateBrokerageUserId(Long id, Long bindUserId) {
  58 + // 校验存在
  59 + validateBrokerageUserExists(id);
  60 +
  61 + // 情况一:清除推广员
  62 + if (bindUserId == null) {
  63 + // 清除推广员
  64 + brokerageUserMapper.updateBindUserIdAndBindUserTimeToNull(id);
  65 + return;
  66 + }
  67 +
  68 + // 情况二:修改推广员
  69 + // TODO @疯狂:要复用一些 validateCanBindUser 的校验哈;
  70 + brokerageUserMapper.updateById(new BrokerageUserDO().setId(id)
  71 + .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
  72 + }
  73 +
  74 + @Override
  75 + public void updateBrokerageUserEnabled(Long id, Boolean enabled) {
  76 + // 校验存在
  77 + validateBrokerageUserExists(id);
  78 + if (BooleanUtil.isTrue(enabled)) {
  79 + // 开通推广资格
  80 + brokerageUserMapper.updateById(new BrokerageUserDO().setId(id)
  81 + .setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now()));
  82 + } else {
  83 + // 取消推广资格
  84 + brokerageUserMapper.updateEnabledFalseAndBrokerageTimeToNull(id);
  85 + }
  86 + }
  87 +
  88 + private void validateBrokerageUserExists(Long id) {
  89 + if (brokerageUserMapper.selectById(id) == null) {
  90 + throw exception(BROKERAGE_USER_NOT_EXISTS);
  91 + }
  92 + }
  93 +
  94 + @Override
  95 + public BrokerageUserDO getBindBrokerageUser(Long id) {
  96 + return Optional.ofNullable(id)
  97 + .map(this::getBrokerageUser)
  98 + .map(BrokerageUserDO::getBindUserId)
  99 + .map(this::getBrokerageUser)
  100 + .orElse(null);
  101 + }
  102 +
  103 + @Override
  104 + public void updateUserPrice(Long id, Integer price) {
  105 + if (price > 0) {
  106 + brokerageUserMapper.updatePriceIncr(id, price);
  107 + } else if (price < 0) {
  108 + brokerageUserMapper.updatePriceDecr(id, price);
  109 + }
  110 + }
  111 +
  112 + @Override
  113 + public void updateUserFrozenPrice(Long id, Integer frozenPrice) {
  114 + if (frozenPrice > 0) {
  115 + brokerageUserMapper.updateFrozenPriceIncr(id, frozenPrice);
  116 + } else if (frozenPrice < 0) {
  117 + brokerageUserMapper.updateFrozenPriceDecr(id, frozenPrice);
  118 + }
  119 + }
  120 +
  121 + @Override
  122 + public void updateFrozenPriceDecrAndPriceIncr(Long id, Integer frozenPrice) {
  123 + Assert.isTrue(frozenPrice < 0);
  124 + int updateRows = brokerageUserMapper.updateFrozenPriceDecrAndPriceIncr(id, frozenPrice);
  125 + if (updateRows == 0) {
  126 + throw exception(BROKERAGE_USER_FROZEN_PRICE_NOT_ENOUGH);
  127 + }
  128 + }
  129 +
  130 + @Override
  131 + public Long getBrokerageUserCountByBindUserId(Long bindUserId) {
  132 + // TODO @疯狂:mapper 封装下哈;不直接在 service 调用这种基础 mapper 的基础方法
  133 + return brokerageUserMapper.selectCount(BrokerageUserDO::getBindUserId, bindUserId);
  134 + }
  135 +
  136 + // TODO @疯狂:因为现在 user 会存在使用验证码直接注册,所以 isNewUser 不太好传递;我们是不是可以约定绑定的时间,createTime 在 30 秒内,就认为新用户;
  137 + @Override
  138 + public boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser) {
  139 + // TODO @疯狂:userId 为空,搞到参数校验里哇;
  140 + if (userId == null) {
  141 + throw exception(0);
  142 + }
  143 +
  144 + // 1. 获得分销用户
  145 + boolean isNewBrokerageUser = false;
  146 + BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(userId);
  147 + if (brokerageUser == null) { // 分销用户不存在的情况:1. 新注册;2. 旧数据;3. 分销功能关闭后又打开
  148 + isNewBrokerageUser = true;
  149 + brokerageUser = new BrokerageUserDO().setId(userId).setBrokerageEnabled(false).setBrokeragePrice(0).setFrozenPrice(0);
  150 + }
  151 +
  152 + // 2.1 校验能否绑定
  153 + boolean validated = validateCanBindUser(brokerageUser, bindUserId, isNewUser);
  154 + if (!validated) {
  155 + return false;
  156 + }
  157 +
  158 + // 2.2 绑定用户
  159 + if (isNewBrokerageUser) {
  160 + Integer enabledCondition = tradeConfigService.getTradeConfig().getBrokerageEnabledCondition();
  161 + if (BrokerageEnabledConditionEnum.ALL.getCondition().equals(enabledCondition)) { // 人人分销:用户默认就有分销资格
  162 + // TODO @疯狂:应该设置下 brokerageTime,而不是 bindUserTime
  163 + brokerageUser.setBrokerageEnabled(true).setBindUserTime(LocalDateTime.now());
  164 + }
  165 + // TODO @疯狂:这里是不是要设置 bindUserId、bindUserTime 字段哈;
  166 + brokerageUserMapper.insert(brokerageUser);
  167 + } else {
  168 + brokerageUserMapper.updateById(new BrokerageUserDO().setId(userId)
  169 + .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
  170 + }
  171 + return true;
  172 + }
  173 +
  174 + // TODO @疯狂:validate 方法,一般不返回 true false,而是抛出异常;如果要返回 true false 这种,方法名字可以改成 isUserCanBind
  175 + private boolean validateCanBindUser(BrokerageUserDO user, Long bindUserId, Boolean isNewUser) {
  176 + // TODO @疯狂:bindUserId 为空,搞到参数校验里哇;
  177 + if (bindUserId == null) {
  178 + return false;
  179 + }
  180 +
  181 + // 校验分销功能是否启用
  182 + TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
  183 + if (tradeConfig == null || BooleanUtil.isFalse(tradeConfig.getBrokerageEnabled())) {
  184 + return false;
  185 + }
  186 +
  187 + // 校验绑定自己
  188 + if (Objects.equals(user.getId(), bindUserId)) {
  189 + throw exception(BROKERAGE_BIND_SELF);
  190 + }
  191 +
  192 + // 校验要绑定的用户有无推广资格
  193 + BrokerageUserDO bindUser = brokerageUserMapper.selectById(bindUserId);
  194 + if (bindUser == null || BooleanUtil.isFalse(bindUser.getBrokerageEnabled())) {
  195 + throw exception(BROKERAGE_BIND_USER_NOT_ENABLED);
  196 + }
  197 +
  198 + // 校验分佣模式:仅可后台手动设置推广员
  199 + if (BrokerageEnabledConditionEnum.ADMIN.getCondition().equals(tradeConfig.getBrokerageEnabledCondition())) {
  200 + throw exception(BROKERAGE_BIND_CONDITION_ADMIN);
  201 + }
  202 +
  203 + // 校验分销关系绑定模式
  204 + if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) {
  205 + if (!BooleanUtil.isTrue(isNewUser)) {
  206 + throw exception(BROKERAGE_BIND_MODE_REGISTER); // 只有在注册时可以绑定
  207 + }
  208 + } else if (BrokerageBindModeEnum.ANYTIME.getMode().equals(tradeConfig.getBrokerageBindMode())) {
  209 + if (user.getBindUserId() != null) {
  210 + throw exception(BROKERAGE_BIND_OVERRIDE); // 已绑定了推广人
  211 + }
  212 + }
  213 +
  214 + // TODO @疯狂:这块是不是一直查询到根节点,中间不允许出现自己;就是不能形成环。虽然目前是 2 级,但是未来可能会改多级; = = 环的话,就会存在问题哈
  215 + // A->B->A:下级不能绑定自己的上级, A->B->C->A可以!!
  216 + if (Objects.equals(user.getId(), bindUser.getBindUserId())) {
  217 + throw exception(BROKERAGE_BIND_LOOP);
  218 + }
  219 + return true;
  220 + }
  221 +
  222 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.config;
  2 +
  3 +import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  5 +
  6 +import javax.validation.Valid;
  7 +
  8 +/**
  9 + * 交易中心配置 Service 接口
  10 + *
  11 + * @author owen
  12 + */
  13 +public interface TradeConfigService {
  14 +
  15 + /**
  16 + * 更新交易中心配置
  17 + *
  18 + * @param updateReqVO 更新信息
  19 + */
  20 + void saveTradeConfig(@Valid TradeConfigSaveReqVO updateReqVO);
  21 +
  22 + /**
  23 + * 获得交易中心配置
  24 + *
  25 + * @return 交易中心配置
  26 + */
  27 + TradeConfigDO getTradeConfig();
  28 +
  29 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.config;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  4 +import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
  5 +import cn.iocoder.yudao.module.trade.convert.config.TradeConfigConvert;
  6 +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  7 +import cn.iocoder.yudao.module.trade.dal.mysql.config.TradeConfigMapper;
  8 +import org.springframework.stereotype.Service;
  9 +import org.springframework.validation.annotation.Validated;
  10 +
  11 +import javax.annotation.Resource;
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * 交易中心配置 Service 实现类
  16 + *
  17 + * @author owen
  18 + */
  19 +@Service
  20 +@Validated
  21 +public class TradeConfigServiceImpl implements TradeConfigService {
  22 +
  23 + @Resource
  24 + private TradeConfigMapper tradeConfigMapper;
  25 +
  26 + @Override
  27 + public void saveTradeConfig(TradeConfigSaveReqVO saveReqVO) {
  28 + // 存在,则进行更新
  29 + TradeConfigDO dbConfig = getTradeConfig();
  30 + if (dbConfig != null) {
  31 + tradeConfigMapper.updateById(TradeConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
  32 + return;
  33 + }
  34 + // 不存在,则进行插入
  35 + tradeConfigMapper.insert(TradeConfigConvert.INSTANCE.convert(saveReqVO));
  36 + }
  37 +
  38 + @Override
  39 + public TradeConfigDO getTradeConfig() {
  40 + List<TradeConfigDO> list = tradeConfigMapper.selectList();
  41 + return CollectionUtils.getFirst(list);
  42 + }
  43 +
  44 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.service.order; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.service.order;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 -import cn.hutool.core.util.IdUtil;  
6 import cn.hutool.core.util.ObjectUtil; 5 import cn.hutool.core.util.ObjectUtil;
7 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
8 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
@@ -25,12 +24,14 @@ import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; @@ -25,12 +24,14 @@ import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
25 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; 24 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
26 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; 25 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
27 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; 26 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
  27 +import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
28 import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi; 28 import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
29 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi; 29 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
30 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO; 30 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
31 -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;  
32 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; 31 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
33 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; 32 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
  33 +import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
  34 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
34 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; 35 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
35 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; 36 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
36 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO; 37 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
@@ -46,10 +47,14 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; @@ -46,10 +47,14 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
46 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; 47 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
47 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; 48 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
48 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; 49 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
  50 +import cn.iocoder.yudao.module.trade.dal.redis.no.TradeOrderNoRedisDAO;
49 import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants; 51 import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
  52 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
50 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; 53 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
51 import cn.iocoder.yudao.module.trade.enums.order.*; 54 import cn.iocoder.yudao.module.trade.enums.order.*;
52 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; 55 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
  56 +import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
  57 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
53 import cn.iocoder.yudao.module.trade.service.cart.CartService; 58 import cn.iocoder.yudao.module.trade.service.cart.CartService;
54 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; 59 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
55 import cn.iocoder.yudao.module.trade.service.message.TradeMessageService; 60 import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
@@ -71,7 +76,7 @@ import java.util.Set; @@ -71,7 +76,7 @@ import java.util.Set;
71 76
72 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 77 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
73 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 78 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
74 -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_NOT_FOUND; 79 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_UPDATE_PRICE_FAIL_EQUAL;
75 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_UPDATE_PRICE_FAIL_PAID; 80 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_UPDATE_PRICE_FAIL_PAID;
76 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; 81 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
77 82
@@ -89,6 +94,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -89,6 +94,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
89 private TradeOrderMapper tradeOrderMapper; 94 private TradeOrderMapper tradeOrderMapper;
90 @Resource 95 @Resource
91 private TradeOrderItemMapper tradeOrderItemMapper; 96 private TradeOrderItemMapper tradeOrderItemMapper;
  97 + @Resource
  98 + private TradeOrderNoRedisDAO orderNoRedisDAO;
92 99
93 @Resource 100 @Resource
94 private CartService cartService; 101 private CartService cartService;
@@ -112,12 +119,18 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -112,12 +119,18 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
112 @Resource 119 @Resource
113 private BargainRecordApi bargainRecordApi; 120 private BargainRecordApi bargainRecordApi;
114 @Resource 121 @Resource
  122 + private SeckillActivityApi seckillActivityApi;
  123 + @Resource
  124 + private BargainActivityApi bargainActivityApi;
  125 + @Resource
115 private MemberUserApi memberUserApi; 126 private MemberUserApi memberUserApi;
116 @Resource 127 @Resource
117 private MemberLevelApi memberLevelApi; 128 private MemberLevelApi memberLevelApi;
118 @Resource 129 @Resource
119 private MemberPointApi memberPointApi; 130 private MemberPointApi memberPointApi;
120 @Resource 131 @Resource
  132 + private BrokerageRecordService brokerageRecordService;
  133 + @Resource
121 private ProductCommentApi productCommentApi; 134 private ProductCommentApi productCommentApi;
122 135
123 @Resource 136 @Resource
@@ -190,22 +203,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -190,22 +203,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
190 // TODO @puhui999:这个逻辑,先抽个小方法;未来要通过设计模式,把这些拼团之类的逻辑,抽象出去 203 // TODO @puhui999:这个逻辑,先抽个小方法;未来要通过设计模式,把这些拼团之类的逻辑,抽象出去
191 // 拼团 204 // 拼团
192 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) { 205 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
193 - MemberUserRespDTO user = memberUserApi.getUser(userId);  
194 - List<CombinationRecordRespDTO> recordRespDTOS = combinationRecordApi.getRecordListByUserIdAndActivityId(userId, createReqVO.getCombinationActivityId());  
195 - // TODO 拼团一次应该只能选择一种规格的商品  
196 - TradeOrderItemDO orderItemDO = orderItems.get(0);  
197 - if (CollUtil.isNotEmpty(recordRespDTOS)) {  
198 - List<Long> skuIds = convertList(recordRespDTOS, CombinationRecordRespDTO::getSkuId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));  
199 - List<TradeOrderItemDO> tradeOrderItemDOS = tradeOrderItemMapper.selectListByOrderIdAnSkuId(convertList(recordRespDTOS,  
200 - CombinationRecordRespDTO::getOrderId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);  
201 - combinationRecordApi.validateCombinationLimitCount(createReqVO.getCombinationActivityId(),  
202 - CollectionUtils.getSumValue(tradeOrderItemDOS, TradeOrderItemDO::getCount, Integer::sum), orderItemDO.getCount());  
203 - }  
204 -  
205 - combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user)); 206 + createCombinationRecord(userId, createReqVO, orderItems, order);
206 } 207 }
207 // 3.2 秒杀的特殊逻辑 208 // 3.2 秒杀的特殊逻辑
208 - // TODO 秒杀扣减库存是下单就扣除还是等待订单支付成功再扣除  
209 if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) { 209 if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) {
210 210
211 } 211 }
@@ -215,6 +215,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -215,6 +215,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
215 return order; 215 return order;
216 } 216 }
217 217
  218 + private void createCombinationRecord(Long userId, AppTradeOrderCreateReqVO createReqVO, List<TradeOrderItemDO> orderItems, TradeOrderDO order) {
  219 + MemberUserRespDTO user = memberUserApi.getUser(userId);
  220 + List<CombinationRecordRespDTO> recordRespDTOS = combinationRecordApi.getRecordListByUserIdAndActivityId(userId, createReqVO.getCombinationActivityId());
  221 + // TODO 拼团一次应该只能选择一种规格的商品
  222 + TradeOrderItemDO orderItemDO = orderItems.get(0);
  223 + if (CollUtil.isNotEmpty(recordRespDTOS)) {
  224 + List<Long> skuIds = convertList(recordRespDTOS, CombinationRecordRespDTO::getSkuId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));
  225 + List<TradeOrderItemDO> tradeOrderItemDOS = tradeOrderItemMapper.selectListByOrderIdAnSkuId(convertList(recordRespDTOS,
  226 + CombinationRecordRespDTO::getOrderId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);
  227 + combinationRecordApi.validateCombinationLimitCount(createReqVO.getCombinationActivityId(),
  228 + CollectionUtils.getSumValue(tradeOrderItemDOS, TradeOrderItemDO::getCount, Integer::sum), orderItemDO.getCount());
  229 + }
  230 +
  231 + combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user));
  232 + }
  233 +
218 // TODO @puhui999:订单超时,自动取消; 234 // TODO @puhui999:订单超时,自动取消;
219 235
220 /** 236 /**
@@ -241,8 +257,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -241,8 +257,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
241 address = validateAddress(userId, createReqVO.getAddressId()); 257 address = validateAddress(userId, createReqVO.getAddressId());
242 } 258 }
243 TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address); 259 TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
  260 + String no = orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX);
244 order.setType(validateActivity(createReqVO)); 261 order.setType(validateActivity(createReqVO));
245 - order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @puhui999: 参考支付订单,的 no 生成哈; 262 + order.setNo(no);
246 order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); 263 order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
247 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); 264 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
248 order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); 265 order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
@@ -294,19 +311,23 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -294,19 +311,23 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
294 private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO, 311 private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
295 TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems, 312 TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems,
296 TradePriceCalculateRespBO calculateRespBO) { 313 TradePriceCalculateRespBO calculateRespBO) {
297 - // 下单时扣减商品库存  
298 - // TODO @puhui999:扣库存,需要前置; 314 + Integer count = getSumValue(orderItems, TradeOrderItemDO::getCount, Integer::sum);
299 // 1)如果是秒杀商品:额外扣减秒杀的库存; 315 // 1)如果是秒杀商品:额外扣减秒杀的库存;
300 - // 2)如果是拼团活动:额外扣减拼团的库存;  
301 - // 3)如果是砍价活动:额外扣减砍价的库存;  
302 - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));  
303 -  
304 - // 删除购物车商品  
305 - Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);  
306 - if (CollUtil.isNotEmpty(cartIds)) {  
307 - cartService.deleteCart(userId, cartIds);  
308 - }  
309 - 316 + if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), tradeOrderDO.getType())) {
  317 + SeckillActivityUpdateStockReqDTO updateStockReqDTO = new SeckillActivityUpdateStockReqDTO();
  318 + updateStockReqDTO.setActivityId(createReqVO.getSeckillActivityId());
  319 + updateStockReqDTO.setCount(count);
  320 + updateStockReqDTO.setItems(CollectionUtils.convertList(orderItems, item -> {
  321 + SeckillActivityUpdateStockReqDTO.Item item1 = new SeckillActivityUpdateStockReqDTO.Item();
  322 + item1.setSpuId(item.getSpuId());
  323 + item1.setSkuId(item.getSkuId());
  324 + item1.setCount(item.getCount());
  325 + return item1;
  326 + }));
  327 + seckillActivityApi.updateSeckillStock(updateStockReqDTO);
  328 + }
  329 + // 2)如果是砍价活动:额外扣减砍价的库存;
  330 + bargainActivityApi.updateBargainActivityStock(createReqVO.getBargainActivityId(), count);
310 // 扣减积分 TODO 芋艿:待实现,需要前置; 331 // 扣减积分 TODO 芋艿:待实现,需要前置;
311 // 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣 332 // 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣
312 333
@@ -316,6 +337,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -316,6 +337,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
316 .setOrderId(tradeOrderDO.getId())); 337 .setOrderId(tradeOrderDO.getId()));
317 } 338 }
318 339
  340 + // 下单时扣减商品库存
  341 + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
  342 +
  343 + // 删除购物车商品
  344 + Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
  345 + if (CollUtil.isNotEmpty(cartIds)) {
  346 + cartService.deleteCart(userId, cartIds);
  347 + }
  348 +
319 // 生成预支付 349 // 生成预支付
320 createPayOrder(tradeOrderDO, orderItems, calculateRespBO); 350 createPayOrder(tradeOrderDO, orderItems, calculateRespBO);
321 351
@@ -352,8 +382,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -352,8 +382,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
352 // 1、拼团活动 382 // 1、拼团活动
353 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) { 383 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
354 // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录 384 // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
355 - combinationRecordApi.updateCombinationRecordStatus(new CombinationRecordUpdateStatusReqDTO().setUserId(order.getUserId())  
356 - .setOrderId(order.getId()).setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()).setStartTime(LocalDateTime.now())); 385 + combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now());
357 } 386 }
358 // TODO 芋艿:发送订单变化的消息 387 // TODO 芋艿:发送订单变化的消息
359 388
@@ -365,6 +394,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -365,6 +394,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
365 getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId()); 394 getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId());
366 // 增加用户经验 395 // 增加用户经验
367 getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId()); 396 getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
  397 + // 增加用户佣金
  398 + getSelf().addBrokerageAsync(order.getUserId(), order.getId());
368 } 399 }
369 400
370 /** 401 /**
@@ -466,11 +497,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -466,11 +497,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
466 */ 497 */
467 private TradeOrderDO validateOrderDeliverable(Long id) { 498 private TradeOrderDO validateOrderDeliverable(Long id) {
468 TradeOrderDO order = validateOrderExists(id); 499 TradeOrderDO order = validateOrderExists(id);
469 - // 校验订单是否是待发货状态  
470 - // TODO @puhui999:已经发货,可以重新发货,修改信息;  
471 - if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())) {  
472 - throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);  
473 - }  
474 // 校验订单是否退款 500 // 校验订单是否退款
475 if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { 501 if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
476 throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE); 502 throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE);
@@ -536,20 +562,35 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -536,20 +562,35 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
536 } 562 }
537 563
538 @Override 564 @Override
  565 + // TODO @puhui999:考虑事务性
539 public void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO) { 566 public void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO) {
540 // 校验交易订单 567 // 校验交易订单
541 TradeOrderDO order = validateOrderExists(reqVO.getId()); 568 TradeOrderDO order = validateOrderExists(reqVO.getId());
542 if (order.getPayStatus()) { 569 if (order.getPayStatus()) {
543 throw exception(ORDER_UPDATE_PRICE_FAIL_PAID); 570 throw exception(ORDER_UPDATE_PRICE_FAIL_PAID);
544 } 571 }
545 - // TODO @puhui999:如果改价,需要校验下是否真的变化;  
546 -  
547 - // 更新  
548 - // TODO @puhui999:TradeOrderItemDO 需要做 adjustPrice 的分摊;另外,支付订单那的价格,需要 update 下; 572 + if (ObjectUtil.equal(order.getAdjustPrice(), reqVO.getAdjustPrice())) {
  573 + throw exception(ORDER_UPDATE_PRICE_FAIL_EQUAL);
  574 + }
  575 +
  576 + // TODO @puhui999:应该是按照 payPrice 分配;并且要考虑取余问题;payPrice 也要考虑,item 里的
  577 + List<TradeOrderItemDO> itemDOs = tradeOrderItemMapper.selectListByOrderId(order.getId());
  578 + // TradeOrderItemDO 需要做 adjustPrice 的分摊
  579 + int price = reqVO.getAdjustPrice() / itemDOs.size();
  580 + itemDOs.forEach(item -> {
  581 + item.setAdjustPrice(price);
  582 + });
  583 + // 更新 TradeOrderItem
  584 + // TODO @puhui999:不要整个对象去更新哈;应该 new 一下;
  585 + tradeOrderItemMapper.updateBatch(itemDOs);
  586 + // 更新订单
  587 + // TODO @puhui999:要考虑多次修改价格,不能单单的 payPrice + 价格;
549 TradeOrderDO update = TradeOrderConvert.INSTANCE.convert(reqVO); 588 TradeOrderDO update = TradeOrderConvert.INSTANCE.convert(reqVO);
550 update.setPayPrice(update.getPayPrice() + update.getAdjustPrice()); 589 update.setPayPrice(update.getPayPrice() + update.getAdjustPrice());
551 // TODO @芋艿:改价时,赠送的积分,要不要做改动??? 590 // TODO @芋艿:改价时,赠送的积分,要不要做改动???
552 tradeOrderMapper.updateById(update); 591 tradeOrderMapper.updateById(update);
  592 + // 更新支付订单
  593 + payOrderApi.updatePayOrderPriceById(order.getPayOrderId(), update.getPayPrice());
553 } 594 }
554 595
555 @Override 596 @Override
@@ -629,12 +670,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -629,12 +670,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
629 .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice)); 670 .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
630 } 671 }
631 672
632 - // TODO 芋艿:未来如果有分佣,需要更新相关分佣订单为已失效  
633 -  
634 // 扣减用户积分 673 // 扣减用户积分
635 getSelf().reduceUserPointAsync(order.getUserId(), orderRefundPrice, afterSaleId); 674 getSelf().reduceUserPointAsync(order.getUserId(), orderRefundPrice, afterSaleId);
636 // 扣减用户经验 675 // 扣减用户经验
637 getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId); 676 getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
  677 + // 更新分佣记录为已失效
  678 + getSelf().cancelBrokerageAsync(order.getUserId(), id);
638 } 679 }
639 680
640 @Override 681 @Override
@@ -742,6 +783,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -742,6 +783,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
742 memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId)); 783 memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
743 } 784 }
744 785
  786 +
  787 + @Async
  788 + protected void addBrokerageAsync(Long userId, Long orderId) {
  789 + List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
  790 + List<BrokerageAddReqBO> list = convertList(orderItems,
  791 + item -> TradeOrderConvert.INSTANCE.convert(item, productSkuApi.getSku(item.getSkuId())));
  792 + brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, list);
  793 + }
  794 +
  795 + @Async
  796 + protected void cancelBrokerageAsync(Long userId, Long orderItemId) {
  797 + brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
  798 + }
  799 +
745 /** 800 /**
746 * 获得自身的代理对象,解决 AOP 生效问题 801 * 获得自身的代理对象,解决 AOP 生效问题
747 * 802 *
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.record;
  2 +
  3 +import cn.hutool.core.util.NumberUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
  8 +import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.BrokerageRecordMapper;
  9 +import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
  10 +import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
  11 +import org.junit.jupiter.api.Disabled;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.springframework.boot.test.mock.mockito.MockBean;
  14 +import org.springframework.context.annotation.Import;
  15 +
  16 +import javax.annotation.Resource;
  17 +import java.math.RoundingMode;
  18 +
  19 +import static cn.hutool.core.util.RandomUtil.randomEle;
  20 +import static cn.hutool.core.util.RandomUtil.randomInt;
  21 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  22 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  23 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  24 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomInteger;
  25 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  26 +import static org.junit.jupiter.api.Assertions.assertEquals;
  27 +
  28 +// TODO @芋艿:单测后续看看
  29 +/**
  30 + * {@link BrokerageRecordServiceImpl} 的单元测试类
  31 + *
  32 + * @author owen
  33 + */
  34 +@Import(BrokerageRecordServiceImpl.class)
  35 +public class BrokerageRecordServiceImplTest extends BaseDbUnitTest {
  36 +
  37 + @Resource
  38 + private BrokerageRecordServiceImpl brokerageRecordService;
  39 + @Resource
  40 + private BrokerageRecordMapper brokerageRecordMapper;
  41 +
  42 + @MockBean
  43 + private TradeConfigService tradeConfigService;
  44 + @MockBean
  45 + private BrokerageUserService brokerageUserService;
  46 +
  47 + @Test
  48 + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
  49 + public void testGetBrokerageRecordPage() {
  50 + // mock 数据
  51 + BrokerageRecordDO dbBrokerageRecord = randomPojo(BrokerageRecordDO.class, o -> { // 等会查询到
  52 + o.setUserId(null);
  53 + o.setBizType(null);
  54 + o.setStatus(null);
  55 + o.setCreateTime(null);
  56 + });
  57 + brokerageRecordMapper.insert(dbBrokerageRecord);
  58 + // 测试 userId 不匹配
  59 + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setUserId(null)));
  60 + // 测试 bizType 不匹配
  61 + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setBizType(null)));
  62 + // 测试 status 不匹配
  63 + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setStatus(null)));
  64 + // 测试 createTime 不匹配
  65 + brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setCreateTime(null)));
  66 + // 准备参数
  67 + BrokerageRecordPageReqVO reqVO = new BrokerageRecordPageReqVO();
  68 + reqVO.setUserId(null);
  69 + reqVO.setBizType(null);
  70 + reqVO.setStatus(null);
  71 + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
  72 +
  73 + // 调用
  74 + PageResult<BrokerageRecordDO> pageResult = brokerageRecordService.getBrokerageRecordPage(reqVO);
  75 + // 断言
  76 + assertEquals(1, pageResult.getTotal());
  77 + assertEquals(1, pageResult.getList().size());
  78 + assertPojoEquals(dbBrokerageRecord, pageResult.getList().get(0));
  79 + }
  80 +
  81 + @Test
  82 + public void testCalculatePrice_useFixedPrice() {
  83 + // mock 数据
  84 + Integer payPrice = randomInteger();
  85 + Integer percent = randomInt(1, 101);
  86 + Integer fixedPrice = randomInt();
  87 + // 调用
  88 + int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice);
  89 + // 断言
  90 + assertEquals(brokerage, fixedPrice);
  91 + }
  92 +
  93 + @Test
  94 + public void testCalculatePrice_usePercent() {
  95 + // mock 数据
  96 + Integer payPrice = randomInteger();
  97 + Integer percent = randomInt(1, 101);
  98 + Integer fixedPrice = randomEle(new Integer[]{0, null});
  99 + System.out.println("fixedPrice=" + fixedPrice);
  100 + // 调用
  101 + int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice);
  102 + // 断言
  103 + assertEquals(brokerage, NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue());
  104 + }
  105 +
  106 + @Test
  107 + public void testCalculatePrice_equalsZero() {
  108 + // mock 数据
  109 + Integer payPrice = null;
  110 + Integer percent = null;
  111 + Integer fixedPrice = null;
  112 + // 调用
  113 + int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice);
  114 + // 断言
  115 + assertEquals(brokerage, 0);
  116 + }
  117 +}
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.user;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  5 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
  6 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
  7 +import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.BrokerageUserMapper;
  8 +import org.junit.jupiter.api.Disabled;
  9 +import org.junit.jupiter.api.Test;
  10 +import org.springframework.context.annotation.Import;
  11 +
  12 +import javax.annotation.Resource;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  15 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  16 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  17 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  18 +import static org.junit.jupiter.api.Assertions.assertEquals;
  19 +
  20 +// TODO @芋艿:单测后续看看
  21 +/**
  22 + * {@link BrokerageUserServiceImpl} 的单元测试类
  23 + *
  24 + * @author owen
  25 + */
  26 +@Import(BrokerageUserServiceImpl.class)
  27 +public class BrokerageUserServiceImplTest extends BaseDbUnitTest {
  28 +
  29 + @Resource
  30 + private BrokerageUserServiceImpl brokerageUserService;
  31 +
  32 + @Resource
  33 + private BrokerageUserMapper brokerageUserMapper;
  34 +
  35 + @Test
  36 + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
  37 + public void testGetBrokerageUserPage() {
  38 + // mock 数据
  39 + BrokerageUserDO dbBrokerageUser = randomPojo(BrokerageUserDO.class, o -> { // 等会查询到
  40 + o.setBindUserId(null);
  41 + o.setBrokerageEnabled(null);
  42 + o.setCreateTime(null);
  43 + });
  44 + brokerageUserMapper.insert(dbBrokerageUser);
  45 + // 测试 brokerageUserId 不匹配
  46 + brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBindUserId(null)));
  47 + // 测试 brokerageEnabled 不匹配
  48 + brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBrokerageEnabled(null)));
  49 + // 测试 createTime 不匹配
  50 + brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setCreateTime(null)));
  51 + // 准备参数
  52 + BrokerageUserPageReqVO reqVO = new BrokerageUserPageReqVO();
  53 + reqVO.setBindUserId(null);
  54 + reqVO.setBrokerageEnabled(null);
  55 + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
  56 +
  57 + // 调用
  58 + PageResult<BrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(reqVO);
  59 + // 断言
  60 + assertEquals(1, pageResult.getTotal());
  61 + assertEquals(1, pageResult.getList().size());
  62 + assertPojoEquals(dbBrokerageUser, pageResult.getList().get(0));
  63 + }
  64 +
  65 +}
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
@@ -2,3 +2,5 @@ DELETE FROM trade_order; @@ -2,3 +2,5 @@ DELETE FROM trade_order;
2 DELETE FROM trade_order_item; 2 DELETE FROM trade_order_item;
3 DELETE FROM trade_after_sale; 3 DELETE FROM trade_after_sale;
4 DELETE FROM trade_after_sale_log; 4 DELETE FROM trade_after_sale_log;
  5 +DELETE FROM trade_brokerage_user;
  6 +DELETE FROM trade_brokerage_record;
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
@@ -125,3 +125,42 @@ CREATE TABLE IF NOT EXISTS &quot;trade_after_sale_log&quot; ( @@ -125,3 +125,42 @@ CREATE TABLE IF NOT EXISTS &quot;trade_after_sale_log&quot; (
125 "deleted" bit NOT NULL DEFAULT FALSE, 125 "deleted" bit NOT NULL DEFAULT FALSE,
126 PRIMARY KEY ("id") 126 PRIMARY KEY ("id")
127 ) COMMENT '交易售后日志'; 127 ) COMMENT '交易售后日志';
  128 +
  129 +CREATE TABLE IF NOT EXISTS "trade_brokerage_user"
  130 +(
  131 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  132 + "bind_user_id" bigint NOT NULL,
  133 + "bind_user_time" varchar,
  134 + "brokerage_enabled" bit NOT NULL,
  135 + "brokerage_time" varchar,
  136 + "price" int NOT NULL,
  137 + "frozen_price" int NOT NULL,
  138 + "creator" varchar DEFAULT '',
  139 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  140 + "updater" varchar DEFAULT '',
  141 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  142 + "deleted" bit NOT NULL DEFAULT FALSE,
  143 + "tenant_id" bigint NOT NULL DEFAULT '0',
  144 + PRIMARY KEY ("id")
  145 +) COMMENT '分销用户';
  146 +CREATE TABLE IF NOT EXISTS "trade_brokerage_record"
  147 +(
  148 + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  149 + "user_id" bigint NOT NULL,
  150 + "biz_id" varchar NOT NULL,
  151 + "biz_type" varchar NOT NULL,
  152 + "title" varchar NOT NULL,
  153 + "price" int NOT NULL,
  154 + "total_price" int NOT NULL,
  155 + "description" varchar NOT NULL,
  156 + "status" varchar NOT NULL,
  157 + "frozen_days" int NOT NULL,
  158 + "unfreeze_time" varchar,
  159 + "creator" varchar DEFAULT '',
  160 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  161 + "updater" varchar DEFAULT '',
  162 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  163 + "deleted" bit NOT NULL DEFAULT FALSE,
  164 + "tenant_id" bigint not null default '0',
  165 + PRIMARY KEY ("id")
  166 +) COMMENT '佣金记录';
128 \ No newline at end of file 167 \ No newline at end of file
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
@@ -28,6 +28,17 @@ tenant-id: {{appTenentId}} @@ -28,6 +28,17 @@ tenant-id: {{appTenentId}}
28 "code": 9999 28 "code": 9999
29 } 29 }
30 30
  31 +### 请求 /social-login 接口 => 成功
  32 +POST {{appApi}}/member/auth/social-login
  33 +Content-Type: application/json
  34 +tenant-id: {{appTenentId}}
  35 +
  36 +{
  37 + "type": 34,
  38 + "code": "0e1oc9000CTjFQ1oim200bhtb61oc90g",
  39 + "state": "default"
  40 +}
  41 +
31 ### 请求 /weixin-mini-app-login 接口 => 成功 42 ### 请求 /weixin-mini-app-login 接口 => 成功
32 POST {{appApi}}/member/auth/weixin-mini-app-login 43 POST {{appApi}}/member/auth/weixin-mini-app-login
33 Content-Type: application/json 44 Content-Type: application/json
@@ -38,7 +49,6 @@ tenant-id: {{appTenentId}} @@ -38,7 +49,6 @@ tenant-id: {{appTenentId}}
38 "loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR" 49 "loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR"
39 } 50 }
40 51
41 -  
42 ### 请求 /logout 接口 => 成功 52 ### 请求 /logout 接口 => 成功
43 POST {{appApi}}/member/auth/logout 53 POST {{appApi}}/member/auth/logout
44 Content-Type: application/json 54 Content-Type: application/json
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
@@ -27,4 +27,12 @@ public class AppAuthLoginRespVO { @@ -27,4 +27,12 @@ public class AppAuthLoginRespVO {
27 @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED) 27 @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
28 private LocalDateTime expiresTime; 28 private LocalDateTime expiresTime;
29 29
  30 + /**
  31 + * 仅社交登录、社交绑定时会返回
  32 + *
  33 + * 为什么需要返回?微信公众号、微信小程序支付需要传递 openid 给支付接口
  34 + */
  35 + @Schema(description = "社交用户 openid", example = "qq768")
  36 + private String openid;
  37 +
30 } 38 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java
@@ -29,7 +29,6 @@ public class AppMemberUserController { @@ -29,7 +29,6 @@ public class AppMemberUserController {
29 29
30 @Resource 30 @Resource
31 private MemberUserService userService; 31 private MemberUserService userService;
32 -  
33 @Resource 32 @Resource
34 private MemberLevelService levelService; 33 private MemberLevelService levelService;
35 34
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java
@@ -29,6 +29,9 @@ public class AppMemberUserInfoRespVO { @@ -29,6 +29,9 @@ public class AppMemberUserInfoRespVO {
29 @Schema(description = "用户等级") 29 @Schema(description = "用户等级")
30 private Level level; 30 private Level level;
31 31
  32 + @Schema(description = "是否成为推广员", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  33 + private Boolean brokerageEnabled;
  34 +
32 @Schema(description = "用户 App - 会员等级") 35 @Schema(description = "用户 App - 会员等级")
33 @Data 36 @Data
34 public static class Level { 37 public static class Level {
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
@@ -25,7 +25,7 @@ public interface AuthConvert { @@ -25,7 +25,7 @@ public interface AuthConvert {
25 SmsCodeUseReqDTO convert(AppMemberUserResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp); 25 SmsCodeUseReqDTO convert(AppMemberUserResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
26 SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp); 26 SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
27 27
28 - AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean); 28 + AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean, String openid);
29 29
30 SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean); 30 SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean);
31 31
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java
@@ -6,14 +6,12 @@ import com.baomidou.mybatisplus.annotation.TableId; @@ -6,14 +6,12 @@ import com.baomidou.mybatisplus.annotation.TableId;
6 import com.baomidou.mybatisplus.annotation.TableName; 6 import com.baomidou.mybatisplus.annotation.TableName;
7 import lombok.*; 7 import lombok.*;
8 8
9 -import java.math.BigDecimal;  
10 -  
11 /** 9 /**
12 * 会员积分配置 DO 10 * 会员积分配置 DO
13 * 11 *
14 * @author QingX 12 * @author QingX
15 */ 13 */
16 -@TableName("member_point_config") 14 +@TableName(value = "member_point_config", autoResultMap = true)
17 @KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 15 @KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
18 @Data 16 @Data
19 @EqualsAndHashCode(callSuper = true) 17 @EqualsAndHashCode(callSuper = true)
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
@@ -20,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO; @@ -20,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
20 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 20 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
21 import cn.iocoder.yudao.module.system.api.social.SocialUserApi; 21 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
22 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 22 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  23 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
23 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 24 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
24 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 25 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
25 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants; 26 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
@@ -65,13 +66,14 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -65,13 +66,14 @@ public class MemberAuthServiceImpl implements MemberAuthService {
65 MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword()); 66 MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
66 67
67 // 如果 socialType 非空,说明需要绑定社交用户 68 // 如果 socialType 非空,说明需要绑定社交用户
  69 + String openid = null;
68 if (reqVO.getSocialType() != null) { 70 if (reqVO.getSocialType() != null) {
69 - socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), 71 + openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
70 reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())); 72 reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
71 } 73 }
72 74
73 // 创建 Token 令牌,记录登录日志 75 // 创建 Token 令牌,记录登录日志
74 - return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE); 76 + return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, openid);
75 } 77 }
76 78
77 @Override 79 @Override
@@ -86,32 +88,33 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -86,32 +88,33 @@ public class MemberAuthServiceImpl implements MemberAuthService {
86 Assert.notNull(user, "获取用户失败,结果为空"); 88 Assert.notNull(user, "获取用户失败,结果为空");
87 89
88 // 如果 socialType 非空,说明需要绑定社交用户 90 // 如果 socialType 非空,说明需要绑定社交用户
  91 + String openid = null;
89 if (reqVO.getSocialType() != null) { 92 if (reqVO.getSocialType() != null) {
90 - socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), 93 + openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
91 reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())); 94 reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
92 } 95 }
93 96
94 // 创建 Token 令牌,记录登录日志 97 // 创建 Token 令牌,记录登录日志
95 - return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS); 98 + return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, openid);
96 } 99 }
97 100
98 @Override 101 @Override
99 public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) { 102 public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) {
100 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 103 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
101 - Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(), 104 + SocialUserRespDTO socialUser = socialUserApi.getSocialUser(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
102 reqVO.getCode(), reqVO.getState()); 105 reqVO.getCode(), reqVO.getState());
103 - if (userId == null) { 106 + if (socialUser == null) {
104 throw exception(AUTH_THIRD_LOGIN_NOT_BIND); 107 throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
105 } 108 }
106 109
107 // 自动登录 110 // 自动登录
108 - MemberUserDO user = userService.getUser(userId); 111 + MemberUserDO user = userService.getUser(socialUser.getUserId());
109 if (user == null) { 112 if (user == null) {
110 throw exception(USER_NOT_EXISTS); 113 throw exception(USER_NOT_EXISTS);
111 } 114 }
112 115
113 // 创建 Token 令牌,记录登录日志 116 // 创建 Token 令牌,记录登录日志
114 - return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL); 117 + return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, socialUser.getOpenid());
115 } 118 }
116 119
117 @Override 120 @Override
@@ -129,14 +132,15 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -129,14 +132,15 @@ public class MemberAuthServiceImpl implements MemberAuthService {
129 Assert.notNull(user, "获取用户失败,结果为空"); 132 Assert.notNull(user, "获取用户失败,结果为空");
130 133
131 // 绑定社交用户 134 // 绑定社交用户
132 - socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), 135 + String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
133 SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), "")); 136 SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), ""));
134 137
135 // 创建 Token 令牌,记录登录日志 138 // 创建 Token 令牌,记录登录日志
136 - return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL); 139 + return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, openid);
137 } 140 }
138 141
139 - private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) { 142 + private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile,
  143 + LoginLogTypeEnum logType, String openid) {
140 // 插入登陆日志 144 // 插入登陆日志
141 createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS); 145 createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
142 // 创建 Token 令牌 146 // 创建 Token 令牌
@@ -144,7 +148,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -144,7 +148,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
144 .setUserId(user.getId()).setUserType(getUserType().getValue()) 148 .setUserId(user.getId()).setUserType(getUserType().getValue())
145 .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT)); 149 .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
146 // 构建返回结果 150 // 构建返回结果
147 - return AuthConvert.INSTANCE.convert(accessTokenRespDTO); 151 + return AuthConvert.INSTANCE.convert(accessTokenRespDTO, openid);
148 } 152 }
149 153
150 @Override 154 @Override
@@ -231,7 +235,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -231,7 +235,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
231 public AppAuthLoginRespVO refreshToken(String refreshToken) { 235 public AppAuthLoginRespVO refreshToken(String refreshToken) {
232 OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, 236 OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken,
233 OAuth2ClientConstants.CLIENT_ID_DEFAULT); 237 OAuth2ClientConstants.CLIENT_ID_DEFAULT);
234 - return AuthConvert.INSTANCE.convert(accessTokenDO); 238 + return AuthConvert.INSTANCE.convert(accessTokenDO, null);
235 } 239 }
236 240
237 private void createLogoutLog(Long userId) { 241 private void createLogoutLog(Long userId) {
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
@@ -90,3 +90,24 @@ CREATE TABLE IF NOT EXISTS &quot;member_group&quot; @@ -90,3 +90,24 @@ CREATE TABLE IF NOT EXISTS &quot;member_group&quot;
90 "tenant_id" bigint not null default '0', 90 "tenant_id" bigint not null default '0',
91 PRIMARY KEY ("id") 91 PRIMARY KEY ("id")
92 ) COMMENT '用户分组'; 92 ) COMMENT '用户分组';
  93 +CREATE TABLE IF NOT EXISTS "member_brokerage_record"
  94 +(
  95 + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  96 + "user_id" bigint NOT NULL,
  97 + "biz_id" varchar NOT NULL,
  98 + "biz_type" varchar NOT NULL,
  99 + "title" varchar NOT NULL,
  100 + "price" int NOT NULL,
  101 + "total_price" int NOT NULL,
  102 + "description" varchar NOT NULL,
  103 + "status" varchar NOT NULL,
  104 + "frozen_days" int NOT NULL,
  105 + "unfreeze_time" varchar,
  106 + "creator" varchar DEFAULT '',
  107 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  108 + "updater" varchar DEFAULT '',
  109 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  110 + "deleted" bit NOT NULL DEFAULT FALSE,
  111 + "tenant_id" bigint not null default '0',
  112 + PRIMARY KEY ("id")
  113 +) COMMENT '佣金记录';
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java
@@ -29,4 +29,13 @@ public interface PayOrderApi { @@ -29,4 +29,13 @@ public interface PayOrderApi {
29 */ 29 */
30 PayOrderRespDTO getOrder(Long id); 30 PayOrderRespDTO getOrder(Long id);
31 31
  32 + // TODO @puhui999:可以去掉 byId;然后 payOrderId 参数改成 id;
  33 + /**
  34 + * 更新支付订单价格
  35 + *
  36 + * @param payOrderId 支付单编号
  37 + * @param payPrice 支付单价格
  38 + */
  39 + void updatePayOrderPriceById(Long payOrderId, Integer payPrice);
  40 +
32 } 41 }
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
@@ -28,6 +28,7 @@ public interface ErrorCodeConstants { @@ -28,6 +28,7 @@ public interface ErrorCodeConstants {
28 ErrorCode ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}"); 28 ErrorCode ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}");
29 ErrorCode ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款"); 29 ErrorCode ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款");
30 ErrorCode ORDER_UPDATE_PRICE_FAIL_PAID = new ErrorCode(1_007_002_006, "支付订单调价失败,原因:支付订单已付款,不能调价"); 30 ErrorCode ORDER_UPDATE_PRICE_FAIL_PAID = new ErrorCode(1_007_002_006, "支付订单调价失败,原因:支付订单已付款,不能调价");
  31 + ErrorCode ORDER_UPDATE_PRICE_FAIL_EQUAL = new ErrorCode(1007002007, "支付订单调价失败,原因:价格没有变化");
31 32
32 // ========== ORDER 模块(拓展单) 1-007-003-000 ========== 33 // ========== ORDER 模块(拓展单) 1-007-003-000 ==========
33 ErrorCode ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在"); 34 ErrorCode ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在");
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java
@@ -31,4 +31,9 @@ public class PayOrderApiImpl implements PayOrderApi { @@ -31,4 +31,9 @@ public class PayOrderApiImpl implements PayOrderApi {
31 return PayOrderConvert.INSTANCE.convert2(order); 31 return PayOrderConvert.INSTANCE.convert2(order);
32 } 32 }
33 33
  34 + @Override
  35 + public void updatePayOrderPriceById(Long payOrderId, Integer payPrice) {
  36 + payOrderService.updatePayOrderPriceById(payOrderId, payPrice);
  37 + }
  38 +
34 } 39 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
@@ -5,13 +5,16 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -5,13 +5,16 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 6 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
7 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 7 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  8 +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
8 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*; 9 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
9 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; 10 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
10 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; 11 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
11 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; 12 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
12 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; 13 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
  14 +import cn.iocoder.yudao.module.pay.framework.pay.wallet.WalletPayClient;
13 import cn.iocoder.yudao.module.pay.service.app.PayAppService; 15 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
14 import cn.iocoder.yudao.module.pay.service.order.PayOrderService; 16 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  17 +import com.google.common.collect.Maps;
15 import io.swagger.v3.oas.annotations.Operation; 18 import io.swagger.v3.oas.annotations.Operation;
16 import io.swagger.v3.oas.annotations.Parameter; 19 import io.swagger.v3.oas.annotations.Parameter;
17 import io.swagger.v3.oas.annotations.tags.Tag; 20 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -26,11 +29,14 @@ import java.io.IOException; @@ -26,11 +29,14 @@ import java.io.IOException;
26 import java.util.ArrayList; 29 import java.util.ArrayList;
27 import java.util.List; 30 import java.util.List;
28 import java.util.Map; 31 import java.util.Map;
  32 +import java.util.Objects;
29 33
30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
32 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; 36 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
33 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 37 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
  38 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  39 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
34 40
35 @Tag(name = "管理后台 - 支付订单") 41 @Tag(name = "管理后台 - 支付订单")
36 @RestController 42 @RestController
@@ -70,6 +76,16 @@ public class PayOrderController { @@ -70,6 +76,16 @@ public class PayOrderController {
70 @PostMapping("/submit") 76 @PostMapping("/submit")
71 @Operation(summary = "提交支付订单") 77 @Operation(summary = "提交支付订单")
72 public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { 78 public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
  79 + // 1. 钱包支付事,需要额外传 user_id 和 user_type
  80 + if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
  81 + Map<String, String> channelExtras = reqVO.getChannelExtras() == null ?
  82 + Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
  83 + channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
  84 + channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
  85 + reqVO.setChannelExtras(channelExtras);
  86 + }
  87 +
  88 + // 2. 提交支付
73 PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP()); 89 PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP());
74 return success(respVO); 90 return success(respVO);
75 } 91 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java
@@ -37,7 +37,7 @@ public class AppPayWalletController { @@ -37,7 +37,7 @@ public class AppPayWalletController {
37 @Operation(summary = "获取钱包") 37 @Operation(summary = "获取钱包")
38 @PreAuthenticated 38 @PreAuthenticated
39 public CommonResult<AppPayWalletRespVO> getPayWallet() { 39 public CommonResult<AppPayWalletRespVO> getPayWallet() {
40 - PayWalletDO wallet = payWalletService.getPayWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue()); 40 + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
41 return success(PayWalletConvert.INSTANCE.convert(wallet)); 41 return success(PayWalletConvert.INSTANCE.convert(wallet));
42 } 42 }
43 43
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java
@@ -15,9 +15,6 @@ public class AppPayWalletTransactionRespVO { @@ -15,9 +15,6 @@ public class AppPayWalletTransactionRespVO {
15 @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 15 @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
16 private Integer bizType; 16 private Integer bizType;
17 17
18 - @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")  
19 - private LocalDateTime transactionTime;  
20 -  
21 @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") 18 @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
22 private Long price; 19 private Long price;
23 20
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java
@@ -11,9 +11,9 @@ public class AppPayWalletRespVO { @@ -11,9 +11,9 @@ public class AppPayWalletRespVO {
11 private Integer balance; 11 private Integer balance;
12 12
13 @Schema(description = "累计支出, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") 13 @Schema(description = "累计支出, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
14 - private Long totalExpense; 14 + private Integer totalExpense;
15 15
16 @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") 16 @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
17 - private Long totalRecharge; 17 + private Integer totalRecharge;
18 18
19 } 19 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.convert.wallet; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.convert.wallet;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO; 4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO;
5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
  6 +import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
6 import org.mapstruct.Mapper; 7 import org.mapstruct.Mapper;
7 import org.mapstruct.factory.Mappers; 8 import org.mapstruct.factory.Mappers;
8 9
@@ -12,4 +13,7 @@ public interface PayWalletTransactionConvert { @@ -12,4 +13,7 @@ public interface PayWalletTransactionConvert {
12 PayWalletTransactionConvert INSTANCE = Mappers.getMapper(PayWalletTransactionConvert.class); 13 PayWalletTransactionConvert INSTANCE = Mappers.getMapper(PayWalletTransactionConvert.class);
13 14
14 PageResult<AppPayWalletTransactionRespVO> convertPage(PageResult<PayWalletTransactionDO> page); 15 PageResult<AppPayWalletTransactionRespVO> convertPage(PageResult<PayWalletTransactionDO> page);
  16 +
  17 + PayWalletTransactionDO convert(CreateWalletTransactionBO bean);
  18 +
15 } 19 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java
@@ -45,10 +45,10 @@ public class PayWalletDO extends BaseDO { @@ -45,10 +45,10 @@ public class PayWalletDO extends BaseDO {
45 /** 45 /**
46 * 累计支出,单位分 46 * 累计支出,单位分
47 */ 47 */
48 - private Long totalExpense; 48 + private Integer totalExpense;
49 /** 49 /**
50 * 累计充值,单位分 50 * 累计充值,单位分
51 */ 51 */
52 - private Long totalRecharge; 52 + private Integer totalRecharge;
53 53
54 } 54 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java
@@ -7,8 +7,6 @@ import com.baomidou.mybatisplus.annotation.TableId; @@ -7,8 +7,6 @@ import com.baomidou.mybatisplus.annotation.TableId;
7 import com.baomidou.mybatisplus.annotation.TableName; 7 import com.baomidou.mybatisplus.annotation.TableName;
8 import lombok.Data; 8 import lombok.Data;
9 9
10 -import java.time.LocalDateTime;  
11 -  
12 /** 10 /**
13 * 会员钱包流水 DO 11 * 会员钱包流水 DO
14 * 12 *
@@ -24,6 +22,7 @@ public class PayWalletTransactionDO extends BaseDO { @@ -24,6 +22,7 @@ public class PayWalletTransactionDO extends BaseDO {
24 */ 22 */
25 @TableId 23 @TableId
26 private Long id; 24 private Long id;
  25 +
27 /** 26 /**
28 * 流水号 27 * 流水号
29 */ 28 */
@@ -42,34 +41,26 @@ public class PayWalletTransactionDO extends BaseDO { @@ -42,34 +41,26 @@ public class PayWalletTransactionDO extends BaseDO {
42 * 枚举 {@link PayWalletBizTypeEnum#getType()} 41 * 枚举 {@link PayWalletBizTypeEnum#getType()}
43 */ 42 */
44 private Integer bizType; 43 private Integer bizType;
45 - // TODO @jason:使用 string;因为可能有业务是 string 接入哈。 44 +
46 /** 45 /**
47 * 关联业务编号 46 * 关联业务编号
48 */ 47 */
49 - private Long bizId; 48 + private String bizId;
50 49
51 - // TODO @jason:想了下,改成 title;流水标题;因为账户明细那,会看到这个;  
52 /** 50 /**
53 - * 附加说明 51 + * 流水说明
54 */ 52 */
55 - private String description; 53 + private String title;
56 54
57 - // TODO @jason:使用 price 哈。项目里,金额都是用这个为主;  
58 /** 55 /**
59 * 交易金额,单位分 56 * 交易金额,单位分
60 * 57 *
61 * 正值表示余额增加,负值表示余额减少 58 * 正值表示余额增加,负值表示余额减少
62 */ 59 */
63 - private Integer amount; 60 + private Integer price;
  61 +
64 /** 62 /**
65 * 交易后余额,单位分 63 * 交易后余额,单位分
66 */ 64 */
67 private Integer balance; 65 private Integer balance;
68 -  
69 - // TODO @jason:使用 createTime 就够啦  
70 - /**  
71 - * 交易时间  
72 - */  
73 - private LocalDateTime transactionTime;  
74 -  
75 } 66 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.dal.mysql.wallet; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.dal.mysql.wallet;
3 3
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
  6 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
6 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Mapper;
7 8
8 @Mapper 9 @Mapper
@@ -12,6 +13,35 @@ public interface PayWalletMapper extends BaseMapperX&lt;PayWalletDO&gt; { @@ -12,6 +13,35 @@ public interface PayWalletMapper extends BaseMapperX&lt;PayWalletDO&gt; {
12 return selectOne(PayWalletDO::getUserId, userId, 13 return selectOne(PayWalletDO::getUserId, userId,
13 PayWalletDO::getUserType, userType); 14 PayWalletDO::getUserType, userType);
14 } 15 }
  16 +
  17 + /**
  18 + * 当消费退款时候, 更新钱包
  19 + *
  20 + * @param price 消费金额
  21 + * @param id 钱包 id
  22 + */
  23 + default int updateWhenConsumptionRefund(Integer price, Long id){
  24 + LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
  25 + .setSql(" balance = balance + " + price
  26 + + ", total_expense = total_expense - " + price)
  27 + .eq(PayWalletDO::getId, id);
  28 + return update(null, lambdaUpdateWrapper);
  29 + }
  30 +
  31 + /**
  32 + * 当消费时候, 更新钱包
  33 + *
  34 + * @param price 消费金额
  35 + * @param id 钱包 id
  36 + */
  37 + default int updateWhenConsumption(Integer price, Long id){
  38 + LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
  39 + .setSql(" balance = balance - " + price
  40 + + ", total_expense = total_expense + " + price)
  41 + .eq(PayWalletDO::getId, id)
  42 + .ge(PayWalletDO::getBalance, price); // cas 逻辑
  43 + return update(null, lambdaUpdateWrapper);
  44 + }
15 } 45 }
16 46
17 47
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
@@ -18,9 +18,9 @@ public interface PayWalletTransactionMapper extends BaseMapperX&lt;PayWalletTransac @@ -18,9 +18,9 @@ public interface PayWalletTransactionMapper extends BaseMapperX&lt;PayWalletTransac
18 LambdaQueryWrapperX<PayWalletTransactionDO> query = new LambdaQueryWrapperX<PayWalletTransactionDO>() 18 LambdaQueryWrapperX<PayWalletTransactionDO> query = new LambdaQueryWrapperX<PayWalletTransactionDO>()
19 .eq(PayWalletTransactionDO::getWalletId, walletId); 19 .eq(PayWalletTransactionDO::getWalletId, walletId);
20 if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_INCOME)) { 20 if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_INCOME)) {
21 - query.gt(PayWalletTransactionDO::getAmount, 0); 21 + query.gt(PayWalletTransactionDO::getPrice, 0);
22 } else if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_EXPENSE)) { 22 } else if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_EXPENSE)) {
23 - query.lt(PayWalletTransactionDO::getAmount, 0); 23 + query.lt(PayWalletTransactionDO::getPrice, 0);
24 } 24 }
25 query.orderByDesc(PayWalletTransactionDO::getId); 25 query.orderByDesc(PayWalletTransactionDO::getId);
26 return selectPage(pageReqVO, query); 26 return selectPage(pageReqVO, query);
@@ -30,9 +30,8 @@ public interface PayWalletTransactionMapper extends BaseMapperX&lt;PayWalletTransac @@ -30,9 +30,8 @@ public interface PayWalletTransactionMapper extends BaseMapperX&lt;PayWalletTransac
30 return selectOne(PayWalletTransactionDO::getNo, no); 30 return selectOne(PayWalletTransactionDO::getNo, no);
31 } 31 }
32 32
33 - default PayWalletTransactionDO selectByWalletIdAndBiz(Long walletId, Long bizId, Integer bizType) {  
34 - return selectOne(PayWalletTransactionDO::getWalletId, walletId,  
35 - PayWalletTransactionDO::getBizId, bizId, 33 + default PayWalletTransactionDO selectByBiz(String bizId, Integer bizType) {
  34 + return selectOne(PayWalletTransactionDO::getBizId, bizId,
36 PayWalletTransactionDO::getBizType, bizType); 35 PayWalletTransactionDO::getBizType, bizType);
37 } 36 }
38 37
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java
1 package cn.iocoder.yudao.module.pay.framework.pay.wallet; 1 package cn.iocoder.yudao.module.pay.framework.pay.wallet;
2 2
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.map.MapUtil;
3 import cn.hutool.extra.spring.SpringUtil; 5 import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.common.exception.ServiceException; 6 import cn.iocoder.yudao.framework.common.exception.ServiceException;
5 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; 7 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
@@ -9,13 +11,23 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReq @@ -9,13 +11,23 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReq
9 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 11 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
10 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; 12 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
11 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 13 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
  14 +import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
  15 +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
  16 +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
12 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; 17 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
  18 +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
  19 +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
  20 +import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  21 +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
13 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; 22 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
  23 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
14 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
15 25
16 import java.util.Map; 26 import java.util.Map;
17 27
18 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 28 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  29 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_EXTENSION_NOT_FOUND;
  30 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FOUND;
19 31
20 /** 32 /**
21 * 钱包支付的 PayClient 实现类 33 * 钱包支付的 PayClient 实现类
@@ -25,7 +37,13 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -25,7 +37,13 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
25 @Slf4j 37 @Slf4j
26 public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> { 38 public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
27 39
  40 + public static final String USER_ID_KEY = "user_id";
  41 + public static final String USER_TYPE_KEY = "user_type";
  42 +
28 private PayWalletService wallService; 43 private PayWalletService wallService;
  44 + private PayWalletTransactionService walletTransactionService;
  45 + private PayOrderService orderService;
  46 + private PayRefundService refundService;
29 47
30 public WalletPayClient(Long channelId, NonePayClientConfig config) { 48 public WalletPayClient(Long channelId, NonePayClientConfig config) {
31 super(channelId, PayChannelEnum.WALLET.getCode(), config); 49 super(channelId, PayChannelEnum.WALLET.getCode(), config);
@@ -36,14 +54,22 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; { @@ -36,14 +54,22 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
36 if (wallService == null) { 54 if (wallService == null) {
37 wallService = SpringUtil.getBean(PayWalletService.class); 55 wallService = SpringUtil.getBean(PayWalletService.class);
38 } 56 }
  57 + if (walletTransactionService == null) {
  58 + walletTransactionService = SpringUtil.getBean(PayWalletTransactionService.class);
  59 + }
39 } 60 }
40 61
41 @Override 62 @Override
42 protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { 63 protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
43 try { 64 try {
44 - PayWalletTransactionDO transaction = wallService.pay(reqDTO.getOutTradeNo(), reqDTO.getPrice()); 65 + Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY);
  66 + Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY);
  67 + Assert.notNull(userId, "用户 id 不能为空");
  68 + Assert.notNull(userType, "用户类型不能为空");
  69 + PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(),
  70 + reqDTO.getPrice());
45 return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(), 71 return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
46 - transaction.getTransactionTime(), 72 + transaction.getCreateTime(),
47 reqDTO.getOutTradeNo(), transaction); 73 reqDTO.getOutTradeNo(), transaction);
48 } catch (Throwable ex) { 74 } catch (Throwable ex) {
49 log.error("[doUnifiedOrder] 失败", ex); 75 log.error("[doUnifiedOrder] 失败", ex);
@@ -66,15 +92,39 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; { @@ -66,15 +92,39 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
66 92
67 @Override 93 @Override
68 protected PayOrderRespDTO doGetOrder(String outTradeNo) { 94 protected PayOrderRespDTO doGetOrder(String outTradeNo) {
69 - throw new UnsupportedOperationException("待实现"); 95 + if (orderService == null) {
  96 + orderService = SpringUtil.getBean(PayOrderService.class);
  97 + }
  98 + PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
  99 + // 支付交易拓展单不存在, 返回关闭状态
  100 + if (orderExtension == null) {
  101 + return PayOrderRespDTO.closedOf(String.valueOf(ORDER_EXTENSION_NOT_FOUND.getCode()),
  102 + ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, "");
  103 + }
  104 + // 关闭状态
  105 + if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) {
  106 + return PayOrderRespDTO.closedOf(orderExtension.getChannelErrorCode(),
  107 + orderExtension.getChannelErrorMsg(), outTradeNo, "");
  108 + }
  109 + // 成功状态
  110 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
  111 + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction(
  112 + String.valueOf(orderExtension.getOrderId()), PayWalletBizTypeEnum.PAYMENT);
  113 + Assert.notNull(walletTransaction, "支付单 {} 钱包流水不能为空", outTradeNo);
  114 + return PayOrderRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreator(),
  115 + walletTransaction.getCreateTime(), outTradeNo, walletTransaction);
  116 + }
  117 + // 其它状态为无效状态
  118 + log.error("[doGetOrder] 支付单 {} 的状态不正确", outTradeNo);
  119 + throw new IllegalStateException(String.format("支付单[%s] 状态不正确", outTradeNo));
70 } 120 }
71 121
72 @Override 122 @Override
73 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { 123 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
74 try { 124 try {
75 - PayWalletTransactionDO payWalletTransaction = wallService.refund(reqDTO.getOutRefundNo(), 125 + PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(),
76 reqDTO.getRefundPrice(), reqDTO.getReason()); 126 reqDTO.getRefundPrice(), reqDTO.getReason());
77 - return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getTransactionTime(), 127 + return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(),
78 reqDTO.getOutRefundNo(), payWalletTransaction); 128 reqDTO.getOutRefundNo(), payWalletTransaction);
79 } catch (Throwable ex) { 129 } catch (Throwable ex) {
80 log.error("[doUnifiedRefund] 失败", ex); 130 log.error("[doUnifiedRefund] 失败", ex);
@@ -97,7 +147,31 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; { @@ -97,7 +147,31 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
97 147
98 @Override 148 @Override
99 protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) { 149 protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
100 - throw new UnsupportedOperationException("待实现"); 150 + if (refundService == null) {
  151 + refundService = SpringUtil.getBean(PayRefundService.class);
  152 + }
  153 + PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo);
  154 + // 支付退款单不存在, 返回退款失败状态
  155 + if (payRefund == null) {
  156 + return PayRefundRespDTO.failureOf(String.valueOf(REFUND_NOT_FOUND), REFUND_NOT_FOUND.getMsg(),
  157 + outRefundNo, "");
  158 + }
  159 + // 退款失败
  160 + if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
  161 + return PayRefundRespDTO.failureOf(payRefund.getChannelErrorCode(), payRefund.getChannelErrorMsg(),
  162 + outRefundNo, "");
  163 + }
  164 + // 退款成功
  165 + if (PayRefundStatusRespEnum.isSuccess(payRefund.getStatus())) {
  166 + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction(
  167 + String.valueOf(payRefund.getId()), PayWalletBizTypeEnum.PAYMENT_REFUND);
  168 + Assert.notNull(walletTransaction, "支付退款单 {} 钱包流水不能为空", outRefundNo);
  169 + return PayRefundRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(),
  170 + outRefundNo, walletTransaction);
  171 + }
  172 + // 其它状态为无效状态
  173 + log.error("[doGetRefund] 支付退款单 {} 的状态不正确", outRefundNo);
  174 + throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo));
101 } 175 }
102 176
103 } 177 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
@@ -33,7 +33,7 @@ public interface PayOrderService { @@ -33,7 +33,7 @@ public interface PayOrderService {
33 /** 33 /**
34 * 获得支付订单 34 * 获得支付订单
35 * 35 *
36 - * @param appId 应用编号 36 + * @param appId 应用编号
37 * @param merchantOrderId 商户订单编号 37 * @param merchantOrderId 商户订单编号
38 * @return 支付订单 38 * @return 支付订单
39 */ 39 */
@@ -75,7 +75,7 @@ public interface PayOrderService { @@ -75,7 +75,7 @@ public interface PayOrderService {
75 * 提交支付 75 * 提交支付
76 * 此时,会发起支付渠道的调用 76 * 此时,会发起支付渠道的调用
77 * 77 *
78 - * @param reqVO 提交请求 78 + * @param reqVO 提交请求
79 * @param userIp 提交 IP 79 * @param userIp 提交 IP
80 * @return 提交结果 80 * @return 提交结果
81 */ 81 */
@@ -93,12 +93,20 @@ public interface PayOrderService { @@ -93,12 +93,20 @@ public interface PayOrderService {
93 /** 93 /**
94 * 更新支付订单的退款金额 94 * 更新支付订单的退款金额
95 * 95 *
96 - * @param id 编号 96 + * @param id 编号
97 * @param incrRefundPrice 增加的退款金额 97 * @param incrRefundPrice 增加的退款金额
98 */ 98 */
99 void updateOrderRefundPrice(Long id, Integer incrRefundPrice); 99 void updateOrderRefundPrice(Long id, Integer incrRefundPrice);
100 100
101 /** 101 /**
  102 + * 更新支付订单价格
  103 + *
  104 + * @param payOrderId 支付单编号
  105 + * @param payPrice 支付单价格
  106 + */
  107 + void updatePayOrderPriceById(Long payOrderId, Integer payPrice);
  108 +
  109 + /**
102 * 获得支付订单 110 * 获得支付订单
103 * 111 *
104 * @param id 编号 112 * @param id 编号
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil; @@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
6 import cn.hutool.extra.spring.SpringUtil; 6 import cn.hutool.extra.spring.SpringUtil;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; 8 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  9 +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
9 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 10 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
10 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 11 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
11 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; 12 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
@@ -31,7 +32,6 @@ import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; @@ -31,7 +32,6 @@ import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
31 import cn.iocoder.yudao.module.pay.service.app.PayAppService; 32 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
32 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; 33 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
33 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 34 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
34 -import cn.iocoder.yudao.module.pay.util.MoneyUtils;  
35 import com.google.common.annotations.VisibleForTesting; 35 import com.google.common.annotations.VisibleForTesting;
36 import lombok.extern.slf4j.Slf4j; 36 import lombok.extern.slf4j.Slf4j;
37 import org.springframework.stereotype.Service; 37 import org.springframework.stereotype.Service;
@@ -412,6 +412,18 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -412,6 +412,18 @@ public class PayOrderServiceImpl implements PayOrderService {
412 } 412 }
413 413
414 @Override 414 @Override
  415 + public void updatePayOrderPriceById(Long payOrderId, Integer payPrice) {
  416 + // TODO @puhui999:不能直接这样修改哈;应该只有未支付状态的订单才可以改;另外,如果价格如果没变,可以直接 return 哈;
  417 + PayOrderDO order = orderMapper.selectById(payOrderId);
  418 + if (order == null) {
  419 + throw exception(ORDER_NOT_FOUND);
  420 + }
  421 +
  422 + order.setPrice(payPrice);
  423 + orderMapper.updateById(order);
  424 + }
  425 +
  426 + @Override
415 public PayOrderExtensionDO getOrderExtension(Long id) { 427 public PayOrderExtensionDO getOrderExtension(Long id) {
416 return orderExtensionMapper.selectById(id); 428 return orderExtensionMapper.selectById(id);
417 } 429 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.wallet; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.wallet;
2 2
3 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; 3 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
4 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; 4 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
  5 +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
5 6
6 /** 7 /**
7 * 钱包 Service 接口 8 * 钱包 Service 接口
@@ -10,30 +11,59 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; @@ -10,30 +11,59 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
10 */ 11 */
11 public interface PayWalletService { 12 public interface PayWalletService {
12 13
13 - // TODO @jason:改成 getOrCreateWallet;因为目前解耦,用户注册时,不会创建钱包;需要这里兜底处理;  
14 /** 14 /**
15 * 获取钱包信息 15 * 获取钱包信息
16 * 16 *
  17 + * 如果不存在,则创建钱包。由于用户注册时候不会创建钱包
  18 + *
17 * @param userId 用户编号 19 * @param userId 用户编号
18 * @param userType 用户类型 20 * @param userType 用户类型
19 */ 21 */
20 - PayWalletDO getPayWallet(Long userId, Integer userType); 22 + PayWalletDO getOrCreateWallet(Long userId, Integer userType);
21 23
22 /** 24 /**
23 - * 钱包支付 25 + * 钱包订单支付
24 * 26 *
  27 + * @param userId 用户 id
  28 + * @param userType 用户类型
25 * @param outTradeNo 外部订单号 29 * @param outTradeNo 外部订单号
26 * @param price 金额 30 * @param price 金额
27 */ 31 */
28 - PayWalletTransactionDO pay(String outTradeNo, Integer price); 32 + PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price);
29 33
30 /** 34 /**
31 - * 钱包支付退款 35 + * 钱包订单支付退款
32 * 36 *
33 * @param outRefundNo 外部退款号 37 * @param outRefundNo 外部退款号
34 * @param refundPrice 退款金额 38 * @param refundPrice 退款金额
35 * @param reason 退款原因 39 * @param reason 退款原因
36 */ 40 */
37 - PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason); 41 + PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason);
  42 +
  43 + /**
  44 + * 扣减钱包余额
  45 + *
  46 + * @param userId 用户 id
  47 + * @param userType 用户类型
  48 + * @param bizId 业务关联 id
  49 + * @param bizType 业务关联分类
  50 + * @param price 扣减金额
  51 + * @return 钱包流水
  52 + */
  53 + PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,
  54 + Long bizId, PayWalletBizTypeEnum bizType, Integer price);
  55 +
  56 + /**
  57 + * 增加钱包余额
  58 + *
  59 + * @param userId 用户 id
  60 + * @param userType 用户类型
  61 + * @param bizId 业务关联 id
  62 + * @param bizType 业务关联分类
  63 + * @param price 增加金额
  64 + * @return 钱包流水
  65 + */
  66 + PayWalletTransactionDO addWalletBalance(Long userId, Integer userType,
  67 + Long bizId, PayWalletBizTypeEnum bizType, Integer price);
38 68
39 } 69 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
1 package cn.iocoder.yudao.module.pay.service.wallet; 1 package cn.iocoder.yudao.module.pay.service.wallet;
2 2
  3 +import cn.hutool.core.lang.Assert;
3 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; 4 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
4 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 5 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; 6 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
6 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; 7 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
7 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper; 8 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
8 -import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; 9 +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
9 import cn.iocoder.yudao.module.pay.service.order.PayOrderService; 10 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
10 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; 11 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
  12 +import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
11 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
12 import org.springframework.context.annotation.Lazy; 14 import org.springframework.context.annotation.Lazy;
13 import org.springframework.stereotype.Service; 15 import org.springframework.stereotype.Service;
@@ -17,8 +19,6 @@ import javax.annotation.Resource; @@ -17,8 +19,6 @@ import javax.annotation.Resource;
17 import java.time.LocalDateTime; 19 import java.time.LocalDateTime;
18 20
19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 21 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
20 -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;  
21 -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType;  
22 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; 22 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
23 import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT; 23 import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT;
24 import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT_REFUND; 24 import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT_REFUND;
@@ -32,105 +32,55 @@ import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYM @@ -32,105 +32,55 @@ import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYM
32 @Slf4j 32 @Slf4j
33 public class PayWalletServiceImpl implements PayWalletService { 33 public class PayWalletServiceImpl implements PayWalletService {
34 34
35 - /**  
36 - * 余额支付的 no 前缀  
37 - */  
38 - private static final String WALLET_PAY_NO_PREFIX = "WP";  
39 - /**  
40 - * 余额退款的 no 前缀  
41 - */  
42 - private static final String WALLET_REFUND_NO_PREFIX = "WR";  
43 -  
44 @Resource 35 @Resource
45 - private PayWalletMapper payWalletMapper; 36 + private PayWalletMapper walletMapper;
46 @Resource 37 @Resource
47 - private PayNoRedisDAO noRedisDAO;  
48 -  
49 - @Resource  
50 - private PayWalletTransactionService payWalletTransactionService; 38 + private PayWalletTransactionService walletTransactionService;
51 @Resource 39 @Resource
52 @Lazy 40 @Lazy
53 - private PayOrderService payOrderService; 41 + private PayOrderService orderService;
54 @Resource 42 @Resource
55 @Lazy 43 @Lazy
56 - private PayRefundService payRefundService; 44 + private PayRefundService refundService;
57 45
58 @Override 46 @Override
59 - public PayWalletDO getPayWallet(Long userId, Integer userType) {  
60 - return payWalletMapper.selectByUserIdAndType(userId, userType); 47 + public PayWalletDO getOrCreateWallet(Long userId, Integer userType) {
  48 + PayWalletDO wallet = walletMapper.selectByUserIdAndType(userId, userType);
  49 + if (wallet == null) {
  50 + wallet = new PayWalletDO().setUserId(userId).setUserType(userType)
  51 + .setBalance(0).setTotalExpense(0).setTotalRecharge(0);
  52 + wallet.setCreateTime(LocalDateTime.now());
  53 + walletMapper.insert(wallet);
  54 + }
  55 + return wallet;
61 } 56 }
62 57
63 - // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);reduceWalletBalance;  
64 - // TODO @jason:最好是,明确传入哪个 userId 或者 walletId;  
65 @Override 58 @Override
66 @Transactional(rollbackFor = Exception.class) 59 @Transactional(rollbackFor = Exception.class)
67 - public PayWalletTransactionDO pay(String outTradeNo, Integer price) {  
68 - // 1.1 判断支付交易拓展单是否存  
69 - PayOrderExtensionDO orderExtension = payOrderService.getOrderExtensionByNo(outTradeNo); 60 + public PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price) {
  61 + // 1. 判断支付交易拓展单是否存
  62 + PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
70 if (orderExtension == null) { 63 if (orderExtension == null) {
71 throw exception(ORDER_EXTENSION_NOT_FOUND); 64 throw exception(ORDER_EXTENSION_NOT_FOUND);
72 } 65 }
73 - // 1.2 判断余额是否足够  
74 - PayWalletDO payWallet = validatePayWallet();  
75 - int afterBalance = payWallet.getBalance() - price;  
76 - if (afterBalance < 0) {  
77 - throw exception(WALLET_BALANCE_NOT_ENOUGH);  
78 - }  
79 -  
80 - // 2.1 扣除余额  
81 - // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 > price,以及 - price  
82 - payWallet.setBalance(afterBalance);  
83 - payWallet.setTotalExpense(payWallet.getTotalExpense() + price);  
84 - payWalletMapper.updateById(payWallet);  
85 -  
86 - // 2.2 生成钱包流水  
87 - String walletNo = noRedisDAO.generate(WALLET_PAY_NO_PREFIX);  
88 - PayWalletTransactionDO walletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())  
89 - .setNo(walletNo).setAmount(price * -1).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())  
90 - .setBizId(orderExtension.getOrderId()).setBizType(PAYMENT.getType());  
91 - payWalletTransactionService.createWalletTransaction(walletTransaction);  
92 - return walletTransaction;  
93 - }  
94 -  
95 - // TODO @jason:不要在 service 里去使用用户上下文,这样和 request 就耦合了。  
96 - private PayWalletDO validatePayWallet() {  
97 - Long userId = getLoginUserId();  
98 - Integer userType = getLoginUserType();  
99 - PayWalletDO payWallet = getPayWallet(userId, userType);  
100 - if (payWallet == null) {  
101 - log.error("[validatePayWallet] 用户 {} 钱包不存在", userId);  
102 - throw exception(WALLET_NOT_FOUND);  
103 - }  
104 - return payWallet; 66 + // 2. 扣减余额
  67 + return reduceWalletBalance(userId, userType, orderExtension.getOrderId(), PAYMENT, price);
105 } 68 }
106 69
107 - // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);addWalletBalance;这样,如果后续充值,应该也是能复用这个方法的;  
108 @Override 70 @Override
109 @Transactional(rollbackFor = Exception.class) 71 @Transactional(rollbackFor = Exception.class)
110 - public PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason) { 72 + public PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason) {
111 // 1.1 判断退款单是否存在 73 // 1.1 判断退款单是否存在
112 - PayRefundDO payRefund = payRefundService.getRefundByNo(outRefundNo); 74 + PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo);
113 if (payRefund == null) { 75 if (payRefund == null) {
114 throw exception(REFUND_NOT_FOUND); 76 throw exception(REFUND_NOT_FOUND);
115 } 77 }
116 // 1.2 校验是否可以退款 78 // 1.2 校验是否可以退款
117 - PayWalletDO payWallet = validatePayWallet();  
118 - validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), payWallet.getId(), refundPrice);  
119 -  
120 - // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 + 金额  
121 - Integer afterBalance = payWallet.getBalance() + refundPrice;  
122 - payWallet.setBalance(afterBalance);  
123 - payWallet.setTotalExpense(payWallet.getTotalExpense() + refundPrice * -1L);  
124 - payWalletMapper.updateById(payWallet);  
125 -  
126 - // 2.2 生成钱包流水  
127 - String walletNo = noRedisDAO.generate(WALLET_REFUND_NO_PREFIX);  
128 - PayWalletTransactionDO newWalletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())  
129 - .setNo(walletNo).setAmount(refundPrice).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())  
130 - .setBizId(payRefund.getId()).setBizType(PAYMENT_REFUND.getType())  
131 - .setDescription(reason);  
132 - payWalletTransactionService.createWalletTransaction(newWalletTransaction);  
133 - return newWalletTransaction; 79 + Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), refundPrice);
  80 + PayWalletDO wallet = walletMapper.selectById(walletId);
  81 + Assert.notNull(wallet, "钱包 {} 不存在", walletId);
  82 + // 2. 增加余额
  83 + return addWalletBalance(wallet.getUserId(), wallet.getUserType(), payRefund.getId(), PAYMENT_REFUND, refundPrice);
134 } 84 }
135 85
136 /** 86 /**
@@ -138,24 +88,78 @@ public class PayWalletServiceImpl implements PayWalletService { @@ -138,24 +88,78 @@ public class PayWalletServiceImpl implements PayWalletService {
138 * 88 *
139 * @param refundId 支付退款单 id 89 * @param refundId 支付退款单 id
140 * @param walletPayNo 钱包支付 no 90 * @param walletPayNo 钱包支付 no
141 - * @param walletId 钱包 id  
142 */ 91 */
143 - // TODO @jason:不要使用基本类型;  
144 - private void validateWalletCanRefund(long refundId, String walletPayNo, long walletId, int refundPrice) {  
145 - // 查询钱包支付交易  
146 - PayWalletTransactionDO payWalletTransaction = payWalletTransactionService.getWalletTransactionByNo(walletPayNo);  
147 - if (payWalletTransaction == null) { 92 + private Long validateWalletCanRefund(Long refundId, String walletPayNo, Integer refundPrice) {
  93 + // 1. 校验钱包支付交易存在
  94 + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransactionByNo(walletPayNo);
  95 + if (walletTransaction == null) {
148 throw exception(WALLET_TRANSACTION_NOT_FOUND); 96 throw exception(WALLET_TRANSACTION_NOT_FOUND);
149 } 97 }
150 // 原来的支付金额 98 // 原来的支付金额
151 - int amount = payWalletTransaction.getAmount() * -1; // TODO @jason:直接 - payWalletTransaction.getAmount() 即可; 99 + // TODO @jason:应该允许多次退款哈;
  100 + int amount = - walletTransaction.getPrice();
152 if (refundPrice != amount) { 101 if (refundPrice != amount) {
153 throw exception(WALLET_REFUND_AMOUNT_ERROR); 102 throw exception(WALLET_REFUND_AMOUNT_ERROR);
154 } 103 }
155 - PayWalletTransactionDO refundTransaction = payWalletTransactionService.getWalletTransaction(walletId, refundId, PAYMENT_REFUND); 104 + PayWalletTransactionDO refundTransaction = walletTransactionService.getWalletTransaction(
  105 + String.valueOf(refundId), PAYMENT_REFUND);
156 if (refundTransaction != null) { 106 if (refundTransaction != null) {
157 throw exception(WALLET_REFUND_EXIST); 107 throw exception(WALLET_REFUND_EXIST);
158 } 108 }
  109 + return walletTransaction.getWalletId();
  110 + }
  111 +
  112 + @Override
  113 + public PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,
  114 + Long bizId, PayWalletBizTypeEnum bizType, Integer price) {
  115 + // 1. 获取钱包
  116 + PayWalletDO payWallet = getOrCreateWallet(userId, userType);
  117 +
  118 + // 2.1 扣除余额
  119 + int updateCounts = 0 ;
  120 + switch (bizType) {
  121 + case PAYMENT: {
  122 + updateCounts = walletMapper.updateWhenConsumption(price, payWallet.getId());
  123 + break;
  124 + }
  125 + case RECHARGE_REFUND: {
  126 + // TODO
  127 + break;
  128 + }
  129 + }
  130 + if (updateCounts == 0) {
  131 + throw exception(WALLET_BALANCE_NOT_ENOUGH);
  132 + }
  133 + // 2.2 生成钱包流水
  134 + Integer afterBalance = payWallet.getBalance() - price;
  135 + CreateWalletTransactionBO bo = new CreateWalletTransactionBO().setWalletId(payWallet.getId())
  136 + .setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
  137 + .setBizType(bizType.getType()).setTitle(bizType.getDescription());
  138 + return walletTransactionService.createWalletTransaction(bo);
  139 + }
  140 +
  141 + @Override
  142 + public PayWalletTransactionDO addWalletBalance(Long userId, Integer userType,
  143 + Long bizId, PayWalletBizTypeEnum bizType, Integer price) {
  144 + // 1. 获取钱包
  145 + PayWalletDO payWallet = getOrCreateWallet(userId, userType);
  146 + switch (bizType) {
  147 + case PAYMENT_REFUND: {
  148 + // 更新退款
  149 + walletMapper.updateWhenConsumptionRefund(price, payWallet.getId());
  150 + break;
  151 + }
  152 + case RECHARGE: {
  153 + //TODO
  154 + break;
  155 + }
  156 + }
  157 +
  158 + // 2. 生成钱包流水
  159 + CreateWalletTransactionBO bo = new CreateWalletTransactionBO().setWalletId(payWallet.getId())
  160 + .setPrice(price).setBalance(payWallet.getBalance()+price).setBizId(String.valueOf(bizId))
  161 + .setBizType(bizType.getType()).setTitle(bizType.getDescription());
  162 + return walletTransactionService.createWalletTransaction(bo);
159 } 163 }
160 164
161 } 165 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java
@@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; 4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
6 import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum; 6 import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
  7 +import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
  8 +
  9 +import javax.validation.Valid;
7 10
8 /** 11 /**
9 * 钱包余额流水 Service 接口 12 * 钱包余额流水 Service 接口
@@ -25,10 +28,10 @@ public interface PayWalletTransactionService { @@ -25,10 +28,10 @@ public interface PayWalletTransactionService {
25 /** 28 /**
26 * 新增钱包余额流水 29 * 新增钱包余额流水
27 * 30 *
28 - * @param payWalletTransaction 余额流水  
29 - * @return id 31 + * @param bo 创建钱包流水 bo
  32 + * @return 新建的钱包 do
30 */ 33 */
31 - Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction); 34 + PayWalletTransactionDO createWalletTransaction(@Valid CreateWalletTransactionBO bo);
32 35
33 /** 36 /**
34 * 根据 no,获取钱包余流水 37 * 根据 no,获取钱包余流水
@@ -40,11 +43,10 @@ public interface PayWalletTransactionService { @@ -40,11 +43,10 @@ public interface PayWalletTransactionService {
40 /** 43 /**
41 * 获取钱包流水 44 * 获取钱包流水
42 * 45 *
43 - * @param walletId 钱包编号  
44 * @param bizId 业务编号 46 * @param bizId 业务编号
45 * @param type 业务类型 47 * @param type 业务类型
46 * @return 钱包流水 48 * @return 钱包流水
47 */ 49 */
48 - PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type);  
49 - 50 + PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type);
  51 +
50 } 52 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
@@ -2,18 +2,18 @@ package cn.iocoder.yudao.module.pay.service.wallet; @@ -2,18 +2,18 @@ package cn.iocoder.yudao.module.pay.service.wallet;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; 4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
  5 +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert;
5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; 6 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
6 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; 7 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
7 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper; 8 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper;
  9 +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
8 import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum; 10 import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
  11 +import cn.iocoder.yudao.module.pay.service.wallet.bo.CreateWalletTransactionBO;
9 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
10 import org.springframework.stereotype.Service; 13 import org.springframework.stereotype.Service;
11 14
12 import javax.annotation.Resource; 15 import javax.annotation.Resource;
13 16
14 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
15 -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND;  
16 -  
17 /** 17 /**
18 * 钱包流水 Service 实现类 18 * 钱包流水 Service 实现类
19 * 19 *
@@ -23,27 +23,31 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FO @@ -23,27 +23,31 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FO
23 @Slf4j 23 @Slf4j
24 public class PayWalletTransactionServiceImpl implements PayWalletTransactionService { 24 public class PayWalletTransactionServiceImpl implements PayWalletTransactionService {
25 25
  26 + /**
  27 + * 钱包流水的 no 前缀
  28 + */
  29 + private static final String WALLET_NO_PREFIX = "W";
  30 +
26 @Resource 31 @Resource
27 private PayWalletService payWalletService; 32 private PayWalletService payWalletService;
28 -  
29 @Resource 33 @Resource
30 private PayWalletTransactionMapper payWalletTransactionMapper; 34 private PayWalletTransactionMapper payWalletTransactionMapper;
  35 + @Resource
  36 + private PayNoRedisDAO noRedisDAO;
31 37
32 @Override 38 @Override
33 public PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType, 39 public PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType,
34 AppPayWalletTransactionPageReqVO pageVO) { 40 AppPayWalletTransactionPageReqVO pageVO) {
35 - PayWalletDO wallet = payWalletService.getPayWallet(userId, userType);  
36 - if (wallet == null) {  
37 - log.error("[getWalletTransactionPage][用户({}/{}) 钱包不存在", userId, userType);  
38 - throw exception(WALLET_NOT_FOUND);  
39 - } 41 + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
40 return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO); 42 return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO);
41 } 43 }
42 44
43 @Override 45 @Override
44 - public Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction) {  
45 - payWalletTransactionMapper.insert(payWalletTransaction);  
46 - return payWalletTransaction.getId(); 46 + public PayWalletTransactionDO createWalletTransaction(CreateWalletTransactionBO bo) {
  47 + PayWalletTransactionDO transaction = PayWalletTransactionConvert.INSTANCE.convert(bo)
  48 + .setNo(noRedisDAO.generate(WALLET_NO_PREFIX));
  49 + payWalletTransactionMapper.insert(transaction);
  50 + return transaction;
47 } 51 }
48 52
49 @Override 53 @Override
@@ -52,8 +56,8 @@ public class PayWalletTransactionServiceImpl implements PayWalletTransactionServ @@ -52,8 +56,8 @@ public class PayWalletTransactionServiceImpl implements PayWalletTransactionServ
52 } 56 }
53 57
54 @Override 58 @Override
55 - public PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type) {  
56 - return payWalletTransactionMapper.selectByWalletIdAndBiz(walletId, bizId, type.getType()); 59 + public PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type) {
  60 + return payWalletTransactionMapper.selectByBiz(bizId, type.getType());
57 } 61 }
58 62
59 } 63 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/CreateWalletTransactionBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.service.wallet.bo;
  2 +
  3 +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 创建钱包流水 BO
  8 + *
  9 + * @author jason
  10 + */
  11 +@Data
  12 +public class CreateWalletTransactionBO {
  13 +
  14 + // TODO @jason:bo 的话,最好加个参数校验哈;
  15 +
  16 + /**
  17 + * 钱包编号
  18 + *
  19 + */
  20 + private Long walletId;
  21 +
  22 + /**
  23 + * 交易金额,单位分
  24 + *
  25 + * 正值表示余额增加,负值表示余额减少
  26 + */
  27 + private Integer price;
  28 +
  29 + /**
  30 + * 交易后余额,单位分
  31 + */
  32 + private Integer balance;
  33 +
  34 + /**
  35 + * 关联业务分类
  36 + *
  37 + * 枚举 {@link PayWalletBizTypeEnum#getType()}
  38 + */
  39 + private Integer bizType;
  40 +
  41 + /**
  42 + * 关联业务编号
  43 + */
  44 + private String bizId;
  45 +
  46 + /**
  47 + * 流水说明
  48 + */
  49 + private String title;
  50 +}
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.api.social; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.api.social;
2 2
3 import cn.iocoder.yudao.framework.common.exception.ServiceException; 3 import cn.iocoder.yudao.framework.common.exception.ServiceException;
4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
5 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
6 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; 7 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
7 8
@@ -27,8 +28,9 @@ public interface SocialUserApi { @@ -27,8 +28,9 @@ public interface SocialUserApi {
27 * 绑定社交用户 28 * 绑定社交用户
28 * 29 *
29 * @param reqDTO 绑定信息 30 * @param reqDTO 绑定信息
  31 + * @return 社交用户 openid
30 */ 32 */
31 - void bindSocialUser(@Valid SocialUserBindReqDTO reqDTO); 33 + String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
32 34
33 /** 35 /**
34 * 取消绑定社交用户 36 * 取消绑定社交用户
@@ -38,16 +40,17 @@ public interface SocialUserApi { @@ -38,16 +40,17 @@ public interface SocialUserApi {
38 void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO); 40 void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO);
39 41
40 /** 42 /**
41 - * 获得社交用户的绑定用户编号  
42 - * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号! 43 + * 获得社交用户
  44 + *
43 * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 45 * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
44 * 46 *
45 * @param userType 用户类型 47 * @param userType 用户类型
46 * @param type 社交平台的类型 48 * @param type 社交平台的类型
47 * @param code 授权码 49 * @param code 授权码
48 * @param state state 50 * @param state state
49 - * @return 绑定用户编号 51 + * @return 社交用户
50 */ 52 */
51 - Long getBindUserId(Integer userType, Integer type, String code, String state); 53 + SocialUserRespDTO getSocialUser(Integer userType, Integer type,
  54 + String code, String state);
52 55
53 } 56 }
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.social.dto;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * 社交用户 Response DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class SocialUserRespDTO {
  16 +
  17 + /**
  18 + * 社交用户 openid
  19 + */
  20 + private String openid;
  21 +
  22 + /**
  23 + * 关联的用户编号
  24 + */
  25 + private Long userId;
  26 +
  27 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
1 package cn.iocoder.yudao.module.system.api.social; 1 package cn.iocoder.yudao.module.system.api.social;
2 2
3 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 3 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  4 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; 5 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
5 import cn.iocoder.yudao.module.system.service.social.SocialUserService; 6 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
6 import org.springframework.stereotype.Service; 7 import org.springframework.stereotype.Service;
@@ -26,8 +27,8 @@ public class SocialUserApiImpl implements SocialUserApi { @@ -26,8 +27,8 @@ public class SocialUserApiImpl implements SocialUserApi {
26 } 27 }
27 28
28 @Override 29 @Override
29 - public void bindSocialUser(SocialUserBindReqDTO reqDTO) {  
30 - socialUserService.bindSocialUser(reqDTO); 30 + public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
  31 + return socialUserService.bindSocialUser(reqDTO);
31 } 32 }
32 33
33 @Override 34 @Override
@@ -37,8 +38,8 @@ public class SocialUserApiImpl implements SocialUserApi { @@ -37,8 +38,8 @@ public class SocialUserApiImpl implements SocialUserApi {
37 } 38 }
38 39
39 @Override 40 @Override
40 - public Long getBindUserId(Integer userType, Integer type, String code, String state) {  
41 - return socialUserService.getBindUserId(userType, type, code, state); 41 + public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
  42 + return socialUserService.getSocialUser(userType, type, code, state);
42 } 43 }
43 44
44 } 45 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
9 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; 9 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
10 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 10 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
11 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 11 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  12 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
12 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; 13 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
13 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; 14 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
14 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; 15 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
@@ -155,14 +156,14 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -155,14 +156,14 @@ public class AdminAuthServiceImpl implements AdminAuthService {
155 @Override 156 @Override
156 public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) { 157 public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) {
157 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 158 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
158 - Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), 159 + SocialUserRespDTO socialUser = socialUserService.getSocialUser(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
159 reqVO.getCode(), reqVO.getState()); 160 reqVO.getCode(), reqVO.getState());
160 - if (userId == null) { 161 + if (socialUser == null) {
161 throw exception(AUTH_THIRD_LOGIN_NOT_BIND); 162 throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
162 } 163 }
163 164
164 // 获得用户 165 // 获得用户
165 - AdminUserDO user = userService.getUser(userId); 166 + AdminUserDO user = userService.getUser(socialUser.getUserId());
166 if (user == null) { 167 if (user == null) {
167 throw exception(USER_NOT_EXISTS); 168 throw exception(USER_NOT_EXISTS);
168 } 169 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.social; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.social;
2 2
3 import cn.iocoder.yudao.framework.common.exception.ServiceException; 3 import cn.iocoder.yudao.framework.common.exception.ServiceException;
4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
5 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; 6 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
6 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; 7 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
7 8
@@ -50,8 +51,9 @@ public interface SocialUserService { @@ -50,8 +51,9 @@ public interface SocialUserService {
50 * 绑定社交用户 51 * 绑定社交用户
51 * 52 *
52 * @param reqDTO 绑定信息 53 * @param reqDTO 绑定信息
  54 + * @return 社交用户 openid
53 */ 55 */
54 - void bindSocialUser(@Valid SocialUserBindReqDTO reqDTO); 56 + String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
55 57
56 /** 58 /**
57 * 取消绑定社交用户 59 * 取消绑定社交用户
@@ -64,15 +66,16 @@ public interface SocialUserService { @@ -64,15 +66,16 @@ public interface SocialUserService {
64 void unbindSocialUser(Long userId, Integer userType, Integer type, String openid); 66 void unbindSocialUser(Long userId, Integer userType, Integer type, String openid);
65 67
66 /** 68 /**
67 - * 获得社交用户的绑定用户编号  
68 - * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号! 69 + * 获得社交用户
  70 + *
69 * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 71 * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
70 * 72 *
71 * @param userType 用户类型 73 * @param userType 用户类型
72 * @param type 社交平台的类型 74 * @param type 社交平台的类型
73 * @param code 授权码 75 * @param code 授权码
74 * @param state state 76 * @param state state
75 - * @return 绑定用户编号 77 + * @return 社交用户
76 */ 78 */
77 - Long getBindUserId(Integer userType, Integer type, String code, String state); 79 + SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state);
  80 +
78 } 81 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert; @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils; 5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
6 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; 6 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
7 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 7 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  8 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; 9 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
9 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; 10 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
10 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper; 11 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@@ -98,7 +99,7 @@ public class SocialUserServiceImpl implements SocialUserService { @@ -98,7 +99,7 @@ public class SocialUserServiceImpl implements SocialUserService {
98 99
99 @Override 100 @Override
100 @Transactional 101 @Transactional
101 - public void bindSocialUser(SocialUserBindReqDTO reqDTO) { 102 + public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
102 // 获得社交用户 103 // 获得社交用户
103 SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState()); 104 SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
104 Assert.notNull(socialUser, "社交用户不能为空"); 105 Assert.notNull(socialUser, "社交用户不能为空");
@@ -115,6 +116,7 @@ public class SocialUserServiceImpl implements SocialUserService { @@ -115,6 +116,7 @@ public class SocialUserServiceImpl implements SocialUserService {
115 .userId(reqDTO.getUserId()).userType(reqDTO.getUserType()) 116 .userId(reqDTO.getUserId()).userType(reqDTO.getUserType())
116 .socialUserId(socialUser.getId()).socialType(socialUser.getType()).build(); 117 .socialUserId(socialUser.getId()).socialType(socialUser.getType()).build();
117 socialUserBindMapper.insert(socialUserBind); 118 socialUserBindMapper.insert(socialUserBind);
  119 + return socialUser.getOpenid();
118 } 120 }
119 121
120 @Override 122 @Override
@@ -130,7 +132,7 @@ public class SocialUserServiceImpl implements SocialUserService { @@ -130,7 +132,7 @@ public class SocialUserServiceImpl implements SocialUserService {
130 } 132 }
131 133
132 @Override 134 @Override
133 - public Long getBindUserId(Integer userType, Integer type, String code, String state) { 135 + public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
134 // 获得社交用户 136 // 获得社交用户
135 SocialUserDO socialUser = authSocialUser(type, code, state); 137 SocialUserDO socialUser = authSocialUser(type, code, state);
136 Assert.notNull(socialUser, "社交用户不能为空"); 138 Assert.notNull(socialUser, "社交用户不能为空");
@@ -141,7 +143,7 @@ public class SocialUserServiceImpl implements SocialUserService { @@ -141,7 +143,7 @@ public class SocialUserServiceImpl implements SocialUserService {
141 if (socialUserBind == null) { 143 if (socialUserBind == null) {
142 throw exception(AUTH_THIRD_LOGIN_NOT_BIND); 144 throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
143 } 145 }
144 - return socialUserBind.getUserId(); 146 + return new SocialUserRespDTO(socialUser.getOpenid(), socialUserBind.getUserId());
145 } 147 }
146 148
147 /** 149 /**
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
7 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 7 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
8 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 8 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  9 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
9 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; 10 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
10 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; 11 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 12 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
@@ -235,8 +236,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { @@ -235,8 +236,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
235 AuthSocialLoginReqVO reqVO = randomPojo(AuthSocialLoginReqVO.class); 236 AuthSocialLoginReqVO reqVO = randomPojo(AuthSocialLoginReqVO.class);
236 // mock 方法(绑定的用户编号) 237 // mock 方法(绑定的用户编号)
237 Long userId = 1L; 238 Long userId = 1L;
238 - when(socialUserService.getBindUserId(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()),  
239 - eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(userId); 239 + when(socialUserService.getSocialUser(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()),
  240 + eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(new SocialUserRespDTO(randomString(), userId));
240 // mock(用户) 241 // mock(用户)
241 AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId)); 242 AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId));
242 when(userService.getUser(eq(userId))).thenReturn(user); 243 when(userService.getUser(eq(userId))).thenReturn(user);
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; 4 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  7 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
7 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; 8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; 9 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
9 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper; 10 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@@ -195,10 +196,11 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest { @@ -195,10 +196,11 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
195 .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId())); 196 .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId()));
196 197
197 // 调用 198 // 调用
198 - socialUserService.bindSocialUser(reqDTO); 199 + String openid = socialUserService.bindSocialUser(reqDTO);
199 // 断言 200 // 断言
200 List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectList(); 201 List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectList();
201 assertEquals(1, socialUserBinds.size()); 202 assertEquals(1, socialUserBinds.size());
  203 + assertEquals(socialUser.getOpenid(), openid);
202 } 204 }
203 205
204 @Test 206 @Test
@@ -232,25 +234,26 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest { @@ -232,25 +234,26 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
232 } 234 }
233 235
234 @Test 236 @Test
235 - public void testGetBindUserId() { 237 + public void testGetSocialUser() {
236 // 准备参数 238 // 准备参数
237 Integer userType = UserTypeEnum.ADMIN.getValue(); 239 Integer userType = UserTypeEnum.ADMIN.getValue();
238 Integer type = SocialTypeEnum.GITEE.getType(); 240 Integer type = SocialTypeEnum.GITEE.getType();
239 String code = "tudou"; 241 String code = "tudou";
240 String state = "yuanma"; 242 String state = "yuanma";
241 // mock 社交用户 243 // mock 社交用户
242 - SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);  
243 - socialUserMapper.insert(socialUser); 244 + SocialUserDO socialUserDO = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
  245 + socialUserMapper.insert(socialUserDO);
244 // mock 社交用户的绑定 246 // mock 社交用户的绑定
245 Long userId = randomLong(); 247 Long userId = randomLong();
246 SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId) 248 SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId)
247 - .setSocialType(type).setSocialUserId(socialUser.getId()); 249 + .setSocialType(type).setSocialUserId(socialUserDO.getId());
248 socialUserBindMapper.insert(socialUserBind); 250 socialUserBindMapper.insert(socialUserBind);
249 251
250 // 调用 252 // 调用
251 - Long result = socialUserService.getBindUserId(userType, type, code, state); 253 + SocialUserRespDTO socialUser = socialUserService.getSocialUser(userType, type, code, state);
252 // 断言 254 // 断言
253 - assertEquals(userId, result); 255 + assertEquals(userId, socialUser.getUserId());
  256 + assertEquals(socialUserDO.getOpenid(), socialUser.getOpenid());
254 } 257 }
255 258
256 } 259 }
yudao-server/pom.xml
@@ -41,24 +41,24 @@ @@ -41,24 +41,24 @@
41 <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId> 41 <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
42 </dependency> 42 </dependency>
43 43
44 - <!-- 数据报表 --> 44 + <!-- 数据报表。默认注释,保证编译速度 -->
45 <!-- <dependency>--> 45 <!-- <dependency>-->
46 <!-- <groupId>cn.iocoder.boot</groupId>--> 46 <!-- <groupId>cn.iocoder.boot</groupId>-->
47 <!-- <artifactId>yudao-module-report-biz</artifactId>--> 47 <!-- <artifactId>yudao-module-report-biz</artifactId>-->
48 <!-- <version>${revision}</version>--> 48 <!-- <version>${revision}</version>-->
49 <!-- </dependency>--> 49 <!-- </dependency>-->
50 - <!-- 工作流 --> 50 + <!-- 工作流。默认注释,保证编译速度 -->
51 <!-- <dependency>--> 51 <!-- <dependency>-->
52 <!-- <groupId>cn.iocoder.boot</groupId>--> 52 <!-- <groupId>cn.iocoder.boot</groupId>-->
53 <!-- <artifactId>yudao-module-bpm-biz</artifactId>--> 53 <!-- <artifactId>yudao-module-bpm-biz</artifactId>-->
54 <!-- <version>${revision}</version>--> 54 <!-- <version>${revision}</version>-->
55 <!-- </dependency>--> 55 <!-- </dependency>-->
56 <!-- 支付服务。默认注释,保证编译速度 --> 56 <!-- 支付服务。默认注释,保证编译速度 -->
57 -<!-- <dependency>-->  
58 -<!-- <groupId>cn.iocoder.boot</groupId>-->  
59 -<!-- <artifactId>yudao-module-pay-biz</artifactId>-->  
60 -<!-- <version>${revision}</version>-->  
61 -<!-- </dependency>--> 57 + <dependency>
  58 + <groupId>cn.iocoder.boot</groupId>
  59 + <artifactId>yudao-module-pay-biz</artifactId>
  60 + <version>${revision}</version>
  61 + </dependency>
62 62
63 <!-- 微信公众号模块。默认注释,保证编译速度 --> 63 <!-- 微信公众号模块。默认注释,保证编译速度 -->
64 <!-- <dependency>--> 64 <!-- <dependency>-->
@@ -68,21 +68,21 @@ @@ -68,21 +68,21 @@
68 <!-- </dependency>--> 68 <!-- </dependency>-->
69 69
70 <!-- 商城相关模块。默认注释,保证编译速度 --> 70 <!-- 商城相关模块。默认注释,保证编译速度 -->
71 -<!-- <dependency>-->  
72 -<!-- <groupId>cn.iocoder.boot</groupId>-->  
73 -<!-- <artifactId>yudao-module-promotion-biz</artifactId>-->  
74 -<!-- <version>${revision}</version>-->  
75 -<!-- </dependency>-->  
76 -<!-- <dependency>-->  
77 -<!-- <groupId>cn.iocoder.boot</groupId>-->  
78 -<!-- <artifactId>yudao-module-product-biz</artifactId>-->  
79 -<!-- <version>${revision}</version>-->  
80 -<!-- </dependency>-->  
81 -<!-- <dependency>-->  
82 -<!-- <groupId>cn.iocoder.boot</groupId>-->  
83 -<!-- <artifactId>yudao-module-trade-biz</artifactId>-->  
84 -<!-- <version>${revision}</version>-->  
85 -<!-- </dependency>--> 71 + <dependency>
  72 + <groupId>cn.iocoder.boot</groupId>
  73 + <artifactId>yudao-module-promotion-biz</artifactId>
  74 + <version>${revision}</version>
  75 + </dependency>
  76 + <dependency>
  77 + <groupId>cn.iocoder.boot</groupId>
  78 + <artifactId>yudao-module-product-biz</artifactId>
  79 + <version>${revision}</version>
  80 + </dependency>
  81 + <dependency>
  82 + <groupId>cn.iocoder.boot</groupId>
  83 + <artifactId>yudao-module-trade-biz</artifactId>
  84 + <version>${revision}</version>
  85 + </dependency>
86 86
87 <!-- spring boot 配置所需依赖 --> 87 <!-- spring boot 配置所需依赖 -->
88 <dependency> 88 <dependency>
yudao-server/src/main/resources/application-local.yaml
@@ -165,16 +165,20 @@ debug: false @@ -165,16 +165,20 @@ debug: false
165 --- #################### 微信公众号、小程序相关配置 #################### 165 --- #################### 微信公众号、小程序相关配置 ####################
166 wx: 166 wx:
167 mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档 167 mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
168 - app-id: wx041349c6f39b268b  
169 - secret: 5abee519483bc9f8cb37ce280e814bd0 168 +# app-id: wx041349c6f39b268b
  169 +# secret: 5abee519483bc9f8cb37ce280e814bd0
  170 + app-id: wx5b23ba7a5589ecbb # 测试号
  171 + secret: 2a7b3b20c537e52e74afd395eb85f61f
170 # 存储配置,解决 AccessToken 的跨节点的共享 172 # 存储配置,解决 AccessToken 的跨节点的共享
171 config-storage: 173 config-storage:
172 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 174 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
173 key-prefix: wx # Redis Key 的前缀 175 key-prefix: wx # Redis Key 的前缀
174 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 176 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
175 miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档 177 miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
176 - appid: wx62056c0d5e8db250  
177 - secret: 333ae72f41552af1e998fe1f54e1584a 178 +# appid: wx62056c0d5e8db250
  179 +# secret: 333ae72f41552af1e998fe1f54e1584a
  180 + appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
  181 + secret: 6f270509224a7ae1296bbf1c8cb97aed
178 config-storage: 182 config-storage:
179 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 183 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
180 key-prefix: wa # Redis Key 的前缀 184 key-prefix: wa # Redis Key 的前缀
@@ -219,6 +223,11 @@ justauth: @@ -219,6 +223,11 @@ justauth:
219 client-secret: ${wx.miniapp.secret} 223 client-secret: ${wx.miniapp.secret}
220 ignore-check-redirect-uri: true 224 ignore-check-redirect-uri: true
221 ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验 225 ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验
  226 + WECHAT_MP: # 微信公众号
  227 + client-id: ${wx.mp.app-id}
  228 + client-secret: ${wx.mp.secret}
  229 + ignore-check-redirect-uri: true
  230 + ignore-check-state: true # 微信公众号,未调用后端的 getSocialAuthorizeUrl 方法,所以无法进行 state 校验 TODO 芋艿:后续考虑支持
222 231
223 cache: 232 cache:
224 type: REDIS 233 type: REDIS
yudao-ui-admin/src/utils/constants.js
@@ -163,6 +163,10 @@ export const PayChannelEnum = { @@ -163,6 +163,10 @@ export const PayChannelEnum = {
163 MOCK : { 163 MOCK : {
164 "code": "mock", 164 "code": "mock",
165 "name": "模拟支付" 165 "name": "模拟支付"
  166 + },
  167 + WALLET : {
  168 + "code": "wallet",
  169 + "name": "钱包支付"
166 } 170 }
167 } 171 }
168 172
yudao-ui-admin/src/views/pay/app/components/mockChannelForm.vue renamed to yudao-ui-admin/src/views/pay/app/components/noneConfigChannelForm.vue
@@ -5,7 +5,8 @@ @@ -5,7 +5,8 @@
5 <el-form-item label-width="180px" label="渠道状态" prop="status"> 5 <el-form-item label-width="180px" label="渠道状态" prop="status">
6 <el-radio-group v-model="formData.status" size="medium"> 6 <el-radio-group v-model="formData.status" size="medium">
7 <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value)" 7 <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value)"
8 - :label="parseInt(dict.value)"> 8 + :label="parseInt(dict.value)"
  9 + >
9 {{ dict.label }} 10 {{ dict.label }}
10 </el-radio> 11 </el-radio>
11 </el-radio-group> 12 </el-radio-group>
@@ -22,16 +23,16 @@ @@ -22,16 +23,16 @@
22 </div> 23 </div>
23 </template> 24 </template>
24 <script> 25 <script>
25 -import { createChannel, getChannel, updateChannel } from "@/api/pay/channel";  
26 -import { CommonStatusEnum } from "@/utils/constants"; 26 +import { createChannel, getChannel, updateChannel } from '@/api/pay/channel'
  27 +import { CommonStatusEnum } from '@/utils/constants'
27 28
28 export default { 29 export default {
29 - name: "mockChannelForm", 30 + name: 'noneConfigChannelForm',
30 data() { 31 data() {
31 return { 32 return {
32 dialogVisible: false, 33 dialogVisible: false,
33 formLoading: false, 34 formLoading: false,
34 - title:'', 35 + title: '',
35 formData: { 36 formData: {
36 appId: '', 37 appId: '',
37 code: '', 38 code: '',
@@ -39,54 +40,54 @@ export default { @@ -39,54 +40,54 @@ export default {
39 feeRate: 0, 40 feeRate: 0,
40 remark: '', 41 remark: '',
41 config: { 42 config: {
42 - name: 'mock-conf' 43 + name: 'none-config'
43 } 44 }
44 }, 45 },
45 rules: { 46 rules: {
46 - status: [{ required: true, message: '渠道状态不能为空', trigger: 'blur' }] 47 + status: [{ required: true, message: '渠道状态不能为空', trigger: 'blur' }]
47 } 48 }
48 } 49 }
49 }, 50 },
50 methods: { 51 methods: {
51 open(appId, code) { 52 open(appId, code) {
52 - this.dialogVisible = true;  
53 - this.formLoading = true;  
54 - this.reset(appId, code); 53 + this.dialogVisible = true
  54 + this.formLoading = true
  55 + this.reset(appId, code)
55 getChannel(appId, code).then(response => { 56 getChannel(appId, code).then(response => {
56 if (response.data && response.data.id) { 57 if (response.data && response.data.id) {
57 - this.formData = response.data;  
58 - this.formData.config = JSON.parse(response.data.config); 58 + this.formData = response.data
  59 + this.formData.config = JSON.parse(response.data.config)
59 } 60 }
60 this.title = !this.formData.id ? '创建支付渠道' : '编辑支付渠道' 61 this.title = !this.formData.id ? '创建支付渠道' : '编辑支付渠道'
61 }).finally(() => { 62 }).finally(() => {
62 - this.formLoading = false;  
63 - }); 63 + this.formLoading = false
  64 + })
64 }, 65 },
65 close() { 66 close() {
66 - this.dialogVisible = false;  
67 - this.reset(undefined, undefined); 67 + this.dialogVisible = false
  68 + this.reset(undefined, undefined)
68 }, 69 },
69 submitForm() { 70 submitForm() {
70 this.$refs['form'].validate(valid => { 71 this.$refs['form'].validate(valid => {
71 if (!valid) { 72 if (!valid) {
72 return 73 return
73 } 74 }
74 - const data = { ...this.formData };  
75 - data.config = JSON.stringify(this.formData.config); 75 + const data = { ...this.formData }
  76 + data.config = JSON.stringify(this.formData.config)
76 if (!data.id) { 77 if (!data.id) {
77 createChannel(data).then(response => { 78 createChannel(data).then(response => {
78 - this.$modal.msgSuccess("新增成功"); 79 + this.$modal.msgSuccess('新增成功')
79 this.$emit('success') 80 this.$emit('success')
80 - this.close();  
81 - }); 81 + this.close()
  82 + })
82 } else { 83 } else {
83 updateChannel(data).then(response => { 84 updateChannel(data).then(response => {
84 - this.$modal.msgSuccess("修改成功"); 85 + this.$modal.msgSuccess('修改成功')
85 this.$emit('success') 86 this.$emit('success')
86 - this.close(); 87 + this.close()
87 }) 88 })
88 } 89 }
89 - }); 90 + })
90 }, 91 },
91 /** 重置表单 */ 92 /** 重置表单 */
92 reset(appId, code) { 93 reset(appId, code) {
@@ -97,11 +98,11 @@ export default { @@ -97,11 +98,11 @@ export default {
97 remark: '', 98 remark: '',
98 feeRate: 0, 99 feeRate: 0,
99 config: { 100 config: {
100 - name: 'mock-conf' 101 + name: 'none-config'
101 } 102 }
102 } 103 }
103 this.resetForm('form') 104 this.resetForm('form')
104 - }, 105 + }
105 } 106 }
106 } 107 }
107 </script> 108 </script>
yudao-ui-admin/src/views/pay/app/index.vue
@@ -170,6 +170,19 @@ @@ -170,6 +170,19 @@
170 </template> 170 </template>
171 </el-table-column> 171 </el-table-column>
172 </el-table-column> 172 </el-table-column>
  173 + <el-table-column label="钱包支付配置" align="center">
  174 + <el-table-column :label="payChannelEnum.WALLET.name" align="center">
  175 + <template v-slot="scope">
  176 + <el-button type="success" icon="el-icon-check" circle
  177 + v-if="isChannelExists(scope.row.channelCodes, payChannelEnum.WALLET.code)"
  178 + @click="handleChannel(scope.row, payChannelEnum.WALLET.code)">
  179 + </el-button>
  180 + <el-button v-else type="danger" icon="el-icon-close" circle
  181 + @click="handleChannel(scope.row, payChannelEnum.WALLET.code)">
  182 + </el-button>
  183 + </template>
  184 + </el-table-column>
  185 + </el-table-column>
173 <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> 186 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
174 <template v-slot="scope"> 187 <template v-slot="scope">
175 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" 188 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
@@ -219,7 +232,7 @@ @@ -219,7 +232,7 @@
219 <!-- 对话框(支付应用的配置) --> 232 <!-- 对话框(支付应用的配置) -->
220 <weixin-channel-form ref="weixinChannelFormRef" @success="getList" /> 233 <weixin-channel-form ref="weixinChannelFormRef" @success="getList" />
221 <alipay-channel-form ref="alipayChannelFormRef" @success="getList" /> 234 <alipay-channel-form ref="alipayChannelFormRef" @success="getList" />
222 - <mock-channel-form ref="mockChannelFormRef" @success="getList" /> 235 + <none-config-channel-form ref="noneConfigChannelFormRef" @success="getList" />
223 </div> 236 </div>
224 </template> 237 </template>
225 238
@@ -228,14 +241,14 @@ import { createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage } @@ -228,14 +241,14 @@ import { createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage }
228 import { PayChannelEnum, CommonStatusEnum } from "@/utils/constants"; 241 import { PayChannelEnum, CommonStatusEnum } from "@/utils/constants";
229 import weixinChannelForm from "@/views/pay/app/components/weixinChannelForm"; 242 import weixinChannelForm from "@/views/pay/app/components/weixinChannelForm";
230 import alipayChannelForm from "@/views/pay/app/components/alipayChannelForm"; 243 import alipayChannelForm from "@/views/pay/app/components/alipayChannelForm";
231 -import mockChannelForm from '@/views/pay/app/components/mockChannelForm'; 244 +import noneConfigChannelForm from '@/views/pay/app/components/noneConfigChannelForm';
232 245
233 export default { 246 export default {
234 name: "PayApp", 247 name: "PayApp",
235 components: { 248 components: {
236 weixinChannelForm, 249 weixinChannelForm,
237 alipayChannelForm, 250 alipayChannelForm,
238 - mockChannelForm 251 + noneConfigChannelForm
239 }, 252 },
240 data() { 253 data() {
241 return { 254 return {
@@ -391,7 +404,11 @@ export default { @@ -391,7 +404,11 @@ export default {
391 return 404 return
392 } 405 }
393 if (code === 'mock') { 406 if (code === 'mock') {
394 - this.$refs['mockChannelFormRef'].open(row.id, code); 407 + this.$refs['noneConfigChannelFormRef'].open(row.id, code);
  408 + return
  409 + }
  410 + if (code === 'wallet') {
  411 + this.$refs['noneConfigChannelFormRef'].open(row.id, code);
395 return 412 return
396 } 413 }
397 }, 414 },
yudao-ui-admin/src/views/pay/cashier/index.vue
@@ -136,6 +136,10 @@ export default { @@ -136,6 +136,10 @@ export default {
136 name: '模拟支付', 136 name: '模拟支付',
137 icon: require("@/assets/images/pay/icon/mock.svg"), 137 icon: require("@/assets/images/pay/icon/mock.svg"),
138 code: "mock" 138 code: "mock"
  139 + }, {
  140 + name: '钱包支付',
  141 + icon: require("@/assets/images/pay/icon/mock.svg"),
  142 + code: "wallet"
139 }], 143 }],
140 submitLoading: false, // 提交支付的 loading 144 submitLoading: false, // 提交支付的 loading
141 interval: undefined, // 定时任务,轮询是否完成支付 145 interval: undefined, // 定时任务,轮询是否完成支付