Procházet zdrojové kódy

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

jason před 1 rokem
rodič
revize
db0fbf0596
62 změnil soubory, kde provedl 1363 přidání a 328 odebrání
  1. 2 0
      sql/mysql/mall.sql
  2. 48 21
      sql/mysql/ruoyi-vue-pro.sql
  3. 313 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java
  4. 0 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/article/vo/article/ArticleBaseVO.java
  5. 3 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/article/vo/article/ArticleRespVO.java
  6. 17 12
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java
  7. 2 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/AppArticleCategoryController.java
  8. 11 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/AppArticleController.java
  9. 9 17
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
  10. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/article/ArticleDO.java
  11. 6 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/article/ArticleCategoryMapper.java
  12. 6 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/article/ArticleMapper.java
  13. 12 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
  14. 12 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java
  15. 12 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
  16. 3 7
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/article/ArticleCategoryServiceImpl.java
  17. 15 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/article/ArticleService.java
  18. 14 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/article/ArticleServiceImpl.java
  19. 5 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
  20. 3 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
  21. 5 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
  22. 4 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
  23. 30 42
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
  24. 5 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
  25. 4 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
  26. 13 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java
  27. 3 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderBaseVO.java
  28. 8 12
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageItemRespVO.java
  29. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderSummaryRespVO.java
  30. 6 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  31. 24 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
  32. 0 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/core/aop/TradeOrderLogAspect.java
  33. 9 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
  34. 40 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
  35. 0 1
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  36. 0 4
      yudao-module-member/yudao-module-member-biz/pom.xml
  37. 5 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
  38. 16 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
  39. 31 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java
  40. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
  41. 11 14
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
  42. 42 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java
  43. 0 10
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
  44. 1 1
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserBindReqDTO.java
  45. 4 4
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserUnbindReqDTO.java
  46. 34 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxJsapiSignatureRespDTO.java
  47. 27 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxPhoneNumberInfoRespDTO.java
  48. 2 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  49. 12 6
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
  50. 43 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java
  51. 1 6
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
  52. 4 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  53. 0 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/package-info.java
  54. 0 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/weixin/AppWxMpController.http
  55. 0 38
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/weixin/AppWxMpController.java
  56. 19 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialClientConvert.java
  57. 68 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialClientDO.java
  58. 15 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialClientMapper.java
  59. 58 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java
  60. 258 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
  61. 0 22
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
  62. 42 62
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java

+ 2 - 0
sql/mysql/mall.sql

@@ -0,0 +1,2 @@
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
+VALUES ('核销订单', '', 2, 2, 2166, 'pick-up-order', 'ep:list', 'mall/trade/delivery/pickUpOrder/index', 'PickUpOrder');

+ 48 - 21
sql/mysql/ruoyi-vue-pro.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80034
  File Encoding         : 65001
 
- Date: 17/10/2023 23:22:59
+ Date: 18/10/2023 23:33:15
 */
 
 SET NAMES utf8mb4;
@@ -384,7 +384,7 @@ CREATE TABLE `infra_api_error_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1735 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 1739 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
 
 -- ----------------------------
 -- Records of infra_api_error_log
@@ -422,7 +422,7 @@ CREATE TABLE `infra_codegen_column`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1804 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 1805 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
 
 -- ----------------------------
 -- Records of infra_codegen_column
@@ -455,7 +455,7 @@ CREATE TABLE `infra_codegen_table`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 136 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 137 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
 
 -- ----------------------------
 -- Records of infra_codegen_table
@@ -615,7 +615,7 @@ CREATE TABLE `infra_job`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
+) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
 
 -- ----------------------------
 -- Records of infra_job
@@ -656,7 +656,7 @@ CREATE TABLE `infra_job_log`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 232 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
 
 -- ----------------------------
 -- Records of infra_job_log
@@ -709,7 +709,7 @@ CREATE TABLE `member_address`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_userId`(`user_id` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
+) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
 
 -- ----------------------------
 -- Records of member_address
@@ -768,7 +768,7 @@ CREATE TABLE `member_experience_record`  (
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员经验记录-用户编号',
   INDEX `idx_user_biz_type`(`user_id` ASC, `biz_type` ASC) USING BTREE COMMENT '会员经验记录-用户业务类型'
-) ENGINE = InnoDB AUTO_INCREMENT = 40 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
 
 -- ----------------------------
 -- Records of member_experience_record
@@ -883,7 +883,7 @@ CREATE TABLE `member_level_record`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员等级记录-用户编号'
-) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
 
 -- ----------------------------
 -- Records of member_level_record
