Jelajahi Sumber

Merge remote-tracking branch 'origin/master'

xw 4 minggu lalu
induk
melakukan
e88d3ded30
100 mengubah file dengan 2781 tambahan dan 1193 penghapusan
  1. 13 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java
  2. 36 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCommentController.java
  3. 20 6
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  4. 8 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  5. 0 6
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  6. 15 0
      fs-admin/src/main/java/com/fs/course/controller/PublicCourseWatchStatisticsController.java
  7. 136 0
      fs-admin/src/main/java/com/fs/distribution/controller/DistributionController.java
  8. 30 0
      fs-admin/src/main/java/com/fs/distribution/controller/DistributionWithdrawController.java
  9. 10 1
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  10. 4 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  11. 5 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreCouponIssueScrmController.java
  12. 5 3
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreCouponScrmController.java
  13. 95 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductGroupBuyController.java
  14. 22 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java
  15. 174 0
      fs-admin/src/main/java/com/fs/hisStore/task/GroupBuyExpireTask.java
  16. 0 113
      fs-admin/src/main/java/com/fs/kdniao/config/KdniaoConfig.java
  17. 0 46
      fs-admin/src/main/java/com/fs/kdniao/controller/KdniaoEOrderController.java
  18. 78 0
      fs-admin/src/main/java/com/fs/kdniao/controller/KdniaoUniversalEOrderController.java
  19. 0 25
      fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoAddService.java
  20. 0 47
      fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoCommodity.java
  21. 0 181
      fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoEOrderRequest.java
  22. 0 62
      fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoEOrderResponse.java
  23. 0 55
      fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoPerson.java
  24. 0 192
      fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoSimpleOrderRequest.java
  25. 0 18
      fs-admin/src/main/java/com/fs/kdniao/service/IKdniaoEOrderService.java
  26. 0 214
      fs-admin/src/main/java/com/fs/kdniao/service/impl/KdniaoEOrderServiceImpl.java
  27. 0 114
      fs-admin/src/main/java/com/fs/kdniao/util/KdniaoUtil.java
  28. 0 45
      fs-admin/src/main/java/com/fs/kdniaoNew/controller/KdniaoUniversalEOrderController.java
  29. 58 0
      fs-admin/src/main/java/com/fs/user/controller/FsUserIntegralController.java
  30. 34 4
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  31. 36 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogAddwxController.java
  32. 1 1
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  33. 2 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  34. 23 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java
  35. 2 2
      fs-company/src/main/resources/application.yml
  36. 0 1
      fs-redis/src/main/java/com/fs/framework/config/RedisConfig.java
  37. 8 1
      fs-service/pom.xml
  38. 9 0
      fs-service/src/main/java/com/fs/app/service/AppPayService.java
  39. 90 1
      fs-service/src/main/java/com/fs/app/service/impl/AppPayServiceImpl.java
  40. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogAddwx.java
  41. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogCallphone.java
  42. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogSendmsg.java
  43. 12 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallees.java
  44. 23 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCallLogAddwxMapper.java
  45. 13 7
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java
  46. 16 0
      fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticCallLogAddwxService.java
  47. 18 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCallLogAddwxServiceImpl.java
  48. 11 10
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCalleesServiceImpl.java
  49. 43 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseMarketingEvent.java
  50. 49 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseShareLog.java
  51. 63 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseMarketingEventMapper.java
  52. 64 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseShareLogMapper.java
  53. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchCommentMapper.java
  54. 10 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  55. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseStudyMapper.java
  56. 5 0
      fs-service/src/main/java/com/fs/course/mapper/PublicCourseWatchStatisticsMapper.java
  57. 4 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentPageParam.java
  58. 5 1
      fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java
  59. 2 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseListUParam.java
  60. 2 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseOrderDoPayParam.java
  61. 2 0
      fs-service/src/main/java/com/fs/course/param/FsUserVipOrderPayUParam.java
  62. 61 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseMarketingEventService.java
  63. 61 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseShareLogService.java
  64. 1 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchCommentService.java
  65. 4 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseOrderService.java
  66. 4 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVipOrderService.java
  67. 3 0
      fs-service/src/main/java/com/fs/course/service/IPublicCourseWatchStatisticsService.java
  68. 96 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseMarketingEventServiceImpl.java
  69. 49 23
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  70. 95 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseShareLogServiceImpl.java
  71. 6 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchCommentServiceImpl.java
  72. 4 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCategoryServiceImpl.java
  73. 190 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseOrderServiceImpl.java
  74. 55 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  75. 2 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  76. 191 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVipOrderServiceImpl.java
  77. 17 0
      fs-service/src/main/java/com/fs/course/service/impl/PublicCourseWatchStatisticsServiceImpl.java
  78. 40 0
      fs-service/src/main/java/com/fs/course/vo/CatalogUserStudyVO.java
  79. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCoursePublicAppVO.java
  80. 49 0
      fs-service/src/main/java/com/fs/distribution/constant/DistributionConstants.java
  81. 51 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionAccount.java
  82. 52 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionCommissionRecord.java
  83. 44 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionConfig.java
  84. 43 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionUserRelation.java
  85. 34 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionWithdrawRecord.java
  86. 25 0
      fs-service/src/main/java/com/fs/distribution/dto/BindRelationRequest.java
  87. 24 0
      fs-service/src/main/java/com/fs/distribution/dto/CreateCommissionRequest.java
  88. 25 0
      fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountRequest.java
  89. 18 0
      fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountResponse.java
  90. 16 0
      fs-service/src/main/java/com/fs/distribution/dto/WithdrawApplyRequest.java
  91. 19 0
      fs-service/src/main/java/com/fs/distribution/dto/WithdrawAuditRequest.java
  92. 61 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionAccountMapper.java
  93. 23 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionCommissionRecordMapper.java
  94. 10 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionConfigMapper.java
  95. 23 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionUserRelationMapper.java
  96. 24 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionWithdrawRecordMapper.java
  97. 53 0
      fs-service/src/main/java/com/fs/distribution/service/DistributionService.java
  98. 12 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionAccountService.java
  99. 16 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionCommissionRecordService.java
  100. 4 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionConfigService.java

+ 13 - 2
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java

@@ -90,6 +90,17 @@ public class FsUserCourseCategoryController extends BaseController
         return AjaxResult.success(list);
     }
 
+    /**
+     * 查询课堂分类列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseCategory:list')")
+    @GetMapping("/publicList")
+    public AjaxResult publicList(FsUserCourseCategory fsUserCourseCategory)
+    {
+        List<FsUserCourseCategory> list = fsUserCourseCategoryService.selectFsUserCourseCategoryList(fsUserCourseCategory);
+        return AjaxResult.success(list);
+    }
+
     /**
      * 导出课堂分类列表
      */
@@ -178,10 +189,10 @@ public class FsUserCourseCategoryController extends BaseController
                     continue;
                 }
                 if (fsUserCourseMapper.countCourseByCategoryId(cateId) > 0) {
-                    return AjaxResult.error("该分类下有关联课程,不能删除");
+                    return AjaxResult.error("该分类下有关联课程或者素材,不能删除");
                 }
                 if (fsVideoResourceMapper.countVideoResourceByCourseCategoryId(cateId) > 0) {
-                    return AjaxResult.error("该分类下有关联课程,不能删除");
+                    return AjaxResult.error("该分类下有关联课程或者素材,不能删除");
                 }
             }
         }

+ 36 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCommentController.java

@@ -3,8 +3,10 @@ package com.fs.course.controller;
 import java.util.List;
 
 import com.fs.course.param.FsUserCourseCommentParam;
+import com.fs.course.param.FsCourseWatchCommentPageParam;
 import com.fs.course.vo.FsUserCourseCommentListVO;
 import com.fs.course.vo.FsUserCourseCommentVO;
+import com.fs.course.vo.FsCourseWatchCommentListVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -21,6 +23,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.enums.BusinessType;
 import com.fs.course.domain.FsUserCourseComment;
 import com.fs.course.service.IFsUserCourseCommentService;
+import com.fs.course.service.IFsCourseWatchCommentService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
 
@@ -37,6 +40,9 @@ public class FsUserCourseCommentController extends BaseController
     @Autowired
     private IFsUserCourseCommentService fsUserCourseCommentService;
 
+    @Autowired
+    private IFsCourseWatchCommentService fsCourseWatchCommentService;
+
     /**
      * 查询课堂评论列表
      */
@@ -49,6 +55,23 @@ public class FsUserCourseCommentController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询公域视频下的评论列表(给公域课页面复用)。
+     * <p>
+     * 这里复用的是 fs_course_watch_comment(看课评论)表的查询逻辑;
+     * 由 fs-service 的 Mapper 在 cateType=1(公域看课评论)时完成公域视频范围过滤。
+     * </p>
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:list')")
+    @GetMapping("/publicCourseVideoWatchComment/list")
+    public TableDataInfo publicCourseVideoWatchCommentList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam)
+    {
+        startPage();
+        // 前端传 cateType=1、isAll=true 时,Mapper 会自动限定“公域课程 -> 对应视频 -> 评论”范围
+        List<FsCourseWatchCommentListVO> list = fsCourseWatchCommentService.selectFsCourseWatchCommentPublicList(fsCourseWatchCommentPageParam);
+        return getDataTable(list);
+    }
+
     /**
      * 导出课堂评论列表
      */
@@ -62,6 +85,19 @@ public class FsUserCourseCommentController extends BaseController
         return util.exportExcel(list, "课堂评论数据");
     }
 
+    /**
+     * 导出公域看课评论列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:export')")
+    @Log(title = "公域看课评论", businessType = BusinessType.EXPORT)
+    @GetMapping("/publicCourseVideoWatchComment/export")
+    public AjaxResult exportPublicCourseWatchComment(FsCourseWatchCommentPageParam param)
+    {
+        List<FsCourseWatchCommentListVO> list = fsCourseWatchCommentService.selectFsCourseWatchCommentPublicList(param);
+        ExcelUtil<FsCourseWatchCommentListVO> util = new ExcelUtil<FsCourseWatchCommentListVO>(FsCourseWatchCommentListVO.class);
+        return util.exportExcel(list, "公域看课评论数据");
+    }
+
     /**
      * 获取课堂评论详细信息
      */

+ 20 - 6
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -1,7 +1,9 @@
 package com.fs.course.controller;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.controller.BaseController;
@@ -11,6 +13,7 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.cache.PublicCourseAppCacheNames;
 import com.fs.course.config.CourseConfig;
@@ -22,14 +25,20 @@ import com.fs.course.vo.FsUserCourseListPVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.dto.StoreOrderProductDTO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportZMVO;
 import com.fs.qw.param.FsUserCourseRedPageParam;
 import com.fs.sop.service.IQwSopTempService;
 import com.fs.system.service.ISysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * 课程Controller
@@ -76,6 +85,9 @@ public class FsUserCourseController extends BaseController {
         return getDataTable(list);
     }
 
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     /**
      * 查询公域课程列表
      */
@@ -83,12 +95,14 @@ public class FsUserCourseController extends BaseController {
     @GetMapping("/publicList")
     public TableDataInfo publicList(FsUserCourse fsUserCourse) {
         startPage();
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        Long userId = loginUser.getUser().getUserId();
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-            fsUserCourse.setUserId(userId);
+        if(!"北京卓美".equals(signProjectName)){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            Long userId = loginUser.getUser().getUserId();
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+            if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
+                fsUserCourse.setUserId(userId);
+            }
         }
         List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
         return getDataTable(list);

+ 8 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -248,6 +248,14 @@ public class FsUserCourseVideoController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/getPublicVideoListByCourseId")
+    public TableDataInfo getPublicVideoListByCourseId(FsUserCourseVideo fsUserCourseVideo)
+    {
+        startPage();
+        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+        return getDataTable(list);
+    }
+
     @GetMapping("/getVideoListByCourseIdAll")
     public TableDataInfo getVideoListByCourseIdAll(Long courseId)
     {

+ 0 - 6
fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java

@@ -114,12 +114,6 @@ public class FsVideoResourceController extends BaseController {
             videoType = 1;
         }
         params.put("videoType", videoType);
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-            params.put("userId", loginUser.getUser().getUserId());
-        }
         PageHelper.startPage(pageNum, pageSize);
         List<FsVideoResourceVO> list = fsVideoResourceService.selectPublicVideoResourceListByMap(params);
         return getDataTable(list);

+ 15 - 0
fs-admin/src/main/java/com/fs/course/controller/PublicCourseWatchStatisticsController.java

@@ -8,12 +8,14 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
 import com.fs.course.service.IPublicCourseWatchStatisticsService;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
@@ -61,4 +63,17 @@ public class PublicCourseWatchStatisticsController extends BaseController {
         ExcelUtil<PublicCourseWatchStatCatalogVO> util = new ExcelUtil<>(PublicCourseWatchStatCatalogVO.class);
         return util.exportExcel(list, "公域看课统计-目录数据");
     }
+
+    /**
+     * 查询目录下用户学习记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:publicCourseWatchStat:list')")
+    @GetMapping("/catalog/userStudy")
+    public TableDataInfo catalogUserStudy(@RequestParam Long videoId,
+                                          @RequestParam(required = false) String nickName,
+                                          @RequestParam(required = false) String phone) {
+        startPage();
+        List<CatalogUserStudyVO> list = publicCourseWatchStatisticsService.listCatalogUserStudy(videoId, nickName, phone);
+        return getDataTable(list);
+    }
 }

+ 136 - 0
fs-admin/src/main/java/com/fs/distribution/controller/DistributionController.java

@@ -0,0 +1,136 @@
+package com.fs.distribution.controller;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.distribution.domain.DistributionConfig;
+import com.fs.distribution.dto.*;
+import com.fs.distribution.service.DistributionService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 分销控制器
+ */
+@RestController
+@RequestMapping("/distribution")
+public class DistributionController {
+
+    @Resource
+    private DistributionService distributionService;
+
+    /**
+     * 绑定分销关系
+     *
+     * 一般不建议前端单独调用这个接口,
+     * 最好是在注册成功后,后端注册逻辑内部调用 distributionService.bindRelation()
+     */
+    @PostMapping("/bind")
+    public AjaxResult bind(@RequestBody BindRelationRequest request) {
+        distributionService.bindRelation(request);
+        return AjaxResult.success();
+    }
+
+
+    /**
+     * 查询分销账户列表
+     */
+    @PostMapping("/account/list")
+    public AjaxResult accountList(@RequestBody QueryDistributionAccountRequest queryDistributionAccountRequest) {
+        return AjaxResult.success(distributionService.getAccountList(queryDistributionAccountRequest));
+
+    }
+
+    /**
+     * 我的分销账户
+     */
+    @GetMapping("/account/{userId}")
+    public AjaxResult account(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getAccount(userId));
+    }
+
+    /**
+     * 我的分销关系
+     */
+    @GetMapping("/relation/{userId}")
+    public AjaxResult relation(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getRelation(userId));
+    }
+
+    /**
+     * 我的佣金明细
+     */
+    @GetMapping("/commission/list/{userId}")
+    public AjaxResult commissionList(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getCommissionList(userId));
+    }
+
+    /**
+     * 申请提现
+     */
+    @PostMapping("/withdraw/apply")
+    public AjaxResult applyWithdraw(@RequestBody WithdrawApplyRequest request) {
+        distributionService.applyWithdraw(request);
+        return AjaxResult.success("提现申请成功");
+    }
+
+    /**
+     * 我的提现记录
+     */
+    @GetMapping("/withdraw/list/{userId}")
+    public AjaxResult withdrawList(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getWithdrawList(userId));
+    }
+
+
+
+    /**
+     * 后台审核提现
+     */
+    @PostMapping("/withdraw/audit")
+    public AjaxResult auditWithdraw(@RequestBody WithdrawAuditRequest request) {
+        distributionService.auditWithdraw(request);
+        return AjaxResult.success("审核成功");
+    }
+
+    /**
+     * 订单支付成功后生成佣金
+     *
+     * 这个接口一般不暴露给前端,
+     * 应该在订单支付回调成功后由订单服务内部调用。
+     */
+    @PostMapping("/commission/createAfterPay")
+    public AjaxResult createCommissionAfterPay(@RequestBody CreateCommissionRequest request) {
+        distributionService.createCommissionAfterPay(request);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 退款后佣金失效
+     *
+     * 这个接口一般不暴露给前端,
+     * 应该在退款成功后由订单服务内部调用。
+     */
+    @PostMapping("/commission/invalid/{orderId}")
+    public AjaxResult invalidCommission(@PathVariable Long orderId) {
+        distributionService.invalidCommissionByRefund(orderId);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 查询分销配置
+     */
+    @GetMapping("/config")
+    public AjaxResult getConfig() {
+        return AjaxResult.success(distributionService.getConfig());
+    }
+
+    /**
+     * 保存分销配置
+     */
+    @PostMapping("/config")
+    public AjaxResult saveConfig(@RequestBody DistributionConfig config) {
+        distributionService.saveConfig(config);
+        return AjaxResult.success("保存成功");
+    }
+
+}

+ 30 - 0
fs-admin/src/main/java/com/fs/distribution/controller/DistributionWithdrawController.java

@@ -0,0 +1,30 @@
+package com.fs.distribution.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.distribution.domain.DistributionWithdrawRecord;
+import com.fs.distribution.service.IDistributionWithdrawRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/withdraw")
+public class DistributionWithdrawController extends BaseController {
+
+    @Autowired
+    private IDistributionWithdrawRecordService distributionWithdrawService;
+
+    /**
+     * 查询待审核提现记录
+     */
+    @GetMapping("/audit/list")
+    public TableDataInfo auditList(DistributionWithdrawRecord query) {
+        startPage();
+        List<DistributionWithdrawRecord> list = distributionWithdrawService.selectWithdrawList(query);
+        return getDataTable(list);
+    }
+}

+ 10 - 1
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -16,6 +16,7 @@ import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.uuid.IdUtils;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.his.domain.*;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
@@ -143,6 +144,10 @@ public class FsIntegralOrderController extends BaseController
         return sysRole;
     }
 
+    @Autowired
+    // 260506 积分订单没有接入erp 暂时这么处理 卓美 需要对订单进行发货处理
+    private CloudHostProper cloudHostProper;
+
     /**
      * 导出积分商品订单列表 卓美个性化导出
      */
@@ -167,7 +172,11 @@ public class FsIntegralOrderController extends BaseController
         for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
             if (!(sysRole.getIsCheckPhone()==1)){
                 // 加密手机号
-                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+
+                // 260506 积分订单没有接入erp 暂时这么处理 卓美 需要对订单进行发货处理
+                if (!"北京卓美".equals(cloudHostProper.getCompanyName())) {
+                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+                }
             }
             if (StringUtils.isNotEmpty(vo.getItemJson())) {
                 try {

+ 4 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -138,6 +138,8 @@ public class FsStoreAfterSalesScrmController extends BaseController
                                 zmvo.setCompanyUserNickName(vo.getCompanyUserNickName());
                                 zmvo.setRefundTime(vo.getCreateTime());
                                 zmvo.setRefundMoney(vo.getRefundAmount());
+                                zmvo.setRefundType(vo.getRefundAmount() != null && vo.getPayPrice() != null
+                                        ? (vo.getRefundAmount().compareTo(vo.getPayPrice()) < 0 ? "部分退款" : "全额退款") : "");
                                 zmvo.setBankTransactionId(vo.getBankTransactionId());
                                 zmvo.setReasons(vo.getReasons());
                                 zmvo.setExplains(vo.getExplains());
@@ -159,6 +161,8 @@ public class FsStoreAfterSalesScrmController extends BaseController
         }
         for (FsStoreAfterSalesVO vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setRefundType(vo.getRefundAmount() != null && vo.getPayPrice() != null
+                    ? (vo.getRefundAmount().compareTo(vo.getPayPrice()) < 0 ? "部分退款" : "全额退款") : "");
         }
 
         ExcelUtil<FsStoreAfterSalesVO> util = new ExcelUtil<FsStoreAfterSalesVO>(FsStoreAfterSalesVO.class);

+ 5 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreCouponIssueScrmController.java

@@ -95,4 +95,9 @@ public class FsStoreCouponIssueScrmController extends BaseController
     {
         return toAjax(fsStoreCouponIssueService.deleteFsStoreCouponIssueByIds(ids));
     }
+
+    /**
+     *  用户领取优惠券
+     */
+
 }

