Explorar o código

Merge branch 'master' into develop_zqc

DESKTOP-VAEGFGM\zqc hai 1 ano
pai
achega
0d60f04b50
Modificáronse 100 ficheiros con 1486 adicións e 793 borrados
  1. 0 1
      citu-module-mall/citu-module-product-biz/src/main/java/com/citu/module/product/controller/app/spu/AppProductSpuController.java
  2. 0 2
      citu-module-mall/citu-module-product-biz/src/main/java/com/citu/module/product/dal/dataobject/brand/ProductBrandDO.java
  3. 7 16
      citu-module-mall/citu-module-product-biz/src/main/java/com/citu/module/product/dal/dataobject/sku/ProductSkuDO.java
  4. 20 0
      citu-module-mall/citu-module-promotion-api/src/main/java/com/citu/module/promotion/enums/MessageTemplateConstants.java
  5. 0 25
      citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/controller/app/combination/AppCombinationRecordController.java
  6. 0 1
      citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
  7. 0 18
      citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/service/combination/CombinationRecordService.java
  8. 31 60
      citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/service/combination/CombinationRecordServiceImpl.java
  9. 0 2
      citu-module-mall/citu-module-statistics-biz/src/main/java/com/citu/module/statistics/job/product/ProductStatisticsJob.java
  10. 0 2
      citu-module-mall/citu-module-statistics-biz/src/main/java/com/citu/module/statistics/job/trade/TradeStatisticsJob.java
  11. 3 0
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java
  12. 1 1
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/convert/order/TradeOrderConvert.java
  13. 1 0
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/dal/mysql/aftersale/AfterSaleMapper.java
  14. 0 1
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/framework/order/config/TradeOrderConfig.java
  15. 9 3
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/framework/order/config/TradeOrderProperties.java
  16. 11 10
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/brokerage/BrokerageUserService.java
  17. 24 11
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/brokerage/BrokerageUserServiceImpl.java
  18. 29 2
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  19. 3 2
      citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
  20. 0 1
      citu-module-mall/citu-module-trade-biz/src/main/resources/application.yaml
  21. 3 3
      citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/api/order/dto/PayOrderCreateReqDTO.java
  22. 3 3
      citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/api/refund/dto/PayRefundCreateReqDTO.java
  23. 3 3
      citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
  24. 1 0
      citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/enums/ErrorCodeConstants.java
  25. 5 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/app/vo/PayAppBaseVO.java
  26. 3 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/app/vo/PayAppPageReqVO.java
  27. 3 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/app/vo/PayAppRespVO.java
  28. 12 1
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java
  29. 4 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/app/PayAppDO.java
  30. 5 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/app/PayAppMapper.java
  31. 9 1
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/framework/pay/config/PayProperties.java
  32. 10 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/app/PayAppService.java
  33. 41 5
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/app/PayAppServiceImpl.java
  34. 3 7
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargeServiceImpl.java
  35. 4 4
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/demo/PayDemoOrderServiceImpl.java
  36. 2 2
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/order/PayOrderServiceImpl.java
  37. 10 9
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/refund/PayRefundServiceImpl.java
  38. 5 5
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/transfer/PayTransferServiceImpl.java
  39. 7 8
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/wallet/PayWalletRechargeServiceImpl.java
  40. 11 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
  41. 6 3
      citu-module-pay/citu-module-pay-biz/src/test/java/com/citu/module/pay/service/order/PayOrderServiceTest.java
  42. 16 16
      citu-module-pay/citu-module-pay-biz/src/test/java/com/citu/module/pay/service/refund/PayRefundServiceTest.java
  43. 1 0
      citu-module-pay/citu-module-pay-biz/src/test/resources/sql/create_tables.sql
  44. 21 0
      citu-module-pay/citu-spring-boot-starter-biz-pay/src/main/java/com/citu/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java
  45. 2 3
      citu-module-system/citu-module-system-api/src/main/java/com/citu/module/system/api/logger/dto/OperateLogRespDTO.java
  46. 2 0
      citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/SmsClient.java
  47. 21 28
      citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
  48. 35 35
      citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
  49. 86 219
      citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/impl/TencentSmsClient.java
  50. 1 10
      citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java
  51. 60 21
      citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/SmsClientTests.java
  52. 116 138
      citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java
  53. 8 2
      menduner/menduner-common/src/main/java/com/citu/module/menduner/common/db/TransformDataSourceInterceptor.java
  54. 0 11
      menduner/menduner-mall-api/pom.xml
  55. 2 0
      menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/TradeOrderTypeMq.java
  56. 0 1
      menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/visits/MdeVisitsEnum.java
  57. 121 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/analysis/AnalysisController.java
  58. 11 4
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/enterprise/EnterpriseUserBindController.java
  59. 5 1
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/industry/IndustryController.java
  60. 9 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/job/JobAdvertisedController.java
  61. 6 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/position/PositionController.java
  62. 14 3
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/jobhunt/job/AppJobAdvertisedController.java
  63. 15 16
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/jobhunt/person/AppPersonController.java
  64. 3 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/jobhunt/person/resume/AppPersonInfoSaveReqVO.java
  65. 77 5
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/analysis/AppRecruitAnalysisController.java
  66. 2 22
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/person/cv/AppRecruitJobCvRelRespVO.java
  67. 4 3
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/AnalysisBaseReqVO.java
  68. 25 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitAnalysisReqVO.java
  69. 25 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitInterviewInviteAnalysisRespVO.java
  70. 15 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitJobAnalysisRespVO.java
  71. 23 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitJobCvRelAnalysisRespVO.java
  72. 0 3
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/enterprise/bind/EnterpriseUserBindPageReqVO.java
  73. 35 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/enterprise/bind/EnterpriseUserBindReqVO.java
  74. 4 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/industry/IndustryRespVO.java
  75. 3 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/industry/IndustrySaveReqVO.java
  76. 44 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobAdvertisedReqVO.java
  77. 33 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobCvRelSimpleRespVO.java
  78. 3 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/person/info/PersonInfoPageReqVO.java
  79. 4 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/person/info/PersonInfoRespVO.java
  80. 3 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/person/info/PersonInfoSaveReqVO.java
  81. 4 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/position/PositionRespVO.java
  82. 3 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/position/PositionSaveReqVO.java
  83. 4 1
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/industry/IndustryDO.java
  84. 10 3
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/person/PersonInfoDO.java
  85. 4 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/position/PositionDO.java
  86. 21 2
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/enterprise/EnterpriseUserBindMapper.java
  87. 6 3
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/industry/IndustryMapper.java
  88. 32 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/interview/InterviewInviteMapper.java
  89. 81 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/job/JobAdvertisedMapper.java
  90. 61 4
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/job/JobCvRelMapper.java
  91. 8 4
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/position/PositionMapper.java
  92. 5 7
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/visits/MdeVisitsMapper.java
  93. 8 4
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/mq/consumer/MdeVisitsConsumer.java
  94. 2 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/mq/message/MdeVisitsSendMessage.java
  95. 21 11
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/mq/producer/MdeVisitsProducer.java
  96. 1 1
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/enterprise/EnterpriseServiceImpl.java
  97. 8 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/enterprise/bind/EnterpriseUserBindService.java
  98. 6 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/enterprise/bind/EnterpriseUserBindServiceImpl.java
  99. 5 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/industry/IndustryService.java
  100. 52 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/industry/IndustryServiceImpl.java

+ 0 - 1
citu-module-mall/citu-module-product-biz/src/main/java/com/citu/module/product/controller/app/spu/AppProductSpuController.java

@@ -148,5 +148,4 @@ public class AppProductSpuController {
         return price - newPrice;
     }
 
-    // TODO 芋艿:商品的浏览记录;
 }

+ 0 - 2
citu-module-mall/citu-module-product-biz/src/main/java/com/citu/module/product/dal/dataobject/brand/ProductBrandDO.java

@@ -48,6 +48,4 @@ public class ProductBrandDO extends BaseDO {
      */
     private Integer status;
 
-    // TODO 芋艿:firstLetter 首字母
-
 }

+ 7 - 16
citu-module-mall/citu-module-product-biz/src/main/java/com/citu/module/product/dal/dataobject/sku/ProductSkuDO.java

@@ -1,16 +1,14 @@
 package com.citu.module.product.dal.dataobject.sku;
 
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
-import com.citu.framework.common.util.json.JsonUtils;
 import com.citu.framework.mybatis.core.dataobject.BaseDO;
 import com.citu.module.product.dal.dataobject.property.ProductPropertyDO;
 import com.citu.module.product.dal.dataobject.property.ProductPropertyValueDO;
 import com.citu.module.product.dal.dataobject.spu.ProductSpuDO;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
 import lombok.*;
 
 import java.util.List;
@@ -37,7 +35,7 @@ public class ProductSkuDO extends BaseDO {
     private Long id;
     /**
      * SPU 编号
-     *
+     * <p>
      * 关联 {@link ProductSpuDO#getId()}
      */
     private Long spuId;
@@ -112,7 +110,7 @@ public class ProductSkuDO extends BaseDO {
         /**
          * 属性名字
          * 冗余 {@link ProductPropertyDO#getName()}
-         *
+         * <p>
          * 注意:每次属性名字发生变化时,需要更新该冗余
          */
         private String propertyName;
@@ -125,18 +123,11 @@ public class ProductSkuDO extends BaseDO {
         /**
          * 属性值名字
          * 冗余 {@link ProductPropertyValueDO#getName()}
-         *
+         * <p>
          * 注意:每次属性值名字发生变化时,需要更新该冗余
          */
         private String valueName;
 
     }
-
-    // TODO 芋艿:integral from y
-    // TODO 芋艿:pinkPrice from y
-    // TODO 芋艿:seckillPrice from y
-    // TODO 芋艿:pinkStock from y
-    // TODO 芋艿:seckillStock from y
-
 }
 

+ 20 - 0
citu-module-mall/citu-module-promotion-api/src/main/java/com/citu/module/promotion/enums/MessageTemplateConstants.java

@@ -0,0 +1,20 @@
+package com.citu.module.promotion.enums;
+
+/**
+ * 通知模板枚举类
+ *
+ * @author HUIHUI
+ */
+public interface MessageTemplateConstants {
+
+    // ======================= 短信消息模版 =======================
+
+    String SMS_ORDER_DELIVERY = "order_delivery"; // 短信模版编号
+
+    String SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过)
+    String SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过)
+
+    // ======================= 小程序订阅消息模版 =======================
+
+    String WXA_ORDER_DELIVERY = "订单发货通知";
+}

+ 0 - 25
citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/controller/app/combination/AppCombinationRecordController.java

@@ -43,9 +43,6 @@ public class AppCombinationRecordController {
 
     @Resource
     private CombinationRecordService combinationRecordService;
-    @Resource
-    @Lazy
-    private TradeOrderApi tradeOrderApi;
 
     @GetMapping("/get-summary")
     @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页")
@@ -117,26 +114,4 @@ public class AppCombinationRecordController {
         return success(CombinationActivityConvert.INSTANCE.convert(getLoginUserId(), headRecord, memberRecords));
     }
 
-    @GetMapping("/cancel")
-    @Operation(summary = "取消拼团")
-    @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024")
-    public CommonResult<Boolean> cancelCombinationRecord(@RequestParam("id") Long id) {
-        Long userId = getLoginUserId();
-        // 1、查找这条拼团记录
-        CombinationRecordDO record = combinationRecordService.getCombinationRecordByIdAndUser(userId, id);
-        if (record == null) {
-            return success(Boolean.FALSE);
-        }
-        // 1.1、需要先校验拼团记录未完成;
-        if (!CombinationRecordStatusEnum.isInProgress(record.getStatus())) {
-            return success(Boolean.FALSE);
-        }
-
-        // 2. 取消已支付的订单
-        tradeOrderApi.cancelPaidOrder(userId, record.getOrderId());
-        // 3. 取消拼团记录
-        combinationRecordService.cancelCombinationRecord(userId, record.getId(), record.getHeadId());
-        return success(Boolean.TRUE);
-    }
-
 }

+ 0 - 1
citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java

@@ -72,7 +72,6 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
     default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
         return selectPage(pageReqVO, new LambdaQueryWrapperX<SeckillActivityDO>()
                 .eqIfPresent(SeckillActivityDO::getStatus, status)
-                // TODO 芋艿:对 find in set 的想法;
                 .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
     }
 

+ 0 - 18
citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/service/combination/CombinationRecordService.java

@@ -139,24 +139,6 @@ public interface CombinationRecordService {
                                                               @Nullable Integer status,
                                                               @Nullable Long headId);
 
-    /**
-     * 获取拼团记录
-     *
-     * @param userId 用户编号
-     * @param id     拼团记录编号
-     * @return 拼团记录
-     */
-    CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id);
-
-    /**
-     * 取消拼团
-     *
-     * @param userId 用户编号
-     * @param id     拼团记录编号
-     * @param headId 团长编号
-     */
-    void cancelCombinationRecord(Long userId, Long id, Long headId);
-
     /**
      * 处理过期拼团
      *

+ 31 - 60
citu-module-mall/citu-module-promotion-biz/src/main/java/com/citu/module/promotion/service/combination/CombinationRecordServiceImpl.java

@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import com.citu.framework.common.core.KeyValue;
 import com.citu.framework.common.enums.CommonStatusEnum;
+import com.citu.framework.common.enums.UserTypeEnum;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.common.util.json.JsonUtils;
 import com.citu.module.member.api.user.MemberUserApi;
@@ -23,9 +24,12 @@ import com.citu.module.promotion.dal.dataobject.combination.CombinationProductDO
 import com.citu.module.promotion.dal.dataobject.combination.CombinationRecordDO;
 import com.citu.module.promotion.dal.mysql.combination.CombinationRecordMapper;
 import com.citu.module.promotion.enums.combination.CombinationRecordStatusEnum;
+import com.citu.module.system.api.social.SocialClientApi;
+import com.citu.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
 import com.citu.module.trade.api.order.TradeOrderApi;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -33,13 +37,17 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Nullable;
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 
 import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static com.citu.framework.common.util.collection.CollectionUtils.*;
 import static com.citu.framework.common.util.date.LocalDateTimeUtils.afterNow;
 import static com.citu.framework.common.util.date.LocalDateTimeUtils.beforeNow;
 import static com.citu.module.promotion.enums.ErrorCodeConstants.*;
+import static com.citu.module.promotion.enums.MessageTemplateConstants.WXA_ORDER_DELIVERY;
 
 // TODO 芋艿:等拼团记录做完,完整 review 下
 
@@ -53,20 +61,20 @@ import static com.citu.module.promotion.enums.ErrorCodeConstants.*;
 @Validated
 public class CombinationRecordServiceImpl implements CombinationRecordService {
 
+    @Resource
+    public SocialClientApi socialClientApi;
     @Resource
     private CombinationActivityService combinationActivityService;
     @Resource
     private CombinationRecordMapper combinationRecordMapper;
-
     @Resource
     private MemberUserApi memberUserApi;
     @Resource
     private ProductSpuApi productSpuApi;
     @Resource
     private ProductSkuApi productSkuApi;
-
     @Resource
-    @Lazy
+    @Lazy // 延迟加载,避免循环依赖
     private TradeOrderApi tradeOrderApi;
 
     // TODO @芋艿:在详细预览下;
@@ -205,7 +213,25 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
             }
             updateRecords.add(updateRecord);
         });
-        combinationRecordMapper.updateBatch(updateRecords);
+        Boolean updateSuccess = combinationRecordMapper.updateBatch(updateRecords);
+
+        // 3. 拼团成功发送订阅消息
+        if (updateSuccess && isFull) {
+            records.forEach(item -> {
+                getSelf().sendCombinationResultMessage(item);
+            });
+        }
+    }
+
+    @Async
+    public void sendCombinationResultMessage(CombinationRecordDO record) {
+        // 构建并发送模版消息
+        socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO()
+                .setUserId(record.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
+                .setTemplateTitle(WXA_ORDER_DELIVERY)
+                .setPage("pages/order/detail?id=" + record.getOrderId()) // 订单详情页
+                .addMessage("thing1", "商品拼团活动") // 活动标题
+                .addMessage("thing2", "恭喜您拼团成功!我们将尽快为您发货。")); // 温馨提示
     }
 
     @Override
@@ -264,61 +290,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         return combinationRecordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId);
     }
 
-    @Override
-    public CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id) {
-        return combinationRecordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void cancelCombinationRecord(Long userId, Long id, Long headId) {
-        // 删除记录
-        combinationRecordMapper.deleteById(id);
-
-        // 需要更新的记录
-        List<CombinationRecordDO> updateRecords = new ArrayList<>();
-        // 如果它是团长,则顺序(下单时间)继承
-        if (Objects.equals(headId, CombinationRecordDO.HEAD_ID_GROUP)) { // 情况一:团长
-            // 团员
-            List<CombinationRecordDO> list = getCombinationRecordListByHeadId(id);
-            if (CollUtil.isEmpty(list)) {
-                return;
-            }
-            // 按照创建时间升序排序
-            list.sort(Comparator.comparing(CombinationRecordDO::getCreateTime)); // 影响原 list
-            CombinationRecordDO newHead = list.get(0); // 新团长继位
-            list.forEach(item -> {
-                CombinationRecordDO recordDO = new CombinationRecordDO();
-                recordDO.setId(item.getId());
-                if (ObjUtil.equal(item.getId(), newHead.getId())) { // 新团长
-                    recordDO.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
-                } else {
-                    recordDO.setHeadId(newHead.getId());
-                }
-                recordDO.setUserCount(list.size());
-                updateRecords.add(recordDO);
-            });
-        } else { // 情况二:团员
-            // 团长
-            CombinationRecordDO recordHead = combinationRecordMapper.selectById(headId);
-            // 团员
-            List<CombinationRecordDO> records = getCombinationRecordListByHeadId(headId);
-            if (CollUtil.isEmpty(records)) {
-                return;
-            }
-            records.add(recordHead); // 加入团长,团长数据也需要更新
-            records.forEach(item -> {
-                CombinationRecordDO recordDO = new CombinationRecordDO();
-                recordDO.setId(item.getId());
-                recordDO.setUserCount(records.size());
-                updateRecords.add(recordDO);
-            });
-        }
-
-        // 更新拼团记录
-        combinationRecordMapper.updateBatch(updateRecords);
-    }
-
     @Override
     public KeyValue<Integer, Integer> expireCombinationRecord() {
         // 1. 获取所有正在进行中的过期的父拼团

+ 0 - 2
citu-module-mall/citu-module-statistics-biz/src/main/java/com/citu/module/statistics/job/product/ProductStatisticsJob.java

@@ -11,8 +11,6 @@ import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
 
-// TODO 芋艿:缺个 Job 的配置;等和 Product 一起配置
-
 /**
  * 商品统计 Job
  *

+ 0 - 2
citu-module-mall/citu-module-statistics-biz/src/main/java/com/citu/module/statistics/job/trade/TradeStatisticsJob.java

@@ -10,8 +10,6 @@ import com.xxl.job.core.handler.annotation.XxlJob;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
-
-// TODO 芋艿:缺个 Job 的配置;等和 Product 一起配置
 /**
  * 交易统计 Job
  *

+ 3 - 0
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java

@@ -21,6 +21,9 @@ import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DA
 @ToString(callSuper = true)
 public class AfterSalePageReqVO extends PageParam {
 
+    @Schema(description = "用户编号", example = "1024")
+    private Long userId;
+
     @Schema(description = "售后流水号", example = "202211190847450020500077")
     private String no;
 

+ 1 - 1
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/convert/order/TradeOrderConvert.java

@@ -101,7 +101,7 @@ public interface TradeOrderConvert {
     default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
                                          TradeOrderProperties orderProperties) {
         PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO()
-                .setAppId(orderProperties.getAppId()).setUserIp(order.getUserIp());
+                .setAppKey(orderProperties.getPayAppKey()).setUserIp(order.getUserIp());
         // 商户相关字段
         createReqDTO.setMerchantOrderId(String.valueOf(order.getId()));
         String subject = orderItems.get(0).getSpuName();

+ 1 - 0
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/dal/mysql/aftersale/AfterSaleMapper.java

@@ -16,6 +16,7 @@ public interface AfterSaleMapper extends BaseMapperX<AfterSaleDO> {
 
     default PageResult<AfterSaleDO> selectPage(AfterSalePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<AfterSaleDO>()
+                .eqIfPresent(AfterSaleDO::getUserId, reqVO.getUserId())
                 .likeIfPresent(AfterSaleDO::getNo, reqVO.getNo())
                 .eqIfPresent(AfterSaleDO::getStatus, reqVO.getStatus())
                 .eqIfPresent(AfterSaleDO::getType, reqVO.getType())

+ 0 - 1
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/framework/order/config/TradeOrderConfig.java

@@ -3,7 +3,6 @@ package com.citu.module.trade.framework.order.config;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 
-// TODO @LeeYan9: 可以直接给 TradeOrderProperties 一个 @Component生效哈
 /**
  * @author LeeYan9
  * @since 2022-09-15

+ 9 - 3
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/framework/order/config/TradeOrderProperties.java

@@ -4,9 +4,11 @@ import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.validation.annotation.Validated;
 
+import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.time.Duration;
 
+
 /**
  * 交易订单的配置项
  *
@@ -18,11 +20,15 @@ import java.time.Duration;
 @Validated
 public class TradeOrderProperties {
 
+    private static final String PAY_APP_KEY_DEFAULT = "mall";
+
     /**
-     * 应用编号
+     * 支付应用标识
+     * <p>
+     * 在 pay 模块的 [支付管理 -> 应用信息] 里添加
      */
-    @NotNull(message = "应用编号不能为空")
-    private Long appId;
+    @NotEmpty(message = "Pay 应用标识不能为空")
+    private String payAppKey = PAY_APP_KEY_DEFAULT;
 
     /**
      * 支付超时时间

+ 11 - 10
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/brokerage/BrokerageUserService.java

@@ -27,14 +27,6 @@ public interface BrokerageUserService {
      */
     BrokerageUserDO getBrokerageUser(Long id);
 
-    /**
-     * 获得分销用户列表
-     *
-     * @param ids 编号
-     * @return 分销用户列表
-     */
-    List<BrokerageUserDO> getBrokerageUserList(Collection<Long> ids);
-
     /**
      * 获得分销用户分页
      *
@@ -67,6 +59,14 @@ public interface BrokerageUserService {
      */
     BrokerageUserDO getBindBrokerageUser(Long id);
 
+    /**
+     * 获得或创建分销用户
+     *
+     * @param id 用户编号
+     * @return 分销用户
+     */
+    BrokerageUserDO getOrCreateBrokerageUser(Long id);
+
     /**
      * 更新用户佣金
      *
@@ -104,8 +104,8 @@ public interface BrokerageUserService {
     /**
      * 【会员】绑定推广员
      *
-     * @param userId       用户编号
-     * @param bindUserId   推广员编号
+     * @param userId     用户编号
+     * @param bindUserId 推广员编号
      * @return 是否绑定
      */
     boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId);
@@ -134,4 +134,5 @@ public interface BrokerageUserService {
      * @return 下级分销统计分页
      */
     PageResult<AppBrokerageUserChildSummaryRespVO> getBrokerageUserChildSummaryPage(AppBrokerageUserChildSummaryPageReqVO pageReqVO, Long userId);
+
 }

+ 24 - 11
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/brokerage/BrokerageUserServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.common.util.date.LocalDateTimeUtils;
@@ -59,11 +60,6 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
         return brokerageUserMapper.selectById(id);
     }
 
-    @Override
-    public List<BrokerageUserDO> getBrokerageUserList(Collection<Long> ids) {
-        return brokerageUserMapper.selectBatchIds(ids);
-    }
-
     @Override
     public PageResult<BrokerageUserDO> getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO) {
         List<Long> childIds = getChildUserIdsByLevel(pageReqVO.getBindUserId(), pageReqVO.getLevel());
@@ -127,6 +123,19 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
                 .orElse(null);
     }
 
+    @Override
+    public BrokerageUserDO getOrCreateBrokerageUser(Long id) {
+        BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(id);
+        // 特殊:人人分销的情况下,如果分销人为空则创建分销人
+        if (brokerageUser == null && ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(),
+                tradeConfigService.getTradeConfig().getBrokerageEnabledCondition())) {
+            brokerageUser = new BrokerageUserDO().setId(id).setBrokerageEnabled(true).setBrokeragePrice(0)
+                    .setBrokerageTime(LocalDateTime.now()).setFrozenPrice(0);
+            brokerageUserMapper.insert(brokerageUser);
+        }
+        return brokerageUser;
+    }
+
     @Override
     public boolean updateUserPrice(Long id, Integer price) {
         if (price > 0) {
@@ -184,7 +193,6 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
             if (BrokerageEnabledConditionEnum.ALL.getCondition().equals(enabledCondition)) { // 人人分销:用户默认就有分销资格
                 brokerageUser.setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now());
             }
-            brokerageUser.setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now());
             brokerageUserMapper.insert(fillBindUserData(bindUserId, brokerageUser));
         } else {
             brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(userId)));