@@ -913,7 +913,7 @@ CREATE TABLE `member_point_record`  (
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `index_userId`(`user_id` ASC) USING BTREE,
   INDEX `index_title`(`title` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 59 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 60 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
 
 -- ----------------------------
 -- Records of member_point_record
@@ -1540,7 +1540,7 @@ CREATE TABLE `system_error_code`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 5932 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5933 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
 
 -- ----------------------------
 -- Records of system_error_code
@@ -1569,7 +1569,7 @@ CREATE TABLE `system_login_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 2582 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 2599 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
 
 -- ----------------------------
 -- Records of system_login_log
@@ -1634,7 +1634,7 @@ CREATE TABLE `system_mail_log`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 354 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 355 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
 
 -- ----------------------------
 -- Records of system_mail_log
@@ -2283,7 +2283,7 @@ CREATE TABLE `system_notify_template`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
+) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
 
 -- ----------------------------
 -- Records of system_notify_template
@@ -2311,7 +2311,7 @@ CREATE TABLE `system_oauth2_access_token`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3056 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 3063 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_access_token
@@ -2433,7 +2433,7 @@ CREATE TABLE `system_oauth2_refresh_token`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1063 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 1070 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_refresh_token
@@ -3481,7 +3481,7 @@ CREATE TABLE `system_sms_code`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
-) ENGINE = InnoDB AUTO_INCREMENT = 530 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
+) ENGINE = InnoDB AUTO_INCREMENT = 531 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
 
 -- ----------------------------
 -- Records of system_sms_code
@@ -3574,6 +3574,33 @@ INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `cont
 INSERT INTO `system_sms_template` (`id`, `type`, `status`, `code`, `name`, `content`, `params`, `remark`, `api_template_id`, `channel_id`, `channel_code`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (16, 1, 0, 'user-reset-password', '会员用户 - 重置密码', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '[\"code\"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-08-19 11:34:18', b'0');
 COMMIT;
 
+-- ----------------------------
+-- Table structure for system_social_client
+-- ----------------------------
+DROP TABLE IF EXISTS `system_social_client`;
+CREATE TABLE `system_social_client`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名',
+  `social_type` tinyint NOT NULL COMMENT '社交平台的类型',
+  `user_type` tinyint NOT NULL COMMENT '用户类型',
+  `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端编号',
+  `client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端密钥',
+  `status` tinyint NOT NULL COMMENT '状态',
+  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交客户端表';
+
+-- ----------------------------
+-- Records of system_social_client
+-- ----------------------------
+BEGIN;
+COMMIT;
+
 -- ----------------------------
 -- Table structure for system_social_user
 -- ----------------------------
@@ -3596,7 +3623,7 @@ CREATE TABLE `system_social_user`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
+) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
 
 -- ----------------------------
 -- Records of system_social_user
@@ -3621,7 +3648,7 @@ CREATE TABLE `system_social_user_bind`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 76 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
+) ENGINE = InnoDB AUTO_INCREMENT = 79 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
 
 -- ----------------------------
 -- Records of system_social_user_bind
@@ -3797,7 +3824,7 @@ CREATE TABLE `system_users`  (
 -- Records of system_users
 -- ----------------------------
 BEGIN;
-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);
+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);
 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);
 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);
 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`,
 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);
 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);
 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);
-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);
+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);
 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);
 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);
 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);

+ 313 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java

@@ -0,0 +1,313 @@
+package cn.iocoder.yudao.framework.mybatis.core.query;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.github.yulichang.toolkit.MPJWrappers;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.function.Consumer;
+
+/**
+ * 拓展 MyBatis Plus Join QueryWrapper 类,主要增加如下功能:
+ * <p>
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
+
+    public MPJLambdaWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
+        MPJWrappers.lambdaJoin().like(column, val);
+        if (StringUtils.hasText(val)) {
+            return (MPJLambdaWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (MPJLambdaWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (MPJLambdaWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (MPJLambdaWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (MPJLambdaWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (MPJLambdaWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (MPJLambdaWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (MPJLambdaWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
+        Object val1 = ArrayUtils.get(values, 0);
+        Object val2 = ArrayUtils.get(values, 1);
+        return betweenIfPresent(column, val1, val2);
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> eq(boolean condition, SFunction<X, ?> column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> eq(SFunction<X, ?> column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) {
+        //noinspection unchecked
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> in(SFunction<X, ?> column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz) {
+        super.selectAll(clazz);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz, String prefix) {
+        super.selectAll(clazz, prefix);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, String alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <E> MPJLambdaWrapperX<T> selectAs(String column, SFunction<E, ?> alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <E, X> MPJLambdaWrapperX<T> selectAs(String index, SFunction<E, ?> column, SFunction<X, ?> alias) {
+        super.selectAs(index, column, alias);
+        return this;
+    }
+
+    @Override
+    public <E> MPJLambdaWrapperX<T> selectAsClass(Class<E> source, Class<?> tag) {
+        super.selectAsClass(source, tag);
+        return this;
+    }
+
+    @Override
+    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
+        super.selectSub(clazz, consumer, alias);
+        return this;
+    }
+
+    @Override
+    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, String st, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
+        super.selectSub(clazz, st, consumer, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column) {
+        super.selectCount(column);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectCount(Object column, String alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> selectCount(Object column, SFunction<X, ?> alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, String alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column) {
+        super.selectSum(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, String alias) {
+        super.selectSum(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectSum(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column) {
+        super.selectMax(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, String alias) {
+        super.selectMax(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectMax(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column) {
+        super.selectMin(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, String alias) {
+        super.selectMin(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectMin(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column) {
+        super.selectAvg(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, String alias) {
+        super.selectAvg(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectAvg(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column) {
+        super.selectLen(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, String alias) {
+        super.selectLen(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectLen(column, alias);
+        return this;
+    }
+
+}

+ 0 - 4
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 {
     @Schema(description = "文章简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "这是一个简介")
     private String introduction;
 
-    // TODO @puhui999:浏览量的字段,应该不是后端新增设置的哈;
-    @Schema(description = "浏览次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "111")
-    private String browseCount;
-
     @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "排序不能为空")
     private Integer sort;

+ 3 - 0
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 {
     @Schema(description = "文章编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8606")
     private Long id;
 
+    @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "99999")
+    private Integer browseCount;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 17 - 12
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;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -45,7 +46,7 @@ public class AppActivityController {
     @Parameter(name = "spuId", description = "商品编号", required = true)
     public CommonResult<List<AppActivityRespVO>> getActivityListBySpuId(@RequestParam("spuId") Long spuId) {
         // 每种活动,只返回一个
-        return success(getAppActivityRespVOList(Collections.singletonList(spuId)));
+        return success(getAppActivityList(Collections.singletonList(spuId)));
     }
 
     @GetMapping("/list-by-spu-ids")
@@ -56,17 +57,19 @@ public class AppActivityController {
             return success(MapUtil.empty());
         }
         // 每种活动,只返回一个;key 为 SPU 编号
-        return success(convertMultiMap(getAppActivityRespVOList(spuIds), AppActivityRespVO::getSpuId));
+        return success(convertMultiMap(getAppActivityList(spuIds), AppActivityRespVO::getSpuId));
     }
 
-    private List<AppActivityRespVO> getAppActivityRespVOList(Collection<Long> spuIds) {
+    private List<AppActivityRespVO> getAppActivityList(Collection<Long> spuIds) {
         if (CollUtil.isEmpty(spuIds)) {
             return new ArrayList<>();
         }
+        LocalDateTime now = LocalDateTime.now();
         List<AppActivityRespVO> activityList = new ArrayList<>();
-        // 拼团活动
-        List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatus(
-                spuIds, CommonStatusEnum.ENABLE.getStatus());
+
+        // 1. 拼团活动 - 获取开启的且开始的且没有结束的活动
+        List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(
+                spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
         if (CollUtil.isNotEmpty(combinationActivities)) {
             combinationActivities.forEach(item -> {
                 activityList.add(new AppActivityRespVO().setId(item.getId())
@@ -74,9 +77,10 @@ public class AppActivityController {
                         .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
             });
         }
-        // 秒杀活动
-        List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatus(
-                spuIds, CommonStatusEnum.ENABLE.getStatus());
+
+        // 2. 秒杀活动 - 获取开启的且开始的且没有结束的活动
+        List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(
+                spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
         if (CollUtil.isNotEmpty(seckillActivities)) {
             seckillActivities.forEach(item -> {
                 activityList.add(new AppActivityRespVO().setId(item.getId())
@@ -84,9 +88,10 @@ public class AppActivityController {
                         .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
             });
         }
-        // 砍价活动
-        List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatus(
-                spuIds, CommonStatusEnum.ENABLE.getStatus());
+
+        // 3. 砍价活动 - 获取开启的且开始的且没有结束的活动
+        List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatusAndDateTimeLt(
+                spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
         if (CollUtil.isNotEmpty(bargainActivities)) {
             bargainActivities.forEach(item -> {
                 activityList.add(new AppActivityRespVO().setId(item.getId())

+ 2 - 1
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;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
+import java.util.Comparator;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -31,7 +32,7 @@ public class AppArticleCategoryController {
     public CommonResult<List<AppArticleCategoryRespVO>> getArticleCategoryList() {
         List<ArticleCategoryDO> categoryList = articleCategoryService.getArticleCategoryListByStatus(
                 CommonStatusEnum.ENABLE.getStatus());
-        // TODO @puhui999:排序下
+        categoryList.sort(Comparator.comparing(ArticleCategoryDO::getSort)); // 按 sort 降序排列
         return success(ArticleCategoryConvert.INSTANCE.convertList04(categoryList));
     }
 

+ 11 - 1
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;
 import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -55,5 +56,14 @@ public class AppArticleController {
         return success(ArticleConvert.INSTANCE.convert01(articleService.getArticle(id)));
     }
 
-    // TODO @puhui999:增加浏览量,实现一个接口;先简单做,用户规模不大,只 +1 即可;ps:uniapp 那边也要接下噢
+    // TODO @puhui999:add-browse-count 噢;前端 uniapp 也要接下;就是打开文章的时候,调用下这个接口;
+    @PutMapping("/add-browseCount")
+    @Operation(summary = "增加文章浏览量")
+    @Parameter(name = "id", description = "文章编号", example = "1024")
+    public CommonResult<Boolean> addBrowseCount(@RequestParam("id") Long id) {
+        // TODO @puhui999:addArticleBrowseCount
+        articleService.addBrowseCount(id);
+        return success(true);
+    }
+
 }

+ 9 - 17
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 {
     /**
      * 转换生成虚拟成团虚拟记录
      *
-     * @param virtualGroupHeadRecord 虚拟成团团长记录
+     * @param headRecord 虚拟成团团长记录
      * @return 虚拟记录列表
      */
-    // TODO @puhui999:1)方法名,建议改成 convertVirtualRecordList(CombinationRecordDO headRecord);2)第 220 到 225 可以搞成 mapstruct 一个方法,默认都 copy 进去,然后 set 第 226 到 232 的字段
-    default List<CombinationRecordDO> convertVirtualGroupList(CombinationRecordDO virtualGroupHeadRecord) {
-        List<CombinationRecordDO> createRecords = new ArrayList<>();
-        // 计算需要创建的虚拟成团记录数量
-        int count = virtualGroupHeadRecord.getUserSize() - virtualGroupHeadRecord.getUserCount();
+    default List<CombinationRecordDO> convertVirtualRecordList(CombinationRecordDO headRecord) {
+        int count = headRecord.getUserSize() - headRecord.getUserCount();
+        List<CombinationRecordDO> createRecords = new ArrayList<>(count);
         for (int i = 0; i < count; i++) {
             // 基础信息和团长保持一致
-            CombinationRecordDO newRecord = new CombinationRecordDO().setActivityId(virtualGroupHeadRecord.getActivityId())
-                    .setCombinationPrice(virtualGroupHeadRecord.getCombinationPrice()).setSpuId(virtualGroupHeadRecord.getSpuId()).setSpuName(virtualGroupHeadRecord.getSpuName())
-                    .setPicUrl(virtualGroupHeadRecord.getPicUrl()).setSkuId(virtualGroupHeadRecord.getSkuId()).setHeadId(virtualGroupHeadRecord.getId())
-                    .setStatus(virtualGroupHeadRecord.getStatus()) // 状态保持和创建时一致,创建完成后会接着处理
-                    .setVirtualGroup(virtualGroupHeadRecord.getVirtualGroup()).setExpireTime(virtualGroupHeadRecord.getExpireTime())
-                    .setStartTime(virtualGroupHeadRecord.getStartTime()).setUserSize(virtualGroupHeadRecord.getUserSize()).setUserCount(virtualGroupHeadRecord.getUserCount());
+            CombinationRecordDO newRecord = convert5(headRecord);
             // 虚拟信息
-            newRecord.setCount(0);
-            newRecord.setUserId(0L);
-            newRecord.setNickname("");
-            newRecord.setAvatar("");
-            newRecord.setOrderId(0L);
+            newRecord.setCount(0) // 会单独更新下,在后续的 Service 逻辑里
+                    .setUserId(0L).setNickname("").setAvatar("").setOrderId(0L);
             createRecords.add(newRecord);
         }
         return createRecords;
     }
+    @Mapping(target = "id", ignore = true)
+    CombinationRecordDO convert5(CombinationRecordDO headRecord);
 
 }

+ 1 - 1
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 {
     /**
      * 浏览次数
      */
-    private String browseCount;
+    private Integer browseCount;
     /**
      * 排序
      */

+ 6 - 0
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
 import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleCategoryDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 /**
  * 文章分类 Mapper
  *
@@ -23,4 +25,8 @@ public interface ArticleCategoryMapper extends BaseMapperX<ArticleCategoryDO> {
                 .orderByDesc(ArticleCategoryDO::getSort));
     }
 
+    default List<ArticleCategoryDO> selectListByStatus(Integer status) {
+        return selectList(ArticleCategoryDO::getStatus, status);
+    }
+
 }

+ 6 - 0
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;
 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.article.ArticlePageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.article.vo.article.AppArticlePageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -42,5 +43,10 @@ public interface ArticleMapper extends BaseMapperX<ArticleDO> {
                 .eqIfPresent(ArticleDO::getCategoryId, pageReqVO.getCategoryId()));
     }
 
+    default void updateBrowseCount(Long id) {
+        update(null, new LambdaUpdateWrapper<ArticleDO>()
+                .eq(ArticleDO::getId, id)
+                .setSql("browse_count = browse_count + 1"));
+    }
 
 }

+ 12 - 1
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<BargainActivityDO> {
                 .groupBy("spu_id"));
     }
 
-    default List<BargainActivityDO> selectListByIds(Collection<Long> ids) {
+    // TODO @puhui999:是不是只要 endTime 小于就可以啦;
+    /**
+     * 获取指定活动编号的活动列表且
+     * 开始时间和结束时间小于给定时间 dateTime 的活动列表
+     *
+     * @param ids      活动编号
+     * @param dateTime 指定日期
+     * @return 活动列表
+     */
+    default List<BargainActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
         return selectList(new LambdaQueryWrapperX<BargainActivityDO>()
                 .in(BargainActivityDO::getId, ids)
+                .lt(BargainActivityDO::getStartTime, dateTime)
+                .lt(BargainActivityDO::getEndTime, dateTime)
                 .orderByDesc(BargainActivityDO::getCreateTime));
     }
 

+ 12 - 1
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;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -58,9 +59,19 @@ public interface CombinationActivityMapper extends BaseMapperX<CombinationActivi
                 .groupBy("spu_id"));
     }
 
-    default List<CombinationActivityDO> selectListByIds(Collection<Long> ids) {
+    /**
+     * 获取指定活动编号的活动列表且
+     * 开始时间和结束时间小于给定时间 dateTime 的活动列表
+     *
+     * @param ids      活动编号
+     * @param dateTime 指定日期
+     * @return 活动列表
+     */
+    default List<CombinationActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
         return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
                 .in(CombinationActivityDO::getId, ids)
+                .lt(CombinationActivityDO::getStartTime, dateTime)
+                .lt(CombinationActivityDO::getEndTime, dateTime)
                 .orderByDesc(CombinationActivityDO::getCreateTime));
     }
 

+ 12 - 1
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;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -90,9 +91,19 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
                 .groupBy("spu_id"));
     }
 
-    default List<SeckillActivityDO> selectListByIds(Collection<Long> ids) {
+    /**
+     * 获取指定活动编号的活动列表且
+     * 开始时间和结束时间小于给定时间 dateTime 的活动列表
+     *
+     * @param ids      活动编号
+     * @param dateTime 指定日期
+     * @return 活动列表
+     */
+    default List<SeckillActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
         return selectList(new LambdaQueryWrapperX<SeckillActivityDO>()
                 .in(SeckillActivityDO::getId, ids)
+                .lt(SeckillActivityDO::getStartTime, dateTime)
+                .lt(SeckillActivityDO::getEndTime, dateTime)
                 .orderByDesc(SeckillActivityDO::getCreateTime));
     }
 

+ 3 - 7
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/article/ArticleCategoryServiceImpl.java

@@ -1,13 +1,11 @@
 package cn.iocoder.yudao.module.promotion.service.article;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.convert.article.ArticleCategoryConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleCategoryDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.article.ArticleCategoryMapper;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -59,9 +57,8 @@ public class ArticleCategoryServiceImpl implements ArticleCategoryService {
         // 校验存在
         validateArticleCategoryExists(id);
         // 校验是不是存在关联文章
-        // TODO @puhui999:最好获得数量哈;
-        List<ArticleDO> articleList = articleService.getArticleByCategoryId(id);
-        if (CollUtil.isNotEmpty(articleList)) {
+        Long count = articleService.getArticleCountByCategoryId(id);
+        if (count > 0) {
             throw exception(ARTICLE_CATEGORY_DELETE_FAIL_HAVE_ARTICLES);
         }
 
@@ -87,8 +84,7 @@ public class ArticleCategoryServiceImpl implements ArticleCategoryService {
 
     @Override
     public List<ArticleCategoryDO> getArticleCategoryListByStatus(Integer status) {
-        // TODO @puhui999:selectListByStatus
-        return articleCategoryMapper.selectList(ArticleCategoryDO::getStatus, status);
+        return articleCategoryMapper.selectListByStatus(status);
     }
 
 }

+ 15 - 0
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 {
      */
     List<ArticleDO> getArticleByCategoryId(Long categoryId);
 
+    /**
+     * 获得指定分类的文章数量
+     *
+     * @param categoryId 文章分类编号
+     * @return 文章数量
+     */
+    Long getArticleCountByCategoryId(Long categoryId);
+
+    /**
+     * 增加文章浏览量
+     *
+     * @param id 文章编号
+     */
+    void addBrowseCount(Long id);
+
 }

+ 14 - 0
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 {
 
         // 插入
         ArticleDO article = ArticleConvert.INSTANCE.convert(createReqVO);
+        article.setBrowseCount(0); // 初始浏览量
         articleMapper.insert(article);
         // 返回
         return article.getId();
@@ -104,4 +105,17 @@ public class ArticleServiceImpl implements ArticleService {
         return articleMapper.selectList(ArticleDO::getCategoryId, categoryId);
     }
 
+    @Override
+    public Long getArticleCountByCategoryId(Long categoryId) {
+        return articleMapper.selectCount(ArticleDO::getCategoryId, categoryId);
+    }
+
+    @Override
+    public void addBrowseCount(Long id) {
+        // 校验文章是否存在
+        validateArticleExists(id);
+        // 增加浏览次数
+        articleMapper.updateBrowseCount(id);
+    }
+
 }

+ 5 - 3
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
 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
 
 import javax.validation.Valid;
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -102,10 +103,11 @@ public interface BargainActivityService {
     /**
      * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
      *
-     * @param spuIds spu 编号
-     * @param status 状态
+     * @param spuIds   spu 编号
+     * @param status   状态
+     * @param dateTime 日期时间
      * @return 砍价活动列表
      */
-    List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
+    List<BargainActivityDO> getBargainActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
 
 }

+ 3 - 3
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 {
     }
 
     @Override
-    public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
+    public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
         // 1. 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
-        // TODO @puhui999:我想了下,这种是不是只展示当前正在进行中的。已经结束、或者未开始的,可能没啥意义?
         List<Map<String, Object>> spuIdAndActivityIdMaps = bargainActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
         if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
             return Collections.emptyList();
         }
         // 2. 查询活动详情
-        return bargainActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
+        return bargainActivityMapper.selectListByIdsAndDateTimeLt(
+                convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
     }
 
 }

+ 5 - 3
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
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
 
 import javax.validation.Valid;
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -120,10 +121,11 @@ public interface CombinationActivityService {
     /**
      * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
      *
-     * @param spuIds spu 编号
-     * @param status 状态
+     * @param spuIds   spu 编号
+     * @param status   状态
+     * @param dateTime 日期时间
      * @return 拼团活动列表
      */
-    List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
+    List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
 
 }

+ 4 - 2
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;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -228,14 +229,15 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
     }
 
     @Override
-    public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
+    public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
         // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
         List<Map<String, Object>> spuIdAndActivityIdMaps = combinationActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
         if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
             return Collections.emptyList();
         }
         // 2.查询活动详情
-        return combinationActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
+        return combinationActivityMapper.selectListByIdsAndDateTimeLt(
+                convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
     }
 
 }

+ 30 - 42
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;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
@@ -22,6 +23,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationR
 import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
 import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -45,6 +47,7 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
  * @author HUIHUI
  */
 @Service
+@Slf4j
 @Validated
 public class CombinationRecordServiceImpl implements CombinationRecordService {
 
@@ -353,21 +356,22 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
 
         // 3. 逐个处理拼团,过期 or 虚拟成团
         KeyValue<Integer, Integer> keyValue = new KeyValue<>(0, 0); // 统计过期拼团和虚拟成团
-        for (CombinationRecordDO recordDO : headExpireRecords) {
-            // TODO @puhui999:recordDO 非必要的情况下,不用带 DO;直接 record;
+        for (CombinationRecordDO record : headExpireRecords) {
             try {
-                CombinationActivityDO activity = activityMap.get(recordDO.getActivityId());
+                CombinationActivityDO activity = activityMap.get(record.getActivityId());
                 if (activity == null || !activity.getVirtualGroup()) { // 取不到活动的或者不是虚拟拼团的
                     // 3.1. 处理过期的拼团
-                    getSelf().handleExpireRecord(recordDO);
+                    getSelf().handleExpireRecord(record);
                     keyValue.setKey(keyValue.getKey() + 1);
                 } else {
                     // 3.2. 处理虚拟成团
-                    getSelf().handleVirtualGroupRecord(recordDO);
+                    getSelf().handleVirtualGroupRecord(record);
                     keyValue.setValue(keyValue.getValue() + 1);
                 }
             } catch (Exception ignored) { // 处理异常继续循环
-                // TODO @puhui999:需要打印异常日志
+                // TODO @puhui999:拼团过期 or 虚拟成团 可以改成 expireCombinationRecord;因为找方法更容易一些;
+                log.error("[拼团过期 or 虚拟成团][record({}) 处理异常,请进行处理!record 数据是:{}]",
+                        record.getId(), JsonUtils.toJsonString(record));
             }
         }
         return keyValue;
@@ -376,68 +380,52 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
     /**
      * 处理过期拼团
      *
-     * @param headExpireRecord 过期拼团团长记录列表
+     * @param headRecord 过期拼团团长记录
      */
     @Transactional(rollbackFor = Exception.class)
-    public void handleExpireRecord(CombinationRecordDO headExpireRecord) {
-        // TODO @puhui999:这里的 null 其实不用判断。真出现,应该要处个 npe,因为就是要错哈;
-        // TODO @puhui999:headExpireRecord 可以简化成 headRecord
-        if (headExpireRecord == null) {
-            return;
-        }
-
-        // 1.更新拼团记录
-        List<CombinationRecordDO> headsAndRecords = updateBatchCombinationRecords(headExpireRecord,
+    public void handleExpireRecord(CombinationRecordDO headRecord) {
+        // 1. 更新拼团记录
+        List<CombinationRecordDO> headAndRecords = updateBatchCombinationRecords(headRecord,
                 CombinationRecordStatusEnum.FAILED);
-        // TODO @puhui999:这里的 null 其实不用判断。真出现,应该要处个 npe,因为就是要错哈;
-        if (headsAndRecords == null) {
-            return;
-        }
-
-        // 2.订单取消
-        headsAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId()));
+        // 2. 订单取消
+        headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId()));
     }
 
     /**
      * 处理虚拟拼团
      *
-     * @param virtualGroupHeadRecord 虚拟成团团长记录列表
+     * @param headRecord 虚拟成团团长记录
      */
     @Transactional(rollbackFor = Exception.class)
-    public void handleVirtualGroupRecord(CombinationRecordDO virtualGroupHeadRecord) {
-        // TODO @puhui999:这里的 null 其实不用判断。真出现,应该要处个 npe,因为就是要错哈;
-        // TODO @puhui999:headExpireRecord 可以简化成 headRecord
-        if (virtualGroupHeadRecord == null) {
-            return;
-        }
-
+    public void handleVirtualGroupRecord(CombinationRecordDO headRecord) {
         // 1. 团员补齐
-        combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualGroupList(virtualGroupHeadRecord));
+        combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualRecordList(headRecord));
         // 2. 更新拼团记录
-        updateBatchCombinationRecords(virtualGroupHeadRecord, CombinationRecordStatusEnum.SUCCESS);
+        updateBatchCombinationRecords(headRecord, CombinationRecordStatusEnum.SUCCESS);
     }
 
-    // TODO @puhui999:写下方法注释;
+    /**
+     * 更新拼团记录
+     *
+     * @param headRecord 团长记录
+     * @param status     状态-拼团失败 FAILED 成功 SUCCESS
+     * @return 整团记录(包含团长和团成员)
+     */
     private List<CombinationRecordDO> updateBatchCombinationRecords(CombinationRecordDO headRecord, CombinationRecordStatusEnum status) {
         // 1. 查询团成员(包含团长)
         List<CombinationRecordDO> records = combinationRecordMapper.selectListByHeadId(headRecord.getId());
-        // TODO @puhui999:是不是不用判断空哈;例如说,就一个团长,然后过期。
-        if (CollUtil.isEmpty(records)) {
-            return null;
-        }
         records.add(headRecord);// 把团长加进去
 
         // 2. 批量更新拼团记录 status 和 endTime
         List<CombinationRecordDO> updateRecords = new ArrayList<>(records.size());
         LocalDateTime now = LocalDateTime.now();
         records.forEach(item -> {
-            // TODO @puhui999:record 改成 updateRecord
-            CombinationRecordDO record = new CombinationRecordDO().setId(item.getId())
+            CombinationRecordDO updateRecord = new CombinationRecordDO().setId(item.getId())
                     .setStatus(status.getStatus()).setEndTime(now);
             if (CombinationRecordStatusEnum.isSuccess(status.getStatus())) { // 虚拟成团完事更改状态成功后还需要把参与人数修改为成团需要人数
-                record.setUserCount(record.getUserSize());
+                updateRecord.setUserCount(updateRecord.getUserSize());
             }
-            updateRecords.add(record);
+            updateRecords.add(updateRecord);
         });
         combinationRecordMapper.updateBatch(updateRecords);
         return records;

+ 5 - 3
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
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
 
 import javax.validation.Valid;
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -131,10 +132,11 @@ public interface SeckillActivityService {
     /**
      * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
      *
-     * @param spuIds spu 编号
-     * @param status 状态
+     * @param spuIds   spu 编号
+     * @param status   状态
+     * @param dateTime 日期时间
      * @return 秒杀活动列表
      */
-    List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
+    List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
 
 }

+ 4 - 2
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;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -324,14 +325,15 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
     }
 
     @Override
-    public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
+    public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
         // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
         List<Map<String, Object>> spuIdAndActivityIdMaps = seckillActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
         if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
             return Collections.emptyList();
         }
         // 2.查询活动详情
-        return seckillActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
+        return seckillActivityMapper.selectListByIdsAndDateTimeLt(
+                convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
     }
 
 }

+ 13 - 1
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.*;
 import javax.annotation.Resource;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 
 @Tag(name = "管理后台 - 交易订单")
@@ -56,7 +59,9 @@ public class TradeOrderController {
         }
 
         // 查询用户信息
-        Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(convertSet(pageResult.getList(), TradeOrderDO::getUserId));
+        Set<Long> userIds = CollUtil.unionDistinct(convertList(pageResult.getList(), TradeOrderDO::getUserId),
+                convertList(pageResult.getList(), TradeOrderDO::getBrokerageUserId, Objects::nonNull));
+        Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
         // 查询订单项
         List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(
                 convertSet(pageResult.getList(), TradeOrderDO::getId));
@@ -64,6 +69,13 @@ public class TradeOrderController {
         return success(TradeOrderConvert.INSTANCE.convertPage(pageResult, orderItems, userMap));
     }
 
+    @GetMapping("/summary")
+    @Operation(summary = "获得交易订单统计")
+    @PreAuthorize("@ss.hasPermission('trade:order:query')")
+    public CommonResult<TradeOrderSummaryRespVO> getOrderSummary(TradeOrderPageReqVO reqVO) {
+        return success(tradeOrderQueryService.getOrderSummary(reqVO));
+    }
+
     @GetMapping("/get-detail")
     @Operation(summary = "获得交易订单详情")
     @Parameter(name = "id", description = "订单编号", required = true, example = "1")

+ 3 - 0
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 {
     @Schema(description = "VIP 减免金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
     private Integer vipPrice;
 
+    @Schema(description = "推广人编号", example = "1")
+    private Long brokerageUserId;
+
 }

+ 8 - 12
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageItemRespVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
 
-import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -14,24 +14,20 @@ public class TradeOrderPageItemRespVO extends TradeOrderBaseVO {
     @Schema(description = "收件人地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海 上海市 普陀区")
     private String receiverAreaName;
 
-    /**
-     * 订单项列表
-     */
+    @Schema(description = "订单项列表", requiredMode = Schema.RequiredMode.REQUIRED)
     private List<Item> items;
 
-    // TODO @xiaobai:使用 MemberUserRespVO 返回哈;DTO 不直接给前端
-    /**
-     * 用户信息
-     */
-    private MemberUserRespDTO user;
+    @Schema(description = "用户信息", requiredMode = Schema.RequiredMode.REQUIRED)
+    private MemberUserRespVO user;
+
+    @Schema(description = "推广人信息")
+    private MemberUserRespVO brokerageUser;
 
     @Schema(description = "管理后台 - 交易订单的分页项的订单项目")
     @Data
     public static class Item extends TradeOrderItemBaseVO {
 
-        /**
-         * 属性数组
-         */
+        @Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
         private List<ProductPropertyValueDetailRespVO> properties;
 
     }

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderSummaryRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 交易订单统计 Response VO")
+@Data
+public class TradeOrderSummaryRespVO {
+
+    @Schema(description = "订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long orderCount;
+
+    @Schema(description = "订单金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long orderPayPrice;
+
+    @Schema(description = "退款单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long afterSaleCount;
+
+    @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long afterSalePrice;
+
+}

+ 6 - 2
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 {
             TradeOrderPageItemRespVO orderVO = convert(order, xOrderItems);
             // 处理收货地址
             orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId()));
-            // 增加用户昵称
-            orderVO.setUser(memberUserMap.get(orderVO.getUserId()));
+            // 增加用户信息
+            orderVO.setUser(convertUser(memberUserMap.get(orderVO.getUserId())));
+            // 增加推广人信息
+            orderVO.setBrokerageUser(convertUser(memberUserMap.get(orderVO.getBrokerageUserId())));
             return orderVO;
         });
         return new PageResult<>(orderVOs, pageResult.getTotal());
     }
 
+    MemberUserRespVO convertUser(MemberUserRespDTO memberUserRespDTO);
+
     TradeOrderPageItemRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> items);
 
     ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);

+ 24 - 1
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;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -11,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 @Mapper
@@ -42,6 +44,27 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
                 .orderByDesc(TradeOrderDO::getId));
     }
 
+    // TODO @疯狂:如果用 map 返回,要不这里直接用 TradeOrderSummaryRespVO 返回?也算合理,就当  sql 查询出这么个玩意~~
+    default List<Map<String, Object>> selectOrderSummaryGroupByRefundStatus(TradeOrderPageReqVO reqVO, Set<Long> userIds) {
+        return selectMaps(new MPJLambdaWrapperX<TradeOrderDO>()
+                .selectAs(TradeOrderDO::getRefundStatus, TradeOrderDO::getRefundStatus)  // 售后状态
+                .selectCount(TradeOrderDO::getId, "count") // 售后状态对应的数量
+                .selectSum(TradeOrderDO::getPayPrice, "price")  // 售后状态对应的支付金额
+                .likeIfPresent(TradeOrderDO::getNo, reqVO.getNo())
+                .eqIfPresent(TradeOrderDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(TradeOrderDO::getDeliveryType, reqVO.getDeliveryType())
+                .inIfPresent(TradeOrderDO::getUserId, userIds)
+                .eqIfPresent(TradeOrderDO::getType, reqVO.getType())
+                .eqIfPresent(TradeOrderDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(TradeOrderDO::getPayChannelCode, reqVO.getPayChannelCode())
+                .eqIfPresent(TradeOrderDO::getTerminal, reqVO.getTerminal())
+                .eqIfPresent(TradeOrderDO::getLogisticsId, reqVO.getLogisticsId())
+                .inIfPresent(TradeOrderDO::getPickUpStoreId, reqVO.getPickUpStoreIds())
+                .likeIfPresent(TradeOrderDO::getPickUpVerifyCode, reqVO.getPickUpVerifyCode())
+                .betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime())
+                .groupBy(TradeOrderDO::getRefundStatus)); // 按售后状态分组
+    }
+
     default PageResult<TradeOrderDO> selectPage(AppTradeOrderPageReqVO reqVO, Long userId) {
         return selectPage(reqVO, new LambdaQueryWrapperX<TradeOrderDO>()
                 .eq(TradeOrderDO::getUserId, userId)
@@ -92,7 +115,7 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
     default TradeOrderDO selectOneByPickUpVerifyCode(String pickUpVerifyCode) {
         return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode);
     }
-    
+
     default TradeOrderDO selectByUserIdAndCombinationActivityIdAndStatus(Long userId, Long combinationActivityId, Integer status) {
         return selectOne(new LambdaQueryWrapperX<TradeOrderDO>()
                 .eq(TradeOrderDO::getUserId, userId)

+ 0 - 4
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 {
      */
     private static final ThreadLocal<Map<String, Object>> EXTS = new ThreadLocal<>();
 
-    public TradeOrderLogAspect() {
-        System.out.println();
-    }
-
     @Resource
     private TradeOrderLogService orderLogService;
 

+ 9 - 0
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;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderSummaryRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
@@ -64,6 +65,14 @@ public interface TradeOrderQueryService {
      */
     PageResult<TradeOrderDO> getOrderPage(TradeOrderPageReqVO reqVO);
 
+    /**
+     * 获得订单统计
+     *
+     * @param reqVO 请求参数
+     * @return 订单统计
+     */
+    TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO);
+
     /**
      * 【会员】获得交易订单分页
      *

+ 40 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.trade.service.order;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.spring.SpringUtil;
@@ -8,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderSummaryRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -15,6 +17,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
 import cn.iocoder.yudao.module.trade.dal.redis.RedisKeyConstants;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
@@ -85,24 +88,57 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
 
     @Override
     public PageResult<TradeOrderDO> getOrderPage(TradeOrderPageReqVO reqVO) {
+        // 根据用户查询条件构建用户编号列表
+        Set<Long> userIds = buildQueryConditionUserIds(reqVO);
+        if (userIds == null) { // 没查询到用户,说明肯定也没他的订单
+            return PageResult.empty();
+        }
+        // 分页查询
+        return tradeOrderMapper.selectPage(reqVO, userIds);
+    }
+
+    private Set<Long> buildQueryConditionUserIds(TradeOrderPageReqVO reqVO) {
         // 获得 userId 相关的查询
         Set<Long> userIds = new HashSet<>();
         if (StrUtil.isNotEmpty(reqVO.getUserMobile())) {
             MemberUserRespDTO user = memberUserApi.getUserByMobile(reqVO.getUserMobile());
             if (user == null) { // 没查询到用户,说明肯定也没他的订单
-                return new PageResult<>();
+                return null;
             }
             userIds.add(user.getId());
         }
         if (StrUtil.isNotEmpty(reqVO.getUserNickname())) {
             List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(reqVO.getUserNickname());
             if (CollUtil.isEmpty(users)) { // 没查询到用户,说明肯定也没他的订单
-                return new PageResult<>();
+                return null;
             }
             userIds.addAll(convertSet(users, MemberUserRespDTO::getId));
         }
-        // 分页查询
-        return tradeOrderMapper.selectPage(reqVO, userIds);
+        return userIds;
+    }
+
+    @Override
+    public TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO) {
+        // 根据用户查询条件构建用户编号列表
+        Set<Long> userIds = buildQueryConditionUserIds(reqVO);
+        if (userIds == null) { // 没查询到用户,说明肯定也没他的订单
+            return new TradeOrderSummaryRespVO();
+        }
+        // 查询每个售后状态对应的数量、金额
+        List<Map<String, Object>> list = tradeOrderMapper.selectOrderSummaryGroupByRefundStatus(reqVO, null);
+
+        TradeOrderSummaryRespVO vo = new TradeOrderSummaryRespVO().setAfterSaleCount(0L).setAfterSalePrice(0L);
+        for (Map<String, Object> map : list) {
+            Long count = MapUtil.getLong(map, "count", 0L);
+            Long price = MapUtil.getLong(map, "price", 0L);
+            // 未退款的计入订单,部分退款、全部退款计入售后
+            if (TradeOrderRefundStatusEnum.NONE.getStatus().equals(MapUtil.getInt(map, "refundStatus"))) {
+                vo.setOrderCount(count).setOrderPayPrice(price);
+            } else {
+                vo.setAfterSaleCount(vo.getAfterSaleCount() + count).setAfterSalePrice(vo.getAfterSalePrice() + price);
+            }
+        }
+        return vo;
     }
 
     @Override

+ 0 - 1
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 {
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_004_003_000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_004_003_001, "登录失败,账号被禁用");
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_004_003_005, "未绑定账号,需要进行绑定");
-    ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_004_003_006, "获得手机号失败");
     ErrorCode AUTH_MOBILE_USED = new ErrorCode(1_004_003_007, "手机号已经被使用");
 
     // ========== 用户收件地址 1-004-004-000 ==========

+ 0 - 4
yudao-module-member/yudao-module-member-biz/pom.xml

@@ -43,10 +43,6 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
         </dependency>
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
-        </dependency>
 
         <!-- Web 相关 -->
         <dependency>

+ 5 - 0
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}}
 POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
 Content-Type: application/json
 tenant-id: {{appTenentId}}
+
+### 请求 /auth/create-weixin-jsapi-signature 接口 => 成功
+POST {{appApi}}/member/auth/create-weixin-jsapi-signature?url=http://www.iocoder.cn
+Authorization: Bearer {{appToken}}
+tenant-id: {{appTenentId}}

+ 16 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java

@@ -1,12 +1,16 @@
 package cn.iocoder.yudao.module.member.controller.app.auth;
 
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
+import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
+import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Parameters;
@@ -33,6 +37,9 @@ public class AppAuthController {
     @Resource
     private MemberAuthService authService;
 
+    @Resource
+    private SocialClientApi socialClientApi;
+
     @Resource
     private SecurityProperties securityProperties;
 
@@ -109,4 +116,13 @@ public class AppAuthController {
         return success(authService.weixinMiniAppLogin(reqVO));
     }
 
+    @PostMapping("/create-weixin-jsapi-signature")
+    @Operation(summary = "创建微信 JS SDK 初始化所需的签名",
+            description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
+    public CommonResult<SocialWxJsapiSignatureRespDTO> createWeixinMpJsapiSignature(@RequestParam("url") String url) {
+        SocialWxJsapiSignatureRespDTO signature = socialClientApi.createWxMpJsapiSignature(
+                UserTypeEnum.MEMBER.getValue(), url);
+        return success(AuthConvert.INSTANCE.convert(signature));
+    }
+
 }

+ 31 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Schema(description = "用户 APP - 微信公众号 JSAPI 签名 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AuthWeixinJsapiSignatureRespVO {
+
+    @Schema(description = "微信公众号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "hello")
+    private String appId;
+
+    @Schema(description = "匿名串", requiredMode = Schema.RequiredMode.REQUIRED, example = "world")
+    private String nonceStr;
+
+    @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long timestamp;
+
+    @Schema(description = "URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    private String url;
+
+    @Schema(description = "签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "阿巴阿巴")
+    private String signature;
+
+}

+ 3 - 0
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;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -29,4 +30,6 @@ public interface AuthConvert {
 
     SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean);
 
+    SocialWxJsapiSignatureRespDTO convert(SocialWxJsapiSignatureRespDTO bean);
+
 }

+ 11 - 14
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java

@@ -1,7 +1,5 @@
 package cn.iocoder.yudao.module.member.service.auth;
 
-import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -19,9 +17,11 @@ import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
 import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
+import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
@@ -56,10 +56,9 @@ public class MemberAuthServiceImpl implements MemberAuthService {
     @Resource
     private SocialUserApi socialUserApi;
     @Resource
-    private OAuth2TokenApi oauth2TokenApi;
-
+    private SocialClientApi socialClientApi;
     @Resource
-    private WxMaService wxMaService;
+    private OAuth2TokenApi oauth2TokenApi;
 
     @Override
     public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {
@@ -121,15 +120,13 @@ public class MemberAuthServiceImpl implements MemberAuthService {
     @Override
     public AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO) {
         // 获得对应的手机号信息
-        // TODO @芋艿:需要弱化微信小程序的依赖,通过 system 获取手机号
-        WxMaPhoneNumberInfo phoneNumberInfo;
-        try {
-            phoneNumberInfo = wxMaService.getUserService().getPhoneNoInfo(reqVO.getPhoneCode());
-        } catch (Exception exception) {
-            throw exception(AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR);
-        }
+        SocialWxPhoneNumberInfoRespDTO phoneNumberInfo = socialClientApi.getWxMaPhoneNumberInfo(
+                UserTypeEnum.MEMBER.getValue(), reqVO.getPhoneCode());
+        Assert.notNull(phoneNumberInfo, "获得手机信息失败,结果为空");
+
         // 获得获得注册用户
-        MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(), getClientIP(), TerminalEnum.WECHAT_MINI_PROGRAM.getTerminal());
+        MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(),
+                getClientIP(), TerminalEnum.WECHAT_MINI_PROGRAM.getTerminal());
         Assert.notNull(user, "获取用户失败,结果为空");
 
         // 绑定社交用户
@@ -154,7 +151,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
 
     @Override
     public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
-        return socialUserApi.getAuthorizeUrl(type, redirectUri);
+        return socialClientApi.getAuthorizeUrl(type, UserTypeEnum.MEMBER.getValue(), redirectUri);
     }
 
     private MemberUserDO login0(String mobile, String password) {

+ 42 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.system.api.social;
+
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+
+/**
+ * 社交应用的 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface SocialClientApi {
+
+    /**
+     * 获得社交平台的授权 URL
+     *
+     * @param type 社交平台的类型 {@link SocialTypeEnum}
+     * @param userType 用户类型
+     * @param redirectUri 重定向 URL
+     * @return 社交平台的授权 URL
+     */
+    String getAuthorizeUrl(Integer type, Integer userType, String redirectUri);
+
+    /**
+     * 创建微信公众号 JS SDK 初始化所需的签名
+     *
+     * @param userType 用户类型
+     * @param url 访问的 URL 地址
+     * @return 签名
+     */
+    SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url);
+
+    /**
+     * 获得微信小程序的手机信息
+     *
+     * @param userType 用户类型
+     * @param phoneCode 手机授权码
+     * @return 手机信息
+     */
+    SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode);
+
+}

+ 0 - 10
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;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
-import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
 
 import javax.validation.Valid;
 
@@ -15,15 +14,6 @@ import javax.validation.Valid;
  */
 public interface SocialUserApi {
 
-    /**
-     * 获得社交平台的授权 URL
-     *
-     * @param type 社交平台的类型 {@link SocialTypeEnum}
-     * @param redirectUri 重定向 URL
-     * @return 社交平台的授权 URL
-     */
-    String getAuthorizeUrl(Integer type, String redirectUri);
-
     /**
      * 绑定社交用户
      *

+ 1 - 1
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 {
      */
     @InEnum(SocialTypeEnum.class)
     @NotNull(message = "社交平台的类型不能为空")
-    private Integer type;
+    private Integer socialType;
     /**
      * 授权码
      */

+ 4 - 4
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 {
      */
     @InEnum(SocialTypeEnum.class)
     @NotNull(message = "社交平台的类型不能为空")
-    private Integer type;
+    private Integer socialType;
 
     /**
-     * 社交平台的 unionId
+     * 社交平台的 openid
      */
-    @NotEmpty(message = "社交平台的 unionId 不能为空")
-    private String unionId;
+    @NotEmpty(message = "社交平台的 openid 不能为空")
+    private String openid;
 
 }

+ 34 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxJsapiSignatureRespDTO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.system.api.social.dto;
+
+import lombok.Data;
+
+/**
+ * 微信公众号 JSAPI 签名 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class SocialWxJsapiSignatureRespDTO {
+
+    /**
+     * 微信公众号的 appId
+     */
+    private String appId;
+    /**
+     * 匿名串
+     */
+    private String nonceStr;
+    /**
+     * 时间戳
+     */
+    private Long timestamp;
+    /**
+     * URL
+     */
+    private String url;
+    /**
+     * 签名
+     */
+    private String signature;
+
+}

+ 27 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxPhoneNumberInfoRespDTO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.system.api.social.dto;
+
+import lombok.Data;
+
+/**
+ * 微信小程序的手机信息 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class SocialWxPhoneNumberInfoRespDTO {
+
+    /**
+     * 用户绑定的手机号(国外手机号会有区号)
+     */
+    private String phoneNumber;
+
+    /**
+     * 没有区号的手机号
+     */
+    private String purePhoneNumber;
+    /**
+     * 区号
+     */
+    private String countryCode;
+
+}

+ 2 - 0
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 {
     ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1_002_018_001, "社交解绑失败,非当前用户绑定");
     ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1_002_018_002, "社交授权失败,找不到对应的用户");
 
+    ErrorCode SOCIAL_APP_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_002_018_103, "获得手机号失败");
+
     // ========== 系统敏感词 1-002-019-000 =========
     ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_002_019_000, "系统敏感词在所有标签中都不存在");
     ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1_002_019_001, "系统敏感词已在标签中存在");

+ 12 - 6
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 {
 
     /**
      * Gitee
-     * 文档链接:https://gitee.com/api/v5/oauth_doc#/
+     *
+     * @see <a href="https://gitee.com/api/v5/oauth_doc#/">接入文档</a>
      */
     GITEE(10, "GITEE"),
     /**
      * 钉钉
-     * 文档链接:https://developers.dingtalk.com/document/app/obtain-identity-credentials
+     *
+     * @see <a href="https://developers.dingtalk.com/document/app/obtain-identity-credentials">接入文档</a>
      */
     DINGTALK(20, "DINGTALK"),
 
     /**
      * 企业微信
-     * 文档链接:https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html
+     *
+     * @see <a href="https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html">接入文档</a>
      */
     WECHAT_ENTERPRISE(30, "WECHAT_ENTERPRISE"),
     /**
      * 微信公众平台 - 移动端 H5
-     * 文档链接:https://www.cnblogs.com/juewuzhe/p/11905461.html
+     *
+     * @see <a href="https://www.cnblogs.com/juewuzhe/p/11905461.html">接入文档</a>
      */
     WECHAT_MP(31, "WECHAT_MP"),
     /**
      * 微信开放平台 - 网站应用 PC 端扫码授权登录
-     * 文档链接:https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证
+     *
+     * @see <a href="https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证">接入文档</a>
      */
     WECHAT_OPEN(32, "WECHAT_OPEN"),
     /**
      * 微信小程序
-     * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
+     *
+     * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html">接入文档</a>
      */
     WECHAT_MINI_APP(34, "WECHAT_MINI_APP"),
     ;

+ 43 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.system.api.social;
+
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
+import cn.iocoder.yudao.module.system.convert.social.SocialClientConvert;
+import cn.iocoder.yudao.module.system.service.social.SocialClientService;
+import me.chanjar.weixin.common.bean.WxJsapiSignature;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 社交应用的 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class SocialClientApiImpl implements SocialClientApi {
+
+    @Resource
+    private SocialClientService socialClientService;
+
+    @Override
+    public String getAuthorizeUrl(Integer type, Integer userType, String redirectUri) {
+        return socialClientService.getAuthorizeUrl(type, userType, redirectUri);
+    }
+
+    @Override
+    public SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url) {
+        WxJsapiSignature signature = socialClientService.createWxMpJsapiSignature(userType, url);
+        return SocialClientConvert.INSTANCE.convert(signature);
+    }
+
+    @Override
+    public SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode) {
+        WxMaPhoneNumberInfo info = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode);
+        return SocialClientConvert.INSTANCE.convert(info);
+    }
+
+}

+ 1 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java

@@ -21,11 +21,6 @@ public class SocialUserApiImpl implements SocialUserApi {
     @Resource
     private SocialUserService socialUserService;
 
-    @Override
-    public String getAuthorizeUrl(Integer type, String redirectUri) {
-        return socialUserService.getAuthorizeUrl(type, redirectUri);
-    }
-
     @Override
     public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
         return socialUserService.bindSocialUser(reqDTO);
@@ -34,7 +29,7 @@ public class SocialUserApiImpl implements SocialUserApi {
     @Override
     public void unbindSocialUser(SocialUserUnbindReqDTO reqDTO) {
         socialUserService.unbindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(),
-                reqDTO.getType(), reqDTO.getUnionId());
+                reqDTO.getSocialType(), reqDTO.getOpenid());
     }
 
     @Override

+ 4 - 3
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;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
@@ -16,7 +17,7 @@ import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
 import cn.iocoder.yudao.module.system.service.permission.MenuService;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import cn.iocoder.yudao.module.system.service.permission.RoleService;
-import cn.iocoder.yudao.module.system.service.social.SocialUserService;
+import cn.iocoder.yudao.module.system.service.social.SocialClientService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -57,7 +58,7 @@ public class AuthController {
     @Resource
     private PermissionService permissionService;
     @Resource
-    private SocialUserService socialUserService;
+    private SocialClientService socialClientService;
 
     @Resource
     private SecurityProperties securityProperties;
@@ -147,7 +148,7 @@ public class AuthController {
     })
     public CommonResult<String> socialLogin(@RequestParam("type") Integer type,
                                             @RequestParam("redirectUri") String redirectUri) {
-        return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri));
+        return success(socialClientService.getAuthorizeUrl(type, UserTypeEnum.ADMIN.getValue(), redirectUri));
     }
 
     @PostMapping("/social-login")

+ 0 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 占位,避免 package 无法提交到 Git 仓库
- */
-package cn.iocoder.yudao.module.system.controller.app;

+ 0 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/weixin/AppWxMpController.http

@@ -1,4 +0,0 @@
-### 请求 /login 接口 => 成功
-POST {{appApi}}/system/wx-mp/create-jsapi-signature?url=http://www.iocoder.cn
-Authorization: Bearer {{appToken}}
-tenant-id: {{appTenentId}}

+ 0 - 38
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/weixin/AppWxMpController.java

@@ -1,38 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.app.weixin;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Operation;
-import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.bean.WxJsapiSignature;
-import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.mp.api.WxMpService;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "微信公众号")
-@RestController
-@RequestMapping("/system/wx-mp")
-@Validated
-@Slf4j
-public class AppWxMpController {
-
-    @Resource
-    private WxMpService mpService;
-
-    // TODO @芋艿:需要额外考虑个问题;多租户下,如果每个小程序一个微信公众号,则会存在多个 appid;
-    @PostMapping("/create-jsapi-signature")
-    @Operation(summary = "创建微信 JS SDK 初始化所需的签名",
-        description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
-    public CommonResult<WxJsapiSignature> createJsapiSignature(@RequestParam("url") String url) throws WxErrorException {
-        return success(mpService.createJsapiSignature(url));
-    }
-
-}

+ 19 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialClientConvert.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.system.convert.social;
+
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
+import me.chanjar.weixin.common.bean.WxJsapiSignature;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SocialClientConvert {
+
+    SocialClientConvert INSTANCE = Mappers.getMapper(SocialClientConvert.class);
+
+    SocialWxJsapiSignatureRespDTO convert(WxJsapiSignature bean);
+
+    SocialWxPhoneNumberInfoRespDTO convert(WxMaPhoneNumberInfo bean);
+
+}

+ 68 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialClientDO.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.social;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.xingyuv.jushauth.config.AuthConfig;
+import lombok.*;
+
+/**
+ * 社交客户端 DO
+ *
+ * 对应 {@link AuthConfig} 配置,满足不同租户,有自己的客户端配置,实现社交(三方)登录
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "system_social_client", autoResultMap = true)
+@KeySequence("system_social_client_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SocialClientDO extends TenantBaseDO {
+
+    /**
+     * 编号,自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 应用名
+     */
+    private String name;
+    /**
+     * 社交类型
+     *
+     * 枚举 {@link SocialTypeEnum}
+     */
+    private Integer socialType;
+    /**
+     * 用户类型
+     *
+     * 目的:不同用户类型,对应不同的小程序,需要自己的配置
+     *
+     * 枚举 {@link UserTypeEnum}
+     */
+    private Integer userType;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 客户端 id
+     */
+    private String clientId;
+    /**
+     * 客户端 Secret
+     */
+    private String clientSecret;
+
+}

+ 15 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialClientMapper.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.system.dal.mysql.social;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SocialClientMapper extends BaseMapperX<SocialClientDO> {
+
+    default SocialClientDO selectBySocialTypeAndUserType(Integer socialType, Integer userType) {
+        return selectOne(SocialClientDO::getSocialType, socialType,
+                SocialClientDO::getUserType, userType);
+    }
+
+}

+ 58 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.system.service.social;
+
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import com.xingyuv.jushauth.model.AuthUser;
+import me.chanjar.weixin.common.bean.WxJsapiSignature;
+
+/**
+ * 社交应用 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface SocialClientService {
+
+    /**
+     * 获得社交平台的授权 URL
+     *
+     * @param socialType 社交平台的类型 {@link SocialTypeEnum}
+     * @param userType 用户类型
+     * @param redirectUri 重定向 URL
+     * @return 社交平台的授权 URL
+     */
+    String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri);
+
+    /**
+     * 请求社交平台,获得授权的用户
+     *
+     * @param socialType 社交平台的类型
+     * @param userType 用户类型
+     * @param code 授权码
+     * @param state 授权 state
+     * @return 授权的用户
+     */
+    AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state);
+
+    // =================== 微信公众号独有 ===================
+
+    /**
+     * 创建微信公众号的 JS SDK 初始化所需的签名
+     *
+     * @param userType 用户类型
+     * @param url 访问的 URL 地址
+     * @return 签名
+     */
+    WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url);
+
+    // =================== 微信小程序独有 ===================
+
+    /**
+     * 获得微信小程序的手机信息
+     *
+     * @param userType 用户类型
+     * @param phoneCode 手机授权码
+     * @return 手机信息
+     */
+    WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode);
+
+}

+ 258 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java

@@ -0,0 +1,258 @@
+package cn.iocoder.yudao.module.system.service.social;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
+import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
+import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
+import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
+import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.xingyuv.jushauth.config.AuthConfig;
+import com.xingyuv.jushauth.model.AuthCallback;
+import com.xingyuv.jushauth.model.AuthResponse;
+import com.xingyuv.jushauth.model.AuthUser;
+import com.xingyuv.jushauth.request.AuthRequest;
+import com.xingyuv.jushauth.utils.AuthStateUtils;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.WxJsapiSignature;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
+import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_APP_WEIXIN_MINI_APP_PHONE_CODE_ERROR;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_AUTH_FAILURE;
+
+/**
+ * 社交应用 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Slf4j
+public class SocialClientServiceImpl implements SocialClientService {
+
+    @Resource // 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它
+    private YudaoAuthRequestFactory yudaoAuthRequestFactory;
+
+    @Resource
+    private WxMpService wxMpService;
+    @Resource
+    private WxMpProperties wxMpProperties;
+    @Resource
+    private StringRedisTemplate stringRedisTemplate; // WxMpService 需要使用到,所以在 Service 注入了它
+    /**
+     * 缓存 WxMpService 对象
+     *
+     * key:使用微信公众号的 appId + secret 拼接,即 {@link SocialClientDO} 的 clientId 和 clientSecret 属性。
+     * 为什么 key 使用这种格式?因为 {@link SocialClientDO} 在管理后台可以变更,通过这个 key 存储它的单例。
+     *
+     * 为什么要做 WxMpService 缓存?因为 WxMpService 构建成本比较大,所以尽量保证它是单例。
+     */
+    private final LoadingCache<String, WxMpService> wxMpServiceCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofSeconds(10L),
+            new CacheLoader<String, WxMpService>() {
+
+                @Override
+                public WxMpService load(String key) {
+                    String[] keys = key.split(":");
+                    return buildWxMpService(keys[0], keys[1]);
+                }
+
+            });
+
+    @Resource
+    private WxMaService wxMaService;
+    @Resource
+    private WxMaProperties wxMaProperties;
+    /**
+     * 缓存 WxMaService 对象
+     *
+     * 说明同 {@link #wxMpServiceCache} 变量
+     */
+    private final LoadingCache<String, WxMaService> wxMaServiceCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofSeconds(10L),
+            new CacheLoader<String, WxMaService>() {
+
+                @Override
+                public WxMaService load(String key) {
+                    String[] keys = key.split(":");
+                    return buildWxMaService(keys[0], keys[1]);
+                }
+
+            });
+
+    @Resource
+    private SocialClientMapper socialClientMapper;
+
+    @Override
+    public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) {
+        // 获得对应的 AuthRequest 实现
+        AuthRequest authRequest = buildAuthRequest(socialType, userType);
+        // 生成跳转地址
+        String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
+        return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
+    }
+
+    @Override
+    public AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state) {
+        // 构建请求
+        AuthRequest authRequest = buildAuthRequest(socialType, userType);
+        AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();
+        // 执行请求
+        AuthResponse<?> authResponse = authRequest.login(authCallback);
+        log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", socialType,
+                toJsonString(authCallback), toJsonString(authResponse));
+        if (!authResponse.ok()) {
+            throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());
+        }
+        return (AuthUser) authResponse.getData();
+    }
+
+    /**
+     * 构建 AuthRequest 对象,支持多租户配置
+     *
+     * @param socialType 社交类型
+     * @param userType 用户类型
+     * @return AuthRequest 对象
+     */
+    private AuthRequest buildAuthRequest(Integer socialType, Integer userType) {
+        // 1. 先查找默认的配置项,从 application-*.yaml 中读取
+        AuthRequest request = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource());
+        Assert.notNull(request, String.format("社交平台(%d) 不存在", socialType));
+        // 2. 查询 DB 的配置项,如果存在则进行覆盖
+        SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(socialType, userType);
+        if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            // 2.1 构造新的 AuthConfig 对象
+            AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(request, "config");
+            AuthConfig newAuthConfig = ReflectUtil.newInstance(authConfig.getClass());
+            BeanUtil.copyProperties(authConfig, newAuthConfig);
+            // 2.2 修改对应的 clientId + clientSecret 密钥
+            newAuthConfig.setClientId(client.getClientId());
+            newAuthConfig.setClientSecret(client.getClientSecret());
+            // 2.3 设置会 request 里,进行后续使用
+            ReflectUtil.setFieldValue(request, "config", newAuthConfig);
+        }
+        return request;
+    }
+
+    // =================== 微信公众号独有 ===================
+
+    @Override
+    @SneakyThrows
+    public WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url) {
+        WxMpService service = getWxMpService(userType);
+        return service.createJsapiSignature(url);
+    }
+
+    /**
+     * 获得 clientId + clientSecret 对应的 WxMpService 对象
+     *
+     * @param userType 用户类型
+     * @return WxMpService 对象
+     */
+    private WxMpService getWxMpService(Integer userType) {
+        // 第一步,查询 DB 的配置项,获得对应的 WxMpService 对象
+        SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
+                SocialTypeEnum.WECHAT_MP.getType(), userType);
+        if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            return wxMpServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret());
+        }
+        // 第二步,不存在 DB 配置项,则使用 application-*.yaml 对应的 WxMpService 对象
+        return wxMpService;
+    }
+
+    /**
+     * 创建 clientId + clientSecret 对应的 WxMpService 对象
+     *
+     * @param clientId 微信公众号 appId
+     * @param clientSecret 微信公众号 secret
+     * @return WxMpService 对象
+     */
+    private WxMpService buildWxMpService(String clientId, String clientSecret) {
+        // 第一步,创建 WxMpRedisConfigImpl 对象
+        WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(
+                new RedisTemplateWxRedisOps(stringRedisTemplate),
+                wxMpProperties.getConfigStorage().getKeyPrefix());
+        configStorage.setAppId(clientId);
+        configStorage.setSecret(clientSecret);
+
+        // 第二步,创建 WxMpService 对象
+        WxMpService service = new WxMpServiceImpl();
+        service.setWxMpConfigStorage(configStorage);
+        return service;
+    }
+
+    // =================== 微信小程序独有 ===================
+
+    @Override
+    public WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode) {
+        WxMaService service = getWxMaService(userType);
+        try {
+            return service.getUserService().getPhoneNoInfo(phoneCode);
+        } catch (WxErrorException e) {
+            log.error("[getPhoneNoInfo][userType({}) phoneCode({}) 获得手机号失败]", userType, phoneCode, e);
+            throw exception(SOCIAL_APP_WEIXIN_MINI_APP_PHONE_CODE_ERROR);
+        }
+    }
+
+    /**
+     * 获得 clientId + clientSecret 对应的 WxMpService 对象
+     *
+     * @param userType 用户类型
+     * @return WxMpService 对象
+     */
+    private WxMaService getWxMaService(Integer userType) {
+        // 第一步,查询 DB 的配置项,获得对应的 WxMaService 对象
+        SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
+                SocialTypeEnum.WECHAT_MINI_APP.getType(), userType);
+        if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            return wxMaServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret());
+        }
+        // 第二步,不存在 DB 配置项,则使用 application-*.yaml 对应的 WxMaService 对象
+        return wxMaService;
+    }
+
+    /**
+     * 创建 clientId + clientSecret 对应的 WxMaService 对象
+     *
+     * @param clientId 微信小程序 appId
+     * @param clientSecret 微信小程序 secret
+     * @return WxMaService 对象
+     */
+    private WxMaService buildWxMaService(String clientId, String clientSecret) {
+        // 第一步,创建 WxMaRedisBetterConfigImpl 对象
+        WxMaRedisBetterConfigImpl configStorage = new WxMaRedisBetterConfigImpl(
+                new RedisTemplateWxRedisOps(stringRedisTemplate),
+                wxMaProperties.getConfigStorage().getKeyPrefix());
+        configStorage.setAppid(clientId);
+        configStorage.setSecret(clientSecret);
+
+        // 第二步,创建 WxMpService 对象
+        WxMaService service = new WxMaServiceImpl();
+        service.setWxMaConfig(configStorage);
+        return service;
+    }
+
+}

+ 0 - 22
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;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
 
 import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
 import java.util.List;
 
 /**
@@ -17,27 +16,6 @@ import java.util.List;
  */
 public interface SocialUserService {
 
-    /**
-     * 获得社交平台的授权 URL
-     *
-     * @param type 社交平台的类型 {@link SocialTypeEnum}
-     * @param redirectUri 重定向 URL
-     * @return 社交平台的授权 URL
-     */
-    String getAuthorizeUrl(Integer type, String redirectUri);
-
-    /**
-     * 授权获得对应的社交用户
-     * 如果授权失败,则会抛出 {@link ServiceException} 异常
-     *
-     * @param type 社交平台的类型 {@link SocialTypeEnum}
-     * @param code 授权码
-     * @param state state
-     * @return 授权用户
-     */
-    @NotNull
-    SocialUserDO authSocialUser(Integer type, String code, String state);
-
     /**
      * 获得指定用户的社交用户列表
      *

+ 42 - 62
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;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
-import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
@@ -11,24 +10,22 @@ import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
-import com.xingyuv.jushauth.model.AuthCallback;
-import com.xingyuv.jushauth.model.AuthResponse;
 import com.xingyuv.jushauth.model.AuthUser;
-import com.xingyuv.jushauth.request.AuthRequest;
-import com.xingyuv.jushauth.utils.AuthStateUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import javax.validation.constraints.NotNull;
 import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.AUTH_THIRD_LOGIN_NOT_BIND;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;
 
 /**
  * 社交用户 Service 实现类
@@ -40,51 +37,13 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 @Slf4j
 public class SocialUserServiceImpl implements SocialUserService {
 
-    @Resource// 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它
-    private YudaoAuthRequestFactory yudaoAuthRequestFactory;
-
     @Resource
     private SocialUserBindMapper socialUserBindMapper;
     @Resource
     private SocialUserMapper socialUserMapper;
 
-    @Override
-    public String getAuthorizeUrl(Integer type, String redirectUri) {
-        // 获得对应的 AuthRequest 实现
-        AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
-        // 生成跳转地址
-        String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
-        return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
-    }
-
-    @Override
-    public SocialUserDO authSocialUser(Integer type, String code, String state) {
-        // 优先从 DB 中获取,因为 code 有且可以使用一次。
-        // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
-        SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state);
-        if (socialUser != null) {
-            return socialUser;
-        }
-
-        // 请求获取
-        AuthUser authUser = getAuthUser(type, code, state);
-        Assert.notNull(authUser, "三方用户不能为空");
-
-        // 保存到 DB 中
-        socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid());
-        if (socialUser == null) {
-            socialUser = new SocialUserDO();
-        }
-        socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询
-                .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))
-                .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()));
-        if (socialUser.getId() == null) {
-            socialUserMapper.insert(socialUser);
-        } else {
-            socialUserMapper.updateById(socialUser);
-        }
-        return socialUser;
-    }
+    @Resource
+    private SocialClientService socialClientService;
 
     @Override
     public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {
@@ -101,7 +60,8 @@ public class SocialUserServiceImpl implements SocialUserService {
     @Transactional
     public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
         // 获得社交用户
-        SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
+        SocialUserDO socialUser = authSocialUser(reqDTO.getSocialType(), reqDTO.getUserType(),
+                reqDTO.getCode(), reqDTO.getState());
         Assert.notNull(socialUser, "社交用户不能为空");
 
         // 社交用户可能之前绑定过别的用户,需要进行解绑
@@ -134,7 +94,7 @@ public class SocialUserServiceImpl implements SocialUserService {
     @Override
     public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
         // 获得社交用户
-        SocialUserDO socialUser = authSocialUser(type, code, state);
+        SocialUserDO socialUser = authSocialUser(type, userType, code, state);
         Assert.notNull(socialUser, "社交用户不能为空");
 
         // 如果未绑定的社交用户,则无法自动登录,进行报错
@@ -146,24 +106,44 @@ public class SocialUserServiceImpl implements SocialUserService {
         return new SocialUserRespDTO(socialUser.getOpenid(), socialUserBind.getUserId());
     }
 
+    // TODO 芋艿:调整下单测
     /**
-     * 请求社交平台,获得授权的用户
+     * 授权获得对应的社交用户
+     * 如果授权失败,则会抛出 {@link ServiceException} 异常
      *
-     * @param type 社交平台的类型
+     * @param type 社交平台的类型 {@link SocialTypeEnum}
+     * @param userType 用户类型
      * @param code 授权码
-     * @param state 授权 state
-     * @return 授权用户
+     * @param state state
+     * @return 授权用户
      */
-    private AuthUser getAuthUser(Integer type, String code, String state) {
-        AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
-        AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();
-        AuthResponse<?> authResponse = authRequest.login(authCallback);
-        log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type,
-                toJsonString(authCallback), toJsonString(authResponse));
-        if (!authResponse.ok()) {
-            throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());
+    @NotNull
+    public SocialUserDO authSocialUser(Integer type, Integer userType, String code, String state) {
+        // 优先从 DB 中获取,因为 code 有且可以使用一次。
+        // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
+        SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state);
+        if (socialUser != null) {
+            return socialUser;
         }
-        return (AuthUser) authResponse.getData();
+
+        // 请求获取
+        AuthUser authUser = socialClientService.getAuthUser(type, userType, code, state);
+        Assert.notNull(authUser, "三方用户不能为空");
+
+        // 保存到 DB 中
+        socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid());
+        if (socialUser == null) {
+            socialUser = new SocialUserDO();
+        }
+        socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询
+                .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))
+                .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()));
+        if (socialUser.getId() == null) {
+            socialUserMapper.insert(socialUser);
+        } else {
+            socialUserMapper.updateById(socialUser);
+        }
+        return socialUser;
     }
 
 }