+ 5 - 3
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreCouponScrmController.java

@@ -134,9 +134,11 @@ public class FsStoreCouponScrmController extends BaseController
             issue.setCouponType(coupon.getType());
             issue.setStartTime(publishParam.getStartTime());
             issue.setLimitTime(publishParam.getLimitTime());
-            issue.setTotalCount(publishParam.getTotalCount());
-            issue.setRemainCount(0);
-            issue.setIsPermanent(0);
+            issue.setIsPermanent(publishParam.getIsPermanent());
+            if (publishParam.getIsPermanent()==0){
+                issue.setTotalCount(publishParam.getTotalCount());
+                issue.setRemainCount(publishParam.getTotalCount());
+            }
             issue.setStatus(1);
             issue.setCreateTime(new Date());
             fsStoreCouponIssueService.insertFsStoreCouponIssue(issue);

+ 95 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductGroupBuyController.java

@@ -0,0 +1,95 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.hisStore.service.IFsStoreProductGroupBuyService;
+import com.fs.hisStore.vo.FsStoreGroupBuyListVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 后台管理 - 限时团购管理
+ * <p>只读查询,不做 CRUD。团购活动的新增/修改沿用 fs_store_product_activity(activity_type=8)。</p>
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Api("后台-限时团购管理")
+@RestController
+@RequestMapping("/store/store/productGroupBuy")
+public class FsStoreProductGroupBuyController extends BaseController {
+
+    @Autowired
+    private IFsStoreProductGroupBuyService groupBuyService;
+
+    /**
+     * 团购列表(分页)
+     * 前端传:groupNo/productId/productName/status/beginTime/endTime 皆可选
+     */
+    @ApiOperation("团购列表")
+    @PreAuthorize("@ss.hasPermi('store:productGroupBuy:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(value = "groupNo",    required = false) String groupNo,
+                              @RequestParam(value = "productId",  required = false) Long productId,
+                              @RequestParam(value = "productName",required = false) String productName,
+                              @RequestParam(value = "status",     required = false) Integer status,
+                              @RequestParam(value = "beginTime",  required = false) String beginTime,
+                              @RequestParam(value = "endTime",    required = false) String endTime) {
+        startPage();
+        List<FsStoreGroupBuyListVO> list = groupBuyService.selectGroupBuyListForAdmin(
+                groupNo, productId, productName, status, beginTime, endTime);
+        return getDataTable(list);
+    }
+
+    /**
+     * 团购详情(含团员列表及订单状态)
+     */
+    @ApiOperation("团购详情(含团员+订单状态)")
+    @PreAuthorize("@ss.hasPermi('store:productGroupBuy:query')")
+    @GetMapping(value = "/{id}")
+    public R getInfo(@PathVariable("id") Long id) {
+        FsStoreGroupBuyListVO detail = groupBuyService.selectGroupBuyDetailForAdmin(id);
+        if (detail == null) {
+            return R.error("团购不存在或已删除");
+        }
+        return R.ok().put("data", detail);
+    }
+
+    /**
+     * 按商品ID查历史拼团(商品详情页的"历史拼团"入口)
+     */
+    @ApiOperation("按商品ID查历史拼团")
+    @PreAuthorize("@ss.hasPermi('store:productGroupBuy:list')")
+    @GetMapping("/listByProduct/{productId}")
+    public TableDataInfo listByProduct(@PathVariable("productId") Long productId) {
+        startPage();
+        List<FsStoreGroupBuyListVO> list = groupBuyService.selectGroupBuyListByProduct(productId);
+        return getDataTable(list);
+    }
+
+    /**
+     * 团员列表(单独接口,用于详情页弹出/下钻查看)
+     * 也可复用 /{id} 接口,这里保留独立入口方便前端按需调用
+     */
+    @ApiOperation("团员列表")
+    @PreAuthorize("@ss.hasPermi('store:productGroupBuy:query')")
+    @GetMapping("/members/{id}")
+    public AjaxResult members(@PathVariable("id") Long id) {
+        FsStoreGroupBuyListVO detail = groupBuyService.selectGroupBuyDetailForAdmin(id);
+        if (detail == null) {
+            return AjaxResult.error("团购不存在");
+        }
+        return AjaxResult.success(detail.getMembers());
+    }
+}

+ 22 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java

@@ -6,8 +6,10 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
@@ -184,6 +186,26 @@ public class FsStoreProductPackageScrmController extends BaseController
         return toAjax(fsStoreProductPackageService.deleteFsStoreProductPackageByIds(packageIds));
     }
 
+    /**
+     * 一键复制商品组合套餐
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:add')")
+    @Log(title = "商品组合套餐", businessType = BusinessType.INSERT)
+    @PostMapping("/copy/{packageId}")
+    public AjaxResult copyPackage(@PathVariable("packageId") Long packageId)
+    {
+        FsStoreProductPackageScrm source = fsStoreProductPackageService.selectFsStoreProductPackageById(packageId);
+        if (source == null) {
+            return AjaxResult.error("未找到对应的套餐信息");
+        }
+        // 清空主键,让数据库自增生成新ID
+        source.setPackageId(null);
+        // 标题加"-副本"后缀
+        source.setTitle(source.getTitle() + "-副本");
+        // 设置当前登录用户的公司和部门
+        return toAjax(fsStoreProductPackageService.insertFsStoreProductPackage(source));
+    }
+
 
     @GetMapping("/listBySearch")
     public R listBySearCh(FsStoreProductPackageScrm productPackage)

+ 174 - 0
fs-admin/src/main/java/com/fs/hisStore/task/GroupBuyExpireTask.java

@@ -0,0 +1,174 @@
+package com.fs.hisStore.task;
+
+import com.fs.common.core.domain.R;
+import com.fs.hisStore.domain.FsStoreProductGroupBuy;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
+import com.fs.hisStore.mapper.FsStoreProductGroupBuyMapper;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 团购超时兜底定时任务
+ * <p>
+ * 职责两件事:
+ * <ol>
+ *   <li><b>主路径</b>:扫“截团时间已过但还没成团”的团,把团判失败,并把团内已付款的订单自动退款</li>
+ *   <li><b>孤儿订单兜底</b>:扫“团已判失败但团内还有未退款的已付款订单”,补上退款;
+ *       对应“支付回调卡在 endTime 后到达”以及“上一轮退款失败”的极端场景</li>
+ * </ol>
+ * <p>
+ * 几个关键点:
+ * <ul>
+ *   <li>未成团的订单根本没推过 ERP,退款只走本地 + 调支付通道打钱,<b>不调 ERP 取消</b>;
+ *       service 层的 refundOrderMoney 里已经加了团购未成团放行,走到那儿自然跳过 ERP 分支</li>
+ *   <li>标团失败用 CAS(status=0 AND end_time<=now()),多实例并发跑也只会有一个任务赢得处置权,
+ *       其他实例拿到 0 影响行数直接跳过,不会重复退款</li>
+ *   <li>查团内订单复用已有 {@code selectOrderIdsByGroupBuyId},
+ *       条件是 status=1 AND extend_order_id is null——刚好就是“已付款没推 ERP”的未成团订单,
+ *       退成功后订单 status 变 -2,下一轮扫描自然漏掉,幂等</li>
+ *   <li>孤儿订单扫描用 {@code selectOrphanOrderIdsInFailedGroups},条件是 group.status=2 +
+ *       order.status=1 + refund_status=0 + extend_order_id is null,退完 status 也变 -2自然漏掉</li>
+ *   <li>未支付的团购订单由原订单超时取消任务负责,那边已经接入 releaseGroupSlot,
+ *       不用这里重复处理</li>
+ * </ul>
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Slf4j
+@Component("groupBuyExpireTask")
+public class GroupBuyExpireTask {
+
+    /** 单轮最多处理多少个过期团,防止偶尔积压时把一轮跑爆 */
+    private static final int BATCH_LIMIT = 50;
+
+    @Autowired
+    private FsStoreProductGroupBuyMapper groupBuyMapper;
+
+    @Autowired
+    private FsStoreOrderScrmMapper fsStoreOrderMapper;
+
+    @Autowired
+    private IFsStoreOrderScrmService orderService;
+
+    /**
+     * 每分钟扫一次。触发频率跟 endTime 的精度匹配即可,不用更密。
+     * 定时注解先留着但注释掉,生产上线前人工启用,免得测试环境误触发。
+     */
+//    @Scheduled(fixedRate = 60000)
+    public void handleExpiredGroups() {
+        // 第一段:处理新过期的团——标失败 + 退款团内订单
+        processExpiredGroups();
+        // 第二段:扫一遍孤儿订单——支付回调卡在 endTime 后到达、或上轮退款失败残留下的订单兜底退款
+        processOrphanOrders();
+    }
+
+    private void processExpiredGroups() {
+        List<FsStoreProductGroupBuy> expiredList;
+        try {
+            expiredList = groupBuyMapper.selectExpiredUnformedGroups(BATCH_LIMIT);
+        } catch (Exception e) {
+            log.error("[GroupBuyExpireTask] 查询过期团失败", e);
+            return;
+        }
+        if (expiredList == null || expiredList.isEmpty()) {
+            return;
+        }
+
+        log.info("[GroupBuyExpireTask] 发现 {} 个过期未成团的团,开始处理", expiredList.size());
+
+        int handled = 0;
+        for (FsStoreProductGroupBuy group : expiredList) {
+            try {
+                if (handleOneGroup(group)) {
+                    handled++;
+                }
+            } catch (Exception e) {
+                // 单团出错不影响其他团,下一轮还会扫到,异常只打日志
+                log.error("[GroupBuyExpireTask] 团 {} 处理异常,下一轮重试", group.getId(), e);
+            }
+        }
+        log.info("[GroupBuyExpireTask] 本轮成功处理 {} 个团", handled);
+    }
+
+    /**
+     * 孤儿订单扫描:扫走“团已失败但订单还没退款”的残渣。
+     * <p>两种典型场景会走到这里:
+     * <ul>
+     *   <li>用户在 endTime-1s 下单,支付回调延迟,到达时团已被上一轮任务判失败;
+     *       finishPaidOrderInGroup 检测到 status=2 直接 return,订单留在这儿等退款</li>
+     *   <li>上一轮 processExpiredGroups 调 refundOrderMoney 时支付通道抛异常,订单还未退款成功</li>
+     * </ul>
+     */
+    private void processOrphanOrders() {
+        List<Long> orphanOrderIds;
+        try {
+            orphanOrderIds = groupBuyMapper.selectOrphanOrderIdsInFailedGroups(BATCH_LIMIT);
+        } catch (Exception e) {
+            log.error("[GroupBuyExpireTask] 查询孤儿订单失败", e);
+            return;
+        }
+        if (orphanOrderIds == null || orphanOrderIds.isEmpty()) {
+            return;
+        }
+
+        log.info("[GroupBuyExpireTask] 发现 {} 个孤儿订单(团已失败但订单未退款),开始补退", orphanOrderIds.size());
+        for (Long orderId : orphanOrderIds) {
+            try {
+                R r = orderService.refundOrderMoney(orderId);
+                if (r != null && "0".equals(String.valueOf(r.get("code")))) {
+                    log.info("[GroupBuyExpireTask] 孤儿订单 {} 补退成功", orderId);
+                } else {
+                    log.warn("[GroupBuyExpireTask] 孤儿订单 {} 补退失败:{}",
+                            orderId, r != null ? r.get("msg") : "null");
+                }
+            } catch (Exception e) {
+                log.error("[GroupBuyExpireTask] 孤儿订单 {} 补退异常,待下一轮重试", orderId, e);
+            }
+        }
+    }
+
+    /**
+     * 处理单个过期团:原子标失败 + 循环退款团内已付款订单
+     *
+     * @return true=本任务确实处理了这个团 false=被别的实例抢走/或根本没订单要退
+     */
+    private boolean handleOneGroup(FsStoreProductGroupBuy group) {
+        // CAS 标失败,抢不到就让给别人,不往下走
+        int affected = groupBuyMapper.markGroupFailed(group.getId());
+        if (affected == 0) {
+            log.info("[GroupBuyExpireTask] 团 {} 已被别的任务处理,跳过", group.getId());
+            return false;
+        }
+        log.info("[GroupBuyExpireTask] 团 {} 已标记为拼团失败(end_time={})", group.getId(), group.getEndTime());
+
+        // 拿团内所有"已付款 + 没推过 ERP"的订单,这批就是要自动退款的
+        List<Long> orderIds = fsStoreOrderMapper.selectOrderIdsByGroupBuyId(group.getId());
+        if (orderIds == null || orderIds.isEmpty()) {
+            log.info("[GroupBuyExpireTask] 团 {} 无需退款的订单,收工", group.getId());
+            return true;
+        }
+
+        for (Long orderId : orderIds) {
+            try {
+                R r = orderService.refundOrderMoney(orderId);
+                if (r != null && "0".equals(String.valueOf(r.get("code")))) {
+                    log.info("[GroupBuyExpireTask] 团 {} 订单 {} 自动退款成功", group.getId(), orderId);
+                } else {
+                    // 退款失败不中断,下一轮还会扫到这个订单重试
+                    log.warn("[GroupBuyExpireTask] 团 {} 订单 {} 自动退款失败:{}",
+                            group.getId(), orderId, r != null ? r.get("msg") : "null");
+                }
+            } catch (Exception e) {
+                log.error("[GroupBuyExpireTask] 团 {} 订单 {} 退款异常,待下一轮重试",
+                        group.getId(), orderId, e);
+            }
+        }
+        return true;
+    }
+}

+ 0 - 113
fs-admin/src/main/java/com/fs/kdniao/config/KdniaoConfig.java

@@ -1,113 +0,0 @@
-package com.fs.kdniao.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * 快递鸟配置类
- */
-@Data
-@Component
-@ConfigurationProperties(prefix = "kdniao")
-public class KdniaoConfig {
-
-    /**
-     * 快递鸟用户ID
-     */
-    private String eBusinessID;
-
-    /**
-     * 快递鸟API Key
-     */
-    private String apiKey;
-
-    /**
-     * 电子面单接口地址
-     */
-    private String reqURL;
-
-    /**
-     * 默认电子面单账号配置
-     */
-    private Account account;
-
-    /**
-     * 默认发件人配置
-     */
-    private Sender sender;
-
-    /**
-     * 电子面单账号配置
-     */
-    @Data
-    public static class Account {
-
-        /**
-         * 电子面单账号
-         */
-        private String customerName;
-
-        /**
-         * 电子面单密码
-         */
-        private String customerPwd;
-
-        /**
-         * 发件网点编码
-         */
-        private String sendSite;
-
-        /**
-         * 月结号
-         */
-        private String monthCode;
-    }
-
-    /**
-     * 默认发件人配置
-     */
-    @Data
-    public static class Sender {
-
-        /**
-         * 发件公司
-         */
-        private String company;
-
-        /**
-         * 发件人姓名
-         */
-        private String name;
-
-        /**
-         * 发件人手机号
-         */
-        private String mobile;
-
-        /**
-         * 发件省
-         */
-        private String provinceName;
-
-        /**
-         * 发件市
-         */
-        private String cityName;
-
-        /**
-         * 发件区/县
-         */
-        private String expAreaName;
-
-        /**
-         * 发件详细地址
-         */
-        private String address;
-
-        /**
-         * 发件邮编
-         */
-        private String postCode;
-    }
-}

+ 0 - 46
fs-admin/src/main/java/com/fs/kdniao/controller/KdniaoEOrderController.java

@@ -1,46 +0,0 @@
-package com.fs.kdniao.controller;
-
-import com.fs.common.annotation.Log;
-import com.fs.common.core.controller.BaseController;
-import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.enums.BusinessType;
-import com.fs.kdniao.domain.KdniaoEOrderResponse;
-import com.fs.kdniao.domain.KdniaoSimpleOrderRequest;
-import com.fs.kdniao.service.IKdniaoEOrderService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-/**
- * 快递鸟电子面单控制器
- */
-@RestController
-@RequestMapping("/kdniao/eorder")
-public class KdniaoEOrderController extends BaseController {
-
-    @Autowired
-    private IKdniaoEOrderService kdniaoEOrderService;
-
-    /**
-     * 简化参数下单接口
-     * 前端只需要传常用业务参数,后端自动组装 RequestData
-     */
-    @Log(title = "快递鸟电子面单", businessType = BusinessType.INSERT)
-    @PostMapping("/submit")
-    public AjaxResult submit(@RequestBody KdniaoSimpleOrderRequest request) {
-        try {
-            KdniaoEOrderResponse response = kdniaoEOrderService.submitSimpleOrder(request);
-
-            if (Boolean.TRUE.equals(response.getSuccess()) && "100".equals(response.getResultCode())) {
-                return AjaxResult.success("下单成功", response);
-            }
-
-            if ("106".equals(response.getResultCode())) {
-                return AjaxResult.error("订单号重复,快递鸟返回:该订单号已下单成功");
-            }
-
-            return AjaxResult.error("下单失败:" + response.getReason(), response);
-        } catch (Exception e) {
-            return AjaxResult.error("电子面单下单异常:" + e.getMessage());
-        }
-    }
-}

+ 78 - 0
fs-admin/src/main/java/com/fs/kdniao/controller/KdniaoUniversalEOrderController.java

@@ -0,0 +1,78 @@
+package com.fs.kdniao.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.kdniao.domain.KdniaoSubmitCommand;
+import com.fs.kdniao.domain.KdniaoUniversalResponse;
+import com.fs.kdniao.domain.KdniaoWaybill;
+import com.fs.kdniao.service.IKdniaoUniversalEOrderService;
+import com.fs.kdniao.service.KdniaoWaybillService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 快递鸟统一电子面单控制器
+ */
+@RestController
+@RequestMapping("/kdniao/universal/eorder")
+public class KdniaoUniversalEOrderController extends BaseController {
+
+    @Autowired
+    private IKdniaoUniversalEOrderService kdniaoUniversalEOrderService;
+
+    @Autowired
+    private KdniaoWaybillService kdniaoWaybillService;
+
+
+    /**
+     * 统一下单
+     */
+    @Log(title = "快递鸟统一电子面单", businessType = BusinessType.INSERT)
+    @PostMapping("/submit")
+    public AjaxResult submit(@RequestBody KdniaoSubmitCommand command) {
+        try {
+            // 1. 先查本地是否已有面单,避免重复下单
+            KdniaoWaybill exist = kdniaoWaybillService.selectByOrderCode(command.getBizOrderNo());
+            if (exist != null) {
+                return AjaxResult.success("该订单已生成面单,返回已保存数据", exist);
+            }
+
+            KdniaoUniversalResponse response = kdniaoUniversalEOrderService.submit(command);
+
+            if (Boolean.TRUE.equals(response.getSuccess()) && "100".equals(response.getResultCode())) {
+                KdniaoWaybill waybill = kdniaoWaybillService.saveWaybill(command, response);
+                return AjaxResult.success("下单成功", waybill);
+            }
+
+            // 订单重复:优先查本地已保存面单
+            if ("106".equals(response.getResultCode())) {
+                KdniaoWaybill dbWaybill = kdniaoWaybillService.selectByOrderCode(command.getBizOrderNo());
+                if (dbWaybill != null) {
+                    return AjaxResult.success("该订单号已存在面单,返回本地保存数据", dbWaybill);
+                }
+                return AjaxResult.error("订单号重复,快递鸟返回:该订单号已下单成功,但本地未保存到面单");
+            }
+
+            return AjaxResult.error("下单失败:" + response.getReason(), response);
+        } catch (Exception e) {
+            return AjaxResult.error("下单异常:" + e.getMessage());
+        }
+    }
+
+
+    /**
+     * 查看订单面单信息
+     */
+//    @PreAuthorize("@ss.hasPermi('store:storeOrder:query')")
+    @GetMapping("/waybill/{orderCode}")
+    public AjaxResult getWaybillInfo(@PathVariable Long orderCode) {
+        KdniaoWaybill waybill = kdniaoWaybillService.selectByOrderCode(String.valueOf(orderCode));
+        if (waybill == null) {
+            return AjaxResult.error("该订单暂无面单信息");
+        }
+
+        return AjaxResult.success(waybill);
+    }
+}

+ 0 - 25
fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoAddService.java

@@ -1,25 +0,0 @@
-package com.fs.kdniao.domain;
-
-import lombok.Data;
-
-/**
- * 增值服务(保价、代收货款等)
- */
-@Data
-public class KdniaoAddService {
-
-    /**
-     * 服务名称(如 COD、INSURE)
-     */
-    private String Name;
-
-    /**
-     * 服务值(金额/参数)
-     */
-    private String Value;
-
-    /**
-     * 客户标识
-     */
-    private String CustomerID;
-}

+ 0 - 47
fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoCommodity.java

@@ -1,47 +0,0 @@
-package com.fs.kdniao.domain;
-
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-/**
- * 商品信息
- */
-@Data
-public class KdniaoCommodity {
-
-    /**
-     * 商品名称
-     */
-    private String GoodsName;
-
-    /**
-     * 商品编码
-     */
-    private String GoodsCode;
-
-    /**
-     * 商品数量
-     */
-    private Integer Goodsquantity;
-
-    /**
-     * 商品价格
-     */
-    private BigDecimal GoodsPrice;
-
-    /**
-     * 商品重量
-     */
-    private BigDecimal GoodsWeight;
-
-    /**
-     * 商品描述
-     */
-    private String GoodsDesc;
-
-    /**
-     * 商品体积
-     */
-    private BigDecimal GoodsVol;
-}

+ 0 - 181
fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoEOrderRequest.java

@@ -1,181 +0,0 @@
-package com.fs.kdniao.domain;
-
-import lombok.Data;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-/**
- * 快递鸟电子面单下单请求对象(RequestData)
- */
-@Data
-public class KdniaoEOrderRequest {
-
-    /**
-     * 订单编号(必须唯一)
-     * 不能重复,否则返回106(幂等控制字段)
-     */
-    private String OrderCode;
-
-    /**
-     * 快递公司编码(如 SF、YTO、ZTO、JDKY 等)
-     */
-    private String ShipperCode;
-
-    /**
-     * 电子面单账号(部分快递必填,如顺丰、圆通等)
-     */
-    private String CustomerName;
-
-    /**
-     * 电子面单密码
-     */
-    private String CustomerPwd;
-
-    /**
-     * 发件网点编码
-     */
-    private String SendSite;
-
-    /**
-     * 发件业务员
-     */
-    private String SendStaff;
-
-    /**
-     * 月结账号
-     */
-    private String MonthCode;
-
-    /**
-     * 运费支付方式
-     * 1:现付
-     * 2:到付
-     * 3:月结
-     * 4:第三方付(部分公司支持)
-     */
-    private Integer PayType;
-
-    /**
-     * 快递业务类型(不同快递公司不同)
-     */
-    private String ExpType;
-
-    /**
-     * 发件人信息(必填)
-     */
-    private KdniaoPerson Sender;
-
-    /**
-     * 收件人信息(必填)
-     */
-    private KdniaoPerson Receiver;
-
-    /**
-     * 包裹数量(>=1)
-     */
-    private Integer Quantity;
-
-    /**
-     * 总重量(kg)
-     * 京东/快运类必填
-     */
-    private BigDecimal Weight;
-
-    /**
-     * 总体积(m³)
-     * 京东/快运类必填
-     */
-    private BigDecimal Volume;
-
-    /**
-     * 运费(部分到付场景必填)
-     */
-    private BigDecimal Cost;
-
-    /**
-     * 其他费用
-     */
-    private BigDecimal OtherCost;
-
-    /**
-     * 增值服务(保价、代收货款等)
-     */
-    private List<KdniaoAddService> AddService;
-
-    /**
-     * 备注(会打印在面单上)
-     */
-    private String Remark;
-
-    /**
-     * 商品信息(至少一个 GoodsName)
-     */
-    private List<KdniaoCommodity> Commodity;
-
-    /**
-     * 是否返回电子面单模板
-     * 0:否
-     * 1:是
-     */
-    private String IsReturnPrintTemplate;
-
-    /**
-     * 面单模板尺寸(如 130)
-     */
-    private String TemplateSize;
-
-    /**
-     * 自定义打印内容
-     */
-    private String CustomArea;
-
-    /**
-     * 是否订阅轨迹推送
-     * 1:订阅(默认)
-     * 0:不订阅(避免消耗余额)
-     */
-    private String IsSubscribe;
-
-    /**
-     * 自定义回传字段
-     */
-    private String Callback;
-
-    /**
-     * 是否通知快递员上门揽件
-     * 0:通知
-     * 1:不通知
-     */
-    private Integer IsNotice;
-
-    /**
-     * 上门揽件开始时间(格式:yyyy-MM-dd HH:mm:ss)
-     */
-    private String StartDate;
-
-    /**
-     * 上门揽件结束时间
-     */
-    private String EndDate;
-
-    /**
-     * 是否要求签回单
-     * 0:否
-     * 1:是
-     */
-    private Integer IsReturnSignBill;
-
-    /**
-     * 是否发送短信通知
-     * 0:否
-     * 1:是
-     */
-    private Integer IsSendMessage;
-
-    /**
-     * 币种(顺丰港澳台必填)
-     * CNY / HKD / NTD
-     */
-    private String CurrencyCode;
-}

+ 0 - 62
fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoEOrderResponse.java

@@ -1,62 +0,0 @@
-package com.fs.kdniao.domain;
-
-import lombok.Data;
-
-/**
- * 快递鸟电子面单返回对象
- */
-@Data
-public class KdniaoEOrderResponse {
-
-    /**
-     * 用户ID
-     */
-    private String EBusinessID;
-
-    /**
-     * 是否成功
-     */
-    private Boolean Success;
-
-    /**
-     * 返回编码
-     */
-    private String ResultCode;
-
-    /**
-     * 返回原因
-     */
-    private String Reason;
-
-    /**
-     * 订单信息
-     */
-    private Order Order;
-
-    /**
-     * 面单模板
-     */
-    private String PrintTemplate;
-
-    /**
-     * 运单信息
-     */
-    @Data
-    public static class Order {
-
-        /**
-         * 订单编号
-         */
-        private String OrderCode;
-
-        /**
-         * 快递公司编码
-         */
-        private String ShipperCode;
-
-        /**
-         * 运单号
-         */
-        private String LogisticCode;
-    }
-}

+ 0 - 55
fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoPerson.java

@@ -1,55 +0,0 @@
-package com.fs.kdniao.domain;
-
-import lombok.Data;
-
-/**
- * 发件人 / 收件人信息
- */
-@Data
-public class KdniaoPerson {
-
-    /**
-     * 公司名称
-     */
-    private String Company;
-
-    /**
-     * 姓名
-     */
-    private String Name;
-
-    /**
-     * 电话
-     */
-    private String Tel;
-
-    /**
-     * 手机号
-     */
-    private String Mobile;
-
-    /**
-     * 省
-     */
-    private String ProvinceName;
-
-    /**
-     * 市
-     */
-    private String CityName;
-
-    /**
-     * 区/县
-     */
-    private String ExpAreaName;
-
-    /**
-     * 详细地址
-     */
-    private String Address;
-
-    /**
-     * 邮编
-     */
-    private String PostCode;
-}

+ 0 - 192
fs-admin/src/main/java/com/fs/kdniao/domain/KdniaoSimpleOrderRequest.java

@@ -1,192 +0,0 @@
-package com.fs.kdniao.domain;
-
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-/**
- * 前端简化下单请求对象
- */
-@Data
-public class KdniaoSimpleOrderRequest {
-
-    /**
-     * 业务订单号
-     * 传系统自己的订单号
-     */
-    private String bizOrderNo;
-
-    /**
-     * 快递公司编码,如 SF、YTO、ZTO、JDKY、EMS
-     */
-    private String shipperCode;
-
-    /**
-     * 运费支付方式
-     * 1:现付
-     * 2:到付
-     * 3:月结
-     */
-    private Integer payType;
-
-    /**
-     * 快递业务类型
-     * 常见值:1
-     */
-    private String expType;
-
-    /**
-     * 收件人姓名
-     */
-    private String receiverName;
-
-    /**
-     * 收件人手机号
-     */
-    private String receiverMobile;
-
-    /**
-     * 收件人电话
-     */
-    private String receiverTel;
-
-    /**
-     * 收件省
-     */
-    private String receiverProvinceName;
-
-    /**
-     * 收件市
-     */
-    private String receiverCityName;
-
-    /**
-     * 收件区/县
-     */
-    private String receiverExpAreaName;
-
-    /**
-     * 收件详细地址
-     * 不要包含省市区
-     */
-    private String receiverAddress;
-
-    /**
-     * 收件邮编
-     * EMS 场景建议传
-     */
-    private String receiverPostCode;
-
-    /**
-     * 商品名称
-     * 建议传类别,如:文件、衣服、电子产品
-     */
-    private String goodsName;
-
-    /**
-     * 商品数量
-     */
-    private Integer goodsQuantity;
-
-    /**
-     * 商品价格
-     */
-    private BigDecimal goodsPrice;
-
-    /**
-     * 商品重量
-     */
-    private BigDecimal goodsWeight;
-
-    /**
-     * 商品描述
-     */
-    private String goodsDesc;
-
-    /**
-     * 包裹数量
-     */
-    private Integer quantity;
-
-    /**
-     * 总重量(kg)
-     */
-    private BigDecimal weight;
-
-    /**
-     * 总体积(m³)
-     */
-    private BigDecimal volume;
-
-    /**
-     * 运费
-     */
-    private BigDecimal cost;
-
-    /**
-     * 其他费用
-     */
-    private BigDecimal otherCost;
-
-    /**
-     * 备注
-     */
-    private String remark;
-
-    /**
-     * 是否返回面单模板
-     * 0:否
-     * 1:是
-     */
-    private String isReturnPrintTemplate;
-
-    /**
-     * 面单模板尺寸
-     */
-    private String templateSize;
-
-    /**
-     * 是否订阅轨迹推送
-     * 1:订阅
-     * 0:不订阅
-     */
-    private String isSubscribe;
-
-    /**
-     * 是否通知上门取件
-     * 0:通知
-     * 1:不通知
-     */
-    private Integer isNotice;
-
-    /**
-     * 上门取件开始时间
-     * 格式:yyyy-MM-dd HH:mm:ss
-     */
-    private String startDate;
-
-    /**
-     * 上门取件结束时间
-     */
-    private String endDate;
-
-    /**
-     * 是否要求签回单
-     * 0:否
-     * 1:是
-     */
-    private Integer isReturnSignBill;
-
-    /**
-     * 是否发送短信
-     * 0:否
-     * 1:是
-     */
-    private Integer isSendMessage;
-
-    /**
-     * 币种
-     * 特殊场景需要
-     */
-    private String currencyCode;
-}

+ 0 - 18
fs-admin/src/main/java/com/fs/kdniao/service/IKdniaoEOrderService.java

@@ -1,18 +0,0 @@
-package com.fs.kdniao.service;
-
-import com.fs.kdniao.domain.KdniaoEOrderResponse;
-import com.fs.kdniao.domain.KdniaoSimpleOrderRequest;
-
-/**
- * 快递鸟电子面单业务接口
- */
-public interface IKdniaoEOrderService {
-
-    /**
-     * 前端简化参数下单
-     *
-     * @param request 简化请求参数
-     * @return 下单结果
-     */
-    KdniaoEOrderResponse submitSimpleOrder(KdniaoSimpleOrderRequest request);
-}

+ 0 - 214
fs-admin/src/main/java/com/fs/kdniao/service/impl/KdniaoEOrderServiceImpl.java

@@ -1,214 +0,0 @@
-package com.fs.kdniao.service.impl;
-
-import com.alibaba.fastjson.JSON;
-import com.fs.kdniao.config.KdniaoConfig;
-import com.fs.kdniao.domain.KdniaoCommodity;
-import com.fs.kdniao.domain.KdniaoEOrderRequest;
-import com.fs.kdniao.domain.KdniaoEOrderResponse;
-import com.fs.kdniao.domain.KdniaoPerson;
-import com.fs.kdniao.domain.KdniaoSimpleOrderRequest;
-import com.fs.kdniao.service.IKdniaoEOrderService;
-import com.fs.kdniao.util.KdniaoUtil;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
-
-import java.net.URLEncoder;
-import java.util.Collections;
-
-/**
- * 快递鸟电子面单业务实现类
- */
-@Service
-public class KdniaoEOrderServiceImpl implements IKdniaoEOrderService {
-
-    @Autowired
-    private KdniaoConfig kdniaoConfig;
-
-    /**
-     * 前端简化参数下单
-     */
-    @Override
-    public KdniaoEOrderResponse submitSimpleOrder(KdniaoSimpleOrderRequest request) {
-        validateRequest(request);
-
-        KdniaoEOrderRequest eOrderRequest = buildEOrderRequest(request);
-
-        String requestData = KdniaoUtil.toRequestDataJson(eOrderRequest);
-        String dataSign = KdniaoUtil.getDataSign(requestData, kdniaoConfig.getApiKey());
-
-        try {
-            String formData = buildFormData(requestData, dataSign);
-            String result = KdniaoUtil.doPost(kdniaoConfig.getReqURL(), formData);
-            return JSON.parseObject(result, KdniaoEOrderResponse.class);
-        } catch (Exception e) {
-            throw new RuntimeException("电子面单下单失败:" + e.getMessage(), e);
-        }
-    }
-
-    /**
-     * 组装快递鸟标准请求对象
-     */
-    private KdniaoEOrderRequest buildEOrderRequest(KdniaoSimpleOrderRequest request) {
-        KdniaoEOrderRequest eOrderRequest = new KdniaoEOrderRequest();
-
-        // 基础字段
-        eOrderRequest.setOrderCode(buildOrderCode(request.getBizOrderNo()));
-        eOrderRequest.setShipperCode(request.getShipperCode());
-        eOrderRequest.setCustomerName(kdniaoConfig.getAccount().getCustomerName());
-        eOrderRequest.setCustomerPwd(kdniaoConfig.getAccount().getCustomerPwd());
-        eOrderRequest.setSendSite(kdniaoConfig.getAccount().getSendSite());
-        eOrderRequest.setMonthCode(kdniaoConfig.getAccount().getMonthCode());
-        eOrderRequest.setPayType(request.getPayType());
-        eOrderRequest.setExpType(request.getExpType());
-
-        // 发件人
-        eOrderRequest.setSender(buildSender());
-
-        // 收件人
-        eOrderRequest.setReceiver(buildReceiver(request));
-
-        // 商品
-        eOrderRequest.setCommodity(Collections.singletonList(buildCommodity(request)));
-
-        // 包裹信息
-        eOrderRequest.setQuantity(request.getQuantity() == null ? 1 : request.getQuantity());
-        eOrderRequest.setWeight(request.getWeight());
-        eOrderRequest.setVolume(request.getVolume());
-        eOrderRequest.setCost(request.getCost());
-        eOrderRequest.setOtherCost(request.getOtherCost());
-
-        // 打印和备注
-        eOrderRequest.setRemark(trimToNull(request.getRemark()));
-        eOrderRequest.setIsReturnPrintTemplate(StringUtils.hasText(request.getIsReturnPrintTemplate()) ? request.getIsReturnPrintTemplate() : "1");
-        eOrderRequest.setTemplateSize(StringUtils.hasText(request.getTemplateSize()) ? request.getTemplateSize() : "130");
-        eOrderRequest.setIsSubscribe(StringUtils.hasText(request.getIsSubscribe()) ? request.getIsSubscribe() : "0");
-
-        // 上门取件
-        eOrderRequest.setIsNotice(request.getIsNotice());
-        eOrderRequest.setStartDate(trimToNull(request.getStartDate()));
-        eOrderRequest.setEndDate(trimToNull(request.getEndDate()));
-
-        // 其他
-        eOrderRequest.setIsReturnSignBill(request.getIsReturnSignBill());
-        eOrderRequest.setIsSendMessage(request.getIsSendMessage());
-        eOrderRequest.setCurrencyCode(trimToNull(request.getCurrencyCode()));
-
-        return eOrderRequest;
-    }
-
-    /**
-     * 构建订单号
-     * 规则:业务订单号 + 时间戳,保证唯一
-     */
-    private String buildOrderCode(String bizOrderNo) {
-        if (StringUtils.hasText(bizOrderNo)) {
-            return bizOrderNo.trim() + "-" + System.currentTimeMillis();
-        }
-        return "KD" + System.currentTimeMillis();
-    }
-
-    /**
-     * 组装默认发件人
-     */
-    private KdniaoPerson buildSender() {
-        KdniaoPerson sender = new KdniaoPerson();
-        sender.setCompany(trimToNull(kdniaoConfig.getSender().getCompany()));
-        sender.setName(kdniaoConfig.getSender().getName());
-        sender.setMobile(kdniaoConfig.getSender().getMobile());
-        sender.setProvinceName(kdniaoConfig.getSender().getProvinceName());
-        sender.setCityName(kdniaoConfig.getSender().getCityName());
-        sender.setExpAreaName(kdniaoConfig.getSender().getExpAreaName());
-        sender.setAddress(kdniaoConfig.getSender().getAddress());
-        sender.setPostCode(trimToNull(kdniaoConfig.getSender().getPostCode()));
-        return sender;
-    }
-
-    /**
-     * 组装收件人
-     */
-    private KdniaoPerson buildReceiver(KdniaoSimpleOrderRequest request) {
-        KdniaoPerson receiver = new KdniaoPerson();
-        receiver.setName(request.getReceiverName());
-        receiver.setMobile(trimToNull(request.getReceiverMobile()));
-        receiver.setTel(trimToNull(request.getReceiverTel()));
-        receiver.setProvinceName(request.getReceiverProvinceName());
-        receiver.setCityName(request.getReceiverCityName());
-        receiver.setExpAreaName(request.getReceiverExpAreaName());
-        receiver.setAddress(request.getReceiverAddress());
-        receiver.setPostCode(trimToNull(request.getReceiverPostCode()));
-        return receiver;
-    }
-
-    /**
-     * 组装商品信息
-     */
-    private KdniaoCommodity buildCommodity(KdniaoSimpleOrderRequest request) {
-        KdniaoCommodity commodity = new KdniaoCommodity();
-        commodity.setGoodsName(request.getGoodsName());
-        commodity.setGoodsquantity(request.getGoodsQuantity() == null ? 1 : request.getGoodsQuantity());
-        commodity.setGoodsPrice(request.getGoodsPrice());
-        commodity.setGoodsWeight(request.getGoodsWeight());
-        commodity.setGoodsDesc(trimToNull(request.getGoodsDesc()));
-        return commodity;
-    }
-
-    /**
-     * 构建表单请求参数
-     */
-    private String buildFormData(String requestData, String dataSign) throws Exception {
-        StringBuilder sb = new StringBuilder();
-        sb.append("RequestData=").append(URLEncoder.encode(requestData, "UTF-8"));
-        sb.append("&EBusinessID=").append(URLEncoder.encode(kdniaoConfig.getEBusinessID(), "UTF-8"));
-        sb.append("&RequestType=").append(URLEncoder.encode("1007", "UTF-8"));
-        sb.append("&DataSign=").append(dataSign);
-        sb.append("&DataType=").append(URLEncoder.encode("2", "UTF-8"));
-        return sb.toString();
-    }
-
-    /**
-     * 前端简化参数校验
-     */
-    private void validateRequest(KdniaoSimpleOrderRequest request) {
-        if (request == null) {
-            throw new IllegalArgumentException("请求参数不能为空");
-        }
-        if (!StringUtils.hasText(request.getShipperCode())) {
-            throw new IllegalArgumentException("shipperCode不能为空");
-        }
-        if (request.getPayType() == null) {
-            throw new IllegalArgumentException("payType不能为空");
-        }
-        if (!StringUtils.hasText(request.getExpType())) {
-            throw new IllegalArgumentException("expType不能为空");
-        }
-        if (!StringUtils.hasText(request.getReceiverName())) {
-            throw new IllegalArgumentException("receiverName不能为空");
-        }
-        if (!StringUtils.hasText(request.getReceiverMobile()) && !StringUtils.hasText(request.getReceiverTel())) {
-            throw new IllegalArgumentException("receiverMobile和receiverTel至少填写一个");
-        }
-        if (!StringUtils.hasText(request.getReceiverProvinceName())) {
-            throw new IllegalArgumentException("receiverProvinceName不能为空");
-        }
-        if (!StringUtils.hasText(request.getReceiverCityName())) {
-            throw new IllegalArgumentException("receiverCityName不能为空");
-        }
-        if (!StringUtils.hasText(request.getReceiverExpAreaName())) {
-            throw new IllegalArgumentException("receiverExpAreaName不能为空");
-        }
-        if (!StringUtils.hasText(request.getReceiverAddress())) {
-            throw new IllegalArgumentException("receiverAddress不能为空");
-        }
-        if (!StringUtils.hasText(request.getGoodsName())) {
-            throw new IllegalArgumentException("goodsName不能为空");
-        }
-    }
-
-    /**
-     * 去除空白,空字符串转 null
-     */
-    private String trimToNull(String value) {
-        return StringUtils.hasText(value) ? value.trim() : null;
-    }
-}