@@ -290,22 +298,27 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
      */
     private boolean isNewRegisterUser(Long userId) {
         MemberUserRespDTO user = memberUserApi.getUser(userId).getCheckedData();
-        return user != null && LocalDateTimeUtils.beforeNow(user.getCreateTime().plusSeconds(30));
+        return user != null && LocalDateTimeUtils.afterNow(user.getCreateTime().plusSeconds(30));
     }
 
     private void validateCanBindUser(BrokerageUserDO user, Long bindUserId) {
-        // 校验要绑定的用户有无推广资格
-        BrokerageUserDO bindUser = brokerageUserMapper.selectById(bindUserId);
+        // 1.1 校验推广人是否存在
+        MemberUserRespDTO bindUserInfo = memberUserApi.getUser(bindUserId).getCheckedData();
+        if (bindUserInfo == null) {
+            throw exception(BROKERAGE_USER_NOT_EXISTS);
+        }
+        // 1.2 校验要绑定的用户有无推广资格
+        BrokerageUserDO bindUser = getOrCreateBrokerageUser(bindUserId);
         if (bindUser == null || BooleanUtil.isFalse(bindUser.getBrokerageEnabled())) {
             throw exception(BROKERAGE_BIND_USER_NOT_ENABLED);
         }
 
-        // 校验绑定自己
+        // 2. 校验绑定自己
         if (Objects.equals(user.getId(), bindUserId)) {
             throw exception(BROKERAGE_BIND_SELF);
         }
 
-        // 下级不能绑定自己的上级
+        // 3. 下级不能绑定自己的上级
         for (int i = 0; i <= Short.MAX_VALUE; i++) {
             if (Objects.equals(bindUser.getBindUserId(), user.getId())) {
                 throw exception(BROKERAGE_BIND_LOOP);

+ 29 - 2
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -1,11 +1,13 @@
 package com.citu.module.trade.service.order;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import com.alibaba.fastjson.JSON;
 import com.citu.framework.common.core.KeyValue;
@@ -20,6 +22,8 @@ import com.citu.module.pay.api.order.dto.PayOrderRespDTO;
 import com.citu.module.pay.enums.order.PayOrderStatusEnum;
 import com.citu.module.product.api.comment.ProductCommentApi;
 import com.citu.module.product.api.comment.dto.ProductCommentCreateReqDTO;
+import com.citu.module.system.api.social.SocialClientApi;
+import com.citu.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
 import com.citu.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
 import com.citu.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
 import com.citu.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
@@ -51,6 +55,7 @@ import com.citu.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import com.citu.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import com.citu.module.trade.service.price.calculator.TradePriceCalculatorHelper;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -67,6 +72,7 @@ import static com.citu.framework.common.util.collection.CollectionUtils.*;
 import static com.citu.framework.common.util.date.LocalDateTimeUtils.minusTime;
 import static com.citu.framework.common.util.servlet.ServletUtils.getClientIP;
 import static com.citu.framework.web.core.util.WebFrameworkUtils.getTerminal;
+import static com.citu.module.promotion.enums.MessageTemplateConstants.WXA_ORDER_DELIVERY;
 import static com.citu.module.trade.enums.ErrorCodeConstants.*;
 
 /**
@@ -104,6 +110,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     private MemberAddressApi addressApi;
     @Resource
     private ProductCommentApi productCommentApi;
+    @Resource
+    public SocialClientApi socialClientApi;
 
     @Resource
     private TradeOrderProperties tradeOrderProperties;
@@ -366,9 +374,26 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 MapUtil.<String, Object>builder().put("expressName", express != null ? express.getName() : "")
                         .put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "").build());
 
-        // 4. 发送站内信
+        // 4.1 发送站内信
         tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO()
                 .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null));
+        // 4.2 发送订阅消息
+        getSelf().sendDeliveryOrderMessage(order, deliveryReqVO);
+    }
+
+    @Async
+    public void sendDeliveryOrderMessage(TradeOrderDO order, TradeOrderDeliveryReqVO deliveryReqVO) {
+        // 构建并发送模版消息
+        Long orderId = order.getId();
+        socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO()
+                .setUserId(order.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
+                .setTemplateTitle(WXA_ORDER_DELIVERY)
+                .setPage("pages/order/detail?id=" + orderId) // 订单详情页
+                .addMessage("character_string3", String.valueOf(orderId)) // 订单编号
+                .addMessage("phrase6", TradeOrderStatusEnum.DELIVERED.getName()) // 订单状态
+                .addMessage("date4", LocalDateTimeUtil.formatNormal(LocalDateTime.now()))// 发货时间
+                .addMessage("character_string5", StrUtil.blankToDefault(deliveryReqVO.getLogisticsNo(), "-")) // 快递单号
+                .addMessage("thing9", order.getReceiverDetailAddress())); // 收货地址
     }
 
     /**
@@ -832,12 +857,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void cancelPaidOrder(Long userId, Long orderId) {
-        // TODO 芋艿:这里实现要优化下;
+        // TODO @puhui999:需要校验状态;已支付的情况下,才可以。
         TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
         if (order == null) {
             throw exception(ORDER_NOT_FOUND);
         }
         cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
+
+        // TODO @puhui999:需要退款
     }
 
     /**

+ 3 - 2
citu-module-mall/citu-module-trade-biz/src/main/java/com/citu/module/trade/service/price/calculator/TradePriceCalculatorHelper.java

@@ -1,6 +1,7 @@
 package com.citu.module.trade.service.price.calculator;
 
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
 import com.citu.framework.common.util.collection.CollectionUtils;
 import com.citu.module.product.api.sku.dto.ProductSkuRespDTO;
 import com.citu.module.product.api.spu.dto.ProductSpuRespDTO;
@@ -61,7 +62,7 @@ public class TradePriceCalculatorHelper {
             orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
                     .setDeliveryTemplateId(spu.getDeliveryTemplateId())
                     .setGivePoint(spu.getGiveIntegral()).setUsePoint(0);
-            if (orderItem.getPicUrl() == null) {
+            if (StrUtil.isBlank(orderItem.getPicUrl())) {
                 orderItem.setPicUrl(spu.getPicUrl());
             }
         });
@@ -240,7 +241,7 @@ public class TradePriceCalculatorHelper {
      *
      * 和 {@link #dividePrice(List, Integer)} 逻辑一致,只是传入的是 TradeOrderItemDO 对象
      *
-     * @param items         订单项
+     * @param items 订单项
      * @param price 订单支付金额
      * @return 分摊金额数组,和传入的 orderItems 一一对应
      */

+ 0 - 1
citu-module-mall/citu-module-trade-biz/src/main/resources/application.yaml

@@ -111,7 +111,6 @@ citu:
       - com.citu.module.menduner.system.enums.ErrorCodeConstants
   trade:
     order:
-      app-id: 1 # 商户编号
       pay-expire-time: 2h # 支付的过期时间
       receive-expire-time: 14d # 收货的过期时间
       comment-expire-time: 7d # 评论的过期时间

+ 3 - 3
citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/api/order/dto/PayOrderCreateReqDTO.java

@@ -18,10 +18,10 @@ public class PayOrderCreateReqDTO implements Serializable {
     public static final int SUBJECT_MAX_LENGTH = 32;
 
     /**
-     * 应用编号
+     * 应用标识
      */
-    @NotNull(message = "应用编号不能为空")
-    private Long appId;
+    @NotNull(message = "应用标识不能为空")
+    private String appKey;
     /**
      * 用户 IP
      */

+ 3 - 3
citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/api/refund/dto/PayRefundCreateReqDTO.java

@@ -16,10 +16,10 @@ import javax.validation.constraints.NotNull;
 public class PayRefundCreateReqDTO {
 
     /**
-     * 应用编号
+     * 应用标识
      */
-    @NotNull(message = "应用编号不能为空")
-    private Long appId;
+    @NotNull(message = "应用标识不能为空")
+    private String appKey;
     /**
      * 用户 IP
      */

+ 3 - 3
citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java

@@ -19,10 +19,10 @@ import java.util.Map;
 public class PayTransferCreateReqDTO {
 
     /**
-     * 应用编号
+     * 应用标识
      */
-    @NotNull(message = "应用编号不能为空")
-    private Long appId;
+    @NotNull(message = "应用标识不能为空")
+    private String appKey;
 
     @NotEmpty(message = "转账渠道不能为空")
     private String channelCode;

+ 1 - 0
citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/enums/ErrorCodeConstants.java

@@ -14,6 +14,7 @@ public interface ErrorCodeConstants {
     ErrorCode APP_IS_DISABLE = new ErrorCode(1_007_000_002, "App 已经被禁用");
     ErrorCode APP_EXIST_ORDER_CANT_DELETE =  new ErrorCode(1_007_000_003, "支付应用存在支付订单,无法删除");
     ErrorCode APP_EXIST_REFUND_CANT_DELETE =  new ErrorCode(1_007_000_004, "支付应用存在退款订单,无法删除");
+    ErrorCode APP_KEY_EXISTS = new ErrorCode(1_007_000_005, "支付应用标识已经存在");
 
     // ========== CHANNEL 模块 1-007-001-000 ==========
     ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1_007_001_000, "支付渠道的配置不存在");

+ 5 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/app/vo/PayAppBaseVO.java

@@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.hibernate.validator.constraints.URL;
 
+import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
 /**
@@ -15,6 +16,10 @@ import javax.validation.constraints.NotNull;
 @Data
 public class PayAppBaseVO {
 
+    @Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
+    @NotEmpty(message = "应用标识不能为空")
+    private String appKey;
+
     @Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小豆")
     @NotNull(message = "应用名不能为空")
     private String name;

+ 3 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/app/vo/PayAppPageReqVO.java

@@ -20,6 +20,9 @@ public class PayAppPageReqVO extends PageParam {
     @Schema(description = "应用名", example = "小豆")
     private String name;
 
+    @Schema(description = "应用标识", example = "yudao")
+    private String appKey;
+
     @Schema(description = "开启状态", example = "0")
     private Integer status;
 

+ 3 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/app/vo/PayAppRespVO.java

@@ -16,6 +16,9 @@ public class PayAppRespVO extends PayAppBaseVO {
     @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
+    @Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
+    private String appKey;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 12 - 1
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java

@@ -1,6 +1,8 @@
 package com.citu.module.pay.controller.admin.wallet.vo.transaction;
 
+import com.citu.framework.common.enums.UserTypeEnum;
 import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.validation.InEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -8,7 +10,16 @@ import lombok.Data;
 @Data
 public class PayWalletTransactionPageReqVO extends PageParam  {
 
-    @Schema(description = "钱包编号",  example = "1")
+
+    @Schema(description = "钱包编号", example = "888")
     private Long walletId;
 
+    @Schema(description = "用户编号", example = "1024")
+    private Long userId;
+
+    @Schema(description = "用户类型", example = "1")
+    @InEnum(UserTypeEnum.class)
+    private Integer userType;
+
+
 }

+ 4 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/app/PayAppDO.java

@@ -31,6 +31,10 @@ public class PayAppDO extends BaseDO {
      */
     @TableId
     private Long id;
+    /**
+     * 应用标识
+     */
+    private String appKey;
     /**
      * 应用名
      */

+ 5 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/app/PayAppMapper.java

@@ -13,9 +13,14 @@ public interface PayAppMapper extends BaseMapperX<PayAppDO> {
     default PageResult<PayAppDO> selectPage(PayAppPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<PayAppDO>()
                 .likeIfPresent(PayAppDO::getName, reqVO.getName())
+                .likeIfPresent(PayAppDO::getAppKey, reqVO.getAppKey())
                 .eqIfPresent(PayAppDO::getStatus, reqVO.getStatus())
                 .betweenIfPresent(PayAppDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(PayAppDO::getId));
     }
 
+    default PayAppDO selectByAppKey(String appKey) {
+        return selectOne(PayAppDO::getAppKey, appKey);
+    }
+
 }

+ 9 - 1
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/framework/pay/config/PayProperties.java

@@ -15,12 +15,14 @@ public class PayProperties {
     private static final String ORDER_NO_PREFIX = "P";
     private static final String REFUND_NO_PREFIX = "R";
 
+    private static final String WALLET_PAY_APP_KEY_DEFAULT = "wallet";
+
     /**
      * 支付回调地址
      *
      * 实际上,对应的 PayNotifyController 的 notifyOrder 方法的 URL
      *
-     * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 orderNotifyUrl 地址 => 业务的 PayAppDO.orderNotifyUrl 地址
+     * 回调顺序:支付渠道(支付宝支付、微信支付) => citu-module-pay 的 orderNotifyUrl 地址 => 业务的 PayAppDO.orderNotifyUrl 地址
      */
     @NotEmpty(message = "支付回调地址不能为空")
     @URL(message = "支付回调地址的格式必须是 URL")
@@ -49,4 +51,10 @@ public class PayProperties {
     @NotEmpty(message = "退款订单 no 的前缀不能为空")
     private String refundNoPrefix = REFUND_NO_PREFIX;
 
+    /**
+     * 钱包支付应用 AppKey
+     */
+    @NotEmpty(message = "钱包支付应用 AppKey 不能为空")
+    private String walletPayAppKey = WALLET_PAY_APP_KEY_DEFAULT;
+
 }

+ 10 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/app/PayAppService.java

@@ -102,4 +102,14 @@ public interface PayAppService {
      */
     PayAppDO validPayApp(Long id);
 
+    /**
+     * 支付应用的合法性
+     * <p>
+     * 如果不合法,抛出 {@link ServiceException} 业务异常
+     *
+     * @param appKey 应用标识
+     * @return 应用
+     */
+    PayAppDO validPayApp(String appKey);
+
 }

+ 41 - 5
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/app/PayAppServiceImpl.java

@@ -43,6 +43,9 @@ public class PayAppServiceImpl implements PayAppService {
 
     @Override
     public Long createApp(PayAppCreateReqVO createReqVO) {
+        // 验证 appKey 是否重复
+        validateAppKeyUnique(null, createReqVO.getAppKey());
+
         // 插入
         PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO);
         appMapper.insert(app);
@@ -54,11 +57,28 @@ public class PayAppServiceImpl implements PayAppService {
     public void updateApp(PayAppUpdateReqVO updateReqVO) {
         // 校验存在
         validateAppExists(updateReqVO.getId());
+        // 验证 appKey 是否重复
+        validateAppKeyUnique(updateReqVO.getId(), updateReqVO.getAppKey());
+
         // 更新
         PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO);
         appMapper.updateById(updateObj);
     }
 
+    void validateAppKeyUnique(Long id, String appKey) {
+        PayAppDO app = appMapper.selectByAppKey(appKey);
+        if (app == null) {
+            return;
+        }
+        // 如果 id 为空,说明不用比较是否为相同 appKey 的应用
+        if (id == null) {
+            throw exception(APP_KEY_EXISTS);
+        }
+        if (!app.getId().equals(id)) {
+            throw exception(APP_KEY_EXISTS);
+        }
+    }
+
     @Override
     public void updateAppStatus(Long id, Integer status) {
         // 校验商户存在
@@ -101,7 +121,7 @@ public class PayAppServiceImpl implements PayAppService {
 
     @Override
     public List<PayAppDO> getAppList() {
-         return appMapper.selectList();
+        return appMapper.selectList();
     }
 
     @Override
@@ -110,17 +130,33 @@ public class PayAppServiceImpl implements PayAppService {
     }
 
     @Override
-    public PayAppDO validPayApp(Long id) {
-        PayAppDO app = appMapper.selectById(id);
+    public PayAppDO validPayApp(Long appId) {
+        PayAppDO app = appMapper.selectById(appId);
+        return validatePayApp(app);
+    }
+
+    @Override
+    public PayAppDO validPayApp(String appKey) {
+        PayAppDO app = appMapper.selectByAppKey(appKey);
+        return validatePayApp(app);
+    }
+
+    /**
+     * 校验支付应用实体的有效性:存在 + 开启
+     *
+     * @param app 待校验的支付应用实体
+     * @return 校验通过的支付应用实体
+     */
+    private PayAppDO validatePayApp(PayAppDO app) {
         // 校验是否存在
         if (app == null) {
             throw exception(ErrorCodeConstants.APP_NOT_FOUND);
         }
         // 校验是否禁用
-        if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
+        if (CommonStatusEnum.isDisable(app.getStatus())) {
             throw exception(ErrorCodeConstants.APP_IS_DISABLE);
         }
         return app;
     }
 
-}
+}

+ 3 - 7
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargeServiceImpl.java

@@ -41,7 +41,6 @@ import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exce
 import static com.citu.framework.common.util.date.LocalDateTimeUtils.addTime;
 import static com.citu.framework.common.util.json.JsonUtils.toJsonString;
 import static com.citu.framework.common.util.number.MoneyUtils.fenToYuanStr;
-import static com.citu.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
 import static com.citu.module.pay.enums.ErrorCodeConstants.*;
 import static com.citu.module.pay.enums.MessageTemplateConstants.WXA_WALLET_RECHARGER_PAID;
 import static com.citu.module.pay.enums.refund.PayRefundStatusEnum.*;
@@ -55,10 +54,7 @@ import static com.citu.module.pay.enums.refund.PayRefundStatusEnum.*;
 @Slf4j
 public class PayCurrencyRechargeServiceImpl implements PayCurrencyRechargeService {
 
-    /**
-     * TODO 芋艿:放到 payconfig
-     */
-    private static final Long WALLET_PAY_APP_ID = 11L;
+    private static final String PAY_APP_KEY = "currency";
 
     private static final String RECHARGE_ORDER_SUBJECT = "账户充值";
     /**
@@ -120,7 +116,7 @@ public class PayCurrencyRechargeServiceImpl implements PayCurrencyRechargeServic
 
         // 2.1 创建支付单
         Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
-                .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+                .setAppKey(PAY_APP_KEY).setUserIp(userIp)
                 .setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号
                 .setSubject(RECHARGE_ORDER_SUBJECT).setBody("")
                 .setPrice(Math.toIntExact(recharge.getPayPrice()))
@@ -224,7 +220,7 @@ public class PayCurrencyRechargeServiceImpl implements PayCurrencyRechargeServic
         String currencyRechargeId = String.valueOf(id);
         String refundId = currencyRechargeId + "-refund";
         Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
-                .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+                .setAppKey(PAY_APP_KEY).setUserIp(userIp)
                 .setMerchantOrderId(currencyRechargeId)
                 .setMerchantRefundId(refundId)
                 .setReason("想退钱").setPrice(Math.toIntExact(currencyRecharge.getPayPrice())));

+ 4 - 4
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/demo/PayDemoOrderServiceImpl.java

@@ -43,11 +43,11 @@ import static com.citu.module.pay.enums.ErrorCodeConstants.*;
 public class PayDemoOrderServiceImpl implements PayDemoOrderService {
 
     /**
-     * 接入的实力应用编号
+     * 接入的支付应用标识
      *
      * 从 [支付管理 -> 应用信息] 里添加
      */
-    private static final Long PAY_APP_ID = 7L;
+    private static final String PAY_APP_KEY = "demo";
 
     /**
      * 商品信息 Map
@@ -88,7 +88,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
 
         // 2.1 创建支付单
         Long payOrderId = payOrderApi.createOrder(new PayOrderCreateReqDTO()
-                .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
+                .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用
                 .setMerchantOrderId(demoOrder.getId().toString()) // 业务的订单编号
                 .setSubject(spuName).setBody("").setPrice(price) // 价格信息
                 .setExpireTime(addTime(Duration.ofHours(2L)))).getCheckedData(); // 支付的过期时间
@@ -190,7 +190,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         String refundId = order.getId() + "-refund";
         // 2.2 创建退款单
         Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
-                .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
+                .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用
                 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
                 .setMerchantRefundId(refundId)
                 .setReason("想退钱").setPrice(order.getPrice())).getCheckedData();// 价格信息

+ 2 - 2
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/order/PayOrderServiceImpl.java

@@ -111,11 +111,11 @@ public class PayOrderServiceImpl implements PayOrderService {
     @Override
     public Long createOrder(PayOrderCreateReqDTO reqDTO) {
         // 校验 App
-        PayAppDO app = appService.validPayApp(reqDTO.getAppId());
+        PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
 
         // 查询对应的支付交易单是否已经存在。如果是,则直接返回
         PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId(
-                reqDTO.getAppId(), reqDTO.getMerchantOrderId());
+                app.getId(), reqDTO.getMerchantOrderId());
         if (order != null) {
             log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
                     order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况

+ 10 - 9
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/refund/PayRefundServiceImpl.java

@@ -93,9 +93,9 @@ public class PayRefundServiceImpl implements PayRefundService {
     @Override
     public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
         // 1.1 校验 App
-        PayAppDO app = appService.validPayApp(reqDTO.getAppId());
+        PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
         // 1.2 校验支付订单
-        PayOrderDO order = validatePayOrderCanRefund(reqDTO);
+        PayOrderDO order = validatePayOrderCanRefund(reqDTO, app.getId());
         // 1.3 校验支付渠道是否有效
         PayChannelDO channel = channelService.validPayChannel(order.getChannelId());
         PayClient client = channelService.getPayClient(channel.getId());
@@ -113,7 +113,7 @@ public class PayRefundServiceImpl implements PayRefundService {
         // 2.1 插入退款单
         String no = noRedisDAO.generate(payProperties.getRefundNoPrefix());
         refund = PayRefundConvert.INSTANCE.convert(reqDTO)
-                .setNo(no).setOrderId(order.getId()).setOrderNo(order.getNo())
+                .setNo(no).setAppId(app.getId()).setOrderId(order.getId()).setOrderNo(order.getNo())
                 .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode())
                 // 商户相关的字段
                 .setNotifyUrl(app.getRefundNotifyUrl())
@@ -153,8 +153,8 @@ public class PayRefundServiceImpl implements PayRefundService {
      * @param reqDTO 退款申请信息
      * @return 支付订单
      */
-    private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) {
-        PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId());
+    private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO, Long appId) {
+        PayOrderDO order = orderService.getOrder(appId, reqDTO.getMerchantOrderId());
         if (order == null) {
             throw exception(PAY_ORDER_NOT_FOUND);
         }
@@ -164,11 +164,11 @@ public class PayRefundServiceImpl implements PayRefundService {
         }
 
         // 校验金额,退款金额不能大于原定的金额
-        if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){
+        if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()) {
             throw exception(REFUND_PRICE_EXCEED);
         }
         // 是否有退款中的订单
-        if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(),
+        if (refundMapper.selectCountByAppIdAndOrderId(appId, order.getId(),
                 PayRefundStatusEnum.WAITING.getStatus()) > 0) {
             throw exception(REFUND_HAS_REFUNDING);
         }
@@ -197,9 +197,10 @@ public class PayRefundServiceImpl implements PayRefundService {
      * 通知并更新订单的退款结果
      *
      * @param channel 支付渠道
-     * @param notify 通知
+     * @param notify  通知
      */
-    @Transactional(rollbackFor = Exception.class)  // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效
+    // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效
+    @Transactional(rollbackFor = Exception.class)
     public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) {
         // 情况一:退款成功
         if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {

+ 5 - 5
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/transfer/PayTransferServiceImpl.java

@@ -79,16 +79,16 @@ public class PayTransferServiceImpl implements PayTransferService {
     @Override
     public Long createTransfer(PayTransferCreateReqDTO reqDTO) {
         // 1.1 校验 App
-        PayAppDO payApp = appService.validPayApp(reqDTO.getAppId());
+        PayAppDO payApp = appService.validPayApp(reqDTO.getAppKey());
         // 1.2 校验支付渠道是否有效
-        PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
+        PayChannelDO channel = channelService.validPayChannel(payApp.getId(), reqDTO.getChannelCode());
         PayClient client = channelService.getPayClient(channel.getId());
         if (client == null) {
             log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
             throw exception(CHANNEL_NOT_FOUND);
         }
         // 1.3 校验转账单已经发起过转账。
-        PayTransferDO transfer = validateTransferCanCreate(reqDTO);
+        PayTransferDO transfer = validateTransferCanCreate(reqDTO, payApp.getId());
 
         if (transfer == null) {
             // 2.不存在创建转账单. 否则允许使用相同的 no 再次发起转账
@@ -116,8 +116,8 @@ public class PayTransferServiceImpl implements PayTransferService {
         return transfer.getId();
     }
 
-    private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto) {
-        PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(dto.getAppId(), dto.getMerchantTransferId());
+    private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto, Long appId) {
+        PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, dto.getMerchantTransferId());
         if (transfer != null) {
             // 已经存在,并且状态不为等待状态。说明已经调用渠道转账并返回结果.
             if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) {

+ 7 - 8
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/wallet/PayWalletRechargeServiceImpl.java

@@ -20,6 +20,7 @@ import com.citu.module.pay.dal.mysql.wallet.PayWalletRechargeMapper;
 import com.citu.module.pay.enums.order.PayOrderStatusEnum;
 import com.citu.module.pay.enums.refund.PayRefundStatusEnum;
 import com.citu.module.pay.enums.wallet.PayWalletBizTypeEnum;
+import com.citu.module.pay.framework.pay.config.PayProperties;
 import com.citu.module.pay.service.order.PayOrderService;
 import com.citu.module.pay.service.refund.PayRefundService;
 import com.citu.module.system.api.social.SocialClientApi;
@@ -53,11 +54,6 @@ import static com.citu.module.pay.enums.refund.PayRefundStatusEnum.*;
 @Slf4j
 public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
 
-    /**
-     * TODO 芋艿:放到 payconfig
-     */
-    private static final Long WALLET_PAY_APP_ID = 8L;
-
     private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值";
     @Resource
     public SocialClientApi socialClientApi;
@@ -71,6 +67,9 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
     private PayRefundService payRefundService;
     @Resource
     private PayWalletRechargePackageService payWalletRechargePackageService;
+    @Resource
+    private PayProperties payProperties;
+
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -93,7 +92,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
 
         // 2.1 创建支付单
         Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
-                .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+                .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
                 .setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号
                 .setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("")
                 .setPrice(recharge.getPayPrice())
@@ -113,7 +112,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
 
     @Override
     public PageResult<PayWalletRechargeDO> page(PayWalletRechargeCombinationPageReqVO reqVO) {
-        if (null != reqVO.getUserId() ) {
+        if (null != reqVO.getUserId()) {
             PayWalletDO wallet = payWalletService.getOrCreateWallet(reqVO.getUserId(), UserTypeEnum.MEMBER.getValue());
             reqVO.setWalletId(wallet.getId());
         }
@@ -184,7 +183,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
         String walletRechargeId = String.valueOf(id);
         String refundId = walletRechargeId + "-refund";
         Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
-                .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+                .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
                 .setMerchantOrderId(walletRechargeId)
                 .setMerchantRefundId(refundId)
                 .setReason("想退钱").setPrice(walletRecharge.getPayPrice()));

+ 11 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/wallet/PayWalletTransactionServiceImpl.java

@@ -1,5 +1,6 @@
 package com.citu.module.pay.service.wallet;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO;
 import com.citu.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
@@ -52,6 +53,16 @@ public class PayWalletTransactionServiceImpl implements PayWalletTransactionServ
 
     @Override
     public PageResult<PayWalletTransactionDO> getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO) {
+        // 基于 userId + userType 查询钱包
+        if (pageVO.getWalletId() == null
+                && ObjectUtil.isAllNotEmpty(pageVO.getUserId(), pageVO.getUserType())) {
+            PayWalletDO wallet = payWalletService.getOrCreateWallet(pageVO.getUserId(), pageVO.getUserType());
+            if (wallet != null) {
+                pageVO.setWalletId(wallet.getId());
+            }
+        }
+
+        // 查询分页
         return payWalletTransactionMapper.selectPage(pageVO.getWalletId(), null, pageVO, null);
     }
 

+ 6 - 3
citu-module-pay/citu-module-pay-biz/src/test/java/com/citu/module/pay/service/order/PayOrderServiceTest.java

@@ -218,11 +218,11 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
     public void testCreateOrder_success() {
         // mock 参数
         PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("10")
+                o -> o.setAppKey("demo").setMerchantOrderId("10")
                         .setSubject(randomString()).setBody(randomString()));
         // mock 方法
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1"));
-        when(appService.validPayApp(eq(reqDTO.getAppId()))).thenReturn(app);
+        when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app);
 
         // 调用
         Long orderId = orderService.createOrder(reqDTO);
@@ -239,10 +239,13 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
     public void testCreateOrder_exists() {
         // mock 参数
         PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("10"));
+                o -> o.setAppKey("demo").setMerchantOrderId("10"));
         // mock 数据
         PayOrderDO dbOrder = randomPojo(PayOrderDO.class,  o -> o.setAppId(1L).setMerchantOrderId("10"));
         orderMapper.insert(dbOrder);
+        // mock 方法
+        PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1"));
+        when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app);
 
         // 调用
         Long orderId = orderService.createOrder(reqDTO);

+ 16 - 16
citu-module-pay/citu-module-pay-biz/src/test/java/com/citu/module/pay/service/refund/PayRefundServiceTest.java

@@ -209,10 +209,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
     @Test
     public void testCreateRefund_orderNotFound() {
         PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                o -> o.setAppId(1L));
+                o -> o.setAppKey("demo"));
         // mock 方法(app)
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        when(appService.validPayApp(eq("demo"))).thenReturn(app);
 
         // 调用,并断言异常
         assertServiceException(() -> refundService.createPayRefund(reqDTO),
@@ -232,10 +232,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
     private void testCreateRefund_orderWaitingOrClosed(Integer status) {
         // 准备参数
         PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("100"));
+                o -> o.setAppKey("demo").setMerchantOrderId("100"));
         // mock 方法(app)
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        when(appService.validPayApp(eq("demo"))).thenReturn(app);
         // mock 数据(order)
         PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
         when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
@@ -249,10 +249,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
     public void testCreateRefund_refundPriceExceed() {
         // 准备参数
         PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
+                o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10));
         // mock 方法(app)
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        when(appService.validPayApp(eq("demo"))).thenReturn(app);
         // mock 数据(order)
         PayOrderDO order = randomPojo(PayOrderDO.class, o ->
                 o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -268,10 +268,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
     public void testCreateRefund_orderHasRefunding() {
         // 准备参数
         PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
+                o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10));
         // mock 方法(app)
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        when(appService.validPayApp(eq("demo"))).thenReturn(app);
         // mock 数据(order)
         PayOrderDO order = randomPojo(PayOrderDO.class, o ->
                 o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -291,10 +291,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
     public void testCreateRefund_channelNotFound() {
         // 准备参数
         PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9));
+                o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9));
         // mock 方法(app)
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        when(appService.validPayApp(eq("demo"))).thenReturn(app);
         // mock 数据(order)
         PayOrderDO order = randomPojo(PayOrderDO.class, o ->
                 o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -315,11 +315,11 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
     public void testCreateRefund_refundExists() {
         // 准备参数
         PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
+                o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)
                         .setMerchantRefundId("200").setReason("测试退款"));
         // mock 方法(app)
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        when(appService.validPayApp(eq("demo"))).thenReturn(app);
         // mock 数据(order)
         PayOrderDO order = randomPojo(PayOrderDO.class, o ->
                 o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -347,11 +347,11 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
     public void testCreateRefund_invokeException() {
         // 准备参数
         PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
+                o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)
                         .setMerchantRefundId("200").setReason("测试退款"));
         // mock 方法(app)
         PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        when(appService.validPayApp(eq("demo"))).thenReturn(app);
         // mock 数据(order)
         PayOrderDO order = randomPojo(PayOrderDO.class, o ->
                 o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -391,11 +391,11 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
 
             // 准备参数
             PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
-                    o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
+                    o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)
                             .setMerchantRefundId("200").setReason("测试退款"));
             // mock 方法(app)
             PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
-            when(appService.validPayApp(eq(1L))).thenReturn(app);
+            when(appService.validPayApp(eq("demo"))).thenReturn(app);
             // mock 数据(order)
             PayOrderDO order = randomPojo(PayOrderDO.class, o ->
                     o.setStatus(PayOrderStatusEnum.REFUND.getStatus())

+ 1 - 0
citu-module-pay/citu-module-pay-biz/src/test/resources/sql/create_tables.sql

@@ -1,5 +1,6 @@
 CREATE TABLE IF NOT EXISTS "pay_app" (
     "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "app_key"              varchar(64)   NOT NULL,
     "name"              varchar(64)   NOT NULL,
     "status"            tinyint       NOT NULL,
     "remark"            varchar(255)           DEFAULT NULL,

+ 21 - 0
citu-module-pay/citu-spring-boot-starter-biz-pay/src/main/java/com/citu/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java

@@ -26,6 +26,11 @@ public class AlipayPayClientConfig implements PayClientConfig {
      */
     public static final Integer MODE_CERTIFICATE = 2;
 
+    /**
+     * 接口内容加密方式 - AES 加密
+     */
+    public static final String ENC_TYPE_AES = "AES";
+
     /**
      * 签名算法类型 - RSA
      */
@@ -92,6 +97,22 @@ public class AlipayPayClientConfig implements PayClientConfig {
     @NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class})
     private String rootCertContent;
 
+    /**
+     * 接口内容加密方式
+     *
+     * 1. 如果为空,将使用无加密方式
+     * 2. 如果要加密,目前支付宝只有 AES 一种加密方式
+     *
+     * @see <a href="https://opendocs.alipay.com/common/02mse3">支付宝开放平台</a>
+     * @see AlipayPayClientConfig#ENC_TYPE_AES
+     */
+    private String encryptType;
+
+    /**
+     * 接口内容加密的私钥
+     */
+    private String encryptKey;
+
     public interface ModePublicKey {
     }
 

+ 2 - 3
citu-module-system/citu-module-system-api/src/main/java/com/citu/module/system/api/logger/dto/OperateLogRespDTO.java

@@ -1,5 +1,6 @@
 package com.citu.module.system.api.logger.dto;
 
+import com.citu.module.system.api.user.AdminUserApi;
 import com.fhs.core.trans.anno.Trans;
 import com.fhs.core.trans.constant.TransType;
 import com.fhs.core.trans.vo.VO;
@@ -14,12 +15,10 @@ public class OperateLogRespDTO implements VO {
 
     @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
-
     @Schema(description = "链路追踪编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "89aca178-a370-411c-ae02-3f0d672be4ab")
     private String traceId;
     @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
-    @Trans(type = TransType.RPC, targetClassName = "com.citu.module.system.dal.dataobject.user.AdminUserDO",
-            fields = "nickname", ref = "userName")
+    @Trans(type = TransType.AUTO_TRANS, key = AdminUserApi.PREFIX, fields = "nickname", ref = "userName")
     private Long userId;
     @Schema(description = "用户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
     private String userName;

+ 2 - 0
citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/SmsClient.java

@@ -46,6 +46,8 @@ public interface SmsClient {
     /**
      * 查询指定的短信模板
      *
+     * 如果查询失败,则返回 null 空
+     *
      * @param apiTemplateId 短信 API 的模板编号
      * @return 短信模板
      */

+ 21 - 28
citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/impl/AliyunSmsClient.java

@@ -50,21 +50,6 @@ public class AliyunSmsClient extends AbstractSmsClient {
         Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
     }
 
-    /**
-     * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范
-     *
-     * @param str 需要进行 URL 编码的字符串
-     * @return 编码后的字符串
-     */
-    @SneakyThrows
-    private static String percentCode(String str) {
-        Assert.notNull(str, "str 不能为空");
-        return URLEncoder.encode(str, StandardCharsets.UTF_8.name())
-                .replace("+", "%20") // 加号 "+" 被替换为 "%20"
-                .replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
-                .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
-    }
-
     @Override
     protected void doInit() {
     }
@@ -117,8 +102,6 @@ public class AliyunSmsClient extends AbstractSmsClient {
         queryParam.put("TemplateCode", apiTemplateId);
         JSONObject response = request("QuerySmsTemplate", queryParam);
 
-        System.out.println("getSmsTemplate response is =====" + response.toString());
-
         // 2.1 请求失败
         String code = response.getStr("Code");
         if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) {
@@ -136,24 +119,20 @@ public class AliyunSmsClient extends AbstractSmsClient {
     @VisibleForTesting
     Integer convertSmsTemplateAuditStatus(Integer templateStatus) {
         switch (templateStatus) {
-            case 0:
-                return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
-            case 1:
-                return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
-            case 2:
-                return SmsTemplateAuditStatusEnum.FAIL.getStatus();
-            default:
-                throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
+            case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
+            case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
+            case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
+            default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
         }
     }
 
     /**
      * 请求阿里云短信
      *
-     * @param apiName     请求的 API 名称
+     * @see <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">V3 版本请求体&签名机制</>
+     * @param apiName 请求的 API 名称
      * @param queryParams 请求参数
      * @return 请求结果
-     * @see <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">V3 版本请求体&签名机制</>
      */
     private JSONObject request(String apiName, TreeMap<String, Object> queryParams) {
         // 1. 请求参数
@@ -189,7 +168,6 @@ public class AliyunSmsClient extends AbstractSmsClient {
         // 4. 构建 Authorization 签名
         String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
         String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest);
-
         String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest;
         String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
         headers.put("Authorization", "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey()
@@ -200,4 +178,19 @@ public class AliyunSmsClient extends AbstractSmsClient {
         return JSONUtil.parseObj(responseBody);
     }
 
+    /**
+     * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范
+     *
+     * @param str 需要进行 URL 编码的字符串
+     * @return 编码后的字符串
+     */
+    @SneakyThrows
+    private static String percentCode(String str) {
+        Assert.notNull(str, "str 不能为空");
+        return URLEncoder.encode(str, StandardCharsets.UTF_8.name())
+                .replace("+", "%20") // 加号 "+" 被替换为 "%20"
+                .replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
+                .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
+    }
+
 }

+ 35 - 35
citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java

@@ -31,6 +31,7 @@ import static com.citu.framework.common.util.collection.CollectionUtils.convertL
 import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 import static com.citu.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
 
+// todo @scholar:参考阿里云在优化下
 /**
  * 华为短信客户端的实现类
  *
@@ -47,44 +48,20 @@ public class HuaweiSmsClient extends AbstractSmsClient {
     public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443";
     public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date";
 
+    @Override
+    protected void doInit() {
+    }
+
     public HuaweiSmsClient(SmsChannelProperties properties) {
         super(properties);
         Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
         Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
     }
 
-    static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
-                                   String statusCallBack, String signature) throws UnsupportedEncodingException {
-        if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
-                || templateId.isEmpty()) {
-            System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
-            return null;
-        }
-
-        StringBuilder body = new StringBuilder();
-        appendToBody(body, "from=", sender);
-        appendToBody(body, "&to=", receiver);
-        appendToBody(body, "&templateId=", templateId);
-        appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas));
-        appendToBody(body, "&statusCallback=", statusCallBack);
-        appendToBody(body, "&signature=", signature);
-        return body.toString();
-    }
-
-    private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException {
-        if (null != val && !val.isEmpty()) {
-            body.append(key).append(URLEncoder.encode(val, "UTF-8"));
-        }
-    }
-
-    @Override
-    protected void doInit() {
-
-    }
-
     @Override
     public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
                                   List<KeyValue<String, Object>> templateParams) throws Throwable {
+        // 参考链接 https://support.huaweicloud.com/api-msgsms/sms_05_0001.html
         // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构
         // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。
         String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号
@@ -95,13 +72,13 @@ public class HuaweiSmsClient extends AbstractSmsClient {
 
         List<String> templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue()));
 
-        JSONObject JsonResponse = sendSmsRequest(sender, mobile, templateId, templateParas, statusCallBack);
+        JSONObject JsonResponse = sendSmsRequest(sender,mobile,templateId,templateParas,statusCallBack);
         SmsResponse smsResponse = getSmsSendResponse(JsonResponse);
 
         return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString());
     }
 
-    JSONObject sendSmsRequest(String sender, String mobile, String templateId, List<String> templateParas, String statusCallBack) throws UnsupportedEncodingException {
+    JSONObject sendSmsRequest(String sender,String mobile,String templateId,List<String> templateParas,String statusCallBack) throws UnsupportedEncodingException {
 
         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -112,7 +89,7 @@ public class HuaweiSmsClient extends AbstractSmsClient {
         String canonicalUri = "/sms/batchSendSms/v1/";
         String canonicalQueryString = "";//查询参数为空
         String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
-                + "host:" + HOST + "\n"
+                + "host:"+ HOST +"\n"
                 + "x-sdk-date:" + sdkDate + "\n";
         //请求Body,不携带签名名称时,signature请填null
         String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null);
@@ -138,7 +115,7 @@ public class HuaweiSmsClient extends AbstractSmsClient {
         HttpResponse response = HttpRequest.post(URL)
                 .header("Content-Type", "application/x-www-form-urlencoded")
                 .header("X-Sdk-Date", sdkDate)
-                .header("host", HOST)
+                .header("host",HOST)
                 .header("Authorization", authorization)
                 .body(body)
                 .execute();
@@ -153,10 +130,33 @@ public class HuaweiSmsClient extends AbstractSmsClient {
         return smsResponse;
     }
 
+    static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
+                                   String statusCallBack, String signature) throws UnsupportedEncodingException {
+        if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
+                || templateId.isEmpty()) {
+            System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
+            return null;
+        }
+
+        StringBuilder body = new StringBuilder();
+        appendToBody(body, "from=", sender);
+        appendToBody(body, "&to=", receiver);
+        appendToBody(body, "&templateId=", templateId);
+        appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas));
+        appendToBody(body, "&statusCallback=", statusCallBack);
+        appendToBody(body, "&signature=", signature);
+        return body.toString();
+    }
+
+    private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException {
+        if (null != val && !val.isEmpty()) {
+            body.append(key).append(URLEncoder.encode(val, "UTF-8"));
+        }
+    }
     @Override
     public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
         List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
-        return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(Objects.equals(status.getStatus(), "DELIVRD"))
+        return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(Objects.equals(status.getStatus(),"DELIVRD"))
                 .setErrorCode(status.getStatus()).setErrorMsg(status.getStatus())
                 .setMobile(status.getPhoneNumber()).setReceiveTime(status.getUpdateTime())
                 .setSerialNo(status.getSmsMsgId()));
@@ -188,7 +188,7 @@ public class HuaweiSmsClient extends AbstractSmsClient {
 
     /**
      * 短信接收状态
-     * <p>
+     *
      * 参见 <a href="https://support.huaweicloud.com/api-msgsms/sms_05_0003.html">文档</a>
      *
      * @author scholar

+ 86 - 219
citu-module-system/citu-module-system-biz/src/main/java/com/citu/module/system/framework/sms/core/client/impl/TencentSmsClient.java

@@ -2,38 +2,28 @@ package com.citu.module.system.framework.sms.core.client.impl;
 
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
-import cn.hutool.http.HttpRequest;
-import cn.hutool.http.HttpResponse;
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.citu.framework.common.core.KeyValue;
 import com.citu.framework.common.util.collection.ArrayUtils;
-import com.citu.framework.common.util.json.JsonUtils;
+import com.citu.framework.common.util.http.HttpUtils;
 import com.citu.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import com.citu.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
 import com.citu.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import com.citu.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import com.citu.module.system.framework.sms.core.property.SmsChannelProperties;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.annotations.VisibleForTesting;
-import lombok.Data;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 import javax.xml.bind.DatatypeConverter;
 import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
-import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
 import static com.citu.framework.common.util.collection.CollectionUtils.convertList;
-import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-import static com.citu.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
-
-// TODO @scholar 建议参考 AliyunSmsClient 优化下
 
 /**
  * 腾讯云短信功能实现
@@ -48,7 +38,8 @@ public class TencentSmsClient extends AbstractSmsClient {
      * 调用成功 code
      */
     public static final String API_CODE_SUCCESS = "Ok";
-
+    private static final String VERSION = "2021-01-11";
+    private static final String REGION = "ap-guangzhou";
     /**
      * 是否国际/港澳台短信:
      * <p>
@@ -57,7 +48,6 @@ public class TencentSmsClient extends AbstractSmsClient {
      */
     private static final long INTERNATIONAL_CHINA = 0L;
 
-
     public TencentSmsClient(SmsChannelProperties properties) {
         super(properties);
         Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
@@ -80,7 +70,8 @@ public class TencentSmsClient extends AbstractSmsClient {
         Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");
     }
 
-    public static byte[] hmac256(byte[] key, String msg) throws Exception {
+    // TODO @scholar:使用 hutool 简化下
+    private static byte[] hmac256(byte[] key, String msg) throws Exception {
         Mac mac = Mac.getInstance("HmacSHA256");
         SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
         mac.init(secretKeySpec);
@@ -89,7 +80,6 @@ public class TencentSmsClient extends AbstractSmsClient {
 
     @Override
     protected void doInit() {
-
     }
 
     private String getSdkAppId() {
@@ -103,31 +93,99 @@ public class TencentSmsClient extends AbstractSmsClient {
     @Override
     public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
                                   String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
-        // 构建请求
+        // 1. 执行请求
+        // 参考链接 https://cloud.tencent.com/document/product/382/55981
         TreeMap<String, Object> body = new TreeMap<>();
-        String[] phones = {mobile};
-        body.put("PhoneNumberSet", phones);
+        body.put("PhoneNumberSet", new String[]{mobile});
         body.put("SmsSdkAppId", getSdkAppId());
         body.put("SignName", properties.getSignature());
         body.put("TemplateId", apiTemplateId);
-        body.put("TemplateParamSet", ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
+        body.put("TemplateParamSet", ArrayUtils.toArray(templateParams, param -> String.valueOf(param.getValue())));
+        JSONObject response = request("SendSms", body);
+
+        // 2. 解析请求
+        JSONObject responseResult = response.getJSONObject("Response");
+        JSONObject error = responseResult.getJSONObject("Error");
+        if (error != null) {
+            return new SmsSendRespDTO().setSuccess(false)
+                    .setApiRequestId(responseResult.getStr("RequestId"))
+                    .setApiCode(error.getStr("Code"))
+                    .setApiMsg(error.getStr("Message"));
+        }
+        JSONObject responseData = responseResult.getJSONArray("SendStatusSet").getJSONObject(0);
+        return new SmsSendRespDTO().setSuccess(Objects.equals(API_CODE_SUCCESS, responseData.getStr("Code")))
+                .setApiRequestId(responseResult.getStr("RequestId"))
+                .setSerialNo(responseData.getStr("SerialNo"))
+                .setApiMsg(responseData.getStr("Message"));
+    }
 
-        JSONObject JsonResponse = sendSmsRequest(body, "SendSms", "2021-01-11", "ap-guangzhou");
-        SmsResponse smsResponse = getSmsSendResponse(JsonResponse);
+    @Override
+    public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
+        JSONArray statuses = JSONUtil.parseArray(text);
+        // 字段参考
+        return convertList(statuses, status -> {
+            JSONObject statusObj = (JSONObject) status;
+            return new SmsReceiveRespDTO()
+                    .setSuccess("SUCCESS".equals(statusObj.getStr("report_status"))) // 是否接收成功
+                    .setErrorCode(statusObj.getStr("errmsg")) // 状态报告编码
+                    .setMobile(statusObj.getStr("mobile")) // 手机号
+                    .setReceiveTime(statusObj.getLocalDateTime("user_receive_time", null)) // 状态报告时间
+                    .setSerialNo(statusObj.getStr("sid")); // 发送序列号
+        });
+    }
+
+    @Override
+    public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
+        // 1. 构建请求
+        // 参考链接 https://cloud.tencent.com/document/product/382/52067
+        TreeMap<String, Object> body = new TreeMap<>();
+        body.put("International", INTERNATIONAL_CHINA);
+        body.put("TemplateIdSet", new Integer[]{Integer.valueOf(apiTemplateId)});
+        JSONObject response = request("DescribeSmsTemplateList", body);
 
-        return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString());
+        // TODO @scholar:会有请求失败的情况么?类似发送的(那块逻辑我补充了)
+        JSONObject TemplateStatusSet = response.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").getJSONObject(0);
+        String content = TemplateStatusSet.get("TemplateContent").toString();
+        int templateStatus = Integer.parseInt(TemplateStatusSet.get("StatusCode").toString());
+        String auditReason = TemplateStatusSet.get("ReviewReply").toString();
 
+        return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(content)
+                .setAuditStatus(convertSmsTemplateAuditStatus(templateStatus)).setAuditReason(auditReason);
     }
 
-    JSONObject sendSmsRequest(TreeMap<String, Object> body, String action, String version, String region) throws Exception {
+    @VisibleForTesting
+    Integer convertSmsTemplateAuditStatus(int templateStatus) {
+        switch (templateStatus) {
+            case 1:
+                return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
+            case 0:
+                return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
+            case -1:
+                return SmsTemplateAuditStatusEnum.FAIL.getStatus();
+            default:
+                throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
+        }
+    }
 
+    /**
+     * 请求腾讯云短信
+     *
+     * @param action 请求的 API 名称
+     * @param body   请求参数
+     * @return 请求结果
+     * @see <a href="https://cloud.tencent.com/document/product/382/52072">签名方法 v3</a>
+     */
+    private JSONObject request(String action, TreeMap<String, Object> body) throws Exception {
         String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+        // TODO @scholar:这个 format,看看怎么写的可以简化点
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
         // 注意时区,否则容易出错
         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
         String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
 
+        // TODO @scholar:这个步骤,看看怎么参考阿里云 client,归类下;1. 2.1 2.2 这种
         // ************* 步骤 1:拼接规范请求串 *************
+        // TODO @scholar:这个 hsot 枚举下;
         String host = "sms.tencentcloudapi.com"; //APP接入地址+接口访问URI
         String httpMethod = "POST"; // 请求方式
         String canonicalUri = "/";
@@ -137,6 +195,7 @@ public class TencentSmsClient extends AbstractSmsClient {
                 + "host:" + host + "\n" + "x-tc-action:" + action.toLowerCase() + "\n";
         String signedHeaders = "content-type;host;x-tc-action";
         String hashedRequestBody = sha256Hex(JSONUtil.toJsonStr(body));
+        // TODO @scholar:换行下,不然单行太长了
         String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
 
         // ************* 步骤 2:拼接待签名字符串 *************
@@ -161,203 +220,11 @@ public class TencentSmsClient extends AbstractSmsClient {
         headers.put("Host", host);
         headers.put("X-TC-Action", action);
         headers.put("X-TC-Timestamp", timestamp);
-        headers.put("X-TC-Version", version);
-        headers.put("X-TC-Region", region);
-
-        HttpResponse response = HttpRequest.post("https://" + host)
-                .addHeaders(headers)
-                .body(JSONUtil.toJsonStr(body))
-                .execute();
-
-        return JSONUtil.parseObj(response.body());
-    }
-
-    private SmsResponse getSmsSendResponse(JSONObject resJson) {
-        SmsResponse smsResponse = new SmsResponse();
-        JSONArray statusJson = resJson.getJSONObject("Response").getJSONArray("SendStatusSet");
-        smsResponse.setSuccess("Ok".equals(statusJson.getJSONObject(0).getStr("Code")));
-        smsResponse.setData(resJson);
-        return smsResponse;
-    }
-
-    @Override
-    public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
-        List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);
-        return convertList(callback, status -> new SmsReceiveRespDTO()
-                .setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()))
-                .setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription())
-                .setMobile(status.getMobile()).setReceiveTime(status.getReceiveTime())
-                .setSerialNo(status.getSerialNo()).setLogId(status.getSessionContext().getLogId()));
-    }
+        headers.put("X-TC-Version", VERSION);
+        headers.put("X-TC-Region", REGION);
 
-    @Override
-    public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
-
-        // 构建请求
-        TreeMap<String, Object> body = new TreeMap<>();
-        body.put("International", 0);
-        Integer[] templateIds = {Integer.valueOf(apiTemplateId)};
-        body.put("TemplateIdSet", templateIds);
+        String responseBody = HttpUtils.post("https://" + host, headers, JSONUtil.toJsonStr(body));
 
-        JSONObject JsonResponse = sendSmsRequest(body, "DescribeSmsTemplateList", "2021-01-11", "ap-guangzhou");
-        QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(JsonResponse);
-        String templateId = Integer.toString(smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateId());
-        String content = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateContent();
-        Integer templateStatus = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getStatusCode();
-        String auditReason = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getReviewReply();
-
-        return new SmsTemplateRespDTO().setId(templateId).setContent(content)
-                .setAuditStatus(convertSmsTemplateAuditStatus(templateStatus)).setAuditReason(auditReason);
+        return JSONUtil.parseObj(responseBody);
     }
-
-    private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) {
-
-        QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse();
-
-        smsTemplateResponse.setRequestId(resJson.getJSONObject("Response").getStr("RequestId"));
-
-        smsTemplateResponse.setDescribeTemplateStatusSet(new ArrayList<>());
-
-        QuerySmsTemplateResponse.TemplateInfo templateInfo = new QuerySmsTemplateResponse.TemplateInfo();
-
-        Object statusObject = resJson.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").get(0);
-
-        JSONObject statusJSON = new JSONObject(statusObject);
-
-        templateInfo.setTemplateContent(statusJSON.get("TemplateContent").toString());
-
-        templateInfo.setStatusCode(Integer.parseInt(statusJSON.get("StatusCode").toString()));
-
-        templateInfo.setReviewReply(statusJSON.get("ReviewReply").toString());
-
-        templateInfo.setTemplateId(Integer.parseInt(statusJSON.get("TemplateId").toString()));
-
-        smsTemplateResponse.getDescribeTemplateStatusSet().add(templateInfo);
-
-        return smsTemplateResponse;
-    }
-
-    @VisibleForTesting
-    Integer convertSmsTemplateAuditStatus(int templateStatus) {
-        switch (templateStatus) {
-            case 1:
-                return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
-            case 0:
-                return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
-            case -1:
-                return SmsTemplateAuditStatusEnum.FAIL.getStatus();
-            default:
-                throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
-        }
-    }
-
-    @Data
-    public static class SmsResponse {
-
-        /**
-         * 是否成功
-         */
-        private boolean success;
-
-        /**
-         * 厂商原返回体
-         */
-        private Object data;
-
-    }
-
-
-    /**
-     * <p>类名: QuerySmsTemplateResponse
-     * <p>说明:  sms模板查询返回信息
-     *
-     * @author :scholar
-     * 2024/07/17  0:25
-     **/
-    @Data
-    public static class QuerySmsTemplateResponse {
-        private List<TemplateInfo> DescribeTemplateStatusSet;
-        private String RequestId;
-
-        @Data
-        static class TemplateInfo {
-            private String TemplateName;
-            private Integer TemplateId;
-            private Integer International;
-            private String ReviewReply;
-            private long CreateTime;
-            private String TemplateContent;
-            private Integer StatusCode;
-        }
-    }
-
-    @Data
-    private static class SmsReceiveStatus {
-
-        /**
-         * 短信接受成功 code
-         */
-        public static final String SUCCESS_CODE = "SUCCESS";
-
-        /**
-         * 用户实际接收到短信的时间
-         */
-        @JsonProperty("user_receive_time")
-        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
-        private LocalDateTime receiveTime;
-
-        /**
-         * 国家(或地区)码
-         */
-        @JsonProperty("nationcode")
-        private String nationCode;
-
-        /**
-         * 手机号码
-         */
-        private String mobile;
-
-        /**
-         * 实际是否收到短信接收状态,SUCCESS(成功)、FAIL(失败)
-         */
-        @JsonProperty("report_status")
-        private String status;
-
-        /**
-         * 用户接收短信状态码错误信息
-         */
-        @JsonProperty("errmsg")
-        private String errCode;
-
-        /**
-         * 用户接收短信状态描述
-         */
-        @JsonProperty("description")
-        private String description;
-
-        /**
-         * 本次发送标识 ID(与发送接口返回的SerialNo对应)
-         */
-        @JsonProperty("sid")
-        private String serialNo;
-
-        /**
-         * 用户的 session 内容(与发送接口的请求参数 SessionContext 一致)
-         */
-        @JsonProperty("ext")
-        private SessionContext sessionContext;
-
-    }
-
-    @VisibleForTesting
-    @Data
-    static class SessionContext {
-
-        /**
-         * 发送短信记录id
-         */
-        private Long logId;
-
-    }
-
 }

+ 1 - 10
citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java

@@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mockStatic;
 
 /**
- * {@link AliyunSmsClient} 的单元测试
+ * {@link com.citu.module.system.framework.sms.core.client.impl.AliyunSmsClient} 的单元测试
  *
  * @author 芋道源码
  */
@@ -38,15 +38,6 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
     @InjectMocks
     private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);
 
-    @Test
-    public void testDoInit() {
-        // 准备参数
-        // mock 方法
-
-        // 调用
-        smsClient.doInit();
-    }
-
     @Test
     public void tesSendSms_success() throws Throwable {
         try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {

+ 60 - 21
citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/SmsClientTests.java

@@ -10,7 +10,6 @@ import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
-
 /**
  * 各种 {@link SmsClientTests  集成测试
  *
@@ -18,24 +17,6 @@ import java.util.List;
  */
 public class SmsClientTests {
 
-    @Test
-    @Disabled
-    public void testHuaweiSmsClient_sendSms() throws Throwable {
-        SmsChannelProperties properties = new SmsChannelProperties()
-                .setApiKey("123")
-                .setApiSecret("456");
-        HuaweiSmsClient client = new HuaweiSmsClient(properties);
-        // 准备参数
-        Long sendLogId = System.currentTimeMillis();
-        String mobile = "15601691323";
-        String apiTemplateId = "xx test01";
-        List<KeyValue<String, Object>> templateParams = ListUtil.of(new KeyValue<>("code", "1024"));
-        // 调用
-        SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-        // 打印结果
-        System.out.println(smsSendRespDTO);
-    }
-
     // ========== 阿里云 ==========
 
     @Test
@@ -59,11 +40,11 @@ public class SmsClientTests {
         SmsChannelProperties properties = new SmsChannelProperties()
                 .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
                 .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
-                .setSignature("Ballcat");
+                .setSignature("runpu");
         AliyunSmsClient client = new AliyunSmsClient(properties);
         // 准备参数
         Long sendLogId = System.currentTimeMillis();
-        String mobile = "173213154791";
+        String mobile = "15601691323";
         String apiTemplateId = "SMS_207945135";
         // 调用
         SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024")));
@@ -100,4 +81,62 @@ public class SmsClientTests {
         System.out.println(statuses);
     }
 
+    // ========== 腾讯云 ==========
+
+    @Test
+    @Disabled
+    public void testTencentSmsClient_sendSms() throws Throwable {
+        SmsChannelProperties properties = new SmsChannelProperties()
+                .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523")
+                .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
+                .setSignature("芋道源码");
+        TencentSmsClient client = new TencentSmsClient(properties);
+        // 准备参数
+        Long sendLogId = System.currentTimeMillis();
+        String mobile = "15601691323";
+        String apiTemplateId = "2136358";
+        // 调用
+        SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024")));
+        // 打印结果
+        System.out.println(sendRespDTO);
+    }
+
+    @Test
+    @Disabled
+    public void testTencentSmsClient_getSmsTemplate() throws Throwable {
+        SmsChannelProperties properties = new SmsChannelProperties()
+                .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523")
+                .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
+                .setSignature("芋道源码");
+        TencentSmsClient client = new TencentSmsClient(properties);
+        // 准备参数
+        String apiTemplateId = "2136358";
+        // 调用
+        SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId);
+        // 打印结果
+        System.out.println(template);
+    }
+
+    // ========== 华为云 ==========
+
+    @Test
+    @Disabled
+    public void testHuaweiSmsClient_sendSms() throws Throwable {
+        SmsChannelProperties properties = new SmsChannelProperties()
+                .setApiKey("123")
+                .setApiSecret("456")
+                .setSignature("runpu");
+        HuaweiSmsClient client = new HuaweiSmsClient(properties);
+        // 准备参数
+        Long sendLogId = System.currentTimeMillis();
+        String mobile = "15601691323";
+        String apiTemplateId = "xx test01";
+        List<KeyValue<String, Object>> templateParams = ListUtil.of(new KeyValue<>("code", "1024"));
+        // 调用
+        SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
+        // 打印结果
+        System.out.println(smsSendRespDTO);
+    }
+
 }
+

+ 116 - 138
citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java

@@ -1,20 +1,27 @@
 package com.citu.module.system.framework.sms.core.client.impl;
 
-import cn.hutool.core.util.ReflectUtil;
+import com.citu.framework.common.core.KeyValue;
+import com.citu.framework.common.util.http.HttpUtils;
 import com.citu.framework.test.core.ut.BaseMockitoUnitTest;
-import com.citu.module.system.framework.sms.core.client.SmsClient;
 import com.citu.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import com.citu.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
+import com.citu.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import com.citu.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import com.citu.module.system.framework.sms.core.property.SmsChannelProperties;
+import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
-import org.mockito.Mock;
+import org.mockito.MockedStatic;
 
 import java.time.LocalDateTime;
 import java.util.List;
 
+import static com.citu.framework.test.core.util.RandomUtils.randomLongId;
 import static com.citu.framework.test.core.util.RandomUtils.randomString;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mockStatic;
 
 // TODO @芋艿:补全单测
 
@@ -33,115 +40,85 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
     @InjectMocks
     private TencentSmsClient smsClient = new TencentSmsClient(properties);
 
-    @Mock
-    private SmsClient client;
-
     @Test
-    public void testDoInit() {
-        // 准备参数
-        // mock 方法
-
-        // 调用
-        smsClient.doInit();
-        // 断言
-        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
+    public void testDoSendSms_success() throws Throwable {
+        try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+            // 准备参数
+            Long sendLogId = randomLongId();
+            String mobile = randomString();
+            String apiTemplateId = randomString();
+            List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
+                    new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
+            // mock 方法
+            httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+                    .thenReturn("{\n" +
+                            "    \"Response\": {\n" +
+                            "        \"SendStatusSet\": [\n" +
+                            "            {\n" +
+                            "                \"SerialNo\": \"5000:1045710669157053657849499619\",\n" +
+                            "                \"PhoneNumber\": \"+8618511122233\",\n" +
+                            "                \"Fee\": 1,\n" +
+                            "                \"SessionContext\": \"test\",\n" +
+                            "                \"Code\": \"Ok\",\n" +
+                            "                \"Message\": \"send success\",\n" +
+                            "                \"IsoCode\": \"CN\"\n" +
+                            "            },\n" +
+                            "        ],\n" +
+                            "        \"RequestId\": \"a0aabda6-cf91-4f3e-a81f-9198114a2279\"\n" +
+                            "    }\n" +
+                            "}");
+
+            // 调用
+            SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
+                    apiTemplateId, templateParams);
+            // 断言
+            assertTrue(result.getSuccess());
+            assertEquals("5000:1045710669157053657849499619", result.getSerialNo());
+            assertEquals("a0aabda6-cf91-4f3e-a81f-9198114a2279", result.getApiRequestId());
+            assertEquals("send success", result.getApiMsg());
+        }
     }
 
     @Test
-    public void testRefresh() {
-        // 准备参数
-        SmsChannelProperties p = new SmsChannelProperties()
-                .setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
-                .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
-                .setSignature("芋道源码");
-        // 调用
-        smsClient.refresh(p);
-        // 断言
-        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
+    public void testDoSendSms_fail() throws Throwable {
+        try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+            // 准备参数
+            Long sendLogId = randomLongId();
+            String mobile = randomString();
+            String apiTemplateId = randomString();
+            List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
+                    new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
+
+            // mock 方法
+            httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+                    .thenReturn("{\n" +
+                            "    \"Response\": {\n" +
+                            "        \"SendStatusSet\": [\n" +
+                            "            {\n" +
+                            "                \"SerialNo\": \"5000:1045710669157053657849499619\",\n" +
+                            "                \"PhoneNumber\": \"+8618511122233\",\n" +
+                            "                \"Fee\": 1,\n" +
+                            "                \"SessionContext\": \"test\",\n" +
+                            "                \"Code\": \"ERROR\",\n" +
+                            "                \"Message\": \"send success\",\n" +
+                            "                \"IsoCode\": \"CN\"\n" +
+                            "            },\n" +
+                            "        ],\n" +
+                            "        \"RequestId\": \"a0aabda6-cf91-4f3e-a81f-9198114a2279\"\n" +
+                            "    }\n" +
+                            "}");
+
+            // 调用
+            SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
+                    apiTemplateId, templateParams);
+            // 断言
+            assertFalse(result.getSuccess());
+            assertEquals("5000:1045710669157053657849499619", result.getSerialNo());
+            assertEquals("a0aabda6-cf91-4f3e-a81f-9198114a2279", result.getApiRequestId());
+            assertEquals("send success", result.getApiMsg());
+        }
     }
 
-//    @Test
-//    public void testDoSendSms_success() throws Throwable {
-//        // 准备参数
-//        Long sendLogId = randomLongId();
-//        String mobile = randomString();
-//        String apiTemplateId = randomString();
-//        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
-//                new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
-//        String requestId = randomString();
-//        String serialNo = randomString();
-//        // mock 方法
-//        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
-//            o.setRequestId(requestId);
-//            SendStatus[] sendStatuses = new SendStatus[1];
-//            o.setSendStatusSet(sendStatuses);
-//            SendStatus sendStatus = new SendStatus();
-//            sendStatuses[0] = sendStatus;
-//            sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
-//            sendStatus.setMessage("send success");
-//            sendStatus.setSerialNo(serialNo);
-//        });
-//        when(client.SendSms(argThat(request -> {
-//            assertEquals(mobile, request.getPhoneNumberSet()[0]);
-//            assertEquals(properties.getSignature(), request.getSignName());
-//            assertEquals(apiTemplateId, request.getTemplateId());
-//            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
-//                    toJsonString(request.getTemplateParamSet()));
-//            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
-//            return true;
-//        }))).thenReturn(response);
-//
-//        // 调用
-//        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-//        // 断言
-//        assertTrue(result.getSuccess());
-//        assertEquals(response.getRequestId(), result.getApiRequestId());
-//        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
-//        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
-//        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
-//    }
-
-//    @Test
-//    public void testDoSendSms_fail() throws Throwable {
-//        // 准备参数
-//        Long sendLogId = randomLongId();
-//        String mobile = randomString();
-//        String apiTemplateId = randomString();
-//        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
-//                new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
-//        String requestId = randomString();
-//        String serialNo = randomString();
-//        // mock 方法
-//        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
-//            o.setRequestId(requestId);
-//            SendStatus[] sendStatuses = new SendStatus[1];
-//            o.setSendStatusSet(sendStatuses);
-//            SendStatus sendStatus = new SendStatus();
-//            sendStatuses[0] = sendStatus;
-//            sendStatus.setCode("ERROR");
-//            sendStatus.setMessage("send success");
-//            sendStatus.setSerialNo(serialNo);
-//        });
-//        when(client.SendSms(argThat(request -> {
-//            assertEquals(mobile, request.getPhoneNumberSet()[0]);
-//            assertEquals(properties.getSignature(), request.getSignName());
-//            assertEquals(apiTemplateId, request.getTemplateId());
-//            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
-//                    toJsonString(request.getTemplateParamSet()));
-//            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
-//            return true;
-//        }))).thenReturn(response);
-//
-//        // 调用
-//        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-//        // 断言
-//        assertFalse(result.getSuccess());
-//        assertEquals(response.getRequestId(), result.getApiRequestId());
-//        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
-//        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
-//        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
-//    }
-
     @Test
     public void testParseSmsReceiveStatus() {
         // 准备参数
@@ -157,7 +134,6 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
                 "        \"ext\": {\"logId\":\"67890\"}\n" +
                 "    }\n" +
                 "]";
-        // mock 方法
 
         // 调用
         List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
@@ -165,42 +141,44 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
         assertEquals(1, statuses.size());
         assertTrue(statuses.get(0).getSuccess());
         assertEquals("DELIVRD", statuses.get(0).getErrorCode());
-        assertEquals("用户短信送达成功", statuses.get(0).getErrorMsg());
         assertEquals("13900000001", statuses.get(0).getMobile());
         assertEquals(LocalDateTime.of(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());
         assertEquals("12345", statuses.get(0).getSerialNo());
-        assertEquals(67890L, statuses.get(0).getLogId());
     }
 
-//    @Test
-//    public void testGetSmsTemplate() throws Throwable {
-//        // 准备参数
-//        Long apiTemplateId = randomLongId();
-//        String requestId = randomString();
-//
-//        // mock 方法
-//        DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
-//            DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
-//            DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
-//            templateStatus.setTemplateId(apiTemplateId);
-//            templateStatus.setStatusCode(0L);// 设置模板通过
-//            describeTemplateListStatuses[0] = templateStatus;
-//            o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
-//            o.setRequestId(requestId);
-//        });
-//        when(client.DescribeSmsTemplateList(argThat(request -> {
-//            assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
-//            return true;
-//        }))).thenReturn(response);
-//
-//        // 调用
-//        SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
-//        // 断言
-//        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
-//        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
-//        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
-//        assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
-//    }
+    @Test
+    public void testGetSmsTemplate() throws Throwable {
+        try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+            // 准备参数
+            String apiTemplateId = "1122";
+
+            // mock 方法
+            httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+                    .thenReturn("{     \"Response\": {\n" +
+                            "        \"DescribeTemplateStatusSet\": [\n" +
+                            "            {\n" +
+                            "                \"TemplateName\": \"验证码\",\n" +
+                            "                \"TemplateId\": 1122,\n" +
+                            "                \"International\": 0,\n" +
+                            "                \"ReviewReply\": \"审批备注\",\n" +
+                            "                \"CreateTime\": 1617379200,\n" +
+                            "                \"TemplateContent\": \"您的验证码是{1}\",\n" +
+                            "                \"StatusCode\": 0\n" +
+                            "            },\n" +
+                            "            \n" +
+                            "        ],\n" +
+                            "        \"RequestId\": \"f36e4f00-605e-49b1-ad0d-bfaba81c7325\"\n" +
+                            "    }}");
+
+            // 调用
+            SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
+            // 断言
+            assertEquals("1122", result.getId());
+            assertEquals("您的验证码是{1}", result.getContent());
+            assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
+            assertEquals("审批备注", result.getAuditReason());
+        }
+    }
 
     @Test
     public void testConvertSmsTemplateAuditStatus() {

+ 8 - 2
menduner/menduner-common/src/main/java/com/citu/module/menduner/common/db/TransformDataSourceInterceptor.java

@@ -1,5 +1,8 @@
 package com.citu.module.menduner.common.db;
 
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.annotation.Master;
+import com.baomidou.dynamic.datasource.annotation.Slave;
 import com.baomidou.dynamic.datasource.enums.DdConstants;
 import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import lombok.extern.slf4j.Slf4j;
@@ -15,6 +18,9 @@ import org.apache.ibatis.plugin.Signature;
 import org.apache.ibatis.session.ResultHandler;
 import org.apache.ibatis.session.RowBounds;
 
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+
 /**
  * 切换数据源拦截器
  * 读写分离   主写从读
@@ -35,13 +41,13 @@ public class TransformDataSourceInterceptor implements Interceptor {
         MappedStatement ms = (MappedStatement) args[0];
         String dataSource = null;
         try {
-             dataSource = DdConstants.MASTER;
+            dataSource = DdConstants.MASTER;
             if (SqlCommandType.SELECT == ms.getSqlCommandType()) {
                 // select
                 dataSource = DdConstants.SLAVE;
             }
             DynamicDataSourceContextHolder.push(dataSource);
-            log.info(" ----- use datasource ["+DynamicDataSourceContextHolder.peek()+"]  ----- ");
+            log.info(" ----- use datasource [" + DynamicDataSourceContextHolder.peek() + "]  ----- ");
             return invocation.proceed();
         } finally {
             if (null != dataSource) {

+ 0 - 11
menduner/menduner-mall-api/pom.xml

@@ -11,18 +11,7 @@
 
     <artifactId>menduner-mall-api</artifactId>
 
-    <properties>
-        <maven.compiler.source>11</maven.compiler.source>
-        <maven.compiler.target>11</maven.compiler.target>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    </properties>
     <dependencies>
-        <dependency>
-            <groupId>cn.hutool</groupId>
-            <artifactId>hutool-all</artifactId>
-            <version>5.8.26</version>
-        </dependency>
-
         <dependency>
             <groupId>com.citu</groupId>
             <artifactId>menduner-common</artifactId>

+ 2 - 0
menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/TradeOrderTypeMq.java

@@ -14,4 +14,6 @@ public class TradeOrderTypeMq {
 
     /** 发布众聘职位订单 **/
     public static final String PUBLISH_JOB_HIRE_ORDER_TOPIC = "PUBLISH_JOB_HIRE_ORDER_TOPIC";
+
+
 }

+ 0 - 1
menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/visits/MdeVisitsEnum.java

@@ -11,7 +11,6 @@ import lombok.Getter;
 public enum MdeVisitsEnum {
 
     POSITION_CLICK("0", "职位类型点击"),
-
     POSITION_PUBLISH_CLICK("1", "发布职位点击"),
     ENTERPRISE_CLICK("2", "企业点击");
 

+ 121 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/analysis/AnalysisController.java

@@ -0,0 +1,121 @@
+package com.citu.module.menduner.system.controller.admin.analysis;
+
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.security.core.annotations.PreAuthenticated;
+import com.citu.module.menduner.common.util.LoginUserContext;
+import com.citu.module.menduner.system.controller.base.CommonRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitAnalysisReqVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitInterviewInviteAnalysisRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitJobAnalysisRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitJobCvRelAnalysisRespVO;
+import com.citu.module.menduner.system.service.interview.InterviewInviteService;
+import com.citu.module.menduner.system.service.job.JobAdvertisedService;
+import com.citu.module.menduner.system.service.job.JobCvRelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 统计分析")
+@RestController
+@RequestMapping("/menduner/system/analysis")
+@Validated
+@Slf4j
+public class AnalysisController {
+
+    @Resource
+    private JobCvRelService jobCvcRelService;
+
+    @Resource
+    private InterviewInviteService interviewInviteService;
+
+    @Resource
+    private JobAdvertisedService jobAdvertisedService;
+
+    @GetMapping("/get/job/cv/sex/count")
+    @Operation(summary = "获取投递简历的性别分布")
+    @PreAuthenticated
+    public CommonResult<List<CommonRespVO>> getJobCvSexCount(@Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobCvcRelService.getJobCvSexCount(reqVO));
+    }
+
+    @GetMapping("/get/job/cv/age/count")
+    @Operation(summary = "获取投递简历的年龄分布")
+    @PreAuthenticated
+    public CommonResult<Map<String, Object[]>> getJobCvAgeCount(@Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobCvcRelService.getJobCvAgeCount(reqVO));
+    }
+
+    @GetMapping("/get/job/cv/edu/count")
+    @Operation(summary = "获取投递简历的学历分布")
+    @PreAuthenticated
+    public CommonResult<Map<String, Object[]>> getJobCvEduCount(@Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobCvcRelService.getJobCvEduCount(reqVO));
+    }
+
+    @GetMapping("/get/job/cv/exp/count")
+    @Operation(summary = "获取投递简历的工作经验分布")
+    @PreAuthenticated
+    public CommonResult<Map<String, Object[]>> getJobCvExpCount(@Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobCvcRelService.getJobCvExpCount(reqVO));
+    }
+
+    @GetMapping("/get/job/cv/new/page")
+    @Operation(summary = "获取新投递简历统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitJobCvRelAnalysisRespVO>> getNewCvRel(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobCvcRelService.getNewCvRel(reqVO));
+    }
+
+    @GetMapping("/get/job/cv/look/page")
+    @Operation(summary = "获取已查看简历统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitJobCvRelAnalysisRespVO>> getLookCvRel(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobCvcRelService.getLookCvRel(reqVO));
+    }
+
+    @GetMapping("/get/interview/wait/page")
+    @Operation(summary = "获取待面试统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitInterviewInviteAnalysisRespVO>> getWaitInterview(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        return success(interviewInviteService.getWaitInterview(reqVO));
+    }
+
+    @GetMapping("/get/interview/complete/page")
+    @Operation(summary = "获取完成面试统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitInterviewInviteAnalysisRespVO>> getCompleteInterview(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        return success(interviewInviteService.getCompleteInterview(reqVO));
+    }
+
+    @GetMapping("/get/job/browse/num/page")
+    @Operation(summary = "获取发布职位浏览量统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitJobAnalysisRespVO>> getBrowseNum(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobAdvertisedService.getBrowseNum(reqVO));
+    }
+
+    @GetMapping("/get/job/browse/num")
+    @Operation(summary = "获取发布职位浏览量总数")
+    public CommonResult<Long> getBrowseNumCount(@Valid RecruitAnalysisReqVO reqVO) {
+        return success(jobAdvertisedService.getBrowseNumCount(reqVO));
+    }
+
+}

+ 11 - 4
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/enterprise/EnterpriseUserBindController.java

@@ -6,10 +6,7 @@ import com.citu.framework.common.pojo.PageParam;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.common.util.object.BeanUtils;
 import com.citu.framework.excel.core.util.ExcelUtils;
-import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindDetailRespVO;
-import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindPageReqVO;
-import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindRespVO;
-import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindSaveReqVO;
+import com.citu.module.menduner.system.controller.base.enterprise.bind.*;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterpriseUserBindDO;
 import com.citu.module.menduner.system.service.enterprise.bind.EnterpriseUserBindService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -92,4 +89,14 @@ public class EnterpriseUserBindController {
                 BeanUtils.toBean(list, EnterpriseUserBindRespVO.class));
     }
 
+    @GetMapping("/list")
+    @Operation(summary = "根据条件查询企业用户列表")
+    @PreAuthorize("@ss.hasPermission('menduner:system:enterprise-user-bind:query')")
+    public CommonResult<List<EnterpriseUserBindRespVO>> list
+            (@Valid EnterpriseUserBindReqVO reqVO) {
+        List<EnterpriseUserBindDO> list = enterpriseUserBindService.list(reqVO);
+        return success(BeanUtils.toBean(list, EnterpriseUserBindRespVO.class));
+    }
+
+
 }

+ 5 - 1
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/industry/IndustryController.java

@@ -107,6 +107,10 @@ public class IndustryController {
         industryService.syncBossIndustry();
     }
 
-
+    @GetMapping("/sync/veryeast")
+    @Operation(summary = "同步最佳东方行业信息")
+    public void syncVeryeastIndustry() {
+        industryService.syncVeryeastIndustry();
+    }
 
 }

+ 9 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/job/JobAdvertisedController.java

@@ -8,6 +8,7 @@ import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.common.util.object.BeanUtils;
 import com.citu.framework.excel.core.util.ExcelUtils;
 import com.citu.module.menduner.system.controller.base.job.JobAdvertisedPageReqVO;
+import com.citu.module.menduner.system.controller.base.job.JobAdvertisedReqVO;
 import com.citu.module.menduner.system.controller.base.job.JobAdvertisedRespVO;
 import com.citu.module.menduner.system.controller.base.job.JobAdvertisedSaveReqVO;
 import com.citu.module.menduner.system.dal.dataobject.job.JobAdvertisedDO;
@@ -78,6 +79,14 @@ public class JobAdvertisedController {
         return success(BeanUtils.toBean(pageResult, JobAdvertisedRespVO.class));
     }
 
+    @GetMapping("/list")
+    @Operation(summary = "获得招聘职位列表")
+    @PreAuthorize("@ss.hasPermission('menduner:system:job-advertised:query')")
+    public CommonResult<List<JobAdvertisedRespVO>> getJobAdvertisedList(@Valid JobAdvertisedReqVO reqVO) {
+        List<JobAdvertisedDO> list = jobAdvertisedService.list(reqVO);
+        return success(BeanUtils.toBean(list, JobAdvertisedRespVO.class));
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "导出招聘职位 Excel")
     @PreAuthorize("@ss.hasPermission('menduner:system:job-advertised:export')")

+ 6 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/position/PositionController.java

@@ -106,4 +106,10 @@ public class PositionController {
         positionService.syncBossPosition();
     }
 
+    @GetMapping("/sync/veryeast")
+    @Operation(summary = "同步最佳东方职位类型")
+    public void syncVeryeastPosition() {
+        positionService.syncVeryeastPosition();
+    }
+
 }

+ 14 - 3
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/jobhunt/job/AppJobAdvertisedController.java

@@ -5,6 +5,8 @@ import com.citu.framework.common.pojo.PageParam;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.module.menduner.system.controller.app.jobhunt.job.vo.*;
 import com.citu.module.menduner.system.controller.base.CommonRespVO;
+import com.citu.module.menduner.system.enums.visits.MdeVisitsEnum;
+import com.citu.module.menduner.system.mq.producer.MdeVisitsProducer;
 import com.citu.module.menduner.system.service.job.JobAdvertisedService;
 import com.citu.module.menduner.system.service.job.JobIntegrationService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -35,6 +37,9 @@ public class AppJobAdvertisedController {
     @Resource
     private JobIntegrationService jobIntegrationService;
 
+    @Resource
+    private MdeVisitsProducer visitsProducer;
+
     @GetMapping("/get/recommended")
     @Operation(summary = "获取推荐招聘职位分页")
     public CommonResult<PageResult<AppJobAdvertisedHomeRespVO>> getRecommendedPage(@Valid PageParam pageParam) {
@@ -65,7 +70,8 @@ public class AppJobAdvertisedController {
 
     @GetMapping("/get/acquainted")
     @Operation(summary = "获取相识职位信息分页")
-    public CommonResult<PageResult<AppJobAdvertisedHomeRespVO>> getAcquaintedPage(@Valid AppJobAdvertisedPageReqVO pageReqVO) {
+    public CommonResult<PageResult<AppJobAdvertisedHomeRespVO>> getAcquaintedPage
+            (@Valid AppJobAdvertisedPageReqVO pageReqVO) {
         PageResult<AppJobAdvertisedHomeRespVO> respVO = jobAdvertisedService.getAcquaintedPage(pageReqVO);
         return success(respVO);
     }
@@ -74,6 +80,9 @@ public class AppJobAdvertisedController {
     @Operation(summary = "获取招聘职位详情")
     public CommonResult<AppJobAdvertisedDetailRespVO> detail(@RequestParam("id") Long id) {
         AppJobAdvertisedDetailRespVO respVO = jobAdvertisedService.detail(id);
+        if (null != respVO) {
+            visitsProducer.send(MdeVisitsEnum.POSITION_PUBLISH_CLICK, id);
+        }
         return success(respVO);
     }
 
@@ -88,14 +97,16 @@ public class AppJobAdvertisedController {
 
     @GetMapping("/get/position/count")
     @Operation(summary = "根据企业id统计职位类型的数量")
-    public CommonResult<List<CommonRespVO>> getJobPositionCountByEnterpriseId(@RequestParam("enterpriseId") Long enterpriseId) {
+    public CommonResult<List<CommonRespVO>> getJobPositionCountByEnterpriseId
+            (@RequestParam("enterpriseId") Long enterpriseId) {
         List<CommonRespVO> list = jobAdvertisedService.getJobPositionCountByEnterpriseId(enterpriseId);
         return success(list);
     }
 
     @GetMapping("/get/area/count")
     @Operation(summary = "根据企业id获取企业职位区域")
-    public CommonResult<List<CommonRespVO>> getJobAreaCountByEnterpriseId(@RequestParam("enterpriseId") Long enterpriseId) {
+    public CommonResult<List<CommonRespVO>> getJobAreaCountByEnterpriseId
+            (@RequestParam("enterpriseId") Long enterpriseId) {
         List<CommonRespVO> list = jobAdvertisedService.getJobAreaCountByEnterpriseId(enterpriseId);
         return success(list);
     }

+ 15 - 16
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/jobhunt/person/AppPersonController.java

@@ -63,6 +63,13 @@ public class AppPersonController {
         return success(result);
     }
 
+    @PreAuthenticated
+    @DeleteMapping("/job/unfavorite")
+    @Operation(summary = "用户取消收藏职位")
+    public CommonResult<Boolean> unFavorite(@RequestParam("jobId") Long jobId) {
+        Boolean result = jobFavoriteService.unFavorite(jobId);
+        return success(result);
+    }
 
     @PreAuthenticated
     @PostMapping("/enterprise/subscribe")
@@ -72,6 +79,14 @@ public class AppPersonController {
         return success(result);
     }
 
+    @PreAuthenticated
+    @DeleteMapping("/enterprise/unsubscribe")
+    @Operation(summary = "用户取消关注订阅企业")
+    public CommonResult<Boolean> unsubscribe(@RequestParam("enterpriseId") Long enterpriseId) {
+        Boolean result = enterpriseSubscribeService.unSubscribe(enterpriseId);
+        return success(result);
+    }
+
     @PreAuthenticated
     @PostMapping("/get/job/favorite/page")
     @Operation(summary = "获取收藏的招聘职位分页")
@@ -88,22 +103,6 @@ public class AppPersonController {
         return success(jobIntegrationService.getEnterpriseSubscribePage(reqVO));
     }
 
-    @PreAuthenticated
-    @DeleteMapping("/job/unfavorite")
-    @Operation(summary = "用户取消收藏职位")
-    public CommonResult<Boolean> unFavorite(@RequestParam("jobId") Long jobId) {
-        Boolean result = jobFavoriteService.unFavorite(jobId);
-        return success(result);
-    }
-
-    @PreAuthenticated
-    @DeleteMapping("/enterprise/unsubscribe")
-    @Operation(summary = "用户取消关注订阅企业")
-    public CommonResult<Boolean> unsubscribe(Long enterpriseId) {
-        Boolean result = enterpriseSubscribeService.unSubscribe(enterpriseId);
-        return success(result);
-    }
-
 
     @PreAuthenticated
     @GetMapping("/job/favorite/count")

+ 3 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/jobhunt/person/resume/AppPersonInfoSaveReqVO.java

@@ -44,6 +44,9 @@ public class AppPersonInfoSaveReqVO {
     @Schema(description = "所在城市", example = "8366")
     private Long areaId;
 
+    @Schema(description = "户籍地", example = "8366")
+    private Long regId;
+
     @Schema(description = "求职类型(0全职 1兼职 2临时 3实习)", example = "1")
     private String jobType;
 

+ 77 - 5
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/analysis/AppRecruitAnalysisController.java

@@ -1,9 +1,17 @@
 package com.citu.module.menduner.system.controller.app.recruit.analysis;
 
 import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.security.core.annotations.PreAuthenticated;
-import com.citu.module.menduner.system.controller.app.recruit.analysis.vo.AppRecruitAnalysisReqVO;
+import com.citu.module.menduner.common.util.LoginUserContext;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitAnalysisReqVO;
 import com.citu.module.menduner.system.controller.base.CommonRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitInterviewInviteAnalysisRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitJobAnalysisRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitJobCvRelAnalysisRespVO;
+import com.citu.module.menduner.system.dal.mysql.interview.InterviewInviteMapper;
+import com.citu.module.menduner.system.service.interview.InterviewInviteService;
+import com.citu.module.menduner.system.service.job.JobAdvertisedService;
 import com.citu.module.menduner.system.service.job.JobCvRelService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -15,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -30,33 +39,96 @@ public class AppRecruitAnalysisController {
     @Resource
     private JobCvRelService jobCvcRelService;
 
+    @Resource
+    private InterviewInviteService interviewInviteService;
+
+    @Resource
+    private JobAdvertisedService jobAdvertisedService;
+
     @GetMapping("/get/job/cv/sex/count")
     @Operation(summary = "获取投递简历的性别分布")
     @PreAuthenticated
-    public CommonResult<List<CommonRespVO>> getJobCvSexCount(@Valid AppRecruitAnalysisReqVO reqVO) {
+    public CommonResult<List<CommonRespVO>> getJobCvSexCount(@Valid RecruitAnalysisReqVO reqVO) {
         return success(jobCvcRelService.getJobCvSexCount(reqVO));
     }
 
     @GetMapping("/get/job/cv/age/count")
     @Operation(summary = "获取投递简历的年龄分布")
     @PreAuthenticated
-    public CommonResult<Map<String, Object[]>> getJobCvAgeCount(@Valid AppRecruitAnalysisReqVO reqVO) {
+    public CommonResult<Map<String, Object[]>> getJobCvAgeCount(@Valid RecruitAnalysisReqVO reqVO) {
         return success(jobCvcRelService.getJobCvAgeCount(reqVO));
     }
 
     @GetMapping("/get/job/cv/edu/count")
     @Operation(summary = "获取投递简历的学历分布")
     @PreAuthenticated
-    public CommonResult<Map<String, Object[]>> getJobCvEduCount(@Valid AppRecruitAnalysisReqVO reqVO) {
+    public CommonResult<Map<String, Object[]>> getJobCvEduCount(@Valid RecruitAnalysisReqVO reqVO) {
         return success(jobCvcRelService.getJobCvEduCount(reqVO));
     }
 
     @GetMapping("/get/job/cv/exp/count")
     @Operation(summary = "获取投递简历的工作经验分布")
     @PreAuthenticated
-    public CommonResult<Map<String, Object[]>> getJobCvExpCount(@Valid AppRecruitAnalysisReqVO reqVO) {
+    public CommonResult<Map<String, Object[]>> getJobCvExpCount(@Valid RecruitAnalysisReqVO reqVO) {
         return success(jobCvcRelService.getJobCvExpCount(reqVO));
     }
 
+    @GetMapping("/get/job/cv/new/page")
+    @Operation(summary = "获取新投递简历统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitJobCvRelAnalysisRespVO>> getNewCvRel(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        setCommonCondition(reqVO);
+        return success(jobCvcRelService.getNewCvRel(reqVO));
+    }
+
+    @GetMapping("/get/job/cv/look/page")
+    @Operation(summary = "获取已查看简历统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitJobCvRelAnalysisRespVO>> getLookCvRel(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        setCommonCondition(reqVO);
+        return success(jobCvcRelService.getLookCvRel(reqVO));
+    }
+
+    @GetMapping("/get/interview/wait/page")
+    @Operation(summary = "获取待面试统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitInterviewInviteAnalysisRespVO>> getWaitInterview(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        setCommonCondition(reqVO);
+        return success(interviewInviteService.getWaitInterview(reqVO));
+    }
+
+    @GetMapping("/get/interview/complete/page")
+    @Operation(summary = "获取完成面试统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitInterviewInviteAnalysisRespVO>> getCompleteInterview(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        setCommonCondition(reqVO);
+        return success(interviewInviteService.getCompleteInterview(reqVO));
+    }
+
+    @GetMapping("/get/job/browse/num/page")
+    @Operation(summary = "获取发布职位浏览量统计分析明细")
+    @PreAuthenticated
+    public CommonResult<PageResult<RecruitJobAnalysisRespVO>> getBrowseNum(
+            @Valid RecruitAnalysisReqVO reqVO) {
+        setCommonCondition(reqVO);
+        return success(jobAdvertisedService.getBrowseNum(reqVO));
+    }
+
+    @GetMapping("/get/job/browse/num")
+    @Operation(summary = "获取发布职位浏览量总数")
+    @PreAuthenticated
+    public CommonResult<Long> getBrowseNumCount(@Valid RecruitAnalysisReqVO reqVO) {
+        setCommonCondition(reqVO);
+        return success(jobAdvertisedService.getBrowseNumCount(reqVO));
+    }
+
+    private void setCommonCondition(RecruitAnalysisReqVO reqVO) {
+        reqVO.setEnterpriseId(LoginUserContext.getEnterpriseId());
+        reqVO.setUserId(Collections.singletonList(LoginUserContext.getUserId()));
+    }
 
 }

+ 2 - 22
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/person/cv/AppRecruitJobCvRelRespVO.java

@@ -2,6 +2,7 @@ package com.citu.module.menduner.system.controller.app.recruit.person.cv;
 
 import com.citu.module.menduner.system.controller.app.recruit.person.vo.AppRecruitPersonSimpleRespVO;
 import com.citu.module.menduner.system.controller.app.recruit.job.vo.AppRecruitJobSimpleRespVO;
+import com.citu.module.menduner.system.controller.base.job.JobCvRelSimpleRespVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -10,28 +11,7 @@ import java.time.LocalDateTime;
 
 @Schema(description = "menduner - 招聘职位简历投递分页 Response VO")
 @Data
-public class AppRecruitJobCvRelRespVO {
-
-    @Schema(description = "id", example = "24200")
-    private Long id;
-
-    @Schema(description = "简历附件标题")
-    private String title;
-
-    @Schema(description = "简历附件地址")
-    private String url;
-
-    @Schema(description = "投递用户id")
-    private Long userId;
-
-    @Schema(description = "投递类型(0 平台投递 | 1 赏金投递)")
-    private String type;
-
-    @Schema(description = "简历状态")
-    private String status;
-
-    @Schema(description = "更新时间")
-    private LocalDateTime updateTime;
+public class AppRecruitJobCvRelRespVO extends JobCvRelSimpleRespVO {
 
     @Schema(description = "投递人员信息")
     private AppRecruitPersonSimpleRespVO person;

+ 4 - 3
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/analysis/vo/AppRecruitAnalysisReqVO.java → menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/AnalysisBaseReqVO.java

@@ -1,5 +1,6 @@
-package com.citu.module.menduner.system.controller.app.recruit.analysis.vo;
+package com.citu.module.menduner.system.controller.base.analysis;
 
+import com.citu.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -8,9 +9,9 @@ import java.time.LocalDateTime;
 
 import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
-@Schema(description = "menduner - 统计分析 Request VO")
+@Schema(description = "统计分析公共 Request VO")
 @Data
-public class AppRecruitAnalysisReqVO {
+public class AnalysisBaseReqVO extends PageParam {
 
     public static final String TYPE_RECENT_7_DAYS = "0";
     public static final String TYPE_LAST_MONTH = "1";

+ 25 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitAnalysisReqVO.java

@@ -0,0 +1,25 @@
+package com.citu.module.menduner.system.controller.base.analysis;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "menduner - 统计分析公共 Request VO")
+@Data
+public class RecruitAnalysisReqVO extends AnalysisBaseReqVO {
+
+    @Schema(description = "企业id")
+    private Long enterpriseId;
+
+    @Schema(description = "部门id")
+    private Long deptId;
+
+    @Schema(description = "用户id")
+    private List<Long> userId;
+
+    @Schema(description = "发布职位id")
+    private Long jobId;
+
+
+}

+ 25 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitInterviewInviteAnalysisRespVO.java

@@ -0,0 +1,25 @@
+package com.citu.module.menduner.system.controller.base.analysis;
+
+
+import com.citu.module.menduner.system.controller.app.recruit.job.vo.AppRecruitJobSimpleRespVO;
+import com.citu.module.menduner.system.controller.app.recruit.person.vo.AppRecruitPersonSimpleRespVO;
+import com.citu.module.menduner.system.controller.base.interview.InterviewInviteRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "面试明细分析 Response VO")
+@Data
+public class RecruitInterviewInviteAnalysisRespVO extends InterviewInviteRespVO {
+
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+
+    @Schema(description = "人才简易信息")
+    private AppRecruitPersonSimpleRespVO person;
+
+    @Schema(description = "招聘职位信息")
+    private AppRecruitJobSimpleRespVO job;
+
+}

+ 15 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitJobAnalysisRespVO.java

@@ -0,0 +1,15 @@
+package com.citu.module.menduner.system.controller.base.analysis;
+
+
+import com.citu.module.menduner.system.controller.base.job.JobAdvertisedSimpleRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "招聘职位明细分析 Response VO")
+@Data
+public class RecruitJobAnalysisRespVO extends JobAdvertisedSimpleRespVO {
+
+    @Schema(description = "浏览量")
+    private Long num;
+
+}

+ 23 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/analysis/RecruitJobCvRelAnalysisRespVO.java

@@ -0,0 +1,23 @@
+package com.citu.module.menduner.system.controller.base.analysis;
+
+import com.citu.module.menduner.system.controller.app.recruit.job.vo.AppRecruitJobSimpleRespVO;
+import com.citu.module.menduner.system.controller.app.recruit.person.vo.AppRecruitPersonSimpleRespVO;
+import com.citu.module.menduner.system.controller.base.job.JobCvRelSimpleRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+
+@Schema(description = "简历明细分析 Response VO")
+@Data
+public class RecruitJobCvRelAnalysisRespVO extends JobCvRelSimpleRespVO {
+
+    @Schema(description = "投递人员信息")
+    private AppRecruitPersonSimpleRespVO person;
+
+    @Schema(description = "推荐的人员信息")
+    private AppRecruitPersonSimpleRespVO recommendPerson;
+
+    @Schema(description = "招聘信息")
+    private AppRecruitJobSimpleRespVO job;
+
+}

+ 0 - 3
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/enterprise/bind/EnterpriseUserBindPageReqVO.java

@@ -27,9 +27,6 @@ public class EnterpriseUserBindPageReqVO extends PageParam {
     @Schema(description = "用户性别")
     private String sex;
 
-    @Schema(description = "头像地址")
-    private String avatar;
-
     @Schema(description = "联系手机号")
     private String phone;
 

+ 35 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/enterprise/bind/EnterpriseUserBindReqVO.java

@@ -0,0 +1,35 @@
+package com.citu.module.menduner.system.controller.base.enterprise.bind;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "企业登录用户 Request VO")
+@Data
+public class EnterpriseUserBindReqVO {
+
+    @Schema(description = "企业id", example = "616")
+    private Long enterpriseId;
+
+    @Schema(description = "用户id", example = "23317")
+    private Long userId;
+
+    @Schema(description = "名称", example = "x女士")
+    private String name;
+
+    @Schema(description = "用户性别")
+    private String sex;
+
+    @Schema(description = "联系手机号")
+    private String phone;
+
+    @Schema(description = "用户邮箱")
+    private String email;
+
+    @Schema(description = "岗位id", example = "23317")
+    private Long postId;
+
+    @Schema(description = "帐号状态(0正常 1停用 2 等待审核 3不通过)", example = "2")
+    private String status;
+
+}

+ 4 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/industry/IndustryRespVO.java

@@ -37,6 +37,10 @@ public class IndustryRespVO {
     @ExcelProperty("层级")
     private Integer level;
 
+    @Schema(description = "显示顺序", example = "1")
+    @ExcelProperty("显示顺序")
+    private Integer sort;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;

+ 3 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/industry/IndustrySaveReqVO.java

@@ -27,4 +27,7 @@ public class IndustrySaveReqVO {
     @Schema(description = "层级")
     private Integer level;
 
+    @Schema(description = "显示顺序", example = "1")
+    private Integer sort;
+
 }

+ 44 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobAdvertisedReqVO.java

@@ -0,0 +1,44 @@
+package com.citu.module.menduner.system.controller.base.job;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "招聘职位查询条件 Request VO")
+@Data
+public class JobAdvertisedReqVO {
+
+    @Schema(description = "招聘职位id", example = "29465")
+    private List<Long> id;
+
+    @Schema(description = "企业id", example = "29465")
+    private Long enterpriseId;
+
+    @Schema(description = "发布用户id", example = "9592")
+    private Long userId;
+
+    @Schema(description = "工作地区", example = "25976")
+    private Long areaId;
+
+    @Schema(description = "职位名称", example = "张三")
+    private String name;
+
+    @Schema(description = "职位类型id", example = "31707")
+    private Long positionId;
+
+    @Schema(description = "招聘类型", example = "2")
+    private String type;
+
+    @Schema(description = "工作经验", example = "1")
+    private String expType;
+
+    @Schema(description = "学历要求", example = "1")
+    private String eduType;
+
+    @Schema(description = "是否雇佣 (众聘)")
+    private boolean hire;
+
+    @Schema(description = "职位状态", example = "2")
+    private String status;
+}

+ 33 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobCvRelSimpleRespVO.java

@@ -0,0 +1,33 @@
+package com.citu.module.menduner.system.controller.base.job;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "招聘职位简历投递简易 Response VO")
+@Data
+public class JobCvRelSimpleRespVO {
+
+    @Schema(description = "id", example = "24200")
+    private Long id;
+
+    @Schema(description = "简历附件标题")
+    private String title;
+
+    @Schema(description = "简历附件地址")
+    private String url;
+
+    @Schema(description = "投递用户id")
+    private Long userId;
+
+    @Schema(description = "投递类型(0 平台投递 | 1 赏金投递)")
+    private String type;
+
+    @Schema(description = "简历状态")
+    private String status;
+
+    @Schema(description = "更新时间")
+    private LocalDateTime updateTime;
+
+}

+ 3 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/person/info/PersonInfoPageReqVO.java

@@ -49,6 +49,9 @@ public class PersonInfoPageReqVO extends PageParam {
     @Schema(description = "现居住地", example = "8366")
     private Long areaId;
 
+    @Schema(description = "户籍地", example = "8366")
+    private Long regId;
+
     @Schema(description = "求职类型(0全职 1兼职 2临时 3实习)", example = "1")
     private String jobType;
 

+ 4 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/person/info/PersonInfoRespVO.java

@@ -56,6 +56,10 @@ public class PersonInfoRespVO {
     @ExcelProperty("现居住地")
     private Long areaId;
 
+    @Schema(description = "户籍地", example = "8366")
+    @ExcelProperty("户籍地")
+    private Long regId;
+
     @Schema(description = "求职类型(0全职 1兼职 2临时 3实习)", example = "1")
     @ExcelProperty("求职类型(0全职 1兼职 2临时 3实习)")
     private String jobType;

+ 3 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/person/info/PersonInfoSaveReqVO.java

@@ -46,6 +46,9 @@ public class PersonInfoSaveReqVO {
     @Schema(description = "现居住地", example = "8366")
     private Long areaId;
 
+    @Schema(description = "户籍地", example = "8366")
+    private Long regId;
+
     @Schema(description = "求职类型(0全职 1兼职 2临时 3实习)", example = "1")
     private String jobType;
 

+ 4 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/position/PositionRespVO.java

@@ -39,6 +39,10 @@ public class PositionRespVO {
     @ExcelProperty("默认职位描述内容")
     private String content;
 
+    @Schema(description = "显示顺序", example = "1")
+    @ExcelProperty("显示顺序")
+    private Integer sort;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;

+ 3 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/position/PositionSaveReqVO.java

@@ -28,4 +28,7 @@ public class PositionSaveReqVO {
     @Schema(description = "默认职位描述内容")
     private String content;
 
+    @Schema(description = "显示顺序", example = "1")
+    private Integer sort;
+
 }

+ 4 - 1
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/industry/IndustryDO.java

@@ -42,5 +42,8 @@ public class IndustryDO extends TenantBaseDO {
      * 层级
      **/
     private Integer level;
-
+    /**
+     * 显示顺序
+     */
+    private Integer sort;
 }

+ 10 - 3
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/person/PersonInfoDO.java

@@ -1,8 +1,6 @@
 package com.citu.module.menduner.system.dal.dataobject.person;
 
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.*;
 import com.citu.framework.tenant.core.db.TenantBaseDO;
 import lombok.*;
 
@@ -47,14 +45,17 @@ public class PersonInfoDO extends TenantBaseDO {
     /**
      * 联系手机号
      */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
     private String phone;
     /**
      * 用户邮箱
      */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
     private String email;
     /**
      * 微信号
      */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
     private String wxCode;
     /**
      * 出生日期
@@ -67,7 +68,13 @@ public class PersonInfoDO extends TenantBaseDO {
     /**
      * 现居住地
      */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
     private Long areaId;
+    /**
+     * 户籍地
+     */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
+    private Long regId;
     /**
      * 求职类型(0全职 1兼职 2临时 3实习)
      */

+ 4 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/position/PositionDO.java

@@ -50,5 +50,9 @@ public class PositionDO extends TenantBaseDO {
      * 默认职位描述内容
      */
     private String content;
+    /**
+     * 显示顺序
+     */
+    private Integer sort;
 
 }

+ 21 - 2
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/enterprise/EnterpriseUserBindMapper.java

@@ -13,6 +13,7 @@ import com.citu.module.menduner.system.controller.app.recruit.user.vo.AppRecruit
 import com.citu.module.menduner.system.controller.base.contact.EnterpriseUserSummaryRespVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindDetailRespVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindPageReqVO;
+import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindReqVO;
 import com.citu.module.menduner.system.controller.base.enterprise.vo.EnterpriseDetailRespVO;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterpriseDO;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterprisePostDO;
@@ -32,12 +33,30 @@ public interface EnterpriseUserBindMapper extends BaseMapperX<EnterpriseUserBind
 
     default PageResult<EnterpriseUserBindDO> selectPage(EnterpriseUserBindPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<EnterpriseUserBindDO>()
+                .eqIfPresent(EnterpriseUserBindDO::getLoginIp, reqVO.getLoginIp())
                 .eqIfPresent(EnterpriseUserBindDO::getEnterpriseId, reqVO.getEnterpriseId())
                 .eqIfPresent(EnterpriseUserBindDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(EnterpriseUserBindDO::getSex, reqVO.getSex())
                 .eqIfPresent(EnterpriseUserBindDO::getStatus, reqVO.getStatus())
-                .eqIfPresent(EnterpriseUserBindDO::getLoginIp, reqVO.getLoginIp())
-                .betweenIfPresent(EnterpriseUserBindDO::getCreateTime, reqVO.getCreateTime())
+                .likeIfPresent(EnterpriseUserBindDO::getPhone, reqVO.getPhone())
+                .likeIfPresent(EnterpriseUserBindDO::getEmail, reqVO.getEmail())
+                .likeIfPresent(EnterpriseUserBindDO::getName, reqVO.getName())
+                .eqIfPresent(EnterpriseUserBindDO::getPostId, reqVO.getPostId())
+                .orderByDesc(EnterpriseUserBindDO::getId));
+    }
+
+    default  List<EnterpriseUserBindDO> list(EnterpriseUserBindReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<EnterpriseUserBindDO>()
+                .eqIfPresent(EnterpriseUserBindDO::getEnterpriseId, reqVO.getEnterpriseId())
+                .eqIfPresent(EnterpriseUserBindDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(EnterpriseUserBindDO::getSex, reqVO.getSex())
+                .eqIfPresent(EnterpriseUserBindDO::getStatus, reqVO.getStatus())
+                .likeIfPresent(EnterpriseUserBindDO::getPhone, reqVO.getPhone())
+                .likeIfPresent(EnterpriseUserBindDO::getEmail, reqVO.getEmail())
+                .likeIfPresent(EnterpriseUserBindDO::getName, reqVO.getName())
+                .eqIfPresent(EnterpriseUserBindDO::getPostId, reqVO.getPostId())
                 .orderByDesc(EnterpriseUserBindDO::getId));
+
     }
 
     default PageResult<EnterpriseUserBindDetailRespVO> page(EnterpriseUserBindPageReqVO reqVO) {

+ 6 - 3
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/industry/IndustryMapper.java

@@ -27,7 +27,8 @@ public interface IndustryMapper extends BaseMapperX<IndustryDO> {
                 .likeRightIfPresent(IndustryDO::getNameCn, reqVO.getNameCn())
                 .likeRightIfPresent(IndustryDO::getNameEn, reqVO.getNameEn())
                 .betweenIfPresent(IndustryDO::getCreateTime, reqVO.getCreateTime())
-                .orderByAsc(IndustryDO::getUpdateTime));
+                .orderByAsc(IndustryDO::getSort, IndustryDO::getUpdateTime)
+        );
     }
 
     default List<IndustryDO> selectList(IndustryListReqVO reqVO) {
@@ -35,7 +36,8 @@ public interface IndustryMapper extends BaseMapperX<IndustryDO> {
                 .likeRightIfPresent(IndustryDO::getNameCn, reqVO.getNameCn())
                 .likeRightIfPresent(IndustryDO::getNameEn, reqVO.getNameEn())
                 .betweenIfPresent(IndustryDO::getCreateTime, reqVO.getCreateTime())
-                .orderByAsc(IndustryDO::getUpdateTime));
+                .orderByAsc(IndustryDO::getSort, IndustryDO::getUpdateTime)
+        );
     }
 
     default List<IndustryDO> selectList(AppIndustryListReqVO reqVO) {
@@ -43,7 +45,8 @@ public interface IndustryMapper extends BaseMapperX<IndustryDO> {
                 .likeRightIfPresent(IndustryDO::getNameCn, reqVO.getNameCn())
                 .likeRightIfPresent(IndustryDO::getNameEn, reqVO.getNameEn())
                 .betweenIfPresent(IndustryDO::getCreateTime, reqVO.getCreateTime())
-                .orderByAsc(IndustryDO::getUpdateTime));
+                .orderByAsc(IndustryDO::getSort, IndustryDO::getUpdateTime)
+        );
     }
 
     default List<IndustryDO> selectListByParentId(Collection<Long> parentIds) {

+ 32 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/interview/InterviewInviteMapper.java

@@ -10,6 +10,8 @@ import com.citu.module.menduner.system.controller.app.jobhunt.interview.vo.AppIn
 import com.citu.module.menduner.system.controller.app.recruit.interview.vo.AppRecruitInterviewInviteReqPageVO;
 import com.citu.module.menduner.system.controller.app.recruit.interview.vo.AppRecruitInterviewInviteRespVO;
 import com.citu.module.menduner.system.controller.base.CommonRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitAnalysisReqVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitInterviewInviteAnalysisRespVO;
 import com.citu.module.menduner.system.controller.base.interview.InterviewInvitePageReqVO;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterpriseDO;
 import com.citu.module.menduner.system.dal.dataobject.interview.InterviewInviteDO;
@@ -180,6 +182,7 @@ public interface InterviewInviteMapper extends BaseMapperX<InterviewInviteDO> {
         return selectJoinList(InterviewInviteDO.class, query);
     }
 
+    /** 获取推荐职位面试会话状态数量 **/
     default List<CommonRespVO> getRecommendCount(Long userId) {
         MPJLambdaWrapperX<InterviewInviteDO> wrapper = new MPJLambdaWrapperX<>();
         wrapper.
@@ -199,5 +202,34 @@ public interface InterviewInviteMapper extends BaseMapperX<InterviewInviteDO> {
         return selectJoinList(CommonRespVO.class, wrapper);
     }
 
+    /**
+     * 面试邀约明细
+     *
+     * @param reqVO     公共条件
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @param status    面试状态
+     **/
+    default PageResult<RecruitInterviewInviteAnalysisRespVO> getAnalysisDetail(
+            RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime, String status) {
+        MPJLambdaWrapperX<InterviewInviteDO> query = new MPJLambdaWrapperX<>();
+        query.selectAll(InterviewInviteDO.class);
+        query.selectAssociation(PersonInfoDO.class, AppRecruitInterviewInviteRespVO::getPerson);
+        query.selectAssociation(JobAdvertisedDO.class, AppRecruitInterviewInviteRespVO::getJob);
+
+        query.innerJoin(PersonInfoDO.class, PersonInfoDO::getUserId, InterviewInviteDO::getUserId);
+        query.innerJoin(JobAdvertisedDO.class, JobAdvertisedDO::getId, InterviewInviteDO::getJobId);
+
+        query.eqIfPresent(InterviewInviteDO::getEnterpriseId, reqVO.getEnterpriseId());
+        query.inIfPresent(InterviewInviteDO::getInviteUserId, reqVO.getUserId());
+        query.eqIfPresent(InterviewInviteDO::getJobId, reqVO.getJobId());
+        query.eqIfPresent(InterviewInviteDO::getStatus, status);
+        query.eqIfPresent(InterviewInviteDO::getConversationStatus, ConversationStatusEnum.INVITE.getStatus());
+        query.between(JobCvRelDO::getCreateTime, startTime, endTime);
+
+        query.orderByDesc(InterviewInviteDO::getUpdateTime);
+
+        return selectJoinPage(reqVO, RecruitInterviewInviteAnalysisRespVO.class, query);
+    }
 
 }

+ 81 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/job/JobAdvertisedMapper.java

@@ -14,15 +14,21 @@ import com.citu.module.menduner.system.controller.app.jobhunt.job.vo.AppJobAdver
 import com.citu.module.menduner.system.controller.app.jobhunt.job.vo.AppJobAdvertisedRespVO;
 import com.citu.module.menduner.system.controller.app.recruit.job.vo.AppRecruitJobPageReqVO;
 import com.citu.module.menduner.system.controller.app.recruit.job.vo.AppRecruitJobSimpleRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitAnalysisReqVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitJobAnalysisRespVO;
 import com.citu.module.menduner.system.controller.base.job.JobAdvertisedPageReqVO;
+import com.citu.module.menduner.system.controller.base.job.JobAdvertisedReqVO;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterpriseDO;
 import com.citu.module.menduner.system.dal.dataobject.job.JobAdvertisedDO;
+import com.citu.module.menduner.system.dal.dataobject.visits.MdeVisitsDO;
 import com.citu.module.menduner.system.enums.MendunerStatusEnum;
 import com.citu.module.menduner.system.enums.job.JobStatusEnum;
+import com.citu.module.menduner.system.enums.visits.MdeVisitsEnum;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 招聘职位 Mapper
@@ -49,6 +55,21 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
                 .orderByDesc(JobAdvertisedDO::getId));
     }
 
+    default List<JobAdvertisedDO> list(JobAdvertisedReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<JobAdvertisedDO>()
+                .inIfPresent(JobAdvertisedDO::getId, reqVO.getId())
+                .eqIfPresent(JobAdvertisedDO::getEnterpriseId, reqVO.getEnterpriseId())
+                .eqIfPresent(JobAdvertisedDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(JobAdvertisedDO::getAreaId, reqVO.getAreaId())
+                .likeIfPresent(JobAdvertisedDO::getName, reqVO.getName())
+                .eqIfPresent(JobAdvertisedDO::getPositionId, reqVO.getPositionId())
+                .eqIfPresent(JobAdvertisedDO::getType, reqVO.getType())
+                .eqIfPresent(JobAdvertisedDO::getExpType, reqVO.getExpType())
+                .eqIfPresent(JobAdvertisedDO::getEduType, reqVO.getEduType())
+                .eqIfPresent(JobAdvertisedDO::getHire, reqVO.isHire())
+                .eqIfPresent(JobAdvertisedDO::getStatus, reqVO.getStatus())
+                .orderByDesc(JobAdvertisedDO::getId));
+    }
     default Long countByEnterpriseId(Long enterpriseId) {
         return selectCount(new LambdaQueryWrapperX<JobAdvertisedDO>()
                 .eq(JobAdvertisedDO::getEnterpriseId, enterpriseId)
@@ -177,4 +198,64 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
         return selectJoinList(AppRecruitJobSimpleRespVO.class, query);
     }
 
+    /**
+     * 职位浏览量明细
+     *
+     * @param reqVO     公共条件
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     **/
+    default PageResult<RecruitJobAnalysisRespVO> getAnalysisDetail(
+            RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime) {
+        MPJLambdaWrapperX<JobAdvertisedDO> query = new MPJLambdaWrapperX<>();
+        query.selectAll(JobAdvertisedDO.class);
+        query.selectSum(MdeVisitsDO::getCount, RecruitJobAnalysisRespVO::getNum);
+
+        query.eqIfPresent(JobAdvertisedDO::getEnterpriseId, reqVO.getEnterpriseId());
+        query.inIfPresent(JobAdvertisedDO::getUserId, reqVO.getUserId());
+        query.eqIfPresent(JobAdvertisedDO::getId, reqVO.getJobId());
+
+        query.innerJoin(MdeVisitsDO.class, on ->
+                on.eq(MdeVisitsDO::getBizId, JobAdvertisedDO::getId)
+                        .eq(MdeVisitsDO::getType, MdeVisitsEnum.POSITION_PUBLISH_CLICK.getType())
+        );
+
+        query.between(MdeVisitsDO::getUpdateTime, startTime, endTime);
+
+        query.groupBy(JobAdvertisedDO::getId);
+        query.orderByDesc(MdeVisitsDO::getUpdateTime);
+
+        return selectJoinPage(reqVO, RecruitJobAnalysisRespVO.class, query);
+    }
+
+    /**
+     * 职位浏览量总数
+     *
+     * @param reqVO     公共条件
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     **/
+    default Long getBrowseNumCount(RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime) {
+        MPJLambdaWrapperX<JobAdvertisedDO> query = new MPJLambdaWrapperX<>();
+
+        query.selectSum(MdeVisitsDO::getCount, "num");
+
+        query.eqIfPresent(JobAdvertisedDO::getEnterpriseId, reqVO.getEnterpriseId());
+        query.inIfPresent(JobAdvertisedDO::getUserId, reqVO.getUserId());
+        query.eqIfPresent(JobAdvertisedDO::getId, reqVO.getJobId());
+
+        query.innerJoin(MdeVisitsDO.class, on ->
+                on.eq(MdeVisitsDO::getBizId, JobAdvertisedDO::getId)
+                        .eq(MdeVisitsDO::getType, MdeVisitsEnum.POSITION_PUBLISH_CLICK.getType())
+        );
+
+        query.between(MdeVisitsDO::getUpdateTime, startTime, endTime);
+
+        Map<String,Object> result= selectJoinMap(query);
+        if(null == result) {
+            return 0L;
+        }
+        return Long.valueOf(result.get("num").toString());
+    }
+
 }

+ 61 - 4
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/job/JobCvRelMapper.java

@@ -12,6 +12,8 @@ import com.citu.module.menduner.system.controller.app.recruit.person.cv.AppRecru
 import com.citu.module.menduner.system.controller.app.recruit.person.hire.AppRecruitHireJobCvRelPageReqVO;
 import com.citu.module.menduner.system.controller.app.recruit.person.hire.AppRecruitHireJobCvRelRespVO;
 import com.citu.module.menduner.system.controller.base.CommonRespVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitAnalysisReqVO;
+import com.citu.module.menduner.system.controller.base.analysis.RecruitJobCvRelAnalysisRespVO;
 import com.citu.module.menduner.system.controller.base.job.JobCvRelPageReqVO;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterpriseDO;
 import com.citu.module.menduner.system.dal.dataobject.interview.InterviewInviteDO;
@@ -223,12 +225,17 @@ public interface JobCvRelMapper extends BaseMapperX<JobCvRelDO> {
     /**
      * 投递简历的性别比例
      **/
-    default List<CommonRespVO> getJobCvSexCount(LocalDateTime startTime, LocalDateTime endTime) {
+    default List<CommonRespVO> getJobCvSexCount
+    (RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime) {
         MPJLambdaWrapperX<JobCvRelDO> wrapper = new MPJLambdaWrapperX<>();
         wrapper.selectAs(PersonInfoDO::getSex, "`key`");
         wrapper.selectCount(JobCvRelDO::getId, CommonRespVO::getValue);
         wrapper.innerJoin(PersonInfoDO.class, PersonInfoDO::getUserId, JobCvRelDO::getUserId);
         wrapper.between(JobCvRelDO::getCreateTime, startTime, endTime);
+
+        wrapper.eqIfPresent(JobCvRelDO::getJobId, reqVO.getJobId());
+        wrapper.eqIfPresent(JobCvRelDO::getEnterpriseId, reqVO.getEnterpriseId());
+        wrapper.inIfPresent(JobCvRelDO::getUserId, reqVO.getUserId());
         wrapper.groupBy(PersonInfoDO::getSex);
         wrapper.orderByDesc("`key`");
         return selectJoinList(CommonRespVO.class, wrapper);
@@ -238,7 +245,8 @@ public interface JobCvRelMapper extends BaseMapperX<JobCvRelDO> {
     /**
      * 投递简历的年龄分布
      **/
-    default List<CommonRespVO> getJobCvAgeCount(LocalDateTime startTime, LocalDateTime endTime) {
+    default List<CommonRespVO> getJobCvAgeCount
+    (RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime) {
         MPJLambdaWrapperX<JobCvRelDO> wrapper = new MPJLambdaWrapperX<>();
 
         String sql = "CASE \n" +
@@ -255,6 +263,10 @@ public interface JobCvRelMapper extends BaseMapperX<JobCvRelDO> {
 
         wrapper.innerJoin(PersonInfoDO.class, PersonInfoDO::getUserId, JobCvRelDO::getUserId);
         wrapper.between(JobCvRelDO::getCreateTime, startTime, endTime);
+
+        wrapper.eqIfPresent(JobCvRelDO::getJobId, reqVO.getJobId());
+        wrapper.eqIfPresent(JobCvRelDO::getEnterpriseId, reqVO.getEnterpriseId());
+        wrapper.inIfPresent(JobCvRelDO::getUserId, reqVO.getUserId());
         wrapper.groupBy("`key`");
         wrapper.orderByDesc("`key`");
         return selectJoinList(CommonRespVO.class, wrapper);
@@ -263,13 +275,18 @@ public interface JobCvRelMapper extends BaseMapperX<JobCvRelDO> {
     /**
      * 投递简历的工作经验分布
      **/
-    default List<CommonRespVO> getJobCvExpCount(LocalDateTime startTime, LocalDateTime endTime) {
+    default List<CommonRespVO> getJobCvExpCount
+    (RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime) {
         MPJLambdaWrapperX<JobCvRelDO> wrapper = new MPJLambdaWrapperX<>();
         wrapper.selectAs(PersonInfoDO::getExpType, "`key`");
         wrapper.selectCount(JobCvRelDO::getId, CommonRespVO::getValue);
 
         wrapper.innerJoin(PersonInfoDO.class, PersonInfoDO::getUserId, JobCvRelDO::getUserId);
         wrapper.between(JobCvRelDO::getCreateTime, startTime, endTime);
+
+        wrapper.eqIfPresent(JobCvRelDO::getJobId, reqVO.getJobId());
+        wrapper.eqIfPresent(JobCvRelDO::getEnterpriseId, reqVO.getEnterpriseId());
+        wrapper.inIfPresent(JobCvRelDO::getUserId, reqVO.getUserId());
         wrapper.groupBy(PersonInfoDO::getExpType);
         wrapper.orderByDesc("`key`");
         return selectJoinList(CommonRespVO.class, wrapper);
@@ -278,18 +295,58 @@ public interface JobCvRelMapper extends BaseMapperX<JobCvRelDO> {
     /**
      * 投递简历的学历分布
      **/
-    default List<CommonRespVO> getJobCvEduCount(LocalDateTime startTime, LocalDateTime endTime) {
+    default List<CommonRespVO> getJobCvEduCount
+    (RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime) {
         MPJLambdaWrapperX<JobCvRelDO> wrapper = new MPJLambdaWrapperX<>();
         wrapper.selectAs(PersonInfoDO::getEduType, "`key`");
         wrapper.selectCount(JobCvRelDO::getId, CommonRespVO::getValue);
 
         wrapper.innerJoin(PersonInfoDO.class, PersonInfoDO::getUserId, JobCvRelDO::getUserId);
         wrapper.between(JobCvRelDO::getCreateTime, startTime, endTime);
+
+        wrapper.eqIfPresent(JobCvRelDO::getJobId, reqVO.getJobId());
+        wrapper.eqIfPresent(JobCvRelDO::getEnterpriseId, reqVO.getEnterpriseId());
+        wrapper.inIfPresent(JobCvRelDO::getUserId, reqVO.getUserId());
         wrapper.groupBy(PersonInfoDO::getEduType);
         wrapper.orderByDesc("`key`");
 
         return selectJoinList(CommonRespVO.class, wrapper);
     }
 
+    /**
+     * 简历明细
+     *
+     * @param reqVO     公共条件
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @param status    投递简历的状态
+     **/
+    default PageResult<RecruitJobCvRelAnalysisRespVO> getAnalysisDetail(
+            RecruitAnalysisReqVO reqVO, LocalDateTime startTime, LocalDateTime endTime, String status) {
+        MPJLambdaWrapperX<JobCvRelDO> wrapper = new MPJLambdaWrapperX<>();
+        wrapper.selectAsClass(JobCvRelDO.class, RecruitJobCvRelAnalysisRespVO.class);
+        wrapper.selectAssociation(JobAdvertisedDO.class, RecruitJobCvRelAnalysisRespVO::getJob);
+        wrapper.selectAssociation("person", PersonInfoDO.class, RecruitJobCvRelAnalysisRespVO::getPerson);
+        wrapper.selectAssociation("recommend", PersonInfoDO.class, RecruitJobCvRelAnalysisRespVO::getRecommendPerson);
+
+        wrapper.eqIfPresent(JobCvRelDO::getEnterpriseId, reqVO.getEnterpriseId());
+        wrapper.inIfPresent(JobCvRelDO::getPublishUserId, reqVO.getUserId());
+        wrapper.eqIfPresent(JobCvRelDO::getJobId, reqVO.getJobId());
+        wrapper.eqIfPresent(JobCvRelDO::getStatus, status);
+        wrapper.between(JobCvRelDO::getCreateTime, startTime, endTime);
+
+        // 投递的职位 inner 招聘职位
+        wrapper.innerJoin(JobAdvertisedDO.class, JobAdvertisedDO::getId, JobCvRelDO::getJobId);
+
+        // 投递的职位 inner 人才信息 (投递人)
+        wrapper.innerJoin(PersonInfoDO.class, "person", PersonInfoDO::getUserId, JobCvRelDO::getUserId);
+
+        // 投递的职位 inner 人才信息 (推荐人)
+        wrapper.leftJoin(PersonInfoDO.class, "recommend", PersonInfoDO::getUserId, JobCvRelDO::getRecommendUserId);
+
+        wrapper.orderByDesc(JobCvRelDO::getUpdateTime);
+        return selectJoinPage(reqVO, RecruitJobCvRelAnalysisRespVO.class, wrapper);
+    }
+
 
 }

+ 8 - 4
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/position/PositionMapper.java

@@ -3,9 +3,9 @@ package com.citu.module.menduner.system.dal.mysql.position;
 import com.citu.framework.common.pojo.PageResult;
 import com.citu.framework.mybatis.core.mapper.BaseMapperX;
 import com.citu.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.citu.module.menduner.system.controller.app.jobhunt.position.vo.AppPositionListReqVO;
 import com.citu.module.menduner.system.controller.base.position.PositionListReqVO;
 import com.citu.module.menduner.system.controller.base.position.PositionPageReqVO;
-import com.citu.module.menduner.system.controller.app.jobhunt.position.vo.AppPositionListReqVO;
 import com.citu.module.menduner.system.dal.dataobject.position.PositionDO;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
@@ -29,7 +29,8 @@ public interface PositionMapper extends BaseMapperX<PositionDO> {
                 .eqIfPresent(PositionDO::getParentId, reqVO.getParentId())
                 .eqIfPresent(PositionDO::getLevel, reqVO.getLevel())
                 .betweenIfPresent(PositionDO::getCreateTime, reqVO.getCreateTime())
-                .orderByAsc(PositionDO::getId));
+                .orderByAsc(PositionDO::getSort, PositionDO::getUpdateTime)
+        );
     }
 
     default List<PositionDO> selectList(PositionListReqVO reqVO) {
@@ -39,7 +40,8 @@ public interface PositionMapper extends BaseMapperX<PositionDO> {
                 .eqIfPresent(PositionDO::getParentId, reqVO.getParentId())
                 .eqIfPresent(PositionDO::getLevel, reqVO.getLevel())
                 .betweenIfPresent(PositionDO::getCreateTime, reqVO.getCreateTime())
-                .orderByAsc(PositionDO::getId));
+                .orderByAsc(PositionDO::getSort, PositionDO::getUpdateTime)
+        );
     }
 
     default List<PositionDO> selectList(AppPositionListReqVO reqVO) {
@@ -49,7 +51,8 @@ public interface PositionMapper extends BaseMapperX<PositionDO> {
                 .eqIfPresent(PositionDO::getParentId, reqVO.getParentId())
                 .eqIfPresent(PositionDO::getLevel, reqVO.getLevel())
                 .betweenIfPresent(PositionDO::getCreateTime, reqVO.getCreateTime())
-                .orderByAsc(PositionDO::getId));
+                .orderByAsc(PositionDO::getSort, PositionDO::getUpdateTime)
+        );
     }
 
     default List<PositionDO> selectListByParentId(Collection<Long> parentIds) {
@@ -68,6 +71,7 @@ public interface PositionMapper extends BaseMapperX<PositionDO> {
 
     /**
      * 根据层级随机获取十条数据
+     *
      * @param level
      * @return List<PositionDO>
      **/

+ 5 - 7
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/visits/MdeVisitsMapper.java

@@ -33,7 +33,7 @@ public interface MdeVisitsMapper extends BaseMapperX<MdeVisitsDO> {
     }
 
 
-    default List<MdeVisitsDO> getBizIdVisitsTop10List(MdeVisitsListReqVO reqVO){
+    default List<MdeVisitsDO> getBizIdVisitsTop10List(MdeVisitsListReqVO reqVO) {
         return selectList(new LambdaQueryWrapperX<MdeVisitsDO>()
                 .eq(MdeVisitsDO::getUserId, reqVO.getUserId())
                 .eq(MdeVisitsDO::getType, reqVO.getType())
@@ -47,15 +47,13 @@ public interface MdeVisitsMapper extends BaseMapperX<MdeVisitsDO> {
     }
 
     default MdeVisitsDO getMdeVisitsByUserIdAndTypeAndBizIdAndDate(Long userId, String type, Long bizId, LocalDateTime date) {
-        LambdaQueryWrapperX<MdeVisitsDO> wrapperX =  new LambdaQueryWrapperX<MdeVisitsDO>()
+        LambdaQueryWrapperX<MdeVisitsDO> wrapperX = new LambdaQueryWrapperX<MdeVisitsDO>()
                 .eqIfPresent(MdeVisitsDO::getType, type)
                 .eqIfPresent(MdeVisitsDO::getBizId, bizId)
                 .eqIfPresent(MdeVisitsDO::getDate, date.format(DateTimeFormatter.ofPattern(DateUtils.FORMAT_YEAR_MONTH_DAY)));
-        if(null == userId) {
-            wrapperX.isNull(MdeVisitsDO::getUserId);
-        }else{
-            wrapperX.eqIfPresent(MdeVisitsDO::getUserId,userId);
-        }
+
+        wrapperX.eqIfPresent(MdeVisitsDO::getUserId, userId);
+
         return selectOne(wrapperX);
     }
 }

+ 8 - 4
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/mq/consumer/MdeVisitsConsumer.java

@@ -4,7 +4,8 @@ import com.citu.module.menduner.system.mq.message.MdeVisitsSendMessage;
 import com.citu.module.menduner.system.mq.producer.MdeVisitsProducer;
 import com.citu.module.menduner.system.service.visits.MdeVisitsService;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.event.EventListener;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
@@ -17,13 +18,16 @@ import javax.annotation.Resource;
  */
 @Component
 @Slf4j
-public class MdeVisitsConsumer {
+@RocketMQMessageListener(
+        topic = MdeVisitsSendMessage.VISITS_CLICK_TOPIC,
+        consumerGroup = MdeVisitsSendMessage.VISITS_CLICK_TOPIC + "_CONSUMER"
+)
+public class MdeVisitsConsumer implements RocketMQListener<MdeVisitsSendMessage> {
 
     @Resource
     private MdeVisitsService service;
 
-    @EventListener
-    @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
+    @Override
     public void onMessage(MdeVisitsSendMessage message) {
         log.info("[onMessage][消息内容({})]", message);
         service.create(message);

+ 2 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/mq/message/MdeVisitsSendMessage.java

@@ -21,6 +21,8 @@ import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DA
 @Schema(description = "访问量新增")
 public class MdeVisitsSendMessage {
 
+    public static final String VISITS_CLICK_TOPIC = "VISITS_CLICK_TOPIC";
+
     @NotBlank(message = "{1_100_015_002}")
     @Schema(description = "业务类型")
     private String type;

+ 21 - 11
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/mq/producer/MdeVisitsProducer.java

@@ -1,8 +1,11 @@
 package com.citu.module.menduner.system.mq.producer;
 
+import com.citu.module.menduner.system.enums.visits.MdeVisitsEnum;
 import com.citu.module.menduner.system.mq.message.MdeVisitsSendMessage;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.ApplicationContext;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
@@ -17,23 +20,30 @@ import java.time.LocalDateTime;
 @Component
 public class MdeVisitsProducer {
 
+
     @Resource
-    private ApplicationContext applicationContext;
+    private RocketMQTemplate rocketMQTemplate;
 
 
     /**
-     * @param type       业务类型
+     * 发送记录访问消息
+     *
+     * @param type  业务类型
      * @param bizId 业务id
-     * @return void
-     * @description 发送记录访问消息
-     * @author Rayson
-     * @date 2024/5/14 下午3:31
      **/
-    public void sendVisitsSendMessage(String type, Long bizId) {
-        applicationContext.publishEvent(MdeVisitsSendMessage.builder()
-                .type(type)
+    public void send(MdeVisitsEnum type, Long bizId) {
+
+        Message<?> message = MessageBuilder.withPayload(MdeVisitsSendMessage.builder()
+                .type(type.getType())
                 .bizId(bizId)
                 .date(LocalDateTime.now())
-                .build());
+                .build()).build();
+        rocketMQTemplate.syncSend(
+                MdeVisitsSendMessage.VISITS_CLICK_TOPIC,
+                message,
+                10000,
+                // 5s
+                2
+        );
     }
 }

+ 1 - 1
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/enterprise/EnterpriseServiceImpl.java

@@ -196,7 +196,7 @@ public class EnterpriseServiceImpl implements EnterpriseService {
 
     @Override
     public void click(AppEnterpriseClickReqVO reqVO) {
-        producer.sendVisitsSendMessage(MdeVisitsEnum.ENTERPRISE_CLICK.getType(), reqVO.getId());
+        producer.send(MdeVisitsEnum.ENTERPRISE_CLICK, reqVO.getId());
     }
 
     @Override

+ 8 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/enterprise/bind/EnterpriseUserBindService.java

@@ -10,6 +10,7 @@ import com.citu.module.menduner.system.controller.base.contact.EnterpriseUserCon
 import com.citu.module.menduner.system.controller.base.contact.EnterpriseUserSummaryRespVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindDetailRespVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindPageReqVO;
+import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindReqVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindSaveReqVO;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterpriseUserBindDO;
 
@@ -116,6 +117,13 @@ public interface EnterpriseUserBindService {
      **/
     void createUser(EnterpriseUserBindDO userBindDO);
 
+    /**
+     * 根据条件查询企业用户列表
+     *
+     * @param reqVO 查询条件
+     **/
+    List<EnterpriseUserBindDO> list(EnterpriseUserBindReqVO reqVO);
+
     // ========== 求职端 ==========
     /**
      * 根据联系人id,获取联系人基本信息

+ 6 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/enterprise/bind/EnterpriseUserBindServiceImpl.java

@@ -15,6 +15,7 @@ import com.citu.module.menduner.system.controller.base.contact.EnterpriseUserCon
 import com.citu.module.menduner.system.controller.base.contact.EnterpriseUserSummaryRespVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindDetailRespVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindPageReqVO;
+import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindReqVO;
 import com.citu.module.menduner.system.controller.base.enterprise.bind.EnterpriseUserBindSaveReqVO;
 import com.citu.module.menduner.system.convert.EnterpriseConvert;
 import com.citu.module.menduner.system.dal.dataobject.enterprise.EnterpriseDO;
@@ -152,6 +153,11 @@ public class EnterpriseUserBindServiceImpl implements EnterpriseUserBindService
         mdePermissionService.assignUserRoleByDefault(userBindDO.getUserId(), MdeDefaultRoleEnum.MENDUNER_ENTERPRISE_ROLE);
     }
 
+    @Override
+    public List<EnterpriseUserBindDO> list(EnterpriseUserBindReqVO reqVO) {
+        return enterpriseUserBindMapper.list(reqVO);
+    }
+
     @Override
     public EnterpriseUserContactRespVO getContact(Long enterpriseId, Long userId) {
         EnterpriseUserBindDO userBindDO = enterpriseUserBindMapper.selectByEnterpriseIdAndUserId(enterpriseId, userId);

+ 5 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/industry/IndustryService.java

@@ -70,6 +70,11 @@ public interface IndustryService {
      */
     void syncBossIndustry();
 
+    /**
+     * 同步最佳东方行业数据
+     */
+    void syncVeryeastIndustry();
+
     /**
      * 根据行业id获得地区列表
      *

+ 52 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/industry/IndustryServiceImpl.java

@@ -143,6 +143,58 @@ public class IndustryServiceImpl implements IndustryService {
         log.info(" ========== 同步BOSS行业信息完成 ========== ");
     }
 
+    @Override
+    @DSTransactional
+    @CacheEvict(cacheNames =
+            {
+                    RedisKeyConstants.MDE_INDUSTRY_CHILDREN,
+                    RedisKeyConstants.MDE_INDUSTRY_TREE
+            },
+            allEntries = true)
+    public void syncVeryeastIndustry() {
+
+        log.info(" ========== 开始解析同步最佳东方行业信息到数据库 ========== ");
+        String url = "https://dfws-file.veimg.cn/dict/ve/cn/common/options.json";
+        ResponseEntity<Map> responseEntity = restTemplate.getForEntity(url, Map.class);
+        log.debug("[httpRequest][RequestType({}) 的响应结果({})", responseEntity);
+        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
+            log.error("同步最佳东方行业信息错误");
+            return;
+        }
+        // 获取响应体中的数据
+        Map<String, Object> responseBody = responseEntity.getBody();
+
+        LinkedHashMap<String, Object> resultMap = (LinkedHashMap<String, Object>) responseBody.get("data");
+        // 行业分类
+        List<Map<String, Object>> companyIndustry = (List<Map<String, Object>>) resultMap.get("company-industry");
+        // 行业类型
+        List<Map<String, Object>> companyType = (List<Map<String, Object>>) resultMap.get("company-type");
+
+        // 清空数据
+        industryMapper.truncate();
+
+        Map<String, Long> idMap = new HashMap<>();
+        for (Map<String, Object> industry : companyIndustry) {
+            IndustryDO obj = IndustryDO.builder()
+                    .nameCn(industry.get("value").toString())
+                    .parentId(0L)
+                    .level(1).build();
+
+            industryMapper.insert(obj);
+            idMap.put(industry.get("id").toString(), obj.getId());
+        }
+        // 再保存companyType
+        for (Map<String, Object> industry : companyType) {
+            IndustryDO obj = IndustryDO.builder()
+                    .nameCn(industry.get("value").toString())
+                    .parentId(idMap.getOrDefault(industry.get("parent_id").toString(),0L))
+                    .level(2).build();
+
+            industryMapper.insert(obj);
+        }
+        log.info(" ========== 同步最佳东方行业信息完成 ========== ");
+    }
+
     /**
      * @param industry 职位对象
      * @param parentId 上级职位code

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio