Commit db0fbf05962304abc88a587c0c2e445b183f3b73

Authored by jason
2 parents 183206e2 6c8e82ca

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

Showing 62 changed files with 1363 additions and 328 deletions
sql/mysql/mall.sql 0 → 100644
  1 +INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
  2 +VALUES ('核销订单', '', 2, 2, 2166, 'pick-up-order', 'ep:list', 'mall/trade/delivery/pickUpOrder/index', 'PickUpOrder');
0 3 \ No newline at end of file
... ...
sql/mysql/ruoyi-vue-pro.sql
... ... @@ -11,7 +11,7 @@
11 11 Target Server Version : 80034
12 12 File Encoding : 65001
13 13  
14   - Date: 17/10/2023 23:22:59
  14 + Date: 18/10/2023 23:33:15
15 15 */
16 16  
17 17 SET NAMES utf8mb4;
... ... @@ -384,7 +384,7 @@ CREATE TABLE `infra_api_error_log` (
384 384 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
385 385 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
386 386 PRIMARY KEY (`id`) USING BTREE
387   -) ENGINE = InnoDB AUTO_INCREMENT = 1735 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
  387 +) ENGINE = InnoDB AUTO_INCREMENT = 1739 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
388 388  
389 389 -- ----------------------------
390 390 -- Records of infra_api_error_log
... ... @@ -422,7 +422,7 @@ CREATE TABLE `infra_codegen_column` (
422 422 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
423 423 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
424 424 PRIMARY KEY (`id`) USING BTREE
425   -) ENGINE = InnoDB AUTO_INCREMENT = 1804 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
  425 +) ENGINE = InnoDB AUTO_INCREMENT = 1805 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
426 426  
427 427 -- ----------------------------
428 428 -- Records of infra_codegen_column
... ... @@ -455,7 +455,7 @@ CREATE TABLE `infra_codegen_table` (
455 455 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
456 456 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
457 457 PRIMARY KEY (`id`) USING BTREE
458   -) ENGINE = InnoDB AUTO_INCREMENT = 136 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
  458 +) ENGINE = InnoDB AUTO_INCREMENT = 137 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
459 459  
460 460 -- ----------------------------
461 461 -- Records of infra_codegen_table
... ... @@ -615,7 +615,7 @@ CREATE TABLE `infra_job` (
615 615 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
616 616 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
617 617 PRIMARY KEY (`id`) USING BTREE
618   -) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
  618 +) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
619 619  
620 620 -- ----------------------------
621 621 -- Records of infra_job
... ... @@ -656,7 +656,7 @@ CREATE TABLE `infra_job_log` (
656 656 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
657 657 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
658 658 PRIMARY KEY (`id`) USING BTREE
659   -) ENGINE = InnoDB AUTO_INCREMENT = 232 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
  659 +) ENGINE = InnoDB AUTO_INCREMENT = 233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
660 660  
661 661 -- ----------------------------
662 662 -- Records of infra_job_log
... ... @@ -709,7 +709,7 @@ CREATE TABLE `member_address` (
709 709 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
710 710 PRIMARY KEY (`id`) USING BTREE,
711 711 INDEX `idx_userId`(`user_id` ASC) USING BTREE
712   -) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
  712 +) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
713 713  
714 714 -- ----------------------------
715 715 -- Records of member_address
... ... @@ -768,7 +768,7 @@ CREATE TABLE `member_experience_record` (
768 768 PRIMARY KEY (`id`) USING BTREE,
769 769 INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员经验记录-用户编号',
770 770 INDEX `idx_user_biz_type`(`user_id` ASC, `biz_type` ASC) USING BTREE COMMENT '会员经验记录-用户业务类型'
771   -) ENGINE = InnoDB AUTO_INCREMENT = 40 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
  771 +) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
772 772  
773 773 -- ----------------------------
774 774 -- Records of member_experience_record
... ... @@ -883,7 +883,7 @@ CREATE TABLE `member_level_record` (
883 883 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
884 884 PRIMARY KEY (`id`) USING BTREE,
885 885 INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员等级记录-用户编号'
886   -) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
  886 +) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
887 887  
888 888 -- ----------------------------
889 889 -- Records of member_level_record
... ... @@ -913,7 +913,7 @@ CREATE TABLE `member_point_record` (
913 913 PRIMARY KEY (`id`) USING BTREE,
914 914 INDEX `index_userId`(`user_id` ASC) USING BTREE,
915 915 INDEX `index_title`(`title` ASC) USING BTREE
916   -) ENGINE = InnoDB AUTO_INCREMENT = 59 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
  916 +) ENGINE = InnoDB AUTO_INCREMENT = 60 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
917 917  
918 918 -- ----------------------------
919 919 -- Records of member_point_record
... ... @@ -1540,7 +1540,7 @@ CREATE TABLE `system_error_code` (
1540 1540 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
1541 1541 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1542 1542 PRIMARY KEY (`id`) USING BTREE
1543   -) ENGINE = InnoDB AUTO_INCREMENT = 5932 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
  1543 +) ENGINE = InnoDB AUTO_INCREMENT = 5933 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
1544 1544  
1545 1545 -- ----------------------------
1546 1546 -- Records of system_error_code
... ... @@ -1569,7 +1569,7 @@ CREATE TABLE `system_login_log` (
1569 1569 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1570 1570 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
1571 1571 PRIMARY KEY (`id`) USING BTREE
1572   -) ENGINE = InnoDB AUTO_INCREMENT = 2582 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
  1572 +) ENGINE = InnoDB AUTO_INCREMENT = 2599 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
1573 1573  
1574 1574 -- ----------------------------
1575 1575 -- Records of system_login_log
... ... @@ -1634,7 +1634,7 @@ CREATE TABLE `system_mail_log` (
1634 1634 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
1635 1635 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1636 1636 PRIMARY KEY (`id`) USING BTREE
1637   -) ENGINE = InnoDB AUTO_INCREMENT = 354 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
  1637 +) ENGINE = InnoDB AUTO_INCREMENT = 355 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
1638 1638  
1639 1639 -- ----------------------------
1640 1640 -- Records of system_mail_log
... ... @@ -2283,7 +2283,7 @@ CREATE TABLE `system_notify_template` (
2283 2283 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
2284 2284 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2285 2285 PRIMARY KEY (`id`) USING BTREE
2286   -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
  2286 +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
2287 2287  
2288 2288 -- ----------------------------
2289 2289 -- Records of system_notify_template
... ... @@ -2311,7 +2311,7 @@ CREATE TABLE `system_oauth2_access_token` (
2311 2311 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2312 2312 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2313 2313 PRIMARY KEY (`id`) USING BTREE
2314   -) ENGINE = InnoDB AUTO_INCREMENT = 3056 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
  2314 +) ENGINE = InnoDB AUTO_INCREMENT = 3063 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
2315 2315  
2316 2316 -- ----------------------------
2317 2317 -- Records of system_oauth2_access_token
... ... @@ -2433,7 +2433,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
2433 2433 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2434 2434 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2435 2435 PRIMARY KEY (`id`) USING BTREE
2436   -) ENGINE = InnoDB AUTO_INCREMENT = 1063 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
  2436 +) ENGINE = InnoDB AUTO_INCREMENT = 1070 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
2437 2437  
2438 2438 -- ----------------------------
2439 2439 -- Records of system_oauth2_refresh_token
... ... @@ -3481,7 +3481,7 @@ CREATE TABLE `system_sms_code` (
3481 3481 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3482 3482 PRIMARY KEY (`id`) USING BTREE,
3483 3483 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
3484   -) ENGINE = InnoDB AUTO_INCREMENT = 530 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
  3484 +) ENGINE = InnoDB AUTO_INCREMENT = 531 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
3485 3485  
3486 3486 -- ----------------------------
3487 3487 -- Records of system_sms_code
... ... @@ -3575,6 +3575,33 @@ INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `cont
3575 3575 COMMIT;
3576 3576  
3577 3577 -- ----------------------------
  3578 +-- Table structure for system_social_client
  3579 +-- ----------------------------
  3580 +DROP TABLE IF EXISTS `system_social_client`;
  3581 +CREATE TABLE `system_social_client` (
  3582 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  3583 + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名',
  3584 + `social_type` tinyint NOT NULL COMMENT '社交平台的类型',
  3585 + `user_type` tinyint NOT NULL COMMENT '用户类型',
  3586 + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
  3587 + `client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端密钥',
  3588 + `status` tinyint NOT NULL COMMENT '状态',
  3589 + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  3590 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  3591 + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
  3592 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  3593 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  3594 + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
  3595 + PRIMARY KEY (`id`) USING BTREE
  3596 +) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交客户端表';
  3597 +
  3598 +-- ----------------------------
  3599 +-- Records of system_social_client
  3600 +-- ----------------------------
  3601 +BEGIN;
  3602 +COMMIT;
  3603 +
  3604 +-- ----------------------------
3578 3605 -- Table structure for system_social_user
3579 3606 -- ----------------------------
3580 3607 DROP TABLE IF EXISTS `system_social_user`;
... ... @@ -3596,7 +3623,7 @@ CREATE TABLE `system_social_user` (
3596 3623 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3597 3624 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3598 3625 PRIMARY KEY (`id`) USING BTREE
3599   -) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
  3626 +) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
3600 3627  
3601 3628 -- ----------------------------
3602 3629 -- Records of system_social_user
... ... @@ -3621,7 +3648,7 @@ CREATE TABLE `system_social_user_bind` (
3621 3648 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3622 3649 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3623 3650 PRIMARY KEY (`id`) USING BTREE
3624   -) ENGINE = InnoDB AUTO_INCREMENT = 76 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
  3651 +) ENGINE = InnoDB AUTO_INCREMENT = 79 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
3625 3652  
3626 3653 -- ----------------------------
3627 3654 -- Records of system_social_user_bind
... ... @@ -3797,7 +3824,7 @@ CREATE TABLE `system_users` (
3797 3824 -- Records of system_users
3798 3825 -- ----------------------------
3799 3826 BEGIN;
3800   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/e1fdd7271685ec143a0900681606406621717a666ad0b2798b096df41422b32f.png', 0, '0:0:0:0:0:0:0:1', '2023-10-17 21:03:03', 'admin', '2021-01-05 17:03:47', NULL, '2023-10-17 21:03:03', b'0', 1);
  3827 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/e1fdd7271685ec143a0900681606406621717a666ad0b2798b096df41422b32f.png', 0, '0:0:0:0:0:0:0:1', '2023-10-18 22:31:35', 'admin', '2021-01-05 17:03:47', NULL, '2023-10-18 22:31:35', b'0', 1);
3801 3828 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1);
3802 3829 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '127.0.0.1', '2022-07-08 01:26:27', '', '2021-01-13 23:50:35', NULL, '2022-07-08 01:26:27', b'0', 1);
3803 3830 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2023-09-24 18:21:19', '', '2021-01-21 02:13:53', NULL, '2023-09-24 18:21:19', b'0', 1);
... ... @@ -3805,7 +3832,7 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`,
3805 3832 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119);
3806 3833 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120);
3807 3834 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '127.0.0.1', '2022-09-25 22:47:33', '1', '2022-02-22 00:56:14', NULL, '2022-09-25 22:47:33', b'0', 121);
3808   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, 'test', '$2a$10$mExveopHUx9Q4QiLtAzhDeH3n4/QlNLzEsM4AqgxKrU.ciUZDXZCy', '测试用户', NULL, NULL, '[]', '', '', 0, '', 0, '', NULL, '110', '2022-02-23 13:14:33', '110', '2022-02-23 13:14:33', b'0', 121);
  3835 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, NULL, '[]', '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2023-10-18 23:31:51', '110', '2022-02-23 13:14:33', NULL, '2023-10-18 23:31:51', b'0', 121);
3809 3836 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (112, 'newobject', '$2a$10$3alwklxqfq8/hKoW6oUV0OJp0IdQpBDauLy4633SpUjrRsStl6kMa', '新对象', NULL, 100, '[]', '', '', 1, '', 0, '0:0:0:0:0:0:0:1', '2023-02-10 13:48:13', '1', '2022-02-23 19:08:03', NULL, '2023-02-10 13:48:13', b'0', 1);
3810 3837 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', NULL, '2022-03-19 18:38:51', b'0', 122);
3811 3838 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[3]', '', '', 0, '', 0, '127.0.0.1', '2022-03-19 22:15:43', '1', '2022-03-19 21:50:58', NULL, '2022-03-19 22:15:43', b'0', 1);
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mybatis.core.query;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
  6 +import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
  7 +import com.github.yulichang.toolkit.MPJWrappers;
  8 +import com.github.yulichang.wrapper.MPJLambdaWrapper;
  9 +import org.springframework.util.StringUtils;
  10 +
  11 +import java.util.Collection;
  12 +import java.util.function.Consumer;
  13 +
  14 +/**
  15 + * 拓展 MyBatis Plus Join QueryWrapper 类,主要增加如下功能:
  16 + * <p>
  17 + * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
  18 + *
  19 + * @param <T> 数据类型
  20 + */
  21 +public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
  22 +
  23 + public MPJLambdaWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
  24 + MPJWrappers.lambdaJoin().like(column, val);
  25 + if (StringUtils.hasText(val)) {
  26 + return (MPJLambdaWrapperX<T>) super.like(column, val);
  27 + }
  28 + return this;
  29 + }
  30 +
  31 + public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
  32 + if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
  33 + return (MPJLambdaWrapperX<T>) super.in(column, values);
  34 + }
  35 + return this;
  36 + }
  37 +
  38 + public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
  39 + if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
  40 + return (MPJLambdaWrapperX<T>) super.in(column, values);
  41 + }
  42 + return this;
  43 + }
  44 +
  45 + public MPJLambdaWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
  46 + if (ObjectUtil.isNotEmpty(val)) {
  47 + return (MPJLambdaWrapperX<T>) super.eq(column, val);
  48 + }
  49 + return this;
  50 + }
  51 +
  52 + public MPJLambdaWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
  53 + if (ObjectUtil.isNotEmpty(val)) {
  54 + return (MPJLambdaWrapperX<T>) super.ne(column, val);
  55 + }
  56 + return this;
  57 + }
  58 +
  59 + public MPJLambdaWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
  60 + if (val != null) {
  61 + return (MPJLambdaWrapperX<T>) super.gt(column, val);
  62 + }
  63 + return this;
  64 + }
  65 +
  66 + public MPJLambdaWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
  67 + if (val != null) {
  68 + return (MPJLambdaWrapperX<T>) super.ge(column, val);
  69 + }
  70 + return this;
  71 + }
  72 +
  73 + public MPJLambdaWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
  74 + if (val != null) {
  75 + return (MPJLambdaWrapperX<T>) super.lt(column, val);
  76 + }
  77 + return this;
  78 + }
  79 +
  80 + public MPJLambdaWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
  81 + if (val != null) {
  82 + return (MPJLambdaWrapperX<T>) super.le(column, val);
  83 + }
  84 + return this;
  85 + }
  86 +
  87 + public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
  88 + if (val1 != null && val2 != null) {
  89 + return (MPJLambdaWrapperX<T>) super.between(column, val1, val2);
  90 + }
  91 + if (val1 != null) {
  92 + return (MPJLambdaWrapperX<T>) ge(column, val1);
  93 + }
  94 + if (val2 != null) {
  95 + return (MPJLambdaWrapperX<T>) le(column, val2);
  96 + }
  97 + return this;
  98 + }
  99 +
  100 + public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
  101 + Object val1 = ArrayUtils.get(values, 0);
  102 + Object val2 = ArrayUtils.get(values, 1);
  103 + return betweenIfPresent(column, val1, val2);
  104 + }
  105 +
  106 + // ========== 重写父类方法,方便链式调用 ==========
  107 +
  108 + @Override
  109 + public <X> MPJLambdaWrapperX<T> eq(boolean condition, SFunction<X, ?> column, Object val) {
  110 + super.eq(condition, column, val);
  111 + return this;
  112 + }
  113 +
  114 + @Override
  115 + public <X> MPJLambdaWrapperX<T> eq(SFunction<X, ?> column, Object val) {
  116 + super.eq(column, val);
  117 + return this;
  118 + }
  119 +
  120 + @Override
  121 + public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) {
  122 + //noinspection unchecked
  123 + super.orderByDesc(true, column);
  124 + return this;
  125 + }
  126 +
  127 + @Override
  128 + public MPJLambdaWrapperX<T> last(String lastSql) {
  129 + super.last(lastSql);
  130 + return this;
  131 + }
  132 +
  133 + @Override
  134 + public <X> MPJLambdaWrapperX<T> in(SFunction<X, ?> column, Collection<?> coll) {
  135 + super.in(column, coll);
  136 + return this;
  137 + }
  138 +
  139 + @Override
  140 + public MPJLambdaWrapperX<T> selectAll(Class<?> clazz) {
  141 + super.selectAll(clazz);
  142 + return this;
  143 + }
  144 +
  145 + @Override
  146 + public MPJLambdaWrapperX<T> selectAll(Class<?> clazz, String prefix) {
  147 + super.selectAll(clazz, prefix);
  148 + return this;
  149 + }
  150 +
  151 + @Override
  152 + public <S> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, String alias) {
  153 + super.selectAs(column, alias);
  154 + return this;
  155 + }
  156 +
  157 + @Override
  158 + public <E> MPJLambdaWrapperX<T> selectAs(String column, SFunction<E, ?> alias) {
  159 + super.selectAs(column, alias);
  160 + return this;
  161 + }
  162 +
  163 + @Override
  164 + public <S, X> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, SFunction<X, ?> alias) {
  165 + super.selectAs(column, alias);
  166 + return this;
  167 + }
  168 +
  169 + @Override
  170 + public <E, X> MPJLambdaWrapperX<T> selectAs(String index, SFunction<E, ?> column, SFunction<X, ?> alias) {
  171 + super.selectAs(index, column, alias);
  172 + return this;
  173 + }
  174 +
  175 + @Override
  176 + public <E> MPJLambdaWrapperX<T> selectAsClass(Class<E> source, Class<?> tag) {
  177 + super.selectAsClass(source, tag);
  178 + return this;
  179 + }
  180 +
  181 + @Override
  182 + public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
  183 + super.selectSub(clazz, consumer, alias);
  184 + return this;
  185 + }
  186 +
  187 + @Override
  188 + public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, String st, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
  189 + super.selectSub(clazz, st, consumer, alias);
  190 + return this;
  191 + }
  192 +
  193 + @Override
  194 + public <S> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column) {
  195 + super.selectCount(column);
  196 + return this;
  197 + }
  198 +
  199 + @Override
  200 + public MPJLambdaWrapperX<T> selectCount(Object column, String alias) {
  201 + super.selectCount(column, alias);
  202 + return this;
  203 + }
  204 +
  205 + @Override
  206 + public <X> MPJLambdaWrapperX<T> selectCount(Object column, SFunction<X, ?> alias) {
  207 + super.selectCount(column, alias);
  208 + return this;
  209 + }
  210 +
  211 + @Override
  212 + public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, String alias) {
  213 + super.selectCount(column, alias);
  214 + return this;
  215 + }
  216 +
  217 + @Override
  218 + public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, SFunction<X, ?> alias) {
  219 + super.selectCount(column, alias);
  220 + return this;
  221 + }
  222 +
  223 + @Override
  224 + public <S> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column) {
  225 + super.selectSum(column);
  226 + return this;
  227 + }
  228 +
  229 + @Override
  230 + public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, String alias) {
  231 + super.selectSum(column, alias);
  232 + return this;
  233 + }
  234 +
  235 + @Override
  236 + public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, SFunction<X, ?> alias) {
  237 + super.selectSum(column, alias);
  238 + return this;
  239 + }
  240 +
  241 + @Override
  242 + public <S> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column) {
  243 + super.selectMax(column);
  244 + return this;
  245 + }
  246 +
  247 + @Override
  248 + public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, String alias) {
  249 + super.selectMax(column, alias);
  250 + return this;
  251 + }
  252 +
  253 + @Override
  254 + public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, SFunction<X, ?> alias) {
  255 + super.selectMax(column, alias);
  256 + return this;
  257 + }
  258 +
  259 + @Override
  260 + public <S> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column) {
  261 + super.selectMin(column);
  262 + return this;
  263 + }
  264 +
  265 + @Override
  266 + public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, String alias) {
  267 + super.selectMin(column, alias);
  268 + return this;
  269 + }
  270 +
  271 + @Override
  272 + public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, SFunction<X, ?> alias) {
  273 + super.selectMin(column, alias);
  274 + return this;
  275 + }
  276 +
  277 + @Override
  278 + public <S> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column) {
  279 + super.selectAvg(column);
  280 + return this;
  281 + }
  282 +
  283 + @Override
  284 + public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, String alias) {
  285 + super.selectAvg(column, alias);
  286 + return this;
  287 + }
  288 +
  289 + @Override
  290 + public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, SFunction<X, ?> alias) {
  291 + super.selectAvg(column, alias);
  292 + return this;
  293 + }
  294 +
  295 + @Override
  296 + public <S> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column) {
  297 + super.selectLen(column);
  298 + return this;
  299 + }
  300 +
  301 + @Override
  302 + public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, String alias) {
  303 + super.selectLen(column, alias);
  304 + return this;
  305 + }
  306 +
  307 + @Override
  308 + public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, SFunction<X, ?> alias) {
  309 + super.selectLen(column, alias);
  310 + return this;
  311 + }
  312 +
  313 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/article/vo/article/ArticleBaseVO.java
... ... @@ -34,10 +34,6 @@ public class ArticleBaseVO {
34 34 @Schema(description = "文章简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "这是一个简介")
35 35 private String introduction;
36 36  
37   - // TODO @puhui999:浏览量的字段,应该不是后端新增设置的哈;
38   - @Schema(description = "浏览次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "111")
39   - private String browseCount;
40   -
41 37 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
42 38 @NotNull(message = "排序不能为空")
43 39 private Integer sort;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/article/vo/article/ArticleRespVO.java
... ... @@ -16,6 +16,9 @@ public class ArticleRespVO extends ArticleBaseVO {
16 16 @Schema(description = "文章编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8606")
17 17 private Long id;
18 18  
  19 + @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "99999")
  20 + private Integer browseCount;
  21 +
19 22 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
20 23 private LocalDateTime createTime;
21 24  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java
... ... @@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestParam;
22 22 import org.springframework.web.bind.annotation.RestController;
23 23  
24 24 import javax.annotation.Resource;
  25 +import java.time.LocalDateTime;
25 26 import java.util.*;
26 27  
27 28 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -45,7 +46,7 @@ public class AppActivityController {
45 46 @Parameter(name = "spuId", description = "商品编号", required = true)
46 47 public CommonResult<List<AppActivityRespVO>> getActivityListBySpuId(@RequestParam("spuId") Long spuId) {
47 48 // 每种活动,只返回一个
48   - return success(getAppActivityRespVOList(Collections.singletonList(spuId)));
  49 + return success(getAppActivityList(Collections.singletonList(spuId)));
49 50 }
50 51  
51 52 @GetMapping("/list-by-spu-ids")
... ... @@ -56,17 +57,19 @@ public class AppActivityController {
56 57 return success(MapUtil.empty());
57 58 }
58 59 // 每种活动,只返回一个;key 为 SPU 编号
59   - return success(convertMultiMap(getAppActivityRespVOList(spuIds), AppActivityRespVO::getSpuId));
  60 + return success(convertMultiMap(getAppActivityList(spuIds), AppActivityRespVO::getSpuId));
60 61 }
61 62  
62   - private List<AppActivityRespVO> getAppActivityRespVOList(Collection<Long> spuIds) {
  63 + private List<AppActivityRespVO> getAppActivityList(Collection<Long> spuIds) {
63 64 if (CollUtil.isEmpty(spuIds)) {
64 65 return new ArrayList<>();
65 66 }
  67 + LocalDateTime now = LocalDateTime.now();
66 68 List<AppActivityRespVO> activityList = new ArrayList<>();
67   - // 拼团活动
68   - List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatus(
69   - spuIds, CommonStatusEnum.ENABLE.getStatus());
  69 +
  70 + // 1. 拼团活动 - 获取开启的且开始的且没有结束的活动
  71 + List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(
  72 + spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
70 73 if (CollUtil.isNotEmpty(combinationActivities)) {
71 74 combinationActivities.forEach(item -> {
72 75 activityList.add(new AppActivityRespVO().setId(item.getId())
... ... @@ -74,9 +77,10 @@ public class AppActivityController {
74 77 .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
75 78 });
76 79 }
77   - // 秒杀活动
78   - List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatus(
79   - spuIds, CommonStatusEnum.ENABLE.getStatus());
  80 +
  81 + // 2. 秒杀活动 - 获取开启的且开始的且没有结束的活动
  82 + List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(
  83 + spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
80 84 if (CollUtil.isNotEmpty(seckillActivities)) {
81 85 seckillActivities.forEach(item -> {
82 86 activityList.add(new AppActivityRespVO().setId(item.getId())
... ... @@ -84,9 +88,10 @@ public class AppActivityController {
84 88 .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
85 89 });
86 90 }
87   - // 砍价活动
88   - List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatus(
89   - spuIds, CommonStatusEnum.ENABLE.getStatus());
  91 +
  92 + // 3. 砍价活动 - 获取开启的且开始的且没有结束的活动
  93 + List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatusAndDateTimeLt(
  94 + spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
90 95 if (CollUtil.isNotEmpty(bargainActivities)) {
91 96 bargainActivities.forEach(item -> {
92 97 activityList.add(new AppActivityRespVO().setId(item.getId())
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/AppArticleCategoryController.java
... ... @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
13 13 import org.springframework.web.bind.annotation.RestController;
14 14  
15 15 import javax.annotation.Resource;
  16 +import java.util.Comparator;
16 17 import java.util.List;
17 18  
18 19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -31,7 +32,7 @@ public class AppArticleCategoryController {
31 32 public CommonResult<List<AppArticleCategoryRespVO>> getArticleCategoryList() {
32 33 List<ArticleCategoryDO> categoryList = articleCategoryService.getArticleCategoryListByStatus(
33 34 CommonStatusEnum.ENABLE.getStatus());
34   - // TODO @puhui999:排序下
  35 + categoryList.sort(Comparator.comparing(ArticleCategoryDO::getSort)); // 按 sort 降序排列
35 36 return success(ArticleCategoryConvert.INSTANCE.convertList04(categoryList));
36 37 }
37 38  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/AppArticleController.java
... ... @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.Parameter;
11 11 import io.swagger.v3.oas.annotations.Parameters;
12 12 import io.swagger.v3.oas.annotations.tags.Tag;
13 13 import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.PutMapping;
14 15 import org.springframework.web.bind.annotation.RequestMapping;
15 16 import org.springframework.web.bind.annotation.RequestParam;
16 17 import org.springframework.web.bind.annotation.RestController;
... ... @@ -55,5 +56,14 @@ public class AppArticleController {
55 56 return success(ArticleConvert.INSTANCE.convert01(articleService.getArticle(id)));
56 57 }
57 58  
58   - // TODO @puhui999:增加浏览量,实现一个接口;先简单做,用户规模不大,只 +1 即可;ps:uniapp 那边也要接下噢
  59 + // TODO @puhui999:add-browse-count 噢;前端 uniapp 也要接下;就是打开文章的时候,调用下这个接口;
  60 + @PutMapping("/add-browseCount")
  61 + @Operation(summary = "增加文章浏览量")
  62 + @Parameter(name = "id", description = "文章编号", example = "1024")
  63 + public CommonResult<Boolean> addBrowseCount(@RequestParam("id") Long id) {
  64 + // TODO @puhui999:addArticleBrowseCount
  65 + articleService.addBrowseCount(id);
  66 + return success(true);
  67 + }
  68 +
59 69 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
... ... @@ -207,31 +207,23 @@ public interface CombinationActivityConvert {
207 207 /**
208 208 * 转换生成虚拟成团虚拟记录
209 209 *
210   - * @param virtualGroupHeadRecord 虚拟成团团长记录
  210 + * @param headRecord 虚拟成团团长记录
211 211 * @return 虚拟记录列表
212 212 */
213   - // TODO @puhui999:1)方法名,建议改成 convertVirtualRecordList(CombinationRecordDO headRecord);2)第 220 到 225 可以搞成 mapstruct 一个方法,默认都 copy 进去,然后 set 第 226 到 232 的字段
214   - default List<CombinationRecordDO> convertVirtualGroupList(CombinationRecordDO virtualGroupHeadRecord) {
215   - List<CombinationRecordDO> createRecords = new ArrayList<>();
216   - // 计算需要创建的虚拟成团记录数量
217   - int count = virtualGroupHeadRecord.getUserSize() - virtualGroupHeadRecord.getUserCount();
  213 + default List<CombinationRecordDO> convertVirtualRecordList(CombinationRecordDO headRecord) {
  214 + int count = headRecord.getUserSize() - headRecord.getUserCount();
  215 + List<CombinationRecordDO> createRecords = new ArrayList<>(count);
218 216 for (int i = 0; i < count; i++) {
219 217 // 基础信息和团长保持一致
220   - CombinationRecordDO newRecord = new CombinationRecordDO().setActivityId(virtualGroupHeadRecord.getActivityId())
221   - .setCombinationPrice(virtualGroupHeadRecord.getCombinationPrice()).setSpuId(virtualGroupHeadRecord.getSpuId()).setSpuName(virtualGroupHeadRecord.getSpuName())
222   - .setPicUrl(virtualGroupHeadRecord.getPicUrl()).setSkuId(virtualGroupHeadRecord.getSkuId()).setHeadId(virtualGroupHeadRecord.getId())
223   - .setStatus(virtualGroupHeadRecord.getStatus()) // 状态保持和创建时一致,创建完成后会接着处理
224   - .setVirtualGroup(virtualGroupHeadRecord.getVirtualGroup()).setExpireTime(virtualGroupHeadRecord.getExpireTime())
225   - .setStartTime(virtualGroupHeadRecord.getStartTime()).setUserSize(virtualGroupHeadRecord.getUserSize()).setUserCount(virtualGroupHeadRecord.getUserCount());
  218 + CombinationRecordDO newRecord = convert5(headRecord);
226 219 // 虚拟信息
227   - newRecord.setCount(0);
228   - newRecord.setUserId(0L);
229   - newRecord.setNickname("");
230   - newRecord.setAvatar("");
231   - newRecord.setOrderId(0L);
  220 + newRecord.setCount(0) // 会单独更新下,在后续的 Service 逻辑里
  221 + .setUserId(0L).setNickname("").setAvatar("").setOrderId(0L);
232 222 createRecords.add(newRecord);
233 223 }
234 224 return createRecords;
235 225 }
  226 + @Mapping(target = "id", ignore = true)
  227 + CombinationRecordDO convert5(CombinationRecordDO headRecord);
236 228  
237 229 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/article/ArticleDO.java
... ... @@ -54,7 +54,7 @@ public class ArticleDO extends BaseDO {
54 54 /**
55 55 * 浏览次数
56 56 */
57   - private String browseCount;
  57 + private Integer browseCount;
58 58 /**
59 59 * 排序
60 60 */
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/article/ArticleCategoryMapper.java
... ... @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.Ar
7 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleCategoryDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
  10 +import java.util.List;
  11 +
10 12 /**
11 13 * 文章分类 Mapper
12 14 *
... ... @@ -23,4 +25,8 @@ public interface ArticleCategoryMapper extends BaseMapperX&lt;ArticleCategoryDO&gt; {
23 25 .orderByDesc(ArticleCategoryDO::getSort));
24 26 }
25 27  
  28 + default List<ArticleCategoryDO> selectListByStatus(Integer status) {
  29 + return selectList(ArticleCategoryDO::getStatus, status);
  30 + }
  31 +
26 32 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/article/ArticleMapper.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.article.ArticlePageReqVO;
7 7 import cn.iocoder.yudao.module.promotion.controller.app.article.vo.article.AppArticlePageReqVO;
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleDO;
  9 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 10 import org.apache.ibatis.annotations.Mapper;
10 11  
11 12 import java.util.List;
... ... @@ -42,5 +43,10 @@ public interface ArticleMapper extends BaseMapperX&lt;ArticleDO&gt; {
42 43 .eqIfPresent(ArticleDO::getCategoryId, pageReqVO.getCategoryId()));
43 44 }
44 45  
  46 + default void updateBrowseCount(Long id) {
  47 + update(null, new LambdaUpdateWrapper<ArticleDO>()
  48 + .eq(ArticleDO::getId, id)
  49 + .setSql("browse_count = browse_count + 1"));
  50 + }
45 51  
46 52 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
... ... @@ -102,9 +102,20 @@ public interface BargainActivityMapper extends BaseMapperX&lt;BargainActivityDO&gt; {
102 102 .groupBy("spu_id"));
103 103 }
104 104  
105   - default List<BargainActivityDO> selectListByIds(Collection<Long> ids) {
  105 + // TODO @puhui999:是不是只要 endTime 小于就可以啦;
  106 + /**
  107 + * 获取指定活动编号的活动列表且
  108 + * 开始时间和结束时间小于给定时间 dateTime 的活动列表
  109 + *
  110 + * @param ids 活动编号
  111 + * @param dateTime 指定日期
  112 + * @return 活动列表
  113 + */
  114 + default List<BargainActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
106 115 return selectList(new LambdaQueryWrapperX<BargainActivityDO>()
107 116 .in(BargainActivityDO::getId, ids)
  117 + .lt(BargainActivityDO::getStartTime, dateTime)
  118 + .lt(BargainActivityDO::getEndTime, dateTime)
108 119 .orderByDesc(BargainActivityDO::getCreateTime));
109 120 }
110 121  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java
... ... @@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
10 10 import org.apache.ibatis.annotations.Mapper;
11 11 import org.apache.ibatis.annotations.Param;
12 12  
  13 +import java.time.LocalDateTime;
13 14 import java.util.Collection;
14 15 import java.util.List;
15 16 import java.util.Map;
... ... @@ -58,9 +59,19 @@ public interface CombinationActivityMapper extends BaseMapperX&lt;CombinationActivi
58 59 .groupBy("spu_id"));
59 60 }
60 61  
61   - default List<CombinationActivityDO> selectListByIds(Collection<Long> ids) {
  62 + /**
  63 + * 获取指定活动编号的活动列表且
  64 + * 开始时间和结束时间小于给定时间 dateTime 的活动列表
  65 + *
  66 + * @param ids 活动编号
  67 + * @param dateTime 指定日期
  68 + * @return 活动列表
  69 + */
  70 + default List<CombinationActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
62 71 return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
63 72 .in(CombinationActivityDO::getId, ids)
  73 + .lt(CombinationActivityDO::getStartTime, dateTime)
  74 + .lt(CombinationActivityDO::getEndTime, dateTime)
64 75 .orderByDesc(CombinationActivityDO::getCreateTime));
65 76 }
66 77  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
... ... @@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
13 13 import org.apache.ibatis.annotations.Mapper;
14 14 import org.apache.ibatis.annotations.Param;
15 15  
  16 +import java.time.LocalDateTime;
16 17 import java.util.Collection;
17 18 import java.util.List;
18 19 import java.util.Map;
... ... @@ -90,9 +91,19 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
90 91 .groupBy("spu_id"));
91 92 }
92 93  
93   - default List<SeckillActivityDO> selectListByIds(Collection<Long> ids) {
  94 + /**
  95 + * 获取指定活动编号的活动列表且
  96 + * 开始时间和结束时间小于给定时间 dateTime 的活动列表
  97 + *
  98 + * @param ids 活动编号
  99 + * @param dateTime 指定日期
  100 + * @return 活动列表
  101 + */
  102 + default List<SeckillActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
94 103 return selectList(new LambdaQueryWrapperX<SeckillActivityDO>()
95 104 .in(SeckillActivityDO::getId, ids)
  105 + .lt(SeckillActivityDO::getStartTime, dateTime)
  106 + .lt(SeckillActivityDO::getEndTime, dateTime)
96 107 .orderByDesc(SeckillActivityDO::getCreateTime));
97 108 }
98 109  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/article/ArticleCategoryServiceImpl.java
1 1 package cn.iocoder.yudao.module.promotion.service.article;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryCreateReqVO;
6 5 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryPageReqVO;
7 6 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryUpdateReqVO;
8 7 import cn.iocoder.yudao.module.promotion.convert.article.ArticleCategoryConvert;
9 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleCategoryDO;
10   -import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleDO;
11 9 import cn.iocoder.yudao.module.promotion.dal.mysql.article.ArticleCategoryMapper;
12 10 import org.springframework.context.annotation.Lazy;
13 11 import org.springframework.stereotype.Service;
... ... @@ -59,9 +57,8 @@ public class ArticleCategoryServiceImpl implements ArticleCategoryService {
59 57 // 校验存在
60 58 validateArticleCategoryExists(id);
61 59 // 校验是不是存在关联文章
62   - // TODO @puhui999:最好获得数量哈;
63   - List<ArticleDO> articleList = articleService.getArticleByCategoryId(id);
64   - if (CollUtil.isNotEmpty(articleList)) {
  60 + Long count = articleService.getArticleCountByCategoryId(id);
  61 + if (count > 0) {
65 62 throw exception(ARTICLE_CATEGORY_DELETE_FAIL_HAVE_ARTICLES);
66 63 }
67 64  
... ... @@ -87,8 +84,7 @@ public class ArticleCategoryServiceImpl implements ArticleCategoryService {
87 84  
88 85 @Override
89 86 public List<ArticleCategoryDO> getArticleCategoryListByStatus(Integer status) {
90   - // TODO @puhui999:selectListByStatus
91   - return articleCategoryMapper.selectList(ArticleCategoryDO::getStatus, status);
  87 + return articleCategoryMapper.selectListByStatus(status);
92 88 }
93 89  
94 90 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/article/ArticleService.java
... ... @@ -80,4 +80,19 @@ public interface ArticleService {
80 80 */
81 81 List<ArticleDO> getArticleByCategoryId(Long categoryId);
82 82  
  83 + /**
  84 + * 获得指定分类的文章数量
  85 + *
  86 + * @param categoryId 文章分类编号
  87 + * @return 文章数量
  88 + */
  89 + Long getArticleCountByCategoryId(Long categoryId);
  90 +
  91 + /**
  92 + * 增加文章浏览量
  93 + *
  94 + * @param id 文章编号
  95 + */
  96 + void addBrowseCount(Long id);
  97 +
83 98 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/article/ArticleServiceImpl.java
... ... @@ -41,6 +41,7 @@ public class ArticleServiceImpl implements ArticleService {
41 41  
42 42 // 插入
43 43 ArticleDO article = ArticleConvert.INSTANCE.convert(createReqVO);
  44 + article.setBrowseCount(0); // 初始浏览量
44 45 articleMapper.insert(article);
45 46 // 返回
46 47 return article.getId();
... ... @@ -104,4 +105,17 @@ public class ArticleServiceImpl implements ArticleService {
104 105 return articleMapper.selectList(ArticleDO::getCategoryId, categoryId);
105 106 }
106 107  
  108 + @Override
  109 + public Long getArticleCountByCategoryId(Long categoryId) {
  110 + return articleMapper.selectCount(ArticleDO::getCategoryId, categoryId);
  111 + }
  112 +
  113 + @Override
  114 + public void addBrowseCount(Long id) {
  115 + // 校验文章是否存在
  116 + validateArticleExists(id);
  117 + // 增加浏览次数
  118 + articleMapper.updateBrowseCount(id);
  119 + }
  120 +
107 121 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
... ... @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.Ba
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
9 9  
10 10 import javax.validation.Valid;
  11 +import java.time.LocalDateTime;
11 12 import java.util.Collection;
12 13 import java.util.List;
13 14 import java.util.Set;
... ... @@ -102,10 +103,11 @@ public interface BargainActivityService {
102 103 /**
103 104 * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
104 105 *
105   - * @param spuIds spu 编号
106   - * @param status 状态
  106 + * @param spuIds spu 编号
  107 + * @param status 状态
  108 + * @param dateTime 日期时间
107 109 * @return 砍价活动列表
108 110 */
109   - List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
  111 + List<BargainActivityDO> getBargainActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
110 112  
111 113 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
... ... @@ -182,15 +182,15 @@ public class BargainActivityServiceImpl implements BargainActivityService {
182 182 }
183 183  
184 184 @Override
185   - public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  185 + public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
186 186 // 1. 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
187   - // TODO @puhui999:我想了下,这种是不是只展示当前正在进行中的。已经结束、或者未开始的,可能没啥意义?
188 187 List<Map<String, Object>> spuIdAndActivityIdMaps = bargainActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
189 188 if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
190 189 return Collections.emptyList();
191 190 }
192 191 // 2. 查询活动详情
193   - return bargainActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
  192 + return bargainActivityMapper.selectListByIdsAndDateTimeLt(
  193 + convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
194 194 }
195 195  
196 196 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationA
9 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
10 10  
11 11 import javax.validation.Valid;
  12 +import java.time.LocalDateTime;
12 13 import java.util.Collection;
13 14 import java.util.Collections;
14 15 import java.util.List;
... ... @@ -120,10 +121,11 @@ public interface CombinationActivityService {
120 121 /**
121 122 * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
122 123 *
123   - * @param spuIds spu 编号
124   - * @param status 状态
  124 + * @param spuIds spu 编号
  125 + * @param status 状态
  126 + * @param dateTime 日期时间
125 127 * @return 拼团活动列表
126 128 */
127   - List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
  129 + List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
128 130  
129 131 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
... ... @@ -25,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
25 25 import org.springframework.validation.annotation.Validated;
26 26  
27 27 import javax.annotation.Resource;
  28 +import java.time.LocalDateTime;
28 29 import java.util.Collection;
29 30 import java.util.Collections;
30 31 import java.util.List;
... ... @@ -228,14 +229,15 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
228 229 }
229 230  
230 231 @Override
231   - public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  232 + public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
232 233 // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
233 234 List<Map<String, Object>> spuIdAndActivityIdMaps = combinationActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
234 235 if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
235 236 return Collections.emptyList();
236 237 }
237 238 // 2.查询活动详情
238   - return combinationActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
  239 + return combinationActivityMapper.selectListByIdsAndDateTimeLt(
  240 + convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
239 241 }
240 242  
241 243 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
... ... @@ -6,6 +6,7 @@ import cn.hutool.extra.spring.SpringUtil;
6 6 import cn.iocoder.yudao.framework.common.core.KeyValue;
7 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
8 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
9 10 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
10 11 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
11 12 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
... ... @@ -22,6 +23,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationR
22 23 import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
23 24 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
24 25 import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
  26 +import lombok.extern.slf4j.Slf4j;
25 27 import org.springframework.stereotype.Service;
26 28 import org.springframework.transaction.annotation.Transactional;
27 29 import org.springframework.validation.annotation.Validated;
... ... @@ -45,6 +47,7 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
45 47 * @author HUIHUI
46 48 */
47 49 @Service
  50 +@Slf4j
48 51 @Validated
49 52 public class CombinationRecordServiceImpl implements CombinationRecordService {
50 53  
... ... @@ -353,21 +356,22 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
353 356  
354 357 // 3. 逐个处理拼团,过期 or 虚拟成团
355 358 KeyValue<Integer, Integer> keyValue = new KeyValue<>(0, 0); // 统计过期拼团和虚拟成团
356   - for (CombinationRecordDO recordDO : headExpireRecords) {
357   - // TODO @puhui999:recordDO 非必要的情况下,不用带 DO;直接 record;
  359 + for (CombinationRecordDO record : headExpireRecords) {
358 360 try {
359   - CombinationActivityDO activity = activityMap.get(recordDO.getActivityId());
  361 + CombinationActivityDO activity = activityMap.get(record.getActivityId());
360 362 if (activity == null || !activity.getVirtualGroup()) { // 取不到活动的或者不是虚拟拼团的
361 363 // 3.1. 处理过期的拼团
362   - getSelf().handleExpireRecord(recordDO);
  364 + getSelf().handleExpireRecord(record);
363 365 keyValue.setKey(keyValue.getKey() + 1);
364 366 } else {
365 367 // 3.2. 处理虚拟成团
366   - getSelf().handleVirtualGroupRecord(recordDO);
  368 + getSelf().handleVirtualGroupRecord(record);
367 369 keyValue.setValue(keyValue.getValue() + 1);
368 370 }
369 371 } catch (Exception ignored) { // 处理异常继续循环
370   - // TODO @puhui999:需要打印异常日志
  372 + // TODO @puhui999:拼团过期 or 虚拟成团 可以改成 expireCombinationRecord;因为找方法更容易一些;
  373 + log.error("[拼团过期 or 虚拟成团][record({}) 处理异常,请进行处理!record 数据是:{}]",
  374 + record.getId(), JsonUtils.toJsonString(record));
371 375 }
372 376 }
373 377 return keyValue;
... ... @@ -376,68 +380,52 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
376 380 /**
377 381 * 处理过期拼团
378 382 *
379   - * @param headExpireRecord 过期拼团团长记录列表
  383 + * @param headRecord 过期拼团团长记录
380 384 */
381 385 @Transactional(rollbackFor = Exception.class)
382   - public void handleExpireRecord(CombinationRecordDO headExpireRecord) {
383   - // TODO @puhui999:这里的 null 其实不用判断。真出现,应该要处个 npe,因为就是要错哈;
384   - // TODO @puhui999:headExpireRecord 可以简化成 headRecord
385   - if (headExpireRecord == null) {
386   - return;
387   - }
388   -
389   - // 1.更新拼团记录
390   - List<CombinationRecordDO> headsAndRecords = updateBatchCombinationRecords(headExpireRecord,
  386 + public void handleExpireRecord(CombinationRecordDO headRecord) {
  387 + // 1. 更新拼团记录
  388 + List<CombinationRecordDO> headAndRecords = updateBatchCombinationRecords(headRecord,
391 389 CombinationRecordStatusEnum.FAILED);
392   - // TODO @puhui999:这里的 null 其实不用判断。真出现,应该要处个 npe,因为就是要错哈;
393   - if (headsAndRecords == null) {
394   - return;
395   - }
396   -
397   - // 2.订单取消
398   - headsAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId()));
  390 + // 2. 订单取消
  391 + headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId()));
399 392 }
400 393  
401 394 /**
402 395 * 处理虚拟拼团
403 396 *
404   - * @param virtualGroupHeadRecord 虚拟成团团长记录列表
  397 + * @param headRecord 虚拟成团团长记录
405 398 */
406 399 @Transactional(rollbackFor = Exception.class)
407   - public void handleVirtualGroupRecord(CombinationRecordDO virtualGroupHeadRecord) {
408   - // TODO @puhui999:这里的 null 其实不用判断。真出现,应该要处个 npe,因为就是要错哈;
409   - // TODO @puhui999:headExpireRecord 可以简化成 headRecord
410   - if (virtualGroupHeadRecord == null) {
411   - return;
412   - }
413   -
  400 + public void handleVirtualGroupRecord(CombinationRecordDO headRecord) {
414 401 // 1. 团员补齐
415   - combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualGroupList(virtualGroupHeadRecord));
  402 + combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualRecordList(headRecord));
416 403 // 2. 更新拼团记录
417   - updateBatchCombinationRecords(virtualGroupHeadRecord, CombinationRecordStatusEnum.SUCCESS);
  404 + updateBatchCombinationRecords(headRecord, CombinationRecordStatusEnum.SUCCESS);
418 405 }
419 406  
420   - // TODO @puhui999:写下方法注释;
  407 + /**
  408 + * 更新拼团记录
  409 + *
  410 + * @param headRecord 团长记录
  411 + * @param status 状态-拼团失败 FAILED 成功 SUCCESS
  412 + * @return 整团记录(包含团长和团成员)
  413 + */
421 414 private List<CombinationRecordDO> updateBatchCombinationRecords(CombinationRecordDO headRecord, CombinationRecordStatusEnum status) {
422 415 // 1. 查询团成员(包含团长)
423 416 List<CombinationRecordDO> records = combinationRecordMapper.selectListByHeadId(headRecord.getId());
424   - // TODO @puhui999:是不是不用判断空哈;例如说,就一个团长,然后过期。
425   - if (CollUtil.isEmpty(records)) {
426   - return null;
427   - }
428 417 records.add(headRecord);// 把团长加进去
429 418  
430 419 // 2. 批量更新拼团记录 status 和 endTime
431 420 List<CombinationRecordDO> updateRecords = new ArrayList<>(records.size());
432 421 LocalDateTime now = LocalDateTime.now();
433 422 records.forEach(item -> {
434   - // TODO @puhui999:record 改成 updateRecord
435   - CombinationRecordDO record = new CombinationRecordDO().setId(item.getId())
  423 + CombinationRecordDO updateRecord = new CombinationRecordDO().setId(item.getId())
436 424 .setStatus(status.getStatus()).setEndTime(now);
437 425 if (CombinationRecordStatusEnum.isSuccess(status.getStatus())) { // 虚拟成团完事更改状态成功后还需要把参与人数修改为成团需要人数
438   - record.setUserCount(record.getUserSize());
  426 + updateRecord.setUserCount(updateRecord.getUserSize());
439 427 }
440   - updateRecords.add(record);
  428 + updateRecords.add(updateRecord);
441 429 });
442 430 combinationRecordMapper.updateBatch(updateRecords);
443 431 return records;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
... ... @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityD
10 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
11 11  
12 12 import javax.validation.Valid;
  13 +import java.time.LocalDateTime;
13 14 import java.util.Collection;
14 15 import java.util.List;
15 16  
... ... @@ -131,10 +132,11 @@ public interface SeckillActivityService {
131 132 /**
132 133 * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
133 134 *
134   - * @param spuIds spu 编号
135   - * @param status 状态
  135 + * @param spuIds spu 编号
  136 + * @param status 状态
  137 + * @param dateTime 日期时间
136 138 * @return 秒杀活动列表
137 139 */
138   - List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
  140 + List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
139 141  
140 142 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
... ... @@ -28,6 +28,7 @@ import org.springframework.transaction.annotation.Transactional;
28 28 import org.springframework.validation.annotation.Validated;
29 29  
30 30 import javax.annotation.Resource;
  31 +import java.time.LocalDateTime;
31 32 import java.util.Collection;
32 33 import java.util.Collections;
33 34 import java.util.List;
... ... @@ -324,14 +325,15 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
324 325 }
325 326  
326 327 @Override
327   - public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  328 + public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
328 329 // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
329 330 List<Map<String, Object>> spuIdAndActivityIdMaps = seckillActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
330 331 if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
331 332 return Collections.emptyList();
332 333 }
333 334 // 2.查询活动详情
334   - return seckillActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
  335 + return seckillActivityMapper.selectListByIdsAndDateTimeLt(
  336 + convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
335 337 }
336 338  
337 339 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java
... ... @@ -24,8 +24,11 @@ import org.springframework.web.bind.annotation.*;
24 24 import javax.annotation.Resource;
25 25 import java.util.List;
26 26 import java.util.Map;
  27 +import java.util.Objects;
  28 +import java.util.Set;
27 29  
28 30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
29 32 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
30 33  
31 34 @Tag(name = "管理后台 - 交易订单")
... ... @@ -56,7 +59,9 @@ public class TradeOrderController {
56 59 }
57 60  
58 61 // 查询用户信息
59   - Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(convertSet(pageResult.getList(), TradeOrderDO::getUserId));
  62 + Set<Long> userIds = CollUtil.unionDistinct(convertList(pageResult.getList(), TradeOrderDO::getUserId),
  63 + convertList(pageResult.getList(), TradeOrderDO::getBrokerageUserId, Objects::nonNull));
  64 + Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
60 65 // 查询订单项
61 66 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(
62 67 convertSet(pageResult.getList(), TradeOrderDO::getId));
... ... @@ -64,6 +69,13 @@ public class TradeOrderController {
64 69 return success(TradeOrderConvert.INSTANCE.convertPage(pageResult, orderItems, userMap));
65 70 }
66 71  
  72 + @GetMapping("/summary")
  73 + @Operation(summary = "获得交易订单统计")
  74 + @PreAuthorize("@ss.hasPermission('trade:order:query')")
  75 + public CommonResult<TradeOrderSummaryRespVO> getOrderSummary(TradeOrderPageReqVO reqVO) {
  76 + return success(tradeOrderQueryService.getOrderSummary(reqVO));
  77 + }
  78 +
67 79 @GetMapping("/get-detail")
68 80 @Operation(summary = "获得交易订单详情")
69 81 @Parameter(name = "id", description = "订单编号", required = true, example = "1")
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderBaseVO.java
... ... @@ -145,4 +145,7 @@ public class TradeOrderBaseVO {
145 145 @Schema(description = "VIP 减免金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
146 146 private Integer vipPrice;
147 147  
  148 + @Schema(description = "推广人编号", example = "1")
  149 + private Long brokerageUserId;
  150 +
148 151 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageItemRespVO.java
1 1 package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
2 2  
3   -import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  3 +import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
4 4 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
5 5 import io.swagger.v3.oas.annotations.media.Schema;
6 6 import lombok.Data;
... ... @@ -14,24 +14,20 @@ public class TradeOrderPageItemRespVO extends TradeOrderBaseVO {
14 14 @Schema(description = "收件人地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海 上海市 普陀区")
15 15 private String receiverAreaName;
16 16  
17   - /**
18   - * 订单项列表
19   - */
  17 + @Schema(description = "订单项列表", requiredMode = Schema.RequiredMode.REQUIRED)
20 18 private List<Item> items;
21 19  
22   - // TODO @xiaobai:使用 MemberUserRespVO 返回哈;DTO 不直接给前端
23   - /**
24   - * 用户信息
25   - */
26   - private MemberUserRespDTO user;
  20 + @Schema(description = "用户信息", requiredMode = Schema.RequiredMode.REQUIRED)
  21 + private MemberUserRespVO user;
  22 +
  23 + @Schema(description = "推广人信息")
  24 + private MemberUserRespVO brokerageUser;
27 25  
28 26 @Schema(description = "管理后台 - 交易订单的分页项的订单项目")
29 27 @Data
30 28 public static class Item extends TradeOrderItemBaseVO {
31 29  
32   - /**
33   - * 属性数组
34   - */
  30 + @Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
35 31 private List<ProductPropertyValueDetailRespVO> properties;
36 32  
37 33 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderSummaryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - 交易订单统计 Response VO")
  7 +@Data
  8 +public class TradeOrderSummaryRespVO {
  9 +
  10 + @Schema(description = "订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  11 + private Long orderCount;
  12 +
  13 + @Schema(description = "订单金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long orderPayPrice;
  15 +
  16 + @Schema(description = "退款单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + private Long afterSaleCount;
  18 +
  19 + @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  20 + private Long afterSalePrice;
  21 +
  22 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
... ... @@ -124,13 +124,17 @@ public interface TradeOrderConvert {
124 124 TradeOrderPageItemRespVO orderVO = convert(order, xOrderItems);
125 125 // 处理收货地址
126 126 orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId()));
127   - // 增加用户昵称
128   - orderVO.setUser(memberUserMap.get(orderVO.getUserId()));
  127 + // 增加用户信息
  128 + orderVO.setUser(convertUser(memberUserMap.get(orderVO.getUserId())));
  129 + // 增加推广人信息
  130 + orderVO.setBrokerageUser(convertUser(memberUserMap.get(orderVO.getBrokerageUserId())));
129 131 return orderVO;
130 132 });
131 133 return new PageResult<>(orderVOs, pageResult.getTotal());
132 134 }
133 135  
  136 + MemberUserRespVO convertUser(MemberUserRespDTO memberUserRespDTO);
  137 +
134 138 TradeOrderPageItemRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> items);
135 139  
136 140 ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.dal.mysql.order;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
7 8 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
8 9 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
... ... @@ -11,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
11 12  
12 13 import java.time.LocalDateTime;
13 14 import java.util.List;
  15 +import java.util.Map;
14 16 import java.util.Set;
15 17  
16 18 @Mapper
... ... @@ -42,6 +44,27 @@ public interface TradeOrderMapper extends BaseMapperX&lt;TradeOrderDO&gt; {
42 44 .orderByDesc(TradeOrderDO::getId));
43 45 }
44 46  
  47 + // TODO @疯狂:如果用 map 返回,要不这里直接用 TradeOrderSummaryRespVO 返回?也算合理,就当 sql 查询出这么个玩意~~
  48 + default List<Map<String, Object>> selectOrderSummaryGroupByRefundStatus(TradeOrderPageReqVO reqVO, Set<Long> userIds) {
  49 + return selectMaps(new MPJLambdaWrapperX<TradeOrderDO>()
  50 + .selectAs(TradeOrderDO::getRefundStatus, TradeOrderDO::getRefundStatus) // 售后状态
  51 + .selectCount(TradeOrderDO::getId, "count") // 售后状态对应的数量
  52 + .selectSum(TradeOrderDO::getPayPrice, "price") // 售后状态对应的支付金额
  53 + .likeIfPresent(TradeOrderDO::getNo, reqVO.getNo())
  54 + .eqIfPresent(TradeOrderDO::getUserId, reqVO.getUserId())
  55 + .eqIfPresent(TradeOrderDO::getDeliveryType, reqVO.getDeliveryType())
  56 + .inIfPresent(TradeOrderDO::getUserId, userIds)
  57 + .eqIfPresent(TradeOrderDO::getType, reqVO.getType())
  58 + .eqIfPresent(TradeOrderDO::getStatus, reqVO.getStatus())
  59 + .eqIfPresent(TradeOrderDO::getPayChannelCode, reqVO.getPayChannelCode())
  60 + .eqIfPresent(TradeOrderDO::getTerminal, reqVO.getTerminal())
  61 + .eqIfPresent(TradeOrderDO::getLogisticsId, reqVO.getLogisticsId())
  62 + .inIfPresent(TradeOrderDO::getPickUpStoreId, reqVO.getPickUpStoreIds())
  63 + .likeIfPresent(TradeOrderDO::getPickUpVerifyCode, reqVO.getPickUpVerifyCode())
  64 + .betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime())
  65 + .groupBy(TradeOrderDO::getRefundStatus)); // 按售后状态分组
  66 + }
  67 +
45 68 default PageResult<TradeOrderDO> selectPage(AppTradeOrderPageReqVO reqVO, Long userId) {
46 69 return selectPage(reqVO, new LambdaQueryWrapperX<TradeOrderDO>()
47 70 .eq(TradeOrderDO::getUserId, userId)
... ... @@ -92,7 +115,7 @@ public interface TradeOrderMapper extends BaseMapperX&lt;TradeOrderDO&gt; {
92 115 default TradeOrderDO selectOneByPickUpVerifyCode(String pickUpVerifyCode) {
93 116 return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode);
94 117 }
95   -
  118 +
96 119 default TradeOrderDO selectByUserIdAndCombinationActivityIdAndStatus(Long userId, Long combinationActivityId, Integer status) {
97 120 return selectOne(new LambdaQueryWrapperX<TradeOrderDO>()
98 121 .eq(TradeOrderDO::getUserId, userId)
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/core/aop/TradeOrderLogAspect.java
... ... @@ -58,10 +58,6 @@ public class TradeOrderLogAspect {
58 58 */
59 59 private static final ThreadLocal<Map<String, Object>> EXTS = new ThreadLocal<>();
60 60  
61   - public TradeOrderLogAspect() {
62   - System.out.println();
63   - }
64   -
65 61 @Resource
66 62 private TradeOrderLogService orderLogService;
67 63  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.order;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
  5 +import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderSummaryRespVO;
5 6 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
6 7 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
7 8 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
... ... @@ -65,6 +66,14 @@ public interface TradeOrderQueryService {
65 66 PageResult<TradeOrderDO> getOrderPage(TradeOrderPageReqVO reqVO);
66 67  
67 68 /**
  69 + * 获得订单统计
  70 + *
  71 + * @param reqVO 请求参数
  72 + * @return 订单统计
  73 + */
  74 + TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO);
  75 +
  76 + /**
68 77 * 【会员】获得交易订单分页
69 78 *
70 79 * @param userId 用户编号
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
1 1 package cn.iocoder.yudao.module.trade.service.order;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
4 5 import cn.hutool.core.util.ObjectUtil;
5 6 import cn.hutool.core.util.StrUtil;
6 7 import cn.hutool.extra.spring.SpringUtil;
... ... @@ -8,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 9 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
9 10 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
10 11 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
  12 +import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderSummaryRespVO;
11 13 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
12 14 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
13 15 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
... ... @@ -15,6 +17,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
15 17 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
16 18 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
17 19 import cn.iocoder.yudao.module.trade.dal.redis.RedisKeyConstants;
  20 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
18 21 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
19 22 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory;
20 23 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
... ... @@ -85,24 +88,57 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
85 88  
86 89 @Override
87 90 public PageResult<TradeOrderDO> getOrderPage(TradeOrderPageReqVO reqVO) {
  91 + // 根据用户查询条件构建用户编号列表
  92 + Set<Long> userIds = buildQueryConditionUserIds(reqVO);
  93 + if (userIds == null) { // 没查询到用户,说明肯定也没他的订单
  94 + return PageResult.empty();
  95 + }
  96 + // 分页查询
  97 + return tradeOrderMapper.selectPage(reqVO, userIds);
  98 + }
  99 +
  100 + private Set<Long> buildQueryConditionUserIds(TradeOrderPageReqVO reqVO) {
88 101 // 获得 userId 相关的查询
89 102 Set<Long> userIds = new HashSet<>();
90 103 if (StrUtil.isNotEmpty(reqVO.getUserMobile())) {
91 104 MemberUserRespDTO user = memberUserApi.getUserByMobile(reqVO.getUserMobile());
92 105 if (user == null) { // 没查询到用户,说明肯定也没他的订单
93   - return new PageResult<>();
  106 + return null;
94 107 }
95 108 userIds.add(user.getId());
96 109 }
97 110 if (StrUtil.isNotEmpty(reqVO.getUserNickname())) {
98 111 List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(reqVO.getUserNickname());
99 112 if (CollUtil.isEmpty(users)) { // 没查询到用户,说明肯定也没他的订单
100   - return new PageResult<>();
  113 + return null;
101 114 }
102 115 userIds.addAll(convertSet(users, MemberUserRespDTO::getId));
103 116 }
104   - // 分页查询
105   - return tradeOrderMapper.selectPage(reqVO, userIds);
  117 + return userIds;
  118 + }
  119 +
  120 + @Override
  121 + public TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO) {
  122 + // 根据用户查询条件构建用户编号列表
  123 + Set<Long> userIds = buildQueryConditionUserIds(reqVO);
  124 + if (userIds == null) { // 没查询到用户,说明肯定也没他的订单
  125 + return new TradeOrderSummaryRespVO();
  126 + }
  127 + // 查询每个售后状态对应的数量、金额
  128 + List<Map<String, Object>> list = tradeOrderMapper.selectOrderSummaryGroupByRefundStatus(reqVO, null);
  129 +
  130 + TradeOrderSummaryRespVO vo = new TradeOrderSummaryRespVO().setAfterSaleCount(0L).setAfterSalePrice(0L);
  131 + for (Map<String, Object> map : list) {
  132 + Long count = MapUtil.getLong(map, "count", 0L);
  133 + Long price = MapUtil.getLong(map, "price", 0L);
  134 + // 未退款的计入订单,部分退款、全部退款计入售后
  135 + if (TradeOrderRefundStatusEnum.NONE.getStatus().equals(MapUtil.getInt(map, "refundStatus"))) {
  136 + vo.setOrderCount(count).setOrderPayPrice(price);
  137 + } else {
  138 + vo.setAfterSaleCount(vo.getAfterSaleCount() + count).setAfterSalePrice(vo.getAfterSalePrice() + price);
  139 + }
  140 + }
  141 + return vo;
106 142 }
107 143  
108 144 @Override
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
... ... @@ -19,7 +19,6 @@ public interface ErrorCodeConstants {
19 19 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_004_003_000, "登录失败,账号密码不正确");
20 20 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_004_003_001, "登录失败,账号被禁用");
21 21 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_004_003_005, "未绑定账号,需要进行绑定");
22   - ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_004_003_006, "获得手机号失败");
23 22 ErrorCode AUTH_MOBILE_USED = new ErrorCode(1_004_003_007, "手机号已经被使用");
24 23  
25 24 // ========== 用户收件地址 1-004-004-000 ==========
... ...
yudao-module-member/yudao-module-member-biz/pom.xml
... ... @@ -43,10 +43,6 @@
43 43 <groupId>cn.iocoder.boot</groupId>
44 44 <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
45 45 </dependency>
46   - <dependency>
47   - <groupId>cn.iocoder.boot</groupId>
48   - <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
49   - </dependency>
50 46  
51 47 <!-- Web 相关 -->
52 48 <dependency>
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
... ... @@ -59,3 +59,8 @@ tenant-id: {{appTenentId}}
59 59 POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
60 60 Content-Type: application/json
61 61 tenant-id: {{appTenentId}}
  62 +
  63 +### 请求 /auth/create-weixin-jsapi-signature 接口 => 成功
  64 +POST {{appApi}}/member/auth/create-weixin-jsapi-signature?url=http://www.iocoder.cn
  65 +Authorization: Bearer {{appToken}}
  66 +tenant-id: {{appTenentId}}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
1 1 package cn.iocoder.yudao.module.member.controller.app.auth;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
6 7 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7 8 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
8 9 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
  10 +import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
9 11 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
  12 +import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
  13 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
10 14 import io.swagger.v3.oas.annotations.Operation;
11 15 import io.swagger.v3.oas.annotations.Parameter;
12 16 import io.swagger.v3.oas.annotations.Parameters;
... ... @@ -34,6 +38,9 @@ public class AppAuthController {
34 38 private MemberAuthService authService;
35 39  
36 40 @Resource
  41 + private SocialClientApi socialClientApi;
  42 +
  43 + @Resource
37 44 private SecurityProperties securityProperties;
38 45  
39 46 @PostMapping("/login")
... ... @@ -109,4 +116,13 @@ public class AppAuthController {
109 116 return success(authService.weixinMiniAppLogin(reqVO));
110 117 }
111 118  
  119 + @PostMapping("/create-weixin-jsapi-signature")
  120 + @Operation(summary = "创建微信 JS SDK 初始化所需的签名",
  121 + description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
  122 + public CommonResult<SocialWxJsapiSignatureRespDTO> createWeixinMpJsapiSignature(@RequestParam("url") String url) {
  123 + SocialWxJsapiSignatureRespDTO signature = socialClientApi.createWxMpJsapiSignature(
  124 + UserTypeEnum.MEMBER.getValue(), url);
  125 + return success(AuthConvert.INSTANCE.convert(signature));
  126 + }
  127 +
112 128 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.auth.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +
  9 +@Schema(description = "用户 APP - 微信公众号 JSAPI 签名 Response VO")
  10 +@Data
  11 +@NoArgsConstructor
  12 +@AllArgsConstructor
  13 +@Builder
  14 +public class AuthWeixinJsapiSignatureRespVO {
  15 +
  16 + @Schema(description = "微信公众号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "hello")
  17 + private String appId;
  18 +
  19 + @Schema(description = "匿名串", requiredMode = Schema.RequiredMode.REQUIRED, example = "world")
  20 + private String nonceStr;
  21 +
  22 + @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  23 + private Long timestamp;
  24 +
  25 + @Schema(description = "URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
  26 + private String url;
  27 +
  28 + @Schema(description = "签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "阿巴阿巴")
  29 + private String signature;
  30 +
  31 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
9 9 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
10 10 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
11 11 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
  12 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
12 13 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
13 14 import org.mapstruct.Mapper;
14 15 import org.mapstruct.factory.Mappers;
... ... @@ -29,4 +30,6 @@ public interface AuthConvert {
29 30  
30 31 SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean);
31 32  
  33 + SocialWxJsapiSignatureRespDTO convert(SocialWxJsapiSignatureRespDTO bean);
  34 +
32 35 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
1 1 package cn.iocoder.yudao.module.member.service.auth;
2 2  
3   -import cn.binarywang.wx.miniapp.api.WxMaService;
4   -import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
5 3 import cn.hutool.core.lang.Assert;
6 4 import cn.hutool.core.util.ObjectUtil;
7 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
... ... @@ -19,9 +17,11 @@ import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
19 17 import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
20 18 import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
21 19 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
  20 +import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
22 21 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
23 22 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
24 23 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
  24 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
25 25 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
26 26 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
27 27 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
... ... @@ -56,10 +56,9 @@ public class MemberAuthServiceImpl implements MemberAuthService {
56 56 @Resource
57 57 private SocialUserApi socialUserApi;
58 58 @Resource
59   - private OAuth2TokenApi oauth2TokenApi;
60   -
  59 + private SocialClientApi socialClientApi;
61 60 @Resource
62   - private WxMaService wxMaService;
  61 + private OAuth2TokenApi oauth2TokenApi;
63 62  
64 63 @Override
65 64 public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {
... ... @@ -121,15 +120,13 @@ public class MemberAuthServiceImpl implements MemberAuthService {
121 120 @Override
122 121 public AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO) {
123 122 // 获得对应的手机号信息
124   - // TODO @芋艿:需要弱化微信小程序的依赖,通过 system 获取手机号
125   - WxMaPhoneNumberInfo phoneNumberInfo;
126   - try {
127   - phoneNumberInfo = wxMaService.getUserService().getPhoneNoInfo(reqVO.getPhoneCode());
128   - } catch (Exception exception) {
129   - throw exception(AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR);
130   - }
  123 + SocialWxPhoneNumberInfoRespDTO phoneNumberInfo = socialClientApi.getWxMaPhoneNumberInfo(
  124 + UserTypeEnum.MEMBER.getValue(), reqVO.getPhoneCode());
  125 + Assert.notNull(phoneNumberInfo, "获得手机信息失败,结果为空");
  126 +
131 127 // 获得获得注册用户
132   - MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(), getClientIP(), TerminalEnum.WECHAT_MINI_PROGRAM.getTerminal());
  128 + MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(),
  129 + getClientIP(), TerminalEnum.WECHAT_MINI_PROGRAM.getTerminal());
133 130 Assert.notNull(user, "获取用户失败,结果为空");
134 131  
135 132 // 绑定社交用户
... ... @@ -154,7 +151,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
154 151  
155 152 @Override
156 153 public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
157   - return socialUserApi.getAuthorizeUrl(type, redirectUri);
  154 + return socialClientApi.getAuthorizeUrl(type, UserTypeEnum.MEMBER.getValue(), redirectUri);
158 155 }
159 156  
160 157 private MemberUserDO login0(String mobile, String password) {
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.social;
  2 +
  3 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
  4 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
  5 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
  6 +
  7 +/**
  8 + * 社交应用的 API 接口
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface SocialClientApi {
  13 +
  14 + /**
  15 + * 获得社交平台的授权 URL
  16 + *
  17 + * @param type 社交平台的类型 {@link SocialTypeEnum}
  18 + * @param userType 用户类型
  19 + * @param redirectUri 重定向 URL
  20 + * @return 社交平台的授权 URL
  21 + */
  22 + String getAuthorizeUrl(Integer type, Integer userType, String redirectUri);
  23 +
  24 + /**
  25 + * 创建微信公众号 JS SDK 初始化所需的签名
  26 + *
  27 + * @param userType 用户类型
  28 + * @param url 访问的 URL 地址
  29 + * @return 签名
  30 + */
  31 + SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url);
  32 +
  33 + /**
  34 + * 获得微信小程序的手机信息
  35 + *
  36 + * @param userType 用户类型
  37 + * @param phoneCode 手机授权码
  38 + * @return 手机信息
  39 + */
  40 + SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode);
  41 +
  42 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
4 4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
5 5 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
6 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
7   -import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
8 7  
9 8 import javax.validation.Valid;
10 9  
... ... @@ -16,15 +15,6 @@ import javax.validation.Valid;
16 15 public interface SocialUserApi {
17 16  
18 17 /**
19   - * 获得社交平台的授权 URL
20   - *
21   - * @param type 社交平台的类型 {@link SocialTypeEnum}
22   - * @param redirectUri 重定向 URL
23   - * @return 社交平台的授权 URL
24   - */
25   - String getAuthorizeUrl(Integer type, String redirectUri);
26   -
27   - /**
28 18 * 绑定社交用户
29 19 *
30 20 * @param reqDTO 绑定信息
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserBindReqDTO.java
... ... @@ -37,7 +37,7 @@ public class SocialUserBindReqDTO {
37 37 */
38 38 @InEnum(SocialTypeEnum.class)
39 39 @NotNull(message = "社交平台的类型不能为空")
40   - private Integer type;
  40 + private Integer socialType;
41 41 /**
42 42 * 授权码
43 43 */
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserUnbindReqDTO.java
... ... @@ -33,12 +33,12 @@ public class SocialUserUnbindReqDTO {
33 33 */
34 34 @InEnum(SocialTypeEnum.class)
35 35 @NotNull(message = "社交平台的类型不能为空")
36   - private Integer type;
  36 + private Integer socialType;
37 37  
38 38 /**
39   - * 社交平台的 unionId
  39 + * 社交平台的 openid
40 40 */
41   - @NotEmpty(message = "社交平台的 unionId 不能为空")
42   - private String unionId;
  41 + @NotEmpty(message = "社交平台的 openid 不能为空")
  42 + private String openid;
43 43  
44 44 }
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxJsapiSignatureRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.social.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 微信公众号 JSAPI 签名 Response DTO
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +@Data
  11 +public class SocialWxJsapiSignatureRespDTO {
  12 +
  13 + /**
  14 + * 微信公众号的 appId
  15 + */
  16 + private String appId;
  17 + /**
  18 + * 匿名串
  19 + */
  20 + private String nonceStr;
  21 + /**
  22 + * 时间戳
  23 + */
  24 + private Long timestamp;
  25 + /**
  26 + * URL
  27 + */
  28 + private String url;
  29 + /**
  30 + * 签名
  31 + */
  32 + private String signature;
  33 +
  34 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxPhoneNumberInfoRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.social.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 微信小程序的手机信息 Response DTO
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +@Data
  11 +public class SocialWxPhoneNumberInfoRespDTO {
  12 +
  13 + /**
  14 + * 用户绑定的手机号(国外手机号会有区号)
  15 + */
  16 + private String phoneNumber;
  17 +
  18 + /**
  19 + * 没有区号的手机号
  20 + */
  21 + private String purePhoneNumber;
  22 + /**
  23 + * 区号
  24 + */
  25 + private String countryCode;
  26 +
  27 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
... ... @@ -119,6 +119,8 @@ public interface ErrorCodeConstants {
119 119 ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1_002_018_001, "社交解绑失败,非当前用户绑定");
120 120 ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1_002_018_002, "社交授权失败,找不到对应的用户");
121 121  
  122 + ErrorCode SOCIAL_APP_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_002_018_103, "获得手机号失败");
  123 +
122 124 // ========== 系统敏感词 1-002-019-000 =========
123 125 ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_002_019_000, "系统敏感词在所有标签中都不存在");
124 126 ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1_002_019_001, "系统敏感词已在标签中存在");
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
... ... @@ -18,33 +18,39 @@ public enum SocialTypeEnum implements IntArrayValuable {
18 18  
19 19 /**
20 20 * Gitee
21   - * 文档链接:https://gitee.com/api/v5/oauth_doc#/
  21 + *
  22 + * @see <a href="https://gitee.com/api/v5/oauth_doc#/">接入文档</a>
22 23 */
23 24 GITEE(10, "GITEE"),
24 25 /**
25 26 * 钉钉
26   - * 文档链接:https://developers.dingtalk.com/document/app/obtain-identity-credentials
  27 + *
  28 + * @see <a href="https://developers.dingtalk.com/document/app/obtain-identity-credentials">接入文档</a>
27 29 */
28 30 DINGTALK(20, "DINGTALK"),
29 31  
30 32 /**
31 33 * 企业微信
32   - * 文档链接:https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html
  34 + *
  35 + * @see <a href="https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html">接入文档</a>
33 36 */
34 37 WECHAT_ENTERPRISE(30, "WECHAT_ENTERPRISE"),
35 38 /**
36 39 * 微信公众平台 - 移动端 H5
37   - * 文档链接:https://www.cnblogs.com/juewuzhe/p/11905461.html
  40 + *
  41 + * @see <a href="https://www.cnblogs.com/juewuzhe/p/11905461.html">接入文档</a>
38 42 */
39 43 WECHAT_MP(31, "WECHAT_MP"),
40 44 /**
41 45 * 微信开放平台 - 网站应用 PC 端扫码授权登录
42   - * 文档链接:https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证
  46 + *
  47 + * @see <a href="https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证">接入文档</a>
43 48 */
44 49 WECHAT_OPEN(32, "WECHAT_OPEN"),
45 50 /**
46 51 * 微信小程序
47   - * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
  52 + *
  53 + * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html">接入文档</a>
48 54 */
49 55 WECHAT_MINI_APP(34, "WECHAT_MINI_APP"),
50 56 ;
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.social;
  2 +
  3 +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
  4 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
  5 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
  6 +import cn.iocoder.yudao.module.system.convert.social.SocialClientConvert;
  7 +import cn.iocoder.yudao.module.system.service.social.SocialClientService;
  8 +import me.chanjar.weixin.common.bean.WxJsapiSignature;
  9 +import org.springframework.stereotype.Service;
  10 +import org.springframework.validation.annotation.Validated;
  11 +
  12 +import javax.annotation.Resource;
  13 +
  14 +/**
  15 + * 社交应用的 API 实现类
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@Service
  20 +@Validated
  21 +public class SocialClientApiImpl implements SocialClientApi {
  22 +
  23 + @Resource
  24 + private SocialClientService socialClientService;
  25 +
  26 + @Override
  27 + public String getAuthorizeUrl(Integer type, Integer userType, String redirectUri) {
  28 + return socialClientService.getAuthorizeUrl(type, userType, redirectUri);
  29 + }
  30 +
  31 + @Override
  32 + public SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url) {
  33 + WxJsapiSignature signature = socialClientService.createWxMpJsapiSignature(userType, url);
  34 + return SocialClientConvert.INSTANCE.convert(signature);
  35 + }
  36 +
  37 + @Override
  38 + public SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode) {
  39 + WxMaPhoneNumberInfo info = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode);
  40 + return SocialClientConvert.INSTANCE.convert(info);
  41 + }
  42 +
  43 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
... ... @@ -22,11 +22,6 @@ public class SocialUserApiImpl implements SocialUserApi {
22 22 private SocialUserService socialUserService;
23 23  
24 24 @Override
25   - public String getAuthorizeUrl(Integer type, String redirectUri) {
26   - return socialUserService.getAuthorizeUrl(type, redirectUri);
27   - }
28   -
29   - @Override
30 25 public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
31 26 return socialUserService.bindSocialUser(reqDTO);
32 27 }
... ... @@ -34,7 +29,7 @@ public class SocialUserApiImpl implements SocialUserApi {
34 29 @Override
35 30 public void unbindSocialUser(SocialUserUnbindReqDTO reqDTO) {
36 31 socialUserService.unbindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(),
37   - reqDTO.getType(), reqDTO.getUnionId());
  32 + reqDTO.getSocialType(), reqDTO.getOpenid());
38 33 }
39 34  
40 35 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.controller.admin.auth;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
8 9 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
... ... @@ -16,7 +17,7 @@ import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
16 17 import cn.iocoder.yudao.module.system.service.permission.MenuService;
17 18 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
18 19 import cn.iocoder.yudao.module.system.service.permission.RoleService;
19   -import cn.iocoder.yudao.module.system.service.social.SocialUserService;
  20 +import cn.iocoder.yudao.module.system.service.social.SocialClientService;
20 21 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
21 22 import io.swagger.v3.oas.annotations.Operation;
22 23 import io.swagger.v3.oas.annotations.Parameter;
... ... @@ -57,7 +58,7 @@ public class AuthController {
57 58 @Resource
58 59 private PermissionService permissionService;
59 60 @Resource
60   - private SocialUserService socialUserService;
  61 + private SocialClientService socialClientService;
61 62  
62 63 @Resource
63 64 private SecurityProperties securityProperties;
... ... @@ -147,7 +148,7 @@ public class AuthController {
147 148 })
148 149 public CommonResult<String> socialLogin(@RequestParam("type") Integer type,
149 150 @RequestParam("redirectUri") String redirectUri) {
150   - return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri));
  151 + return success(socialClientService.getAuthorizeUrl(type, UserTypeEnum.ADMIN.getValue(), redirectUri));
151 152 }
152 153  
153 154 @PostMapping("/social-login")
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/package-info.java deleted
1   -/**
2   - * 占位,避免 package 无法提交到 Git 仓库
3   - */
4   -package cn.iocoder.yudao.module.system.controller.app;
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/weixin/AppWxMpController.http deleted
1   -### 请求 /login 接口 => 成功
2   -POST {{appApi}}/system/wx-mp/create-jsapi-signature?url=http://www.iocoder.cn
3   -Authorization: Bearer {{appToken}}
4   -tenant-id: {{appTenentId}}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/weixin/AppWxMpController.java deleted
1   -package cn.iocoder.yudao.module.system.controller.app.weixin;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import io.swagger.v3.oas.annotations.tags.Tag;
5   -import io.swagger.v3.oas.annotations.Operation;
6   -import lombok.extern.slf4j.Slf4j;
7   -import me.chanjar.weixin.common.bean.WxJsapiSignature;
8   -import me.chanjar.weixin.common.error.WxErrorException;
9   -import me.chanjar.weixin.mp.api.WxMpService;
10   -import org.springframework.validation.annotation.Validated;
11   -import org.springframework.web.bind.annotation.PostMapping;
12   -import org.springframework.web.bind.annotation.RequestMapping;
13   -import org.springframework.web.bind.annotation.RequestParam;
14   -import org.springframework.web.bind.annotation.RestController;
15   -
16   -import javax.annotation.Resource;
17   -
18   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
19   -
20   -@Tag(name = "微信公众号")
21   -@RestController
22   -@RequestMapping("/system/wx-mp")
23   -@Validated
24   -@Slf4j
25   -public class AppWxMpController {
26   -
27   - @Resource
28   - private WxMpService mpService;
29   -
30   - // TODO @芋艿:需要额外考虑个问题;多租户下,如果每个小程序一个微信公众号,则会存在多个 appid;
31   - @PostMapping("/create-jsapi-signature")
32   - @Operation(summary = "创建微信 JS SDK 初始化所需的签名",
33   - description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
34   - public CommonResult<WxJsapiSignature> createJsapiSignature(@RequestParam("url") String url) throws WxErrorException {
35   - return success(mpService.createJsapiSignature(url));
36   - }
37   -
38   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialClientConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.convert.social;
  2 +
  3 +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
  4 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
  5 +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
  6 +import me.chanjar.weixin.common.bean.WxJsapiSignature;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +@Mapper
  11 +public interface SocialClientConvert {
  12 +
  13 + SocialClientConvert INSTANCE = Mappers.getMapper(SocialClientConvert.class);
  14 +
  15 + SocialWxJsapiSignatureRespDTO convert(WxJsapiSignature bean);
  16 +
  17 + SocialWxPhoneNumberInfoRespDTO convert(WxMaPhoneNumberInfo bean);
  18 +
  19 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialClientDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.dataobject.social;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  5 +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
  6 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
  7 +import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableId;
  9 +import com.baomidou.mybatisplus.annotation.TableName;
  10 +import com.xingyuv.jushauth.config.AuthConfig;
  11 +import lombok.*;
  12 +
  13 +/**
  14 + * 社交客户端 DO
  15 + *
  16 + * 对应 {@link AuthConfig} 配置,满足不同租户,有自己的客户端配置,实现社交(三方)登录
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@TableName(value = "system_social_client", autoResultMap = true)
  21 +@KeySequence("system_social_client_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  22 +@Data
  23 +@EqualsAndHashCode(callSuper = true)
  24 +@Builder
  25 +@NoArgsConstructor
  26 +@AllArgsConstructor
  27 +public class SocialClientDO extends TenantBaseDO {
  28 +
  29 + /**
  30 + * 编号,自增
  31 + */
  32 + @TableId
  33 + private Long id;
  34 + /**
  35 + * 应用名
  36 + */
  37 + private String name;
  38 + /**
  39 + * 社交类型
  40 + *
  41 + * 枚举 {@link SocialTypeEnum}
  42 + */
  43 + private Integer socialType;
  44 + /**
  45 + * 用户类型
  46 + *
  47 + * 目的:不同用户类型,对应不同的小程序,需要自己的配置
  48 + *
  49 + * 枚举 {@link UserTypeEnum}
  50 + */
  51 + private Integer userType;
  52 + /**
  53 + * 状态
  54 + *
  55 + * 枚举 {@link CommonStatusEnum}
  56 + */
  57 + private Integer status;
  58 +
  59 + /**
  60 + * 客户端 id
  61 + */
  62 + private String clientId;
  63 + /**
  64 + * 客户端 Secret
  65 + */
  66 + private String clientSecret;
  67 +
  68 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialClientMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.social;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +@Mapper
  8 +public interface SocialClientMapper extends BaseMapperX<SocialClientDO> {
  9 +
  10 + default SocialClientDO selectBySocialTypeAndUserType(Integer socialType, Integer userType) {
  11 + return selectOne(SocialClientDO::getSocialType, socialType,
  12 + SocialClientDO::getUserType, userType);
  13 + }
  14 +
  15 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.social;
  2 +
  3 +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
  4 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
  5 +import com.xingyuv.jushauth.model.AuthUser;
  6 +import me.chanjar.weixin.common.bean.WxJsapiSignature;
  7 +
  8 +/**
  9 + * 社交应用 Service 接口
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public interface SocialClientService {
  14 +
  15 + /**
  16 + * 获得社交平台的授权 URL
  17 + *
  18 + * @param socialType 社交平台的类型 {@link SocialTypeEnum}
  19 + * @param userType 用户类型
  20 + * @param redirectUri 重定向 URL
  21 + * @return 社交平台的授权 URL
  22 + */
  23 + String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri);
  24 +
  25 + /**
  26 + * 请求社交平台,获得授权的用户
  27 + *
  28 + * @param socialType 社交平台的类型
  29 + * @param userType 用户类型
  30 + * @param code 授权码
  31 + * @param state 授权 state
  32 + * @return 授权的用户
  33 + */
  34 + AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state);
  35 +
  36 + // =================== 微信公众号独有 ===================
  37 +
  38 + /**
  39 + * 创建微信公众号的 JS SDK 初始化所需的签名
  40 + *
  41 + * @param userType 用户类型
  42 + * @param url 访问的 URL 地址
  43 + * @return 签名
  44 + */
  45 + WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url);
  46 +
  47 + // =================== 微信小程序独有 ===================
  48 +
  49 + /**
  50 + * 获得微信小程序的手机信息
  51 + *
  52 + * @param userType 用户类型
  53 + * @param phoneCode 手机授权码
  54 + * @return 手机信息
  55 + */
  56 + WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode);
  57 +
  58 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.social;
  2 +
  3 +import cn.binarywang.wx.miniapp.api.WxMaService;
  4 +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
  5 +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
  6 +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;
  7 +import cn.hutool.core.bean.BeanUtil;
  8 +import cn.hutool.core.lang.Assert;
  9 +import cn.hutool.core.util.ReflectUtil;
  10 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  11 +import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
  12 +import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  13 +import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
  14 +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
  15 +import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
  16 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
  17 +import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
  18 +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
  19 +import com.google.common.cache.CacheLoader;
  20 +import com.google.common.cache.LoadingCache;
  21 +import com.xingyuv.jushauth.config.AuthConfig;
  22 +import com.xingyuv.jushauth.model.AuthCallback;
  23 +import com.xingyuv.jushauth.model.AuthResponse;
  24 +import com.xingyuv.jushauth.model.AuthUser;
  25 +import com.xingyuv.jushauth.request.AuthRequest;
  26 +import com.xingyuv.jushauth.utils.AuthStateUtils;
  27 +import lombok.SneakyThrows;
  28 +import lombok.extern.slf4j.Slf4j;
  29 +import me.chanjar.weixin.common.bean.WxJsapiSignature;
  30 +import me.chanjar.weixin.common.error.WxErrorException;
  31 +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
  32 +import me.chanjar.weixin.mp.api.WxMpService;
  33 +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
  34 +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
  35 +import org.springframework.data.redis.core.StringRedisTemplate;
  36 +import org.springframework.stereotype.Service;
  37 +
  38 +import javax.annotation.Resource;
  39 +import java.time.Duration;
  40 +import java.util.Objects;
  41 +
  42 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  43 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
  44 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_APP_WEIXIN_MINI_APP_PHONE_CODE_ERROR;
  45 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_AUTH_FAILURE;
  46 +
  47 +/**
  48 + * 社交应用 Service 实现类
  49 + *
  50 + * @author 芋道源码
  51 + */
  52 +@Service
  53 +@Slf4j
  54 +public class SocialClientServiceImpl implements SocialClientService {
  55 +
  56 + @Resource // 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它
  57 + private YudaoAuthRequestFactory yudaoAuthRequestFactory;
  58 +
  59 + @Resource
  60 + private WxMpService wxMpService;
  61 + @Resource
  62 + private WxMpProperties wxMpProperties;
  63 + @Resource
  64 + private StringRedisTemplate stringRedisTemplate; // WxMpService 需要使用到,所以在 Service 注入了它
  65 + /**
  66 + * 缓存 WxMpService 对象
  67 + *
  68 + * key:使用微信公众号的 appId + secret 拼接,即 {@link SocialClientDO} 的 clientId 和 clientSecret 属性。
  69 + * 为什么 key 使用这种格式?因为 {@link SocialClientDO} 在管理后台可以变更,通过这个 key 存储它的单例。
  70 + *
  71 + * 为什么要做 WxMpService 缓存?因为 WxMpService 构建成本比较大,所以尽量保证它是单例。
  72 + */
  73 + private final LoadingCache<String, WxMpService> wxMpServiceCache = CacheUtils.buildAsyncReloadingCache(
  74 + Duration.ofSeconds(10L),
  75 + new CacheLoader<String, WxMpService>() {
  76 +
  77 + @Override
  78 + public WxMpService load(String key) {
  79 + String[] keys = key.split(":");
  80 + return buildWxMpService(keys[0], keys[1]);
  81 + }
  82 +
  83 + });
  84 +
  85 + @Resource
  86 + private WxMaService wxMaService;
  87 + @Resource
  88 + private WxMaProperties wxMaProperties;
  89 + /**
  90 + * 缓存 WxMaService 对象
  91 + *
  92 + * 说明同 {@link #wxMpServiceCache} 变量
  93 + */
  94 + private final LoadingCache<String, WxMaService> wxMaServiceCache = CacheUtils.buildAsyncReloadingCache(
  95 + Duration.ofSeconds(10L),
  96 + new CacheLoader<String, WxMaService>() {
  97 +
  98 + @Override
  99 + public WxMaService load(String key) {
  100 + String[] keys = key.split(":");
  101 + return buildWxMaService(keys[0], keys[1]);
  102 + }
  103 +
  104 + });
  105 +
  106 + @Resource
  107 + private SocialClientMapper socialClientMapper;
  108 +
  109 + @Override
  110 + public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) {
  111 + // 获得对应的 AuthRequest 实现
  112 + AuthRequest authRequest = buildAuthRequest(socialType, userType);
  113 + // 生成跳转地址
  114 + String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
  115 + return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
  116 + }
  117 +
  118 + @Override
  119 + public AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state) {
  120 + // 构建请求
  121 + AuthRequest authRequest = buildAuthRequest(socialType, userType);
  122 + AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();
  123 + // 执行请求
  124 + AuthResponse<?> authResponse = authRequest.login(authCallback);
  125 + log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", socialType,
  126 + toJsonString(authCallback), toJsonString(authResponse));
  127 + if (!authResponse.ok()) {
  128 + throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());
  129 + }
  130 + return (AuthUser) authResponse.getData();
  131 + }
  132 +
  133 + /**
  134 + * 构建 AuthRequest 对象,支持多租户配置
  135 + *
  136 + * @param socialType 社交类型
  137 + * @param userType 用户类型
  138 + * @return AuthRequest 对象
  139 + */
  140 + private AuthRequest buildAuthRequest(Integer socialType, Integer userType) {
  141 + // 1. 先查找默认的配置项,从 application-*.yaml 中读取
  142 + AuthRequest request = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource());
  143 + Assert.notNull(request, String.format("社交平台(%d) 不存在", socialType));
  144 + // 2. 查询 DB 的配置项,如果存在则进行覆盖
  145 + SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(socialType, userType);
  146 + if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  147 + // 2.1 构造新的 AuthConfig 对象
  148 + AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(request, "config");
  149 + AuthConfig newAuthConfig = ReflectUtil.newInstance(authConfig.getClass());
  150 + BeanUtil.copyProperties(authConfig, newAuthConfig);
  151 + // 2.2 修改对应的 clientId + clientSecret 密钥
  152 + newAuthConfig.setClientId(client.getClientId());
  153 + newAuthConfig.setClientSecret(client.getClientSecret());
  154 + // 2.3 设置会 request 里,进行后续使用
  155 + ReflectUtil.setFieldValue(request, "config", newAuthConfig);
  156 + }
  157 + return request;
  158 + }
  159 +
  160 + // =================== 微信公众号独有 ===================
  161 +
  162 + @Override
  163 + @SneakyThrows
  164 + public WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url) {
  165 + WxMpService service = getWxMpService(userType);
  166 + return service.createJsapiSignature(url);
  167 + }
  168 +
  169 + /**
  170 + * 获得 clientId + clientSecret 对应的 WxMpService 对象
  171 + *
  172 + * @param userType 用户类型
  173 + * @return WxMpService 对象
  174 + */
  175 + private WxMpService getWxMpService(Integer userType) {
  176 + // 第一步,查询 DB 的配置项,获得对应的 WxMpService 对象
  177 + SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
  178 + SocialTypeEnum.WECHAT_MP.getType(), userType);
  179 + if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  180 + return wxMpServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret());
  181 + }
  182 + // 第二步,不存在 DB 配置项,则使用 application-*.yaml 对应的 WxMpService 对象
  183 + return wxMpService;
  184 + }
  185 +
  186 + /**
  187 + * 创建 clientId + clientSecret 对应的 WxMpService 对象
  188 + *
  189 + * @param clientId 微信公众号 appId
  190 + * @param clientSecret 微信公众号 secret
  191 + * @return WxMpService 对象
  192 + */
  193 + private WxMpService buildWxMpService(String clientId, String clientSecret) {
  194 + // 第一步,创建 WxMpRedisConfigImpl 对象
  195 + WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(
  196 + new RedisTemplateWxRedisOps(stringRedisTemplate),
  197 + wxMpProperties.getConfigStorage().getKeyPrefix());
  198 + configStorage.setAppId(clientId);
  199 + configStorage.setSecret(clientSecret);
  200 +
  201 + // 第二步,创建 WxMpService 对象
  202 + WxMpService service = new WxMpServiceImpl();
  203 + service.setWxMpConfigStorage(configStorage);
  204 + return service;
  205 + }
  206 +
  207 + // =================== 微信小程序独有 ===================
  208 +
  209 + @Override
  210 + public WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode) {
  211 + WxMaService service = getWxMaService(userType);
  212 + try {
  213 + return service.getUserService().getPhoneNoInfo(phoneCode);
  214 + } catch (WxErrorException e) {
  215 + log.error("[getPhoneNoInfo][userType({}) phoneCode({}) 获得手机号失败]", userType, phoneCode, e);
  216 + throw exception(SOCIAL_APP_WEIXIN_MINI_APP_PHONE_CODE_ERROR);
  217 + }
  218 + }
  219 +
  220 + /**
  221 + * 获得 clientId + clientSecret 对应的 WxMpService 对象
  222 + *
  223 + * @param userType 用户类型
  224 + * @return WxMpService 对象
  225 + */
  226 + private WxMaService getWxMaService(Integer userType) {
  227 + // 第一步,查询 DB 的配置项,获得对应的 WxMaService 对象
  228 + SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
  229 + SocialTypeEnum.WECHAT_MINI_APP.getType(), userType);
  230 + if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  231 + return wxMaServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret());
  232 + }
  233 + // 第二步,不存在 DB 配置项,则使用 application-*.yaml 对应的 WxMaService 对象
  234 + return wxMaService;
  235 + }
  236 +
  237 + /**
  238 + * 创建 clientId + clientSecret 对应的 WxMaService 对象
  239 + *
  240 + * @param clientId 微信小程序 appId
  241 + * @param clientSecret 微信小程序 secret
  242 + * @return WxMaService 对象
  243 + */
  244 + private WxMaService buildWxMaService(String clientId, String clientSecret) {
  245 + // 第一步,创建 WxMaRedisBetterConfigImpl 对象
  246 + WxMaRedisBetterConfigImpl configStorage = new WxMaRedisBetterConfigImpl(
  247 + new RedisTemplateWxRedisOps(stringRedisTemplate),
  248 + wxMaProperties.getConfigStorage().getKeyPrefix());
  249 + configStorage.setAppid(clientId);
  250 + configStorage.setSecret(clientSecret);
  251 +
  252 + // 第二步,创建 WxMpService 对象
  253 + WxMaService service = new WxMaServiceImpl();
  254 + service.setWxMaConfig(configStorage);
  255 + return service;
  256 + }
  257 +
  258 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
... ... @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
7 7 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
8 8  
9 9 import javax.validation.Valid;
10   -import javax.validation.constraints.NotNull;
11 10 import java.util.List;
12 11  
13 12 /**
... ... @@ -18,27 +17,6 @@ import java.util.List;
18 17 public interface SocialUserService {
19 18  
20 19 /**
21   - * 获得社交平台的授权 URL
22   - *
23   - * @param type 社交平台的类型 {@link SocialTypeEnum}
24   - * @param redirectUri 重定向 URL
25   - * @return 社交平台的授权 URL
26   - */
27   - String getAuthorizeUrl(Integer type, String redirectUri);
28   -
29   - /**
30   - * 授权获得对应的社交用户
31   - * 如果授权失败,则会抛出 {@link ServiceException} 异常
32   - *
33   - * @param type 社交平台的类型 {@link SocialTypeEnum}
34   - * @param code 授权码
35   - * @param state state
36   - * @return 授权用户
37   - */
38   - @NotNull
39   - SocialUserDO authSocialUser(Integer type, String code, String state);
40   -
41   - /**
42 20 * 获得指定用户的社交用户列表
43 21 *
44 22 * @param userId 用户编号
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
... ... @@ -2,8 +2,7 @@ package cn.iocoder.yudao.module.system.service.social;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5   -import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
6   -import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
  5 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
7 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
8 7 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
9 8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
... ... @@ -11,24 +10,22 @@ import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
11 10 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
12 11 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
13 12 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
14   -import com.xingyuv.jushauth.model.AuthCallback;
15   -import com.xingyuv.jushauth.model.AuthResponse;
16 13 import com.xingyuv.jushauth.model.AuthUser;
17   -import com.xingyuv.jushauth.request.AuthRequest;
18   -import com.xingyuv.jushauth.utils.AuthStateUtils;
19 14 import lombok.extern.slf4j.Slf4j;
20 15 import org.springframework.stereotype.Service;
21 16 import org.springframework.transaction.annotation.Transactional;
22 17 import org.springframework.validation.annotation.Validated;
23 18  
24 19 import javax.annotation.Resource;
  20 +import javax.validation.constraints.NotNull;
25 21 import java.util.Collections;
26 22 import java.util.List;
27 23  
28 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
29 25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
30 26 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
31   -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
  27 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.AUTH_THIRD_LOGIN_NOT_BIND;
  28 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;
32 29  
33 30 /**
34 31 * 社交用户 Service 实现类
... ... @@ -40,51 +37,13 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
40 37 @Slf4j
41 38 public class SocialUserServiceImpl implements SocialUserService {
42 39  
43   - @Resource// 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它
44   - private YudaoAuthRequestFactory yudaoAuthRequestFactory;
45   -
46 40 @Resource
47 41 private SocialUserBindMapper socialUserBindMapper;
48 42 @Resource
49 43 private SocialUserMapper socialUserMapper;
50 44  
51   - @Override
52   - public String getAuthorizeUrl(Integer type, String redirectUri) {
53   - // 获得对应的 AuthRequest 实现
54   - AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
55   - // 生成跳转地址
56   - String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
57   - return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
58   - }
59   -
60   - @Override
61   - public SocialUserDO authSocialUser(Integer type, String code, String state) {
62   - // 优先从 DB 中获取,因为 code 有且可以使用一次。
63   - // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
64   - SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state);
65   - if (socialUser != null) {
66   - return socialUser;
67   - }
68   -
69   - // 请求获取
70   - AuthUser authUser = getAuthUser(type, code, state);
71   - Assert.notNull(authUser, "三方用户不能为空");
72   -
73   - // 保存到 DB 中
74   - socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid());
75   - if (socialUser == null) {
76   - socialUser = new SocialUserDO();
77   - }
78   - socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询
79   - .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))
80   - .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()));
81   - if (socialUser.getId() == null) {
82   - socialUserMapper.insert(socialUser);
83   - } else {
84   - socialUserMapper.updateById(socialUser);
85   - }
86   - return socialUser;
87   - }
  45 + @Resource
  46 + private SocialClientService socialClientService;
88 47  
89 48 @Override
90 49 public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {
... ... @@ -101,7 +60,8 @@ public class SocialUserServiceImpl implements SocialUserService {
101 60 @Transactional
102 61 public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
103 62 // 获得社交用户
104   - SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
  63 + SocialUserDO socialUser = authSocialUser(reqDTO.getSocialType(), reqDTO.getUserType(),
  64 + reqDTO.getCode(), reqDTO.getState());
105 65 Assert.notNull(socialUser, "社交用户不能为空");
106 66  
107 67 // 社交用户可能之前绑定过别的用户,需要进行解绑
... ... @@ -134,7 +94,7 @@ public class SocialUserServiceImpl implements SocialUserService {
134 94 @Override
135 95 public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
136 96 // 获得社交用户
137   - SocialUserDO socialUser = authSocialUser(type, code, state);
  97 + SocialUserDO socialUser = authSocialUser(type, userType, code, state);
138 98 Assert.notNull(socialUser, "社交用户不能为空");
139 99  
140 100 // 如果未绑定的社交用户,则无法自动登录,进行报错
... ... @@ -146,24 +106,44 @@ public class SocialUserServiceImpl implements SocialUserService {
146 106 return new SocialUserRespDTO(socialUser.getOpenid(), socialUserBind.getUserId());
147 107 }
148 108  
  109 + // TODO 芋艿:调整下单测
149 110 /**
150   - * 请求社交平台,获得授权的用户
  111 + * 授权获得对应的社交用户
  112 + * 如果授权失败,则会抛出 {@link ServiceException} 异常
151 113 *
152   - * @param type 社交平台的类型
  114 + * @param type 社交平台的类型 {@link SocialTypeEnum}
  115 + * @param userType 用户类型
153 116 * @param code 授权码
154   - * @param state 授权 state
155   - * @return 授权的用户
  117 + * @param state state
  118 + * @return 授权用户
156 119 */
157   - private AuthUser getAuthUser(Integer type, String code, String state) {
158   - AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
159   - AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();
160   - AuthResponse<?> authResponse = authRequest.login(authCallback);
161   - log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type,
162   - toJsonString(authCallback), toJsonString(authResponse));
163   - if (!authResponse.ok()) {
164   - throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());
  120 + @NotNull
  121 + public SocialUserDO authSocialUser(Integer type, Integer userType, String code, String state) {
  122 + // 优先从 DB 中获取,因为 code 有且可以使用一次。
  123 + // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
  124 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state);
  125 + if (socialUser != null) {
  126 + return socialUser;
165 127 }
166   - return (AuthUser) authResponse.getData();
  128 +
  129 + // 请求获取
  130 + AuthUser authUser = socialClientService.getAuthUser(type, userType, code, state);
  131 + Assert.notNull(authUser, "三方用户不能为空");
  132 +
  133 + // 保存到 DB 中
  134 + socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid());
  135 + if (socialUser == null) {
  136 + socialUser = new SocialUserDO();
  137 + }
  138 + socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询
  139 + .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))
  140 + .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()));
  141 + if (socialUser.getId() == null) {
  142 + socialUserMapper.insert(socialUser);
  143 + } else {
  144 + socialUserMapper.updateById(socialUser);
  145 + }
  146 + return socialUser;
167 147 }
168 148  
169 149 }
... ...