+ 0 - 114
fs-admin/src/main/java/com/fs/kdniao/util/KdniaoUtil.java

@@ -1,114 +0,0 @@
-package com.fs.kdniao.util;
-
-import com.alibaba.fastjson.JSON;
-import com.fs.kdniao.domain.KdniaoEOrderRequest;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.security.MessageDigest;
-import java.util.Base64;
-
-/**
- * 快递鸟工具类
- */
-public class KdniaoUtil {
-
-    private KdniaoUtil() {
-    }
-
-    /**
-     * 将标准请求对象转成 RequestData JSON 字符串
-     */
-    public static String toRequestDataJson(KdniaoEOrderRequest request) {
-        return JSON.toJSONString(request);
-    }
-
-    /**
-     * 生成 DataSign
-     * 规则:Base64(MD5(RequestData + ApiKey))
-     */
-    public static String getDataSign(String requestData, String apiKey) {
-        try {
-            String md5Result = md5(requestData + apiKey);
-            String base64 = Base64.getEncoder().encodeToString(md5Result.getBytes("UTF-8"));
-            return URLEncoder.encode(base64, "UTF-8");
-        } catch (Exception e) {
-            throw new RuntimeException("生成DataSign失败", e);
-        }
-    }
-
-    /**
-     * POST 表单请求
-     */
-    public static String doPost(String reqURL, String formData) {
-        HttpURLConnection connection = null;
-        OutputStream os = null;
-        BufferedReader br = null;
-        try {
-            URL url = new URL(reqURL);
-            connection = (HttpURLConnection) url.openConnection();
-            connection.setRequestMethod("POST");
-            connection.setConnectTimeout(10000);
-            connection.setReadTimeout(20000);
-            connection.setDoOutput(true);
-            connection.setDoInput(true);
-            connection.setUseCaches(false);
-            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
-
-            os = connection.getOutputStream();
-            os.write(formData.getBytes("UTF-8"));
-            os.flush();
-
-            br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
-            StringBuilder sb = new StringBuilder();
-            String line;
-            while ((line = br.readLine()) != null) {
-                sb.append(line);
-            }
-            return sb.toString();
-        } catch (Exception e) {
-            throw new RuntimeException("调用快递鸟接口失败", e);
-        } finally {
-            try {
-                if (os != null) {
-                    os.close();
-                }
-            } catch (Exception ignored) {
-            }
-            try {
-                if (br != null) {
-                    br.close();
-                }
-            } catch (Exception ignored) {
-            }
-            if (connection != null) {
-                connection.disconnect();
-            }
-        }
-    }
-
-    /**
-     * MD5
-     */
-    private static String md5(String text) {
-        try {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            byte[] digest = md.digest(text.getBytes("UTF-8"));
-            StringBuilder sb = new StringBuilder();
-            for (byte b : digest) {
-                String hex = Integer.toHexString(b & 0xff);
-                if (hex.length() == 1) {
-                    sb.append("0");
-                }
-                sb.append(hex);
-            }
-            return sb.toString();
-        } catch (Exception e) {
-            throw new RuntimeException("MD5计算失败", e);
-        }
-    }
-}

+ 0 - 45
fs-admin/src/main/java/com/fs/kdniaoNew/controller/KdniaoUniversalEOrderController.java

@@ -1,45 +0,0 @@
-package com.fs.kdniaoNew.controller;
-
-import com.fs.common.annotation.Log;
-import com.fs.common.core.controller.BaseController;
-import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.enums.BusinessType;
-import com.fs.kdniaoNew.domain.KdniaoSubmitCommand;
-import com.fs.kdniaoNew.domain.KdniaoUniversalResponse;
-import com.fs.kdniaoNew.service.IKdniaoUniversalEOrderService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-/**
- * 快递鸟统一电子面单控制器
- */
-@RestController
-@RequestMapping("/kdniao/universal/eorder")
-public class KdniaoUniversalEOrderController extends BaseController {
-
-    @Autowired
-    private IKdniaoUniversalEOrderService kdniaoUniversalEOrderService;
-
-    /**
-     * 统一下单
-     */
-    @Log(title = "快递鸟统一电子面单", businessType = BusinessType.INSERT)
-    @PostMapping("/submit")
-    public AjaxResult submit(@RequestBody KdniaoSubmitCommand command) {
-        try {
-            KdniaoUniversalResponse response = kdniaoUniversalEOrderService.submit(command);
-
-            if (Boolean.TRUE.equals(response.getSuccess()) && "100".equals(response.getResultCode())) {
-                return AjaxResult.success("下单成功", response);
-            }
-
-            if ("106".equals(response.getResultCode())) {
-                return AjaxResult.error("订单号重复,快递鸟返回:该订单号已下单成功");
-            }
-
-            return AjaxResult.error("下单失败:" + response.getReason(), response);
-        } catch (Exception e) {
-            return AjaxResult.error("下单异常:" + e.getMessage());
-        }
-    }
-}

+ 58 - 0
fs-admin/src/main/java/com/fs/user/controller/FsUserIntegralController.java

@@ -1,7 +1,9 @@
 package com.fs.user.controller;
 
+import com.fs.common.annotation.Log;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.StringUtils;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserIntegralLogs;
@@ -14,6 +16,7 @@ import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.math.BigDecimal;
@@ -139,6 +142,7 @@ public class FsUserIntegralController {
      */
     @ApiOperation("添加积分")
     @PostMapping("/add")
+    @PreAuthorize("@ss.hasPermi('user:integral:add')")
     public R addIntegral(@RequestBody AddIntegralParam param) {
         Long userId = param.getUserId();
         Integer logType = param.getLogType();
@@ -183,4 +187,58 @@ public class FsUserIntegralController {
 
         return R.ok("添加积分成功");
     }
+
+    /**
+     * 校验用户是否存在
+     */
+    @ApiOperation("校验用户是否存在")
+    @GetMapping("/checkUser/{userId}")
+    public R checkUser(@PathVariable Long userId) {
+        FsUser user = userService.selectFsUserByUserId(userId);
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+        return R.ok().put("nickName", user.getNickName()).put("phone", user.getPhone()).put("integral", user.getIntegral());
+    }
+
+    /**
+     * 扣除积分
+     */
+    @ApiOperation("扣除积分")
+    @Log(title = "扣除积分", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('user:integral:deduct')")
+    @PostMapping("/deduct")
+    public R deductIntegral(@RequestBody AddIntegralParam param) {
+        Long userId = param.getUserId();
+        Integer logType = param.getLogType();
+        Long integral = param.getIntegral();
+        if (integral == null || integral <= 0) {
+            return R.error("扣除积分必须大于0");
+        }
+        // 查询用户信息
+        FsUser user = userService.selectFsUserByUserId(userId);
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+        // 计算新的积分余额
+        Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
+        if (currentIntegral < integral) {
+            return R.error("用户积分不足,当前积分:" + currentIntegral);
+        }
+        Long newIntegral = currentIntegral - integral;
+        // 更新用户积分(传入负数表示扣除)
+        userService.increaseIntegral(Collections.singletonList(user.getUserId()), -integral);
+        // 创建积分记录
+        Date now = new Date();
+        FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+        integralLogs.setUserId(userId);
+        integralLogs.setLogType(logType);
+        integralLogs.setIntegral(-integral);
+        integralLogs.setBalance(newIntegral);
+        integralLogs.setCreateTime(now);
+        integralLogs.setUpdateTime(now);
+        integralLogsService.insertFsUserIntegralLogs(integralLogs);
+
+        return R.ok("扣除积分成功");
+    }
 }

+ 34 - 4
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -3,10 +3,7 @@ package com.fs.common.core.redis;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.BoundSetOperations;
-import org.springframework.data.redis.core.HashOperations;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.core.*;
 import org.springframework.stereotype.Component;
 
 /**
@@ -228,6 +225,39 @@ public class RedisCache
         return redisTemplate.keys(pattern);
     }
 
+    /**
+     * 使用SCAN命令遍历匹配的key,避免阻塞主线程
+     *
+     * @param pattern 匹配模式(如 "cacheName::*")
+     * @return 匹配的key集合
+     */
+    public Set<String> scan(final String pattern)
+    {
+        return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
+            Set<String> keys = new HashSet<>();
+            Cursor<byte[]> cursor = connection.keyCommands().scan(
+                    ScanOptions.scanOptions()
+                            .match(pattern)
+                            .count(1000)
+                            .build()
+            );
+
+            try {
+                while (cursor.hasNext()) {
+                    keys.add(new String(cursor.next()));
+                }
+            } finally {
+                try {
+                    cursor.close();
+                } catch (Exception e) {
+                    // 忽略关闭异常
+                }
+            }
+
+            return keys;
+        });
+    }
+
 
     public Boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
         return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);

+ 36 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogAddwxController.java

@@ -8,9 +8,12 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogAddWxExportVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import org.springframework.beans.BeanUtils;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -127,6 +130,39 @@ public class CompanyVoiceRoboticCallLogAddwxController extends BaseController
         return toAjax(companyVoiceRoboticCallLogAddwxService.deleteCompanyVoiceRoboticCallLogAddwxByLogIds(logIds));
     }
 
+    @PreAuthorize("@ss.hasPermi('company:sendmsglog:list')")
+    @GetMapping("/listByClientIdAndRoboticId")
+    public TableDataInfo listByClientIdAndRoboticId(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx) {
+        startPage();
+        List<CompanyVoiceRoboticCallLogAddwxVO> list = companyVoiceRoboticCallLogAddwxService.listByRoboticId(companyVoiceRoboticCallLogAddwx);
+        return getDataTable(list);
+
+    }
+
+
+    /**
+     * 加微统计数据(按照任务id分组,任务id-任务名称-查询总任务数量-成功数量)
+     */
+    @PreAuthorize("@ss.hasPermi('company:sendmsglog:list')")
+    @GetMapping("/groupList")
+    public TableDataInfo groupList(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx)
+    {
+        startPage();
+        List<CompanyVoiceRoboticCallLogAddwx> list = companyVoiceRoboticCallLogAddwxService.selectCompanyVoiceRoboticAddwxLogGroupList(companyVoiceRoboticCallLogAddwx);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询调用日志_发送短信列表统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('company:sendmsglog:list')")
+    @GetMapping("/count")
+    public AjaxResult selectCompanyVoiceRoboticAddwxLogCount()
+    {
+        CompanyVoiceRoboticCallLogCount companyVoiceRoboticCallLogCount = companyVoiceRoboticCallLogAddwxService.selectCompanyVoiceRoboticAddwxLogCount();
+        return AjaxResult.success(companyVoiceRoboticCallLogCount);
+    }
+
 //    /**
 //     * 导出调用日志_加微信列表
 //     */

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/live/LiveController.java

@@ -71,7 +71,7 @@ public class LiveController extends BaseController
     {
         startPage();
         List<Live> list = new ArrayList<>();
-        if (CloudHostUtils.hasCloudHostName("济世百康") ) {
+        if (CloudHostUtils.hasCloudHostName("济世百康","蒙牛") ) {
             //直播也发
             list = liveService.listToLiveNoEndNew(live);
         }else{

+ 2 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -110,6 +110,8 @@ public class FsStoreAfterSalesScrmController extends BaseController
         List<FsStoreAfterSalesVO> list = fsStoreAfterSalesService.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
         for (FsStoreAfterSalesVO vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setRefundType(vo.getRefundAmount() != null && vo.getPayPrice() != null
+                    ? (vo.getRefundAmount().compareTo(vo.getPayPrice()) < 0 ? "部分退款" : "全额退款") : "");
         }
         ExcelUtil<FsStoreAfterSalesVO> util = new ExcelUtil<FsStoreAfterSalesVO>(FsStoreAfterSalesVO.class);
         return util.exportExcel(list, "storeAfterSales");

+ 23 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java

@@ -156,6 +156,29 @@ public class FsStoreProductPackageScrmController extends BaseController
         return toAjax(fsStoreProductPackageService.updateFsStoreProductPackage(fsStoreProductPackage));
     }
 
+    /**
+     * 一键复制商品组合套餐
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:add')")
+    @Log(title = "商品组合套餐", businessType = BusinessType.INSERT)
+    @PostMapping("/copy/{packageId}")
+    public AjaxResult copyPackage(@PathVariable("packageId") Long packageId)
+    {
+        FsStoreProductPackageScrm source = fsStoreProductPackageService.selectFsStoreProductPackageById(packageId);
+        if (source == null) {
+            return AjaxResult.error("未找到对应的套餐信息");
+        }
+        // 清空主键,让数据库自增生成新ID
+        source.setPackageId(null);
+        // 标题加"-副本"后缀
+        source.setTitle(source.getTitle() + "-副本");
+        // 设置当前登录用户的公司和部门
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        source.setCompanyId(loginUser.getCompany().getCompanyId());
+        source.setDeptId(loginUser.getUser().getDeptId());
+        return toAjax(fsStoreProductPackageService.insertFsStoreProductPackage(source));
+    }
+    
     /**
      * 删除商品组合套餐
      */

+ 2 - 2
fs-company/src/main/resources/application.yml

@@ -4,12 +4,12 @@ server:
 spring:
   profiles:
 #    active: druid-ylrz
-    active: dev
+#    active: dev
 #    active: druid-jnsyj-test
 #    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt
-#    active: druid-bjzm-test
+    active: druid-bjzm-test
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft

+ 0 - 1
fs-redis/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -12,7 +12,6 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
-import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 

+ 8 - 1
fs-service/pom.xml

@@ -123,7 +123,7 @@
         <dependency>
             <groupId>com.github.binarywang</groupId>
             <artifactId>weixin-java-pay</artifactId>
-            <version>4.7.2.B</version>
+            <version>4.8.0</version>
         </dependency>
         <dependency>
             <groupId>com.github.binarywang</groupId>
@@ -303,6 +303,13 @@
             <version>1.3.1</version>
         </dependency>
 
+        <!-- 支付宝-->
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.38.0.ALL</version>
+        </dependency>
+
 
     </dependencies>
 

+ 9 - 0
fs-service/src/main/java/com/fs/app/service/AppPayService.java

@@ -1,12 +1,21 @@
 package com.fs.app.service;
 
+import com.fs.common.core.domain.R;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
 
+import java.util.Map;
+
 public interface AppPayService {
 
+    /**
+     * 支付宝回调
+     */
+    String aliNotify(Map<String, String> params);
 
     /**
      * 微信支付回调
      */
     String wxNotify(WxPayOrderNotifyResult result);
+
+    R getPayConfig(String appId);
 }

+ 90 - 1
fs-service/src/main/java/com/fs/app/service/impl/AppPayServiceImpl.java

@@ -1,17 +1,29 @@
 package com.fs.app.service.impl;
 
 import com.fs.app.service.AppPayService;
+import com.fs.common.core.domain.R;
+import com.fs.course.service.IFsUserCourseOrderService;
+import com.fs.course.service.IFsUserVipOrderService;
+import com.fs.his.domain.FsPayConfig;
+import com.fs.his.domain.MerchantAppConfig;
 import com.fs.his.service.IFsInquiryOrderService;
 import com.fs.his.service.IFsPackageOrderService;
+import com.fs.his.service.IFsStoreOrderService;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.live.service.ILiveOrderService;
+import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.google.gson.Gson;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import com.alipay.api.internal.util.AlipaySignature;
+
+import java.util.Map;
 
 @Slf4j
 @Service
@@ -31,6 +43,64 @@ public class AppPayServiceImpl implements AppPayService {
     @Autowired
     private IFsPackageOrderService packageOrderService;
 
+    @Autowired
+    private IFsUserCourseOrderService courseOrderService;
+    @Autowired
+    private IFsUserVipOrderService vipOrderService;
+
+    @Lazy
+    @Autowired
+    private IFsStoreOrderService fsStoreOrderService;
+
+
+    /**
+     * 支付宝回调
+     */
+    @Override
+    public String aliNotify(Map<String, String> params) {
+        log.info("支付宝回调参数: {}", params);
+        SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.pay");
+        FsPayConfig fsPayConfig = new Gson().fromJson(sysConfig.getConfigValue(), FsPayConfig.class);
+        try {
+            boolean flag = AlipaySignature.rsaCheckV1(params, fsPayConfig.getAliPublicKey(), "UTF-8","RSA2");
+            if (!flag) {
+                log.info("支付宝回调验签失败:{}", params);
+            }
+
+            String tradeStatus = params.get("trade_status");
+            if (!"TRADE_SUCCESS".equals(tradeStatus)) {
+                return "success";
+            }
+
+            String[] arr = params.get("out_trade_no").split("-");
+            String tradeNo = params.get("trade_no");
+            switch (arr[0]) {
+                case "inquiry":
+                    inquiryOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
+                    break;
+//                case "store":
+//                    storeOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
+//                    break;
+                case "package":
+                    packageOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
+                    break;
+                case "appvip":
+                    vipOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
+                    break;
+                case "course":
+                    courseOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
+                    break;
+            }
+
+
+        } catch (Exception e) {
+            log.info("支付宝回调异常:{}", e);
+            throw new RuntimeException(e);
+        }
+
+        return "success";
+    }
+
     @Override
     public String wxNotify(WxPayOrderNotifyResult result) {
         log.info("微信回调参数: {}", result);
@@ -49,15 +119,34 @@ public class AppPayServiceImpl implements AppPayService {
                 break;
             case "store":
                 storeOrderService.payConfirm(1, null,tradeNoArr[1],"",result.getTransactionId(),"");
-
+                break;
+                // 表示app的处方订单
+            case "storeOrder":
+                fsStoreOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
+                break;
             case "live":
                 liveOrderService.payConfirm(1, null,tradeNoArr[1],"",result.getTransactionId(),"");
                 break;
             case "package":
                 packageOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
                 break;
+            case "appvip":
+                vipOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
+                break;
+            case "course":
+                courseOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
+                break;
         }
         return WxPayNotifyResponse.success("OK");
     }
 
+    @Override
+    public R getPayConfig(String appId) {
+        MerchantAppConfig merchantAppConfig = sysConfigMapper.getPayConfig(appId);
+        if (merchantAppConfig == null || StringUtils.isEmpty(merchantAppConfig.getMerchantType())) {
+            return R.error("商户信息不存在");
+        }
+        return R.ok(merchantAppConfig.getMerchantType());
+    }
+
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogAddwx.java

@@ -72,6 +72,9 @@ public class CompanyVoiceRoboticCallLogAddwx extends BaseEntity{
 
     private Integer qwWxAddWayId;
 
+    @TableField(exist = false)
+    private String successRate;
+
     public static CompanyVoiceRoboticCallLogAddwx initCallLog( String runParam, Long keyId, Long taskId,Long wxAccountId,Long companyId,int qwWxAddWayId) {
         CompanyVoiceRoboticCallLogAddwx log = new CompanyVoiceRoboticCallLogAddwx();
         log.wxClientId = keyId;

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogCallphone.java

@@ -133,6 +133,9 @@ public class CompanyVoiceRoboticCallLogCallphone extends BaseEntity{
     @TableField(exist = false)
     private Integer runningCount;
 
+    @TableField(exist = false)
+    private String successRate;
+
     public static CompanyVoiceRoboticCallLogCallphone initCallLog( String runParam, Long keyId, Long taskId,Long companyId) {
         CompanyVoiceRoboticCallLogCallphone log = new CompanyVoiceRoboticCallLogCallphone();
         log.callerId = keyId;

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogSendmsg.java

@@ -92,6 +92,9 @@ public class CompanyVoiceRoboticCallLogSendmsg extends BaseEntity{
 
     private String phone;
 
+    @TableField(exist = false)
+    private String successRate;
+
 
 
     public static CompanyVoiceRoboticCallLogSendmsg initCallLog( String runParam, Long keyId, Long taskId,Long companyId,Long companyUserId,Long tempId) {

+ 12 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallees.java

@@ -77,4 +77,16 @@ public class CompanyVoiceRoboticCallees{
 
     @TableField(exist = false)
     private List<CrmCustomerProperty> tagList ;
+
+    /**
+    * 是否添加;0否1是2加微中
+    */
+    @TableField(exist = false)
+    private Integer isAdd;
+
+    /**
+    * 客服id
+    */
+    @TableField(exist = false)
+    private Integer customer_id;
 }

+ 23 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCallLogAddwxMapper.java

@@ -4,7 +4,10 @@ import java.util.List;
 import java.util.Map;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogAddwx;
+import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import org.apache.ibatis.annotations.Param;
 
 /**
@@ -70,4 +73,24 @@ public interface CompanyVoiceRoboticCallLogAddwxMapper extends BaseMapper<Compan
 
     Map<String, Long> countListAll(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx);
 
+    /**
+     * 查询加微记录分组列表
+     * @param companyVoiceRoboticCallLogAddwx
+     * @return
+     */
+    List<CompanyVoiceRoboticCallLogAddwx> selectCompanyVoiceRoboticCallLogAddwxGroupList (CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx);
+
+    /**
+     * 查询加微记录详情
+     * @param companyVoiceRoboticCallLogAddwx
+     * @return
+     */
+    List<CompanyVoiceRoboticCallLogAddwxVO> listByRoboticId(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx);
+
+    /**
+     * 查询加微记录统计(所有任务)
+     * @return
+     */
+    CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticAddwxLogCount();
+
 }

+ 13 - 7
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java

@@ -5,20 +5,21 @@ import com.fs.company.domain.CompanyVoiceRoboticCallees;
 import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.vo.SendMsgByTaskVO;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
 import java.util.Set;
 
 /**
  * 任务外呼电话Mapper接口
- * 
+ *
  * @author fs
  * @date 2024-12-04
  */
 public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoiceRoboticCallees> {
     /**
      * 查询任务外呼电话
-     * 
+     *
      * @param id 任务外呼电话ID
      * @return 任务外呼电话
      */
@@ -26,15 +27,20 @@ public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoic
 
     /**
      * 查询任务外呼电话列表
-     * 
+     *
      * @param companyVoiceRoboticCallees 任务外呼电话
      * @return 任务外呼电话集合
      */
     public List<CompanyVoiceRoboticCallees> selectCompanyVoiceRoboticCalleesList(CompanyVoiceRoboticCallees companyVoiceRoboticCallees);
 
+    @Select("select cv.*,cw.is_add,cw.customer_id from company_voice_robotic_callees  cv " +
+            "left join  company_wx_client cw on cv.robotic_id = cw.robotic_id " +
+            "where cv.robotic_id = #{roboticId}")
+    public List<CompanyVoiceRoboticCallees> selectCompanyVoiceRoboticCalleesListByRoboticId(@Param("roboticId") Long id);
+
     /**
      * 新增任务外呼电话
-     * 
+     *
      * @param companyVoiceRoboticCallees 任务外呼电话
      * @return 结果
      */
@@ -42,7 +48,7 @@ public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoic
 
     /**
      * 修改任务外呼电话
-     * 
+     *
      * @param companyVoiceRoboticCallees 任务外呼电话
      * @return 结果
      */
@@ -50,7 +56,7 @@ public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoic
 
     /**
      * 删除任务外呼电话
-     * 
+     *
      * @param id 任务外呼电话ID
      * @return 结果
      */
@@ -58,7 +64,7 @@ public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoic
 
     /**
      * 批量删除任务外呼电话
-     * 
+     *
      * @param ids 需要删除的数据ID
      * @return 结果
      */

+ 16 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticCallLogAddwxService.java

@@ -4,8 +4,11 @@ import java.util.List;
 import java.util.Map;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogAddwx;
+import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 
 /**
  * 调用日志_加微信Service接口
@@ -75,4 +78,17 @@ public interface ICompanyVoiceRoboticCallLogAddwxService extends IService<Compan
 
     Map<String, Long> countListAll(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx);
 
+    List<CompanyVoiceRoboticCallLogAddwx> selectCompanyVoiceRoboticAddwxLogGroupList(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx);
+
+    CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticAddwxLogCount();
+
+    /**
+     * 查询加微记录详情
+     * @param companyVoiceRoboticCallLogAddwx
+     * @return
+     */
+    List<CompanyVoiceRoboticCallLogAddwxVO> listByRoboticId(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx);
+
+
+
 }

+ 18 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCallLogAddwxServiceImpl.java

@@ -6,10 +6,13 @@ import java.util.Map;
 import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.company.domain.CompanyVoiceRoboticCallLog;
+import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.mapper.CompanyVoiceRoboticBusinessMapper;
 import com.fs.company.mapper.CompanyWxClientMapper;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
+import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
@@ -162,4 +165,19 @@ class CompanyVoiceRoboticCallLogAddwxServiceImpl extends ServiceImpl<CompanyVoic
         return baseMapper.countListAll(companyVoiceRoboticCallLogAddwx);
     }
 
+    @Override
+    public List<CompanyVoiceRoboticCallLogAddwx> selectCompanyVoiceRoboticAddwxLogGroupList(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx) {
+        return baseMapper.selectCompanyVoiceRoboticCallLogAddwxGroupList(companyVoiceRoboticCallLogAddwx);
+    }
+
+    @Override
+    public CompanyVoiceRoboticCallLogCount selectCompanyVoiceRoboticAddwxLogCount() {
+        return baseMapper.selectCompanyVoiceRoboticAddwxLogCount();
+    }
+
+    @Override
+    public List<CompanyVoiceRoboticCallLogAddwxVO> listByRoboticId(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx) {
+        return baseMapper.listByRoboticId(companyVoiceRoboticCallLogAddwx);
+    }
+
 }

+ 11 - 10
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCalleesServiceImpl.java

@@ -11,7 +11,7 @@ import java.util.List;
 
 /**
  * 任务外呼电话Service业务层处理
- * 
+ *
  * @author fs
  * @date 2024-12-04
  */
@@ -23,7 +23,7 @@ public class CompanyVoiceRoboticCalleesServiceImpl extends ServiceImpl<CompanyVo
 
     /**
      * 查询任务外呼电话
-     * 
+     *
      * @param id 任务外呼电话ID
      * @return 任务外呼电话
      */
@@ -35,7 +35,7 @@ public class CompanyVoiceRoboticCalleesServiceImpl extends ServiceImpl<CompanyVo
 
     /**
      * 查询任务外呼电话列表
-     * 
+     *
      * @param companyVoiceRoboticCallees 任务外呼电话
      * @return 任务外呼电话
      */
@@ -47,7 +47,7 @@ public class CompanyVoiceRoboticCalleesServiceImpl extends ServiceImpl<CompanyVo
 
     /**
      * 新增任务外呼电话
-     * 
+     *
      * @param companyVoiceRoboticCallees 任务外呼电话
      * @return 结果
      */
@@ -59,7 +59,7 @@ public class CompanyVoiceRoboticCalleesServiceImpl extends ServiceImpl<CompanyVo
 
     /**
      * 修改任务外呼电话
-     * 
+     *
      * @param companyVoiceRoboticCallees 任务外呼电话
      * @return 结果
      */
@@ -71,7 +71,7 @@ public class CompanyVoiceRoboticCalleesServiceImpl extends ServiceImpl<CompanyVo
 
     /**
      * 批量删除任务外呼电话
-     * 
+     *
      * @param ids 需要删除的任务外呼电话ID
      * @return 结果
      */
@@ -83,7 +83,7 @@ public class CompanyVoiceRoboticCalleesServiceImpl extends ServiceImpl<CompanyVo
 
     /**
      * 删除任务外呼电话信息
-     * 
+     *
      * @param id 任务外呼电话ID
      * @return 结果
      */
@@ -95,9 +95,10 @@ public class CompanyVoiceRoboticCalleesServiceImpl extends ServiceImpl<CompanyVo
 
     @Override
     public List<CompanyVoiceRoboticCallees> selectCompanyVoiceRoboticCalleesListByRoboticId(Long id) {
-        CompanyVoiceRoboticCallees param = new CompanyVoiceRoboticCallees();
-        param.setRoboticId(id);
-        List<CompanyVoiceRoboticCallees> companyVoiceRoboticCallees = selectCompanyVoiceRoboticCalleesList(param);
+//        CompanyVoiceRoboticCallees param = new CompanyVoiceRoboticCallees();
+//        param.setRoboticId(id);
+//        List<CompanyVoiceRoboticCallees> companyVoiceRoboticCallees = selectCompanyVoiceRoboticCalleesList(param);
+        List<CompanyVoiceRoboticCallees> companyVoiceRoboticCallees = companyVoiceRoboticCalleesMapper.selectCompanyVoiceRoboticCalleesListByRoboticId(id);
         if(null != companyVoiceRoboticCallees && !companyVoiceRoboticCallees.isEmpty()){
             companyVoiceRoboticCallees.forEach(item -> {
                 item.setIdToString(item.getId().toString());

+ 43 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseMarketingEvent.java

@@ -0,0 +1,43 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+/**
+ * 课程曝光/点击埋点对象 fs_course_marketing_event
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Data
+public class FsCourseMarketingEvent extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 小节id,课程级事件可为空 */
+    @Excel(name = "小节id")
+    private Long videoId;
+
+    /** 0-曝光 1-点击 */
+    @Excel(name = "事件类型", readConverterExp = "0=曝光,1=点击")
+    private Integer eventType;
+
+    /** 0-首页推荐 1-课程中心 */
+    @Excel(name = "曝光位置", readConverterExp = "0=首页推荐,1=课程中心")
+    private Integer exposurePosition;
+}

+ 49 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseShareLog.java

@@ -0,0 +1,49 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 课程分享记录对象 fs_course_share_log
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Data
+public class FsCourseShareLog implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键id */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 小节id */
+    @Excel(name = "小节id")
+    private Long videoId;
+
+    /** 分享类型 */
+    @Excel(name = "分享类型")
+    private Integer shareType;
+
+    /** 曝光位置 0-首页推荐 1-课程中心 */
+    @Excel(name = "曝光位置", readConverterExp = "0=首页推荐,1=课程中心")
+    private Integer exposurePosition;
+
+    /** 创建时间 */
+    @Excel(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 63 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseMarketingEventMapper.java

@@ -0,0 +1,63 @@
+package com.fs.course.mapper;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseMarketingEvent;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 课程曝光/点击埋点Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Repository
+public interface FsCourseMarketingEventMapper
+{
+    /**
+     * 查询课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 课程曝光/点击埋点
+     */
+    public FsCourseMarketingEvent selectFsCourseMarketingEventById(Long id);
+
+    /**
+     * 查询课程曝光/点击埋点列表
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 课程曝光/点击埋点集合
+     */
+    public List<FsCourseMarketingEvent> selectFsCourseMarketingEventList(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 新增课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int insertFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 修改课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int updateFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 删除课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventById(Long id);
+
+    /**
+     * 批量删除课程曝光/点击埋点
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventByIds(Long[] ids);
+}

+ 64 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseShareLogMapper.java

@@ -0,0 +1,64 @@
+package com.fs.course.mapper;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseShareLog;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 课程分享记录Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Repository
+public interface FsCourseShareLogMapper
+{
+    /**
+     * 查询课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 课程分享记录
+     */
+    public FsCourseShareLog selectFsCourseShareLogById(Long id);
+
+    /**
+     * 查询课程分享记录列表
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 课程分享记录集合
+     */
+    public List<FsCourseShareLog> selectFsCourseShareLogList(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 新增课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int insertFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 修改课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int updateFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 删除课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogById(Long id);
+
+    /**
+     * 批量删除课程分享记录
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogByIds(Long[] ids);
+}

+ 2 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchCommentMapper.java

@@ -85,4 +85,6 @@ public interface FsCourseWatchCommentMapper extends BaseMapper<FsCourseWatchComm
      * @return list
      */
     List<FsCourseWatchCommentVO> selectH5CourseWatchCommentsRound(FsCourseWatchCommentListParam param);
+
+    List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentPublicList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam);
 }

+ 10 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -370,4 +370,14 @@ public interface FsUserCourseMapper
     @Select("SELECT COUNT(1) FROM fs_user_course WHERE IFNULL(is_del,0) = 0 AND (cate_id = #{cateId} OR sub_cate_id = #{cateId})")
     int countCourseByCategoryId(@Param("cateId") Long cateId);
 
+    /**
+     * 查询同分类下推荐位排序值重复的课程(首页课程顶部推荐位)
+     */
+    List<FsUserCourse> selectDuplicateRecHomeCourseTopSort(@Param("cateId") Long cateId, @Param("subCateId") Long subCateId, @Param("recHomeCourseTopSort") Integer recHomeCourseTopSort, @Param("excludeCourseId") Long excludeCourseId);
+
+    /**
+     * 查询同分类下推荐位排序值重复的课程(首页长视频瀑布流)
+     */
+    List<FsUserCourse> selectDuplicateRecHomeLongVideoSort(@Param("cateId") Long cateId, @Param("subCateId") Long subCateId, @Param("recHomeLongVideoSort") Integer recHomeLongVideoSort, @Param("excludeCourseId") Long excludeCourseId);
+
 }

+ 6 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseStudyMapper.java

@@ -71,6 +71,12 @@ public interface FsUserCourseStudyMapper
             "<if test = ' maps.userId  '> " +
             "and s.user_id = #{maps.userId} " +
             "</if>" +
+            "<if test = ' maps.courseType != null and maps.courseType == 1 '> " +
+            "and c.cate_id = (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1) " +
+            "</if>" +
+            "<if test = ' maps.courseType == null  '> " +
+            "and c.cate_id != (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1) " +
+            "</if>" +
             " order by s.study_id  "+
             "</script>"})
     List<FsUserCourseStudyListUVO> selectFsUserCourseStudyListUVO(@Param("maps")FsUserCourseListUParam param);

+ 5 - 0
fs-service/src/main/java/com/fs/course/mapper/PublicCourseWatchStatisticsMapper.java

@@ -1,6 +1,7 @@
 package com.fs.course.mapper;
 
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
 import org.apache.ibatis.annotations.Param;
@@ -15,4 +16,8 @@ public interface PublicCourseWatchStatisticsMapper {
     List<PublicCourseWatchStatCourseVO> selectCourseDayStatList(@Param("q") PublicCourseWatchStatQueryParam q);
 
     List<PublicCourseWatchStatCatalogVO> selectCatalogStatList(@Param("q") PublicCourseWatchStatQueryParam q);
+
+    List<CatalogUserStudyVO> selectCatalogUserStudyList(@Param("videoId") Long videoId,
+                                                       @Param("nickName") String nickName,
+                                                       @Param("phone") String phone);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentPageParam.java

@@ -43,4 +43,8 @@ public class FsCourseWatchCommentPageParam extends BaseEntity{
     @ApiModelProperty(value = "分类类型:0-评论,1-公域看课评论")
     private Integer cateType;
 
+    /** 弹幕状态:0-正常,1-屏蔽 */
+    @ApiModelProperty(value = "弹幕状态:0-正常,1-屏蔽")
+    private Integer status;
+
 }

+ 5 - 1
fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java

@@ -39,6 +39,9 @@ public class FsUserCourseCategoryAppQueryParam implements Serializable {
     @ApiModelProperty(value = "原乡行标签:不传或0=只统计/展示下挂课程中无「原乡行」标签的;1=只统计/展示含「原乡行」标签的课程", example = "0")
     private Integer yxxTag;
 
+    @ApiModelProperty(value = "首页排序 1 是首页,后端添加标签排序,0不是首页", example = "0")
+    private Integer homePage;
+
     /**
      * 与 {@link com.fs.course.service.impl.FsUserCourseCategoryServiceImpl#selectFsUserCourseCategoryAppList}
      * 中默认入参(null 补全)一致,供缓存 key 使用。
@@ -48,7 +51,8 @@ public class FsUserCourseCategoryAppQueryParam implements Serializable {
         int ps = pageSize != null ? pageSize : 10;
         int ct = cateType != null ? cateType : 1;
         int yxx = yxxTag != null ? yxxTag : 0;
-        return pn + "|" + ps + "|" + n(cateName) + "|" + n(pid) + "|" + n(isShow) + "|" + ct + "|" + yxx;
+        int home = homePage != null ? homePage : 0;
+        return pn + "|" + ps + "|" + n(cateName) + "|" + n(pid) + "|" + n(isShow) + "|" + ct + "|" + yxx  + "|" + home;
     }
 
     private static String n(Object o) {

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseListUParam.java

@@ -22,6 +22,8 @@ public class FsUserCourseListUParam  implements Serializable {
 
     Integer isBest;
 
+    Integer courseType;
+
     @ApiModelProperty(value = "页码,默认为1")
     private Integer pageNum =1;
     @ApiModelProperty(value = "页大小,默认为10")

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseOrderDoPayParam.java

@@ -11,4 +11,6 @@ public class FsUserCourseOrderDoPayParam implements Serializable {
     Long orderId;
     Long userId;
     private String appId;
+
+    private String payType;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsUserVipOrderPayUParam.java

@@ -12,4 +12,6 @@ public class FsUserVipOrderPayUParam implements Serializable {
    @NotNull(message = "orderId不能为空")
    private Long orderId;
    private String appId;
+
+   private String payType;
 }

+ 61 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseMarketingEventService.java

@@ -0,0 +1,61 @@
+package com.fs.course.service;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseMarketingEvent;
+
+/**
+ * 课程曝光/点击埋点Service接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+public interface IFsCourseMarketingEventService
+{
+    /**
+     * 查询课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 课程曝光/点击埋点
+     */
+    public FsCourseMarketingEvent selectFsCourseMarketingEventById(Long id);
+
+    /**
+     * 查询课程曝光/点击埋点列表
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 课程曝光/点击埋点集合
+     */
+    public List<FsCourseMarketingEvent> selectFsCourseMarketingEventList(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 新增课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int insertFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 修改课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int updateFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 批量删除课程曝光/点击埋点
+     *
+     * @param ids 需要删除的课程曝光/点击埋点主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventByIds(Long[] ids);
+
+    /**
+     * 删除课程曝光/点击埋点信息
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventById(Long id);
+}

+ 61 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseShareLogService.java

@@ -0,0 +1,61 @@
+package com.fs.course.service;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseShareLog;
+
+/**
+ * 课程分享记录Service接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+public interface IFsCourseShareLogService
+{
+    /**
+     * 查询课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 课程分享记录
+     */
+    public FsCourseShareLog selectFsCourseShareLogById(Long id);
+
+    /**
+     * 查询课程分享记录列表
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 课程分享记录集合
+     */
+    public List<FsCourseShareLog> selectFsCourseShareLogList(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 新增课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int insertFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 修改课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int updateFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 批量删除课程分享记录
+     *
+     * @param ids 需要删除的课程分享记录主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogByIds(Long[] ids);
+
+    /**
+     * 删除课程分享记录信息
+     *
+     * @param id 课程分享记录主键
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogById(Long id);
+}

+ 1 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchCommentService.java

@@ -86,4 +86,5 @@ public interface IFsCourseWatchCommentService extends IService<FsCourseWatchComm
      */
     List<FsCourseWatchCommentVO> selectH5CourseWatchComments(FsCourseWatchCommentListParam param);
 
+    List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentPublicList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseOrderService.java

@@ -9,6 +9,8 @@ import com.fs.course.vo.FsUserCourseOrderListPVO;
 import com.fs.course.vo.FsUserCourseOrderListUVO;
 import com.fs.his.param.FsPackageOrderDoPayParam;
 
+import javax.servlet.http.HttpServletRequest;
+
 /**
  * 课程订单信息Service接口
  *
@@ -84,4 +86,6 @@ public interface IFsUserCourseOrderService
     R createIntegralOrder(FsUserCourseOrderCreateParam param);
 
     List<FsUserCourseOrderListUVO> selectFsUserCourseOrderListUVO(FsUserCourseOrderListUParam param);
+
+    R appPayment(FsUserCourseOrderDoPayParam param, HttpServletRequest request);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserVipOrderService.java

@@ -11,6 +11,8 @@ import com.fs.course.param.FsUserVipOrderPayUParam;
 import com.fs.course.vo.FsUserVipOrderListPVO;
 import com.fs.his.param.FsPackageOrderDoPayParam;
 
+import javax.servlet.http.HttpServletRequest;
+
 /**
  * 购买会员订单Service接口
  *
@@ -78,4 +80,6 @@ public interface IFsUserVipOrderService
     R payConfirm(String orderSn,String payCode, String tradeNo,String payType,Integer type,String bankTransactionId,String bankSerialNo);
 
     R aliPayment(FsUserVipOrderPayUParam param);
+
+    R appPayment(FsUserVipOrderPayUParam param, HttpServletRequest request);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/service/IPublicCourseWatchStatisticsService.java

@@ -1,6 +1,7 @@
 package com.fs.course.service;
 
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
 
@@ -14,4 +15,6 @@ public interface IPublicCourseWatchStatisticsService {
     List<PublicCourseWatchStatCourseVO> listCourseDayStat(PublicCourseWatchStatQueryParam param);
 
     List<PublicCourseWatchStatCatalogVO> listCatalogStat(PublicCourseWatchStatQueryParam param);
+
+    List<CatalogUserStudyVO> listCatalogUserStudy(Long videoId, String nickName, String phone);
 }

+ 96 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseMarketingEventServiceImpl.java

@@ -0,0 +1,96 @@
+package com.fs.course.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsCourseMarketingEvent;
+import com.fs.course.mapper.FsCourseMarketingEventMapper;
+import com.fs.course.service.IFsCourseMarketingEventService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 课程曝光/点击埋点Service业务层处理
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Service
+public class FsCourseMarketingEventServiceImpl implements IFsCourseMarketingEventService
+{
+    @Autowired
+    private FsCourseMarketingEventMapper fsCourseMarketingEventMapper;
+
+    /**
+     * 查询课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 课程曝光/点击埋点
+     */
+    @Override
+    public FsCourseMarketingEvent selectFsCourseMarketingEventById(Long id)
+    {
+        return fsCourseMarketingEventMapper.selectFsCourseMarketingEventById(id);
+    }
+
+    /**
+     * 查询课程曝光/点击埋点列表
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 课程曝光/点击埋点
+     */
+    @Override
+    public List<FsCourseMarketingEvent> selectFsCourseMarketingEventList(FsCourseMarketingEvent fsCourseMarketingEvent)
+    {
+        return fsCourseMarketingEventMapper.selectFsCourseMarketingEventList(fsCourseMarketingEvent);
+    }
+
+    /**
+     * 新增课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent)
+    {
+        fsCourseMarketingEvent.setCreateTime(DateUtils.getNowDate());
+        return fsCourseMarketingEventMapper.insertFsCourseMarketingEvent(fsCourseMarketingEvent);
+    }
+
+    /**
+     * 修改课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent)
+    {
+        fsCourseMarketingEvent.setUpdateTime(DateUtils.getNowDate());
+        return fsCourseMarketingEventMapper.updateFsCourseMarketingEvent(fsCourseMarketingEvent);
+    }
+
+    /**
+     * 批量删除课程曝光/点击埋点
+     *
+     * @param ids 需要删除的课程曝光/点击埋点主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseMarketingEventByIds(Long[] ids)
+    {
+        return fsCourseMarketingEventMapper.deleteFsCourseMarketingEventByIds(ids);
+    }
+
+    /**
+     * 删除课程曝光/点击埋点信息
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseMarketingEventById(Long id)
+    {
+        return fsCourseMarketingEventMapper.deleteFsCourseMarketingEventById(id);
+    }
+}

+ 49 - 23
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -645,17 +645,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         if (param.getLinkType() != null && param.getLinkType() == 1) {
             errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideoToday(param.getVideoId(), param.getUserId(), null, null);
         } else {
-            FsCourseWatchLog courseWatchLog = resolveCourseWatchLogForApp(
-                    param.getVideoId(), param.getUserId(), param.getCompanyUserId(), param.getPeriodId());
-            if (courseWatchLog == null) {
+            if (studyRow == null) {
                 return R.error("无看课记录");
             }
-            if (courseWatchLog.getLogType() == null || courseWatchLog.getLogType() != 2) {
-                return R.error("未完课");
-            }
-            logId = courseWatchLog.getLogId();
+            logId = Long.valueOf(studyRow.getLogId());
             errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideoToday(
-                    param.getVideoId(), param.getUserId(), param.getQwUserId(), courseWatchLog.getProject());
+                    param.getVideoId(), param.getUserId(), param.getQwUserId(), null);
         }
 
         if (errorCount >= APP_USER_ANSWER_MAX_ATTEMPTS) {
@@ -663,8 +658,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         }
         int remainCount = APP_USER_ANSWER_MAX_ATTEMPTS - errorCount - 1;
 
+
         List<FsCourseQuestionBank> questions = param.getQuestions();
         if (questions != null && !questions.isEmpty()) {
+//            String json = configService.selectConfigByKey("course.config");
+//            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+//            boolean skipAnswerValidation = CourseConfigUserAnswerExpose.skipValidateAnswerOnSubmit(json);
 //            if (skipAnswerValidation) {
 //                thisRightCount = questions.size();
 //            } else {
@@ -721,6 +720,43 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         if (thisRightCount == questionCount) {
             logs.setIsRight(1);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
+
+            // 答题成功自动发放积分
+            try {
+                FsUserCourseVideo video = courseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
+                if (video != null && video.getIntegralReward() != null && video.getIntegralReward() > 0) {
+                    String businessId = resolveAnswerIntegralBusinessId(param.getVideoId(), param.getPeriodId());
+                    FsUserIntegralLogs existed = fsUserIntegralLogsMapper.selectAnswerRewardIntegralLog(param.getUserId(), businessId);
+                    if (existed == null) {
+                        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+                        if (user != null) {
+                            long baseIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
+                            long reward = video.getIntegralReward().longValue();
+                            long newBalance = baseIntegral + reward;
+
+                            FsUser upd = new FsUser();
+                            upd.setUserId(param.getUserId());
+                            upd.setIntegral(newBalance);
+                            fsUserMapper.updateFsUser(upd);
+
+                            FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+                            integralLogs.setIntegral(reward);
+                            integralLogs.setUserId(param.getUserId());
+                            integralLogs.setBalance(newBalance);
+                            integralLogs.setLogType(FsUserIntegralLogTypeEnum.TYPE_17.getValue());
+                            integralLogs.setBusinessId(businessId);
+                            integralLogs.setRemark(FsUserIntegralLogTypeEnum.TYPE_17.getDesc());
+                            integralLogs.setCreateTime(new Date());
+                            fsUserIntegralLogsService.insertFsUserIntegralLogs(integralLogs);
+
+                            return R.ok("答题成功").put("integral", video.getIntegralReward()).put("balance", newBalance);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+
+            }
+
             return R.ok("答题成功");
         }
         logs.setIsRight(0);
@@ -775,14 +811,10 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (param.getLinkType() != null && param.getLinkType() == 1) {
                 rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideo(param.getVideoId(), param.getUserId(), null);
             } else {
-                FsCourseWatchLog watchLog = resolveCourseWatchLogForApp(
-                        param.getVideoId(), param.getUserId(), param.getCompanyUserId(), param.getPeriodId());
-                if (watchLog == null) {
+                if (studyRow == null) {
                     return R.error("无看课记录");
                 }
-                if (watchLog.getLogType() == null || watchLog.getLogType() != 2) {
-                    return R.error("未完课");
-                }
+
                 if (param.getPeriodId() != null) {
                     rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideoWithPeriodId(
                             param.getVideoId(), param.getUserId(), param.getQwUserId(), param.getPeriodId());
@@ -875,21 +907,15 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             return vo;
         }
 
-        FsCourseWatchLog watchLog = resolveCourseWatchLogForApp(
-                param.getVideoId(), param.getUserId(), param.getCompanyUserId(), param.getPeriodId());
-        if (watchLog == null) {
+
+        if (studyRow == null) {
             vo.setStatus(-2);
             vo.setStatusText("无看课记录");
             return vo;
         }
-        if (watchLog.getLogType() == null || watchLog.getLogType() != 2) {
-            vo.setStatus(-3);
-            vo.setStatusText("未完课");
-            return vo;
-        }
 
         int errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideoToday(
-                param.getVideoId(), param.getUserId(), param.getQwUserId(), watchLog.getProject());
+                param.getVideoId(), param.getUserId(), param.getQwUserId(), null);
         vo.setErrorCount(errorCount);
         fillProgressByErrorCount(vo, errorCount);
         return vo;

+ 95 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseShareLogServiceImpl.java

@@ -0,0 +1,95 @@
+package com.fs.course.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsCourseShareLog;
+import com.fs.course.mapper.FsCourseShareLogMapper;
+import com.fs.course.service.IFsCourseShareLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 课程分享记录Service业务层处理
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Service
+public class FsCourseShareLogServiceImpl implements IFsCourseShareLogService
+{
+    @Autowired
+    private FsCourseShareLogMapper fsCourseShareLogMapper;
+
+    /**
+     * 查询课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 课程分享记录
+     */
+    @Override
+    public FsCourseShareLog selectFsCourseShareLogById(Long id)
+    {
+        return fsCourseShareLogMapper.selectFsCourseShareLogById(id);
+    }
+
+    /**
+     * 查询课程分享记录列表
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 课程分享记录
+     */
+    @Override
+    public List<FsCourseShareLog> selectFsCourseShareLogList(FsCourseShareLog fsCourseShareLog)
+    {
+        return fsCourseShareLogMapper.selectFsCourseShareLogList(fsCourseShareLog);
+    }
+
+    /**
+     * 新增课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseShareLog(FsCourseShareLog fsCourseShareLog)
+    {
+        fsCourseShareLog.setCreateTime(DateUtils.getNowDate());
+        return fsCourseShareLogMapper.insertFsCourseShareLog(fsCourseShareLog);
+    }
+
+    /**
+     * 修改课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseShareLog(FsCourseShareLog fsCourseShareLog)
+    {
+        return fsCourseShareLogMapper.updateFsCourseShareLog(fsCourseShareLog);
+    }
+
+    /**
+     * 批量删除课程分享记录
+     *
+     * @param ids 需要删除的课程分享记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseShareLogByIds(Long[] ids)
+    {
+        return fsCourseShareLogMapper.deleteFsCourseShareLogByIds(ids);
+    }
+
+    /**
+     * 删除课程分享记录信息
+     *
+     * @param id 课程分享记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseShareLogById(Long id)
+    {
+        return fsCourseShareLogMapper.deleteFsCourseShareLogById(id);
+    }
+}

+ 6 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchCommentServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -172,4 +173,9 @@ public class FsCourseWatchCommentServiceImpl extends ServiceImpl<FsCourseWatchCo
         }
     }
 
+    @Override
+    public List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentPublicList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam) {
+        return baseMapper.selectFsCourseWatchCommentPublicList(fsCourseWatchCommentPageParam);
+    }
+
 }

+ 4 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCategoryServiceImpl.java

@@ -70,9 +70,10 @@ public class FsUserCourseCategoryServiceImpl implements IFsUserCourseCategorySer
     }
 
     @Override
-    @Cacheable(
-            value = PublicCourseAppCacheNames.CATEGORY_APP_LIST,
-            key = "#param == null ? 'default' : #param.appListCacheKey()")
+//    @Cacheable(
+//            value = PublicCourseAppCacheNames.CATEGORY_APP_LIST,
+//            key = "#param == null ? 'default' : #param.appListCacheKey()")
+    @Cacheable(value = "publicCourseCategoryApp", key = "#param.appListCacheKey()")
     public PageInfo<FsUserCourseCategory> selectFsUserCourseCategoryAppPage(FsUserCourseCategoryAppQueryParam param) {
         if (param == null) {
             param = new FsUserCourseCategoryAppQueryParam();

+ 190 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseOrderServiceImpl.java

@@ -6,6 +6,12 @@ import java.util.*;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.diagnosis.DiagnosisUtils;
+import com.alipay.api.domain.AlipayTradeAppPayModel;
+import com.alipay.api.request.AlipayTradeAppPayRequest;
+import com.alipay.api.response.AlipayTradeAppPayResponse;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.core.domain.R;
@@ -19,6 +25,7 @@ import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
+import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.course.service.IFsUserCourseStudyService;
 import com.fs.course.vo.FsUserCourseOrderListPVO;
 import com.fs.course.vo.FsUserCourseOrderListUVO;
@@ -29,10 +36,7 @@ import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.mapper.*;
 import com.fs.his.param.FsPackageOrderDoPayParam;
 import com.fs.his.param.FsUserAddIntegralTemplateParam;
-import com.fs.his.service.IFsCouponService;
-import com.fs.his.service.IFsStorePaymentService;
-import com.fs.his.service.IFsUserCouponService;
-import com.fs.his.service.IFsUserIntegralLogsService;
+import com.fs.his.service.*;
 import com.fs.huifuPay.domain.HuiFuCreateOrder;
 import com.fs.huifuPay.domain.HuifuCreateOrderResult;
 import com.fs.huifuPay.service.HuiFuService;
@@ -55,6 +59,7 @@ import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
 import org.checkerframework.checker.units.qual.A;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -63,6 +68,7 @@ import org.springframework.stereotype.Service;
 import com.fs.course.service.IFsUserCourseOrderService;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import javax.servlet.http.HttpServletRequest;
 
 /**
  * 课程订单信息Service业务层处理
@@ -70,6 +76,7 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
  * @author fs
  * @date 2024-05-21
  */
+@Slf4j
 @Service
 public class FsUserCourseOrderServiceImpl implements IFsUserCourseOrderService
 {
@@ -133,6 +140,14 @@ public class FsUserCourseOrderServiceImpl implements IFsUserCourseOrderService
     private IFsUserIntegralLogsService userIntegralLogsService;
     @Autowired
     private FsUserWxMapper fsUserWxMapper;
+    @Autowired
+    private FsUserMapper userMapper;
+    @Autowired
+    private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
+    @Autowired
+    private IMerchantAppConfigService merchantAppConfigService;
+    @Autowired
+    private IFsUserService fsUserService;
 
 
 
@@ -367,6 +382,177 @@ public class FsUserCourseOrderServiceImpl implements IFsUserCourseOrderService
         }
     }
 
+    @Override
+    public R appPayment(FsUserCourseOrderDoPayParam param, HttpServletRequest request) {
+        FsUserCourseOrder order = fsUserCourseOrderMapper.selectFsUserCourseOrderByOrderId(param.getOrderId());
+        if (order == null) {
+            return R.error("订单不存在");
+        }
+        if (order.getStatus() != 1) {
+            return R.error("非法操作");
+        }
+
+        FsUser user = userMapper.selectFsUserByUserId(param.getUserId());
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+
+        if (order.getPayMoney().compareTo(new BigDecimal(0)) <= 0) {
+            this.payConfirm(order.getOrderCode(),"","","",2,null,null);
+            return R.ok().put("isPay",1);
+        }
+
+        FsCoursePlaySourceConfig fsCoursePlaySourceConfig = fsCoursePlaySourceConfigService.selectCoursePlaySourceConfigByAppId(param.getAppId());
+        if (fsCoursePlaySourceConfig == null) {
+            throw new CustomException("未找到appId对应的配置: " + param.getAppId());
+        }
+        Long merchantConfigId = fsCoursePlaySourceConfig.getMerchantConfigId();
+        if (merchantConfigId == null || merchantConfigId <= 0) {
+            throw new CustomException("没有配置商户信息");
+        }
+
+        MerchantAppConfig merchantAppConfig = merchantAppConfigService.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
+        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+        String openId = null;
+        String appId = param.getAppId();
+        if (request.getHeader("sourcetype") != null && "APP".equals(request.getHeader("sourcetype"))) {
+            FsUser fsUser = fsUserService.selectFsUserByUserId(param.getUserId().longValue());
+            openId = fsUser.getAppOpenId();
+        } else {
+            if (StringUtils.isNotBlank(appId)) {
+                //查询fs_user_wx的openId
+                Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery()
+                        .eq(FsUserWx::getFsUserId, param.getUserId())
+                        .eq(FsUserWx::getAppId, appId);
+                FsUserWx fsUserWx = fsUserWxMapper.selectOne(queryWrapper);
+                if (fsUserWx != null) {
+                    openId = fsUserWx.getOpenId();
+                }
+            } else {
+                appId = merchantAppConfig.getAppId();
+                openId = Objects.isNull(user) ? "" : user.getMaOpenId();
+                if (StringUtils.isBlank(openId)){
+                    Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery()
+                            .eq(FsUserWx::getFsUserId, param.getUserId())
+                            .eq(FsUserWx::getAppId, appId);
+                    FsUserWx fsUserWx = fsUserWxMapper.selectOne(queryWrapper);
+                    if (Objects.nonNull(fsUserWx)){
+                        openId = fsUserWx.getOpenId();
+                    }
+                }
+            }
+        }
+
+        if ("hf".equals(merchantAppConfig.getMerchantType()) && "wx".equals(param.getPayType()) && StringUtils.isBlank(openId)) {
+            return R.error("用户OPENID不存在");
+        }
+
+        String payCode = OrderCodeUtils.getOrderSn();
+        if (StringUtils.isEmpty(payCode)) {
+            return R.error("订单生成失败,请重试");
+        }
+
+        FsStorePayment storePayment = new FsStorePayment();
+        storePayment.setStatus(0);
+        storePayment.setPayMode(merchantAppConfig.getMerchantType());
+        storePayment.setBusinessCode(order.getOrderCode());
+        storePayment.setPayCode(payCode);
+        storePayment.setPayMoney(order.getPayMoney());
+        storePayment.setCreateTime(new Date());
+        storePayment.setPayTypeCode("app");
+        storePayment.setBusinessType(4);
+        storePayment.setRemark("课程订单支付");
+        storePayment.setPayTypeCode(param.getPayType());
+        storePayment.setUserId(user.getUserId());
+        storePayment.setBusinessId(order.getOrderId().toString());
+        storePayment.setMerConfigId(merchantAppConfig.getId());
+        storePayment.setAppId(param.getAppId());
+        if (storePaymentService.insertFsStorePayment(storePayment) > 0) {
+            if (!"hf".equals(merchantAppConfig.getMerchantType()) && !"appPay".equals(merchantAppConfig.getMerchantType())) {
+                return R.error("支付暂不可用!");
+            }
+
+            if ("hf".equals(merchantAppConfig.getMerchantType())) {
+                HuiFuCreateOrder o = new HuiFuCreateOrder();
+                o.setTradeType(param.getPayType().equals("wx") ? "T_MINIAPP" : "A_NATIVE");
+                o.setOpenid(openId);
+                o.setAppId(appId);
+                o.setReqSeqId("course-"+storePayment.getPayCode());
+                o.setTransAmt(storePayment.getPayMoney().toString());
+                o.setAppId(param.getAppId());
+                o.setGoodsDesc("课程订单支付");
+                HuifuCreateOrderResult result = huiFuService.createOrder(o);
+                FsStorePayment mt=new FsStorePayment();
+                mt.setPaymentId(storePayment.getPaymentId());
+                mt.setTradeNo(result.getHf_seq_id());
+                mt.setAppId(appId);
+                storePaymentService.updateFsStorePayment(mt);
+                return R.ok().put("isPay",0).put("data",result).put("type","hf");
+            } else if ("appPay".equals(merchantAppConfig.getMerchantType()) && "wx".equals(param.getPayType())) {
+                //创建微信订单
+                WxPayConfig payConfig = new WxPayConfig();
+                payConfig.setAppId(appId);
+                payConfig.setMchId(fsPayConfig.getWxAppMchId());
+                payConfig.setMchKey(fsPayConfig.getWxAppMchKey());
+                payConfig.setSubAppId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+                payConfig.setSubMchId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+                payConfig.setKeyPath(null);
+                payConfig.setNotifyUrl(fsPayConfig.getWxAppNotifyUrl());
+                wxPayService.setConfig(payConfig);
+                WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
+                orderRequest.setBody("课程订单支付");
+                orderRequest.setOutTradeNo("course-"+storePayment.getPayCode());
+                orderRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(storePayment.getPayMoney().toString()));
+                orderRequest.setTradeType("APP");
+                orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(request));
+                orderRequest.setNotifyUrl(fsPayConfig.getNotifyUrlScrm());
+                //调用统一下单接口,获取"预支付交易会话标识"
+                try {
+                    log.info("会员开通订单支付 调用微信入参:{}", orderRequest.toString());
+                    Object result = wxPayService.createOrder(orderRequest);
+                    log.info("会员开通订单支付 调用微信出参:{}", result.toString());
+                    return R.ok().put("data",result).put("type","wxApp").put("isPay",0);
+                } catch (WxPayException e) {
+                    e.printStackTrace();
+                    throw new CustomException("会员开通订单支付"+e.getMessage());
+                }
+            } else if ("appPay".equals(merchantAppConfig.getMerchantType()) && "ali".equals(param.getPayType())) {
+                // 实例化客户端
+                AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", fsPayConfig.getAliAppId(), fsPayConfig.getAliPrivateKey(), "json", "utf-8", fsPayConfig.getAliPublicKey(), "RSA2");
+                // 构造请求参数以调用接口
+                AlipayTradeAppPayRequest aliRequest = new AlipayTradeAppPayRequest();
+                AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
+                // 商户订单号
+                model.setOutTradeNo("course-" + storePayment.getPayCode());
+                // 订单总金额
+                model.setTotalAmount(storePayment.getPayMoney().toString());
+                // 订单标题
+                model.setSubject("课程订单支付");
+                aliRequest.setBizModel(model);
+                aliRequest.setNotifyUrl(fsPayConfig.getAliNotifyUrl());
+
+                try {
+                    log.info("会员开通订单支付 调用支付宝入参:{}", aliRequest.toString());
+                    AlipayTradeAppPayResponse result = alipayClient.sdkExecute(aliRequest);
+                    if (!result.isSuccess()) {
+                        String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(result);
+                        log.error("支付宝支付调用失败 诊断链接:{}", diagnosisUrl);
+                        throw new CustomException(result.getSubMsg());
+                    }
+
+                    log.info("会员开通订单支付 调用支付宝入参:{}", result.toString());
+
+                    return R.ok().put("isPay", 0).put("data", result).put("type", "aliPay");
+                } catch (Exception e){
+                    log.info("支付宝支付异常: {}", e);
+                    throw new CustomException("支付系统异常,请稍后重试");
+                }
+            }
+        }
+
+        return R.error();
+    }
+
     @Override
     public R payment(FsUserCourseOrderDoPayParam param) {
         FsUserCourseOrder order = fsUserCourseOrderMapper.selectFsUserCourseOrderByOrderId(param.getOrderId());

+ 55 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -14,6 +14,8 @@ import java.util.stream.Collectors;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.fs.common.utils.RedisUtil;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.CloudHostUtils;
@@ -43,6 +45,7 @@ import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsUserAddIntegralTemplateParam;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserNewTaskService;
+import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.service.IQwCompanyService;
@@ -131,6 +134,9 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
     @Autowired
     private AsyncUploadQwCourseImageService asyncUploadQwCourseImageService;
     @Autowired
@@ -147,6 +153,49 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     private static final String appRealLink = "/appcourse/pages_course/videovip?course=";
     public static final String appShortLink = "/courseH5/pages_course/videovip?s=";
 
+    @Autowired
+    private RedisCacheUtil redisCacheUtil;
+
+    /**
+     * 校验同分类下推荐位排序值是否重复(仅非北京卓美客户校验)
+     * @param fsUserCourse 课程对象
+     * @param excludeCourseId 排除的课程ID(修改时排除自身,新增时传null)
+     */
+    private void validateDuplicateSort(FsUserCourse fsUserCourse, Long excludeCourseId) {
+        if (!"北京卓美".equals(cloudHostProper.getCompanyName())) {
+            return;
+        }
+        Long cateId = fsUserCourse.getCateId();
+        Long subCateId = fsUserCourse.getSubCateId();
+
+        // 校验首页课程顶部推荐位排序值重复
+        if (fsUserCourse.getRecHomeCourseTopSort() != null) {
+            List<FsUserCourse> duplicateTop = fsUserCourseMapper.selectDuplicateRecHomeCourseTopSort(
+                    cateId, subCateId, fsUserCourse.getRecHomeCourseTopSort(), excludeCourseId);
+            if (!duplicateTop.isEmpty()) {
+                String duplicateNames = duplicateTop.stream()
+                        .map(FsUserCourse::getCourseName)
+                        .collect(java.util.stream.Collectors.joining("、"));
+                throw new com.fs.common.exception.CustomException(
+                        "首页课程顶部推荐位重复:" + duplicateNames);
+            }
+        }
+
+        // 校验首页长视频瀑布流排序值重复
+        if (fsUserCourse.getRecHomeLongVideoSort() != null) {
+            List<FsUserCourse> duplicateLong = fsUserCourseMapper.selectDuplicateRecHomeLongVideoSort(
+                    cateId, subCateId, fsUserCourse.getRecHomeLongVideoSort(), excludeCourseId);
+            if (duplicateLong != null && !duplicateLong.isEmpty()) {
+                String duplicateNames = duplicateLong.stream()
+                        .map(FsUserCourse::getCourseName)
+                        .collect(java.util.stream.Collectors.joining("、"));
+                throw new com.fs.common.exception.CustomException(
+                        "首页长视频瀑布流重复:" + duplicateNames);
+            }
+        }
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.CATEGORY_APP_LIST);
+    }
+
     /**
      * 查询课程
      *
@@ -185,6 +234,8 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
             asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
         }
 
+        // 校验推荐位排序值重复
+        validateDuplicateSort(fsUserCourse, null);
 
         fsUserCourse.setCreateTime(DateUtils.getNowDate());
         return  fsUserCourseMapper.insertFsUserCourse(fsUserCourse);
@@ -203,6 +254,9 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
             asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
         }
 
+        // 校验推荐位排序值重复
+        validateDuplicateSort(fsUserCourse, fsUserCourse.getCourseId());
+
         fsUserCourse.setUpdateTime(DateUtils.getNowDate());
         return fsUserCourseMapper.updateFsUserCourse(fsUserCourse);
     }
@@ -294,9 +348,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     }
 
     @Override
-    @Cacheable(
-            value = PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST,
-            key = "#param == null ? 'default' : #param.appListCacheKey()")
+    @Cacheable(value = "publicCoursePublicApp", key = "#param.appListCacheKey()")
     public PageInfo<FsUserCoursePublicAppVO> selectFsUserCoursePublicAppPage(FsUserCoursePublicAppQueryParam param) {
         if (param == null) {
             param = new FsUserCoursePublicAppQueryParam();

+ 2 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -5472,7 +5472,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 // 调用第三方接口(锁外操作)
                 R sendRedPacket;
                 try {
-                    sendRedPacket = paymentService.sendAppRedPacket(packetParam);
+                    sendRedPacket = paymentService.sendAppRedPacket(packetParam, config);
                 } catch (Exception e) {
                     logger.error("红包发送异常: 异常请求参数{}", packetParam, e);
                     // 异常时回滚余额
@@ -5538,7 +5538,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
 
                 try{
                     // 发送红包
-                    R sendRedPacket = paymentService.sendAppRedPacket(packetParam);
+                    R sendRedPacket = paymentService.sendAppRedPacket(packetParam,config);
                     if (sendRedPacket.get("code").equals(200)) {
                         FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
                         TransferBillsResult transferBillsResult;

+ 191 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserVipOrderServiceImpl.java

@@ -11,6 +11,13 @@ import java.util.Objects;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.diagnosis.DiagnosisUtils;
+import com.alipay.api.domain.AlipayTradeAppPayModel;
+import com.alipay.api.request.AlipayTradeAppPayRequest;
+import com.alipay.api.response.AlipayTradeAppPayResponse;
+import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.core.domain.R;
@@ -34,6 +41,8 @@ import com.fs.his.domain.*;
 import com.fs.his.dto.PayConfigDTO;
 import com.fs.his.mapper.*;
 import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserService;
+import com.fs.his.service.IMerchantAppConfigService;
 import com.fs.hisStore.domain.FsPayConfigScrm;
 import com.fs.huifuPay.domain.HuiFuCreateOrder;
 import com.fs.huifuPay.domain.HuifuCreateOrderResult;
@@ -55,6 +64,7 @@ import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
 import org.checkerframework.checker.units.qual.A;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -66,12 +76,15 @@ import com.fs.course.service.IFsUserVipOrderService;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
 
+import javax.servlet.http.HttpServletRequest;
+
 /**
  * 购买会员订单Service业务层处理
  *
  * @author fs
  * @date 2024-06-27
  */
+@Slf4j
 @Service
 public class FsUserVipOrderServiceImpl implements IFsUserVipOrderService
 {
@@ -114,6 +127,13 @@ public class FsUserVipOrderServiceImpl implements IFsUserVipOrderService
     @Autowired
     private FsUserWxMapper fsUserWxMapper;
 
+    @Autowired
+    private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
+    @Autowired
+    private IMerchantAppConfigService merchantAppConfigService;
+    @Autowired
+    private IFsUserService fsUserService;
+
 
     /**
      * 查询购买会员订单
@@ -221,6 +241,177 @@ public class FsUserVipOrderServiceImpl implements IFsUserVipOrderService
     }
 
 
+    @Override
+    @Transactional
+    public R appPayment(FsUserVipOrderPayUParam param, HttpServletRequest request) {
+        FsUserVipOrder order = fsUserVipOrderMapper.selectFsUserVipOrderByOrderId(param.getOrderId());
+        if (order == null) {
+            return R.error("订单不存在");
+        }
+        if (order.getStatus() != 0) {
+            return R.error("非法操作");
+        }
+
+        FsUser user = userMapper.selectFsUserByUserId(param.getUserId());
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+
+        if (order.getPayMoney().compareTo(new BigDecimal(0)) <= 0) {
+            this.payConfirm(order.getOrderCode(),"","","",2,null,null);
+            handleVipPurchaseCallback(param.getUserId(),order.getPackageId(),order.getOrderId());
+            return R.ok().put("isPay",1);
+        }
+
+        FsCoursePlaySourceConfig fsCoursePlaySourceConfig = fsCoursePlaySourceConfigService.selectCoursePlaySourceConfigByAppId(param.getAppId());
+        if (fsCoursePlaySourceConfig == null) {
+            throw new CustomException("未找到appId对应的配置: " + param.getAppId());
+        }
+        Long merchantConfigId = fsCoursePlaySourceConfig.getMerchantConfigId();
+        if (merchantConfigId == null || merchantConfigId <= 0) {
+            throw new CustomException("没有配置商户信息");
+        }
+
+        MerchantAppConfig merchantAppConfig = merchantAppConfigService.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
+        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+        String openId = null;
+        String appId = param.getAppId();
+        if (request.getHeader("sourcetype") != null && "APP".equals(request.getHeader("sourcetype"))) {
+            FsUser fsUser = fsUserService.selectFsUserByUserId(param.getUserId().longValue());
+            openId = fsUser.getAppOpenId();
+        } else {
+            if (StringUtils.isNotBlank(appId)) {
+                //查询fs_user_wx的openId
+                Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery()
+                        .eq(FsUserWx::getFsUserId, param.getUserId())
+                        .eq(FsUserWx::getAppId, appId);
+                FsUserWx fsUserWx = fsUserWxMapper.selectOne(queryWrapper);
+                if (fsUserWx != null) {
+                    openId = fsUserWx.getOpenId();
+                }
+            } else {
+                appId = merchantAppConfig.getAppId();
+                openId = Objects.isNull(user) ? "" : user.getMaOpenId();
+                if (StringUtils.isBlank(openId)){
+                    Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery()
+                            .eq(FsUserWx::getFsUserId, param.getUserId())
+                            .eq(FsUserWx::getAppId, appId);
+                    FsUserWx fsUserWx = fsUserWxMapper.selectOne(queryWrapper);
+                    if (Objects.nonNull(fsUserWx)){
+                        openId = fsUserWx.getOpenId();
+                    }
+                }
+            }
+        }
+
+        if ("hf".equals(merchantAppConfig.getMerchantType()) && "wx".equals(param.getPayType()) && StringUtils.isBlank(openId)) {
+            return R.error("用户OPENID不存在");
+        }
+
+        String payCode = OrderCodeUtils.getOrderSn();
+        if (StringUtils.isEmpty(payCode)) {
+            return R.error("订单生成失败,请重试");
+        }
+
+        FsStorePayment storePayment = new FsStorePayment();
+        storePayment.setStatus(0);
+        storePayment.setPayMode(merchantAppConfig.getMerchantType());
+        storePayment.setBusinessCode(order.getOrderCode());
+        storePayment.setPayCode(payCode);
+        storePayment.setPayMoney(order.getPayMoney());
+        storePayment.setCreateTime(new Date());
+        storePayment.setPayTypeCode("app");
+        storePayment.setBusinessType(5);
+        storePayment.setRemark("会员开通订单支付");
+        storePayment.setPayTypeCode(param.getPayType());
+        storePayment.setUserId(user.getUserId());
+        storePayment.setBusinessId(order.getOrderId().toString());
+        storePayment.setMerConfigId(merchantAppConfig.getId());
+        storePayment.setAppId(param.getAppId());
+        if (storePaymentService.insertFsStorePayment(storePayment) > 0) {
+            if (!"hf".equals(merchantAppConfig.getMerchantType()) && !"appPay".equals(merchantAppConfig.getMerchantType())) {
+                return R.error("支付暂不可用!");
+            }
+
+            if ("hf".equals(merchantAppConfig.getMerchantType())) {
+                HuiFuCreateOrder o = new HuiFuCreateOrder();
+                o.setTradeType(param.getPayType().equals("wx") ? "T_MINIAPP" : "A_NATIVE");
+                o.setOpenid(openId);
+                o.setAppId(appId);
+                o.setReqSeqId("appvip-"+storePayment.getPayCode());
+                o.setTransAmt(storePayment.getPayMoney().toString());
+                o.setAppId(param.getAppId());
+                o.setGoodsDesc("会员开通订单支付");
+                HuifuCreateOrderResult result = huiFuService.createOrder(o);
+                FsStorePayment mt=new FsStorePayment();
+                mt.setPaymentId(storePayment.getPaymentId());
+                mt.setTradeNo(result.getHf_seq_id());
+                mt.setAppId(appId);
+                storePaymentService.updateFsStorePayment(mt);
+                return R.ok().put("isPay",0).put("data",result).put("type","hf");
+            } else if ("appPay".equals(merchantAppConfig.getMerchantType()) && "wx".equals(param.getPayType())) {
+                //创建微信订单
+                WxPayConfig payConfig = new WxPayConfig();
+                payConfig.setAppId(appId);
+                payConfig.setMchId(fsPayConfig.getWxAppMchId());
+                payConfig.setMchKey(fsPayConfig.getWxAppMchKey());
+                payConfig.setSubAppId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+                payConfig.setSubMchId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+                payConfig.setKeyPath(null);
+                payConfig.setNotifyUrl(fsPayConfig.getWxAppNotifyUrl());
+                wxPayService.setConfig(payConfig);
+                WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
+                orderRequest.setBody("会员开通订单支付");
+                orderRequest.setOutTradeNo("appvip-"+storePayment.getPayCode());
+                orderRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(storePayment.getPayMoney().toString()));
+                orderRequest.setTradeType("APP");
+                orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(request));
+                orderRequest.setNotifyUrl(fsPayConfig.getNotifyUrlScrm());
+                //调用统一下单接口,获取"预支付交易会话标识"
+                try {
+                    log.info("会员开通订单支付 调用微信入参:{}", orderRequest.toString());
+                    Object result = wxPayService.createOrder(orderRequest);
+                    return R.ok().put("data",result).put("type","wxApp").put("isPay",0);
+                } catch (WxPayException e) {
+                    e.printStackTrace();
+                    throw new CustomException("会员开通订单支付"+e.getMessage());
+                }
+            } else if ("appPay".equals(merchantAppConfig.getMerchantType()) && "ali".equals(param.getPayType())) {
+                // 实例化客户端
+                AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", fsPayConfig.getAliAppId(), fsPayConfig.getAliPrivateKey(), "json", "utf-8", fsPayConfig.getAliPublicKey(), "RSA2");
+                // 构造请求参数以调用接口
+                AlipayTradeAppPayRequest aliRequest = new AlipayTradeAppPayRequest();
+                AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
+                // 商户订单号
+                model.setOutTradeNo("appvip-" + storePayment.getPayCode());
+                // 订单总金额
+                model.setTotalAmount(storePayment.getPayMoney().toString());
+                // 订单标题
+                model.setSubject("会员开通订单支付");
+                aliRequest.setBizModel(model);
+                aliRequest.setNotifyUrl(fsPayConfig.getAliNotifyUrl());
+
+                try {
+                    log.info("会员开通订单支付 调用微信入参:{}", aliRequest.toString());
+                    AlipayTradeAppPayResponse result = alipayClient.sdkExecute(aliRequest);
+                    if (!result.isSuccess()) {
+                        String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(result);
+                        log.error("支付宝支付调用失败 诊断链接:{}", diagnosisUrl);
+                        throw new CustomException(result.getSubMsg());
+                    }
+
+                    return R.ok().put("isPay", 0).put("data", result).put("type", "aliPay");
+                } catch (Exception e){
+                    log.info("支付宝支付异常: {}", e);
+                    throw new CustomException("支付系统异常,请稍后重试");
+                }
+            }
+        }
+
+        return R.error();
+    }
+
+
     @Override
     public R payment(FsUserVipOrderPayUParam param) {
         FsUserVipOrder order = fsUserVipOrderMapper.selectFsUserVipOrderByOrderId(param.getOrderId());

+ 17 - 0
fs-service/src/main/java/com/fs/course/service/impl/PublicCourseWatchStatisticsServiceImpl.java

@@ -4,8 +4,11 @@ import com.fs.common.utils.StringUtils;
 import com.fs.course.mapper.PublicCourseWatchStatisticsMapper;
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
 import com.fs.course.service.IPublicCourseWatchStatisticsService;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
+import com.fs.his.domain.FsUserIntegralLogs;
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -22,6 +25,9 @@ public class PublicCourseWatchStatisticsServiceImpl implements IPublicCourseWatc
     @Autowired
     private PublicCourseWatchStatisticsMapper publicCourseWatchStatisticsMapper;
 
+    @Autowired
+    private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
+
     @Override
     public List<PublicCourseWatchStatCourseVO> listCourseDayStat(PublicCourseWatchStatQueryParam param) {
         fillDefaultDateRange(param);
@@ -52,6 +58,17 @@ public class PublicCourseWatchStatisticsServiceImpl implements IPublicCourseWatc
         return publicCourseWatchStatisticsMapper.selectCatalogStatList(param);
     }
 
+    @Override
+    public List<CatalogUserStudyVO> listCatalogUserStudy(Long videoId, String nickName, String phone) {
+        List<CatalogUserStudyVO> list = publicCourseWatchStatisticsMapper.selectCatalogUserStudyList(videoId, nickName, phone);
+        for (CatalogUserStudyVO vo : list) {
+            // 查询用户是否领取过积分(log_type=17 答题积分,business_id = videoId)
+            FsUserIntegralLogs integralLog = fsUserIntegralLogsMapper.selectAnswerRewardIntegralLog(vo.getUserId(), String.valueOf(videoId));
+            vo.setIntegralClaimed(integralLog != null ? "是" : "否");
+        }
+        return list;
+    }
+
     private void fillDefaultDateRange(PublicCourseWatchStatQueryParam param) {
         if (param == null) {
             return;

+ 40 - 0
fs-service/src/main/java/com/fs/course/vo/CatalogUserStudyVO.java

@@ -0,0 +1,40 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 目录用户学习记录VO
+ */
+@Data
+@ApiModel(value = "目录用户学习记录")
+public class CatalogUserStudyVO {
+
+    @ApiModelProperty(value = "用户ID")
+    private Long userId;
+
+    @ApiModelProperty(value = "微信昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "累计学习时长(秒)")
+    private Long totalDuration;
+
+    @ApiModelProperty(value = "首次看课时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date firstWatchTime;
+
+    @ApiModelProperty(value = "最近看课时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastWatchTime;
+
+    @ApiModelProperty(value = "是否领取积分")
+    private String integralClaimed;
+}

+ 3 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCoursePublicAppVO.java

@@ -36,6 +36,9 @@ public class FsUserCoursePublicAppVO implements Serializable {
     @ApiModelProperty("看课人数(看课记录表 send_type=1 下去重 user_id)")
     private Long watchUserCount;
 
+    @ApiModelProperty("看课默认人数")
+    private Long views;
+
     @ApiModelProperty("首页课程顶部推荐位:0否 1是")
     private Integer recHomeCourseTopEnabled;
     @ApiModelProperty("首页顶部推荐方式:1插入 2替换")

+ 49 - 0
fs-service/src/main/java/com/fs/distribution/constant/DistributionConstants.java

@@ -0,0 +1,49 @@
+package com.fs.distribution.constant;
+
+/**
+ * 分销相关常量
+ */
+public class DistributionConstants {
+
+    private DistributionConstants() {}
+
+    /**
+     * 分销关系状态
+     */
+    public static final Integer RELATION_NORMAL = 1;
+    public static final Integer RELATION_DISABLED = 0;
+
+    /**
+     * 分销账户状态
+     */
+    public static final Integer ACCOUNT_NORMAL = 1;
+    public static final Integer ACCOUNT_DISABLED = 0;
+
+    /**
+     * 佣金状态:0冻结中,1可提现,2已提现,3已失效
+     */
+    public static final Integer COMMISSION_FROZEN = 0;
+    public static final Integer COMMISSION_AVAILABLE = 1;
+    public static final Integer COMMISSION_WITHDRAWN = 2;
+    public static final Integer COMMISSION_INVALID = 3;
+
+    /**
+     * 提现状态:0待审核,1已打款,2已拒绝
+     */
+    public static final Integer WITHDRAW_WAIT_AUDIT = 0;
+    public static final Integer WITHDRAW_PAID = 1;
+    public static final Integer WITHDRAW_REJECT = 2;
+
+    /**
+     * 佣金层级
+     */
+    public static final Integer COMMISSION_LEVEL_ONE = 1;
+    public static final Integer COMMISSION_LEVEL_TWO = 2;
+
+    /**
+     * 绑定方式
+     */
+    public static final Integer BIND_TYPE_REGISTER = 1;
+    public static final Integer BIND_TYPE_FIRST_ORDER = 2;
+    public static final Integer BIND_TYPE_ADMIN = 3;
+}

+ 51 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionAccount.java

@@ -0,0 +1,51 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销账户
+ */
+@Data
+public class DistributionAccount {
+
+    private Long id;
+
+    private Long userId;
+
+    /**
+     * 累计佣金
+     */
+    private BigDecimal totalCommission;
+
+    /**
+     * 冻结佣金
+     */
+    private BigDecimal frozenCommission;
+
+    /**
+     * 可提现佣金
+     */
+    private BigDecimal availableCommission;
+
+    /**
+     * 提现中佣金
+     */
+    private BigDecimal withdrawingCommission;
+
+    /**
+     * 已提现佣金
+     */
+    private BigDecimal withdrawnCommission;
+
+    /**
+     * 状态:1正常,0禁用
+     */
+    private Integer status;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 52 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionCommissionRecord.java

@@ -0,0 +1,52 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销佣金记录
+ */
+@Data
+public class DistributionCommissionRecord {
+
+    private Long id;
+
+    private Long orderId;
+
+    private String orderNo;
+
+    private Long buyerUserId;
+
+    private Long distributorUserId;
+
+    /**
+     * 佣金层级:1一级,2二级
+     */
+    private Integer commissionLevel;
+
+    /**
+     * 订单分佣金额
+     */
+    private BigDecimal orderAmount;
+
+    /**
+     * 佣金比例,例如 10 表示 10%
+     */
+    private BigDecimal commissionRate;
+
+    /**
+     * 佣金金额
+     */
+    private BigDecimal commissionAmount;
+
+    /**
+     * 状态:0冻结中,1可提现,2已提现,3已失效
+     */
+    private Integer status;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 44 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionConfig.java

@@ -0,0 +1,44 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销配置
+ */
+@Data
+public class DistributionConfig {
+
+    private Long id;
+
+    /**
+     * 分销开关:1开启,0关闭
+     */
+    private Integer enableStatus;
+
+    /**
+     * 一级佣金比例
+     */
+    private BigDecimal levelOneRate;
+
+    /**
+     * 二级佣金比例
+     */
+    private BigDecimal levelTwoRate;
+
+    /**
+     * 冻结天数
+     */
+    private Integer freezeDays;
+
+    /**
+     * 最低提现金额
+     */
+    private BigDecimal minWithdrawAmount;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 43 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionUserRelation.java

@@ -0,0 +1,43 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 用户分销关系
+ */
+@Data
+public class DistributionUserRelation {
+
+    private Long id;
+
+    /**
+     * 当前用户ID
+     */
+    private Long userId;
+
+    /**
+     * 一级上级用户ID
+     */
+    private Long parentUserId;
+
+    /**
+     * 二级上级用户ID
+     */
+    private Long grandParentUserId;
+
+    /**
+     * 绑定方式:1注册绑定,2首次下单绑定,3后台绑定
+     */
+    private Integer bindType;
+
+    /**
+     * 状态:1正常,0无效
+     */
+    private Integer status;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 34 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionWithdrawRecord.java

@@ -0,0 +1,34 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销提现记录
+ */
+@Data
+public class DistributionWithdrawRecord {
+
+    private Long id;
+
+    private Long userId;
+
+    private String withdrawNo;
+
+    private BigDecimal withdrawAmount;
+
+    /**
+     * 状态:0待审核,1已打款,2已拒绝
+     */
+    private Integer status;
+
+    private String remark;
+
+    private Date createTime;
+
+    private Date auditTime;
+
+    private Date updateTime;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/distribution/dto/BindRelationRequest.java

@@ -0,0 +1,25 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+/**
+ * 绑定分销关系请求
+ */
+@Data
+public class BindRelationRequest {
+
+    /**
+     * 当前注册用户ID
+     */
+    private Long userId;
+
+    /**
+     * 邀请人ID
+     */
+    private Long inviteUserId;
+
+    /**
+     * 绑定方式:1注册绑定,2首次下单绑定,3后台绑定
+     */
+    private Integer bindType;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/distribution/dto/CreateCommissionRequest.java

@@ -0,0 +1,24 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 创建订单佣金请求
+ */
+@Data
+public class CreateCommissionRequest {
+
+    private Long orderId;
+
+    private String orderNo;
+
+    private Long buyerUserId;
+
+    /**
+     * 订单实际参与分佣金额
+     * 商品实付金额,不含运费
+     */
+    private BigDecimal commissionBaseAmount;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountRequest.java

@@ -0,0 +1,25 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+/**
+ * 绑定分销关系请求
+ */
+@Data
+public class QueryDistributionAccountRequest {
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 邀请人ID
+     */
+    private Long inviteUserId;
+
+    /**
+     * 账户状态
+     */
+    private Integer status;
+}

+ 18 - 0
fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountResponse.java

@@ -0,0 +1,18 @@
+package com.fs.distribution.dto;
+
+import com.fs.distribution.domain.DistributionAccount;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 绑定分销关系请求
+ */
+@Data
+public class QueryDistributionAccountResponse {
+
+    /**
+     * 用户ID
+     */
+    private List<DistributionAccount> list;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/distribution/dto/WithdrawApplyRequest.java

@@ -0,0 +1,16 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 用户申请提现
+ */
+@Data
+public class WithdrawApplyRequest {
+
+    private Long userId;
+
+    private BigDecimal withdrawAmount;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/distribution/dto/WithdrawAuditRequest.java

@@ -0,0 +1,19 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+/**
+ * 提现审核
+ */
+@Data
+public class WithdrawAuditRequest {
+
+    private Long withdrawId;
+
+    /**
+     * 1通过,2拒绝
+     */
+    private Integer status;
+
+    private String remark;
+}

+ 61 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionAccountMapper.java

@@ -0,0 +1,61 @@
+package com.fs.distribution.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.distribution.domain.DistributionAccount;
+import org.apache.ibatis.annotations.Param;
+
+import java.math.BigDecimal;
+
+public interface DistributionAccountMapper extends BaseMapper<DistributionAccount> {
+
+    DistributionAccount selectByUserId(@Param("userId") Long userId);
+
+    /**
+     * 加锁查询账户,提现、金额变动时使用
+     */
+    DistributionAccount selectByUserIdForUpdate(@Param("userId") Long userId);
+
+    int insertAccount(DistributionAccount account);
+
+    /**
+     * 增加冻结佣金和累计佣金
+     */
+    int addFrozenCommission(@Param("userId") Long userId,
+                            @Param("amount") BigDecimal amount);
+
+    /**
+     * 冻结转可提现
+     */
+    int frozenToAvailable(@Param("userId") Long userId,
+                          @Param("amount") BigDecimal amount);
+
+    /**
+     * 扣减冻结佣金和累计佣金
+     */
+    int deductFrozenAndTotal(@Param("userId") Long userId,
+                             @Param("amount") BigDecimal amount);
+
+    /**
+     * 扣减可提现和累计佣金
+     */
+    int deductAvailableAndTotal(@Param("userId") Long userId,
+                                @Param("amount") BigDecimal amount);
+
+    /**
+     * 申请提现:可提现转提现中
+     */
+    int availableToWithdrawing(@Param("userId") Long userId,
+                               @Param("amount") BigDecimal amount);
+
+    /**
+     * 提现审核通过:提现中转已提现
+     */
+    int withdrawingToWithdrawn(@Param("userId") Long userId,
+                               @Param("amount") BigDecimal amount);
+
+    /**
+     * 提现审核拒绝:提现中退回可提现
+     */
+    int withdrawingBackToAvailable(@Param("userId") Long userId,
+                                   @Param("amount") BigDecimal amount);
+}

+ 23 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionCommissionRecordMapper.java

@@ -0,0 +1,23 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionCommissionRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+public interface DistributionCommissionRecordMapper {
+
+    int insertRecord(DistributionCommissionRecord record);
+
+    int countByOrderId(@Param("orderId") Long orderId);
+
+    List<DistributionCommissionRecord> selectByOrderId(@Param("orderId") Long orderId);
+
+    List<DistributionCommissionRecord> selectFrozenRecordsBefore(@Param("beforeTime") Date beforeTime);
+
+    int updateStatus(@Param("id") Long id,
+                     @Param("status") Integer status);
+
+    List<DistributionCommissionRecord> selectByUserId(@Param("userId") Long userId);
+}

+ 10 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionConfigMapper.java

@@ -0,0 +1,10 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionConfig;
+
+public interface DistributionConfigMapper {
+
+    DistributionConfig selectConfig();
+
+    int updateConfig(DistributionConfig config);
+}

+ 23 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionUserRelationMapper.java

@@ -0,0 +1,23 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionUserRelation;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface DistributionUserRelationMapper {
+
+    DistributionUserRelation selectByUserId(@Param("userId") Long userId);
+
+    int insertRelation(DistributionUserRelation relation);
+
+    int updateRelation(DistributionUserRelation relation);
+
+    int countFirstLevelTeam(@Param("userId") Long userId);
+
+    int countSecondLevelTeam(@Param("userId") Long userId);
+
+    List<DistributionUserRelation> selectFirstLevelTeam(@Param("userId") Long userId);
+
+    List<DistributionUserRelation> selectSecondLevelTeam(@Param("userId") Long userId);
+}

+ 24 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionWithdrawRecordMapper.java

@@ -0,0 +1,24 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionWithdrawRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface DistributionWithdrawRecordMapper {
+
+    int insertWithdrawRecord(DistributionWithdrawRecord record);
+
+    DistributionWithdrawRecord selectById(@Param("id") Long id);
+
+    DistributionWithdrawRecord selectByIdForUpdate(@Param("id") Long id);
+
+    int updateAuditStatus(DistributionWithdrawRecord record);
+
+    List<DistributionWithdrawRecord> selectByUserId(@Param("userId") Long userId);
+
+    /**
+     * 查询待审核的提现记录
+      */
+    List<DistributionWithdrawRecord> selectWithdrawList(DistributionWithdrawRecord query);
+}

+ 53 - 0
fs-service/src/main/java/com/fs/distribution/service/DistributionService.java

@@ -0,0 +1,53 @@
+package com.fs.distribution.service;
+
+import com.fs.distribution.domain.*;
+import com.fs.distribution.dto.*;
+
+import java.util.List;
+
+public interface DistributionService {
+
+    /**
+     * 注册成功后绑定邀请关系
+     */
+    void bindRelation(BindRelationRequest request);
+
+    /**
+     * 订单支付成功后生成佣金
+     */
+    void createCommissionAfterPay(CreateCommissionRequest request);
+
+    /**
+     * 订单完成并过售后期后,解冻佣金
+     */
+    void unlockFrozenCommission();
+
+    /**
+     * 订单退款时,让佣金失效
+     */
+    void invalidCommissionByRefund(Long orderId);
+
+    /**
+     * 用户申请提现
+     */
+    void applyWithdraw(WithdrawApplyRequest request);
+
+    /**
+     * 后台审核提现
+     */
+    void auditWithdraw(WithdrawAuditRequest request);
+
+    DistributionAccount getAccount(Long userId);
+
+    DistributionUserRelation getRelation(Long userId);
+
+    List<DistributionCommissionRecord> getCommissionList(Long userId);
+
+    List<DistributionWithdrawRecord> getWithdrawList(Long userId);
+
+    DistributionConfig getConfig();
+
+    void saveConfig(DistributionConfig config);
+
+    QueryDistributionAccountResponse getAccountList(QueryDistributionAccountRequest queryDistributionAccountRequest);
+}

+ 12 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionAccountService.java

@@ -0,0 +1,12 @@
+package com.fs.distribution.service;
+
+
+import com.fs.distribution.dto.BindRelationRequest;
+
+public interface IDistributionAccountService {
+
+    /**
+     * 注册成功后绑定邀请关系
+     */
+    void bindRelation(BindRelationRequest request);
+}

+ 16 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionCommissionRecordService.java

@@ -0,0 +1,16 @@
+package com.fs.distribution.service;
+
+import com.fs.distribution.dto.CreateCommissionRequest;
+
+public interface IDistributionCommissionRecordService {
+
+    /**
+     * 订单支付成功后生成佣金
+     */
+    void createCommissionAfterPay(CreateCommissionRequest request);
+
+    /**
+     * 订单完成并过售后期后,解冻佣金
+     */
+    void unlockFrozenCommission();
+}

+ 4 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionConfigService.java

@@ -0,0 +1,4 @@
+package com.fs.distribution.service;
+
+public interface IDistributionConfigService {
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini