فهرست منبع

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

# Conflicts:
#	fs-service/src/main/java/com/fs/course/dto/FsOrderDeliveryNoteDTO.java
#	fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
#	fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
caoliqin 1 هفته پیش
والد
کامیت
1e51d42fbd
100فایلهای تغییر یافته به همراه3366 افزوده شده و 595 حذف شده
  1. 3 8
      fs-admin/src/main/java/com/fs/FSApplication.java
  2. 35 8
      fs-admin/src/main/java/com/fs/course/controller/FsCourseQuestionBankController.java
  3. 62 75
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  4. 76 8
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  5. 23 0
      fs-admin/src/main/java/com/fs/hisStore/task/ExpressTask.java
  6. 10 0
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  7. 6 0
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  8. 2 2
      fs-admin/src/main/java/com/fs/live/controller/LiveAutoTaskController.java
  9. 3 0
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  10. 12 0
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  11. 102 9
      fs-admin/src/main/java/com/fs/live/controller/LiveHealthOrderController.java
  12. 18 0
      fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java
  13. 2 0
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  14. 2 0
      fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java
  15. 1 1
      fs-company-app/src/main/resources/application.yml
  16. 30 0
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCoursePeriodController.java
  17. 2 2
      fs-company/src/main/java/com/fs/company/controller/live/LiveAutoTaskController.java
  18. 13 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveCouponController.java
  19. 21 4
      fs-company/src/main/java/com/fs/company/controller/live/LiveOrderController.java
  20. 1 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  21. 6 5
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  22. 8 8
      fs-live-app/src/main/java/com/fs/live/websocket/handle/LiveChatHandler.java
  23. 15 12
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  24. 2 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  25. 1 1
      fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java
  26. 2 1
      fs-service/src/main/java/com/fs/course/dto/FsCourseQuestionBankImportDTO.java
  27. 17 0
      fs-service/src/main/java/com/fs/course/dto/ImportFailItemDTO.java
  28. 62 0
      fs-service/src/main/java/com/fs/course/dto/ImportResultDTO.java
  29. 18 0
      fs-service/src/main/java/com/fs/course/dto/OrderOpenIdTransDTO.java
  30. 11 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  31. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java
  32. 2 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  33. 2 1
      fs-service/src/main/java/com/fs/course/service/IFsCourseQuestionBankService.java
  34. 1 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodDaysService.java
  35. 4 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodService.java
  36. 6 9
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  37. 32 18
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  38. 83 24
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  39. 11 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  40. 36 15
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  41. 3 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  42. 6 1
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogStatisticsListVO.java
  43. 1 1
      fs-service/src/main/java/com/fs/course/vo/FsUserCoursePeriodVO.java
  44. 3 0
      fs-service/src/main/java/com/fs/course/vo/UpdateCourseTimeVo.java
  45. 3 3
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  46. 3 0
      fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java
  47. 2 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsMenuScrm.java
  48. 2 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  49. 17 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  50. 7 6
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  51. 15 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  52. 365 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  53. 2 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  54. 38 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderCodeOpenIdVo.java
  55. 84 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderDeliveryNoteExportVO.java
  56. 1 1
      fs-service/src/main/java/com/fs/live/domain/LiveAutoTask.java
  57. 3 1
      fs-service/src/main/java/com/fs/live/domain/LiveOrder.java
  58. 4 1
      fs-service/src/main/java/com/fs/live/dto/LiveOrderDeliveryNoteDTO.java
  59. 28 2
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  60. 21 2
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  61. 16 10
      fs-service/src/main/java/com/fs/live/mapper/LiveWatchUserMapper.java
  62. 12 11
      fs-service/src/main/java/com/fs/live/param/LiveDataParam.java
  63. 1 1
      fs-service/src/main/java/com/fs/live/service/ILiveAutoTaskService.java
  64. 17 0
      fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java
  65. 3 0
      fs-service/src/main/java/com/fs/live/service/ILiveService.java
  66. 5 1
      fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java
  67. 26 21
      fs-service/src/main/java/com/fs/live/service/impl/LiveAutoTaskServiceImpl.java
  68. 297 3
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  69. 314 150
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  70. 161 73
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  71. 34 0
      fs-service/src/main/java/com/fs/live/vo/LiveOrderCodeOpenIdVo.java
  72. 81 0
      fs-service/src/main/java/com/fs/live/vo/LiveOrderDeliveryNoteExportVO.java
  73. 2 3
      fs-service/src/main/java/com/fs/live/vo/LiveOrderVoZm.java
  74. 141 0
      fs-service/src/main/java/com/fs/pay/domain/PaymentMiniProgramConfig.java
  75. 70 0
      fs-service/src/main/java/com/fs/pay/mapper/PaymentMiniProgramConfigMapper.java
  76. 5 2
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  77. 7 0
      fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java
  78. 2 1
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempMapper.java
  79. 3 1
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempRulesMapper.java
  80. 6 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempContentService.java
  81. 5 1
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempDayService.java
  82. 6 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempRulesService.java
  83. 5 3
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempService.java
  84. 1 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java
  85. 1 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  86. 15 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempContentServiceImpl.java
  87. 9 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempDayServiceImpl.java
  88. 26 19
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempRulesServiceImpl.java
  89. 148 62
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java
  90. 2 0
      fs-service/src/main/java/com/fs/statis/dto/ModifyMoreDTO.java
  91. 104 0
      fs-service/src/main/java/com/fs/wx/order/domain/FsWxExpressTask.java
  92. 21 0
      fs-service/src/main/java/com/fs/wx/order/dto/Contact.java
  93. 27 0
      fs-service/src/main/java/com/fs/wx/order/dto/OrderKey.java
  94. 77 0
      fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryRequest.java
  95. 236 0
      fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryResponse.java
  96. 17 0
      fs-service/src/main/java/com/fs/wx/order/dto/Payer.java
  97. 26 0
      fs-service/src/main/java/com/fs/wx/order/dto/ShippingItem.java
  98. 42 0
      fs-service/src/main/java/com/fs/wx/order/dto/UploadShippingInfoRequest.java
  99. 21 0
      fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiConfig.java
  100. 20 0
      fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiResponse.java

+ 3 - 8
fs-admin/src/main/java/com/fs/FSApplication.java

@@ -1,11 +1,8 @@
 package com.fs;
 
-import com.qiniu.common.Zone;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.transaction.annotation.Transactional;
@@ -13,14 +10,12 @@ import org.springframework.transaction.annotation.Transactional;
 /**
  * 启动程序
  */
-@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
 @Transactional
 @EnableAsync
 @EnableScheduling
-public class FSApplication
-{
-    public static void main(String[] args)
-    {
+public class FSApplication {
+    public static void main(String[] args) {
         // System.setProperty("spring.devtools.restart.enabled", "false");
         SpringApplication.run(FSApplication.class, args);
         System.out.println("admin启动成功");

+ 35 - 8
fs-admin/src/main/java/com/fs/course/controller/FsCourseQuestionBankController.java

@@ -13,6 +13,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseQuestionBank;
 import com.fs.course.dto.FsCourseQuestionBankImportDTO;
+import com.fs.course.dto.ImportResultDTO;
 import com.fs.course.service.IFsCourseQuestionBankService;
 import com.fs.framework.web.service.TokenService;
 import com.fs.system.service.ISysConfigService;
@@ -21,7 +22,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 题库Controller
@@ -80,6 +83,15 @@ public class FsCourseQuestionBankController extends BaseController
         return util.exportExcel(list, "题库数据");
     }
 
+    @PreAuthorize("@ss.hasPermi('course:courseQuestionBank:exportFail')")
+    @Log(title = "题库", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportFail")
+    public AjaxResult export( @RequestBody List<FsCourseQuestionBankImportDTO> list)
+    {
+        ExcelUtil<FsCourseQuestionBankImportDTO> util = new ExcelUtil<>(FsCourseQuestionBankImportDTO.class);
+        return util.exportExcel(list, "题库错误数据");
+    }
+
     /**
      * 获取题库详细信息
      */
@@ -144,19 +156,34 @@ public class FsCourseQuestionBankController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:courseQuestionBank:importData')")
     @PostMapping("/importData")
     public AjaxResult importData(MultipartFile file) throws Exception {
-        ExcelUtil<FsCourseQuestionBankImportDTO> util = new ExcelUtil<>(FsCourseQuestionBankImportDTO.class);
-        List<FsCourseQuestionBankImportDTO> list = util.importExcel(file.getInputStream());
+
+        ExcelUtil<FsCourseQuestionBankImportDTO> util =
+                new ExcelUtil<>(FsCourseQuestionBankImportDTO.class);
+        List<FsCourseQuestionBankImportDTO> list =
+                util.importExcel(file.getInputStream());
 
         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()){
-            String message = fsCourseQuestionBankService.importData(list, loginUser.getUser().getNickName(),userId);
-            return AjaxResult.success(message);
-        }
-        String message = fsCourseQuestionBankService.importData(list, loginUser.getUser().getNickName(),null);
-        return AjaxResult.success(message);
+
+        // 绑定状态控制 userId
+        Long finalUserId = (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound())
+                ? userId
+                : null;
+
+        // 调用 service
+        ImportResultDTO result =
+                fsCourseQuestionBankService.importData(list, loginUser.getUser().getNickName(), finalUserId);
+
+        // 返回 message + failList
+        Map<String, Object> resp = new HashMap<>();
+        resp.put("message", result.buildResultMessage());
+        resp.put("failList", result.getFailureList());
+
+        return AjaxResult.success(resp);
     }
 
     @GetMapping(value = "/getByIds")

+ 62 - 75
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -1,39 +1,33 @@
 package com.fs.course.controller;
 
-import java.util.List;
-
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
+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.course.config.CourseConfig;
+import com.fs.course.domain.FsUserCourse;
 import com.fs.course.params.FsUserCourseConfigParam;
+import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
 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.qw.param.FsUserCourseRedPageParam;
+import com.fs.sop.service.IQwSopTempService;
 import com.fs.system.service.ISysConfigService;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-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.course.domain.FsUserCourse;
-import com.fs.course.service.IFsUserCourseService;
-import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.common.core.page.TableDataInfo;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
 
 /**
  * 课程Controller
@@ -43,8 +37,7 @@ import com.fs.common.core.page.TableDataInfo;
  */
 @RestController
 @RequestMapping("/course/userCourse")
-public class FsUserCourseController extends BaseController
-{
+public class FsUserCourseController extends BaseController {
     @Autowired
     private IFsUserCourseService fsUserCourseService;
 
@@ -60,19 +53,21 @@ public class FsUserCourseController extends BaseController
     @Autowired
     private ISysConfigService configService;
 
+    @Autowired
+    private IQwSopTempService sopTempService;
+
     /**
      * 查询课程列表
      */
     @PreAuthorize("@ss.hasPermi('course:userCourse:list')")
     @GetMapping("/list")
-    public TableDataInfo list(FsUserCourse fsUserCourse)
-    {
+    public TableDataInfo list(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()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
         List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
@@ -84,14 +79,13 @@ public class FsUserCourseController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicList')")
     @GetMapping("/publicList")
-    public TableDataInfo publicList(FsUserCourse fsUserCourse)
-    {
+    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()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
         List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
@@ -104,13 +98,12 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:export')")
     @Log(title = "课程", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(FsUserCourse fsUserCourse)
-    {
+    public AjaxResult export(FsUserCourse fsUserCourse) {
         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()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
         List<FsUserCourse> list = fsUserCourseService.selectFsUserCourseList(fsUserCourse);
@@ -124,13 +117,12 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicExport')")
     @Log(title = "课程", businessType = BusinessType.EXPORT)
     @GetMapping("/publicExport")
-    public AjaxResult publicExport(FsUserCourse fsUserCourse)
-    {
+    public AjaxResult publicExport(FsUserCourse fsUserCourse) {
         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()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
         List<FsUserCourse> list = fsUserCourseService.selectFsUserCourseList(fsUserCourse);
@@ -143,8 +135,7 @@ public class FsUserCourseController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:userCourse:query')")
     @GetMapping(value = "/{courseId}")
-    public AjaxResult getInfo(@PathVariable("courseId") Long courseId)
-    {
+    public AjaxResult getInfo(@PathVariable("courseId") Long courseId) {
         return AjaxResult.success(fsUserCourseService.selectFsUserCourseByCourseId(courseId));
     }
 
@@ -153,8 +144,7 @@ public class FsUserCourseController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicQuery')")
     @GetMapping(value = "/public/{courseId}")
-    public AjaxResult publicGetInfo(@PathVariable("courseId") Long courseId)
-    {
+    public AjaxResult publicGetInfo(@PathVariable("courseId") Long courseId) {
         return AjaxResult.success(fsUserCourseService.selectFsUserCourseByCourseId(courseId));
     }
 
@@ -164,13 +154,12 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:add')")
     @Log(title = "课程", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody FsUserCourse fsUserCourse)
-    {
+    public AjaxResult add(@RequestBody FsUserCourse fsUserCourse) {
         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()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
         fsUserCourseService.insertFsUserCourse(fsUserCourse);
@@ -185,13 +174,12 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicAdd')")
     @Log(title = "课程", businessType = BusinessType.INSERT)
     @PostMapping("/public")
-    public AjaxResult publicAdd(@RequestBody FsUserCourse fsUserCourse)
-    {
+    public AjaxResult publicAdd(@RequestBody FsUserCourse fsUserCourse) {
         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()){
+        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
         fsUserCourseService.insertFsUserCourse(fsUserCourse);
@@ -206,8 +194,7 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:edit')")
     @Log(title = "课程", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody FsUserCourse fsUserCourse)
-    {
+    public AjaxResult edit(@RequestBody FsUserCourse fsUserCourse) {
         fsUserCourseService.updateFsUserCourse(fsUserCourse);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
@@ -219,8 +206,7 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:editRedPage')")
     @Log(title = "修改课程红包", businessType = BusinessType.UPDATE)
     @PostMapping("/editRedPage")
-    public AjaxResult editRedPage(@RequestBody FsUserCourseRedPageParam redPageParam)
-    {
+    public AjaxResult editRedPage(@RequestBody FsUserCourseRedPageParam redPageParam) {
         courseVideoService.updateFsUserCourseRedPage(redPageParam);
         return toAjax(1);
     }
@@ -231,8 +217,7 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicEdit')")
     @Log(title = "课程", businessType = BusinessType.UPDATE)
     @PutMapping("/public")
-    public AjaxResult publicEdit(@RequestBody FsUserCourse fsUserCourse)
-    {
+    public AjaxResult publicEdit(@RequestBody FsUserCourse fsUserCourse) {
         fsUserCourseService.updateFsUserCourse(fsUserCourse);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
@@ -244,8 +229,7 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:copy')")
     @Log(title = "课程", businessType = BusinessType.DELETE)
     @GetMapping("/copy/{courseId}")
-    public AjaxResult copy(@PathVariable Long courseId)
-    {
+    public AjaxResult copy(@PathVariable Long courseId) {
         int i = fsUserCourseService.copyFsUserCourse(courseId);
         return toAjax(i);
     }
@@ -255,8 +239,8 @@ public class FsUserCourseController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:userCourse:remove')")
     @Log(title = "课程", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{courseIds}")
-    public AjaxResult remove(@PathVariable Long[] courseIds){
+    @DeleteMapping("/{courseIds}")
+    public AjaxResult remove(@PathVariable Long[] courseIds) {
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
@@ -268,8 +252,7 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicRemove')")
     @Log(title = "课程", businessType = BusinessType.DELETE)
     @DeleteMapping("/public/{courseIds}")
-    public AjaxResult publicRemove(@PathVariable Long[] courseIds)
-    {
+    public AjaxResult publicRemove(@PathVariable Long[] courseIds) {
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
@@ -277,8 +260,7 @@ public class FsUserCourseController extends BaseController
 
 
     @GetMapping("/getAllList")
-    public R getAllList()
-    {
+    public R getAllList() {
         List<OptionsVO> list = fsUserCourseService.selectFsUserCourseAllList();
         return R.ok().put("data", list);
     }
@@ -286,18 +268,16 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:updateIsShow')")
     @Log(title = "课程上架", businessType = BusinessType.UPDATE)
     @PostMapping("/updateIsShow")
-    public AjaxResult updateIsShow(@RequestBody FsUserCourse fsUserCourse)
-    {
+    public AjaxResult updateIsShow(@RequestBody FsUserCourse fsUserCourse) {
         fsUserCourseService.updateFsUserCourse(fsUserCourse);
         redisCacheUtil.delRedisKey("getCourseList");
-       return toAjax(1);
+        return toAjax(1);
     }
 
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicUpdateIsShow')")
     @Log(title = "课程上架", businessType = BusinessType.UPDATE)
     @PostMapping("/publicUpdateIsShow")
-    public AjaxResult publicUpdateIsShow(@RequestBody FsUserCourse fsUserCourse)
-    {
+    public AjaxResult publicUpdateIsShow(@RequestBody FsUserCourse fsUserCourse) {
         fsUserCourseService.updateFsUserCourse(fsUserCourse);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
@@ -306,9 +286,8 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:putOn')")
     @Log(title = "课程批量上架", businessType = BusinessType.UPDATE)
     @PostMapping("/putOn/{courseIds}")
-    public AjaxResult putOn(@PathVariable Long[] courseIds)
-    {
-        fsUserCourseService.updateFsUserCourseIsShow(courseIds,1);
+    public AjaxResult putOn(@PathVariable Long[] courseIds) {
+        fsUserCourseService.updateFsUserCourseIsShow(courseIds, 1);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
     }
@@ -316,9 +295,8 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicPutOn')")
     @Log(title = "课程批量上架", businessType = BusinessType.UPDATE)
     @PostMapping("/publicPutOn/{courseIds}")
-    public AjaxResult publicPutOn(@PathVariable Long[] courseIds)
-    {
-        fsUserCourseService.updateFsUserCourseIsShow(courseIds,1);
+    public AjaxResult publicPutOn(@PathVariable Long[] courseIds) {
+        fsUserCourseService.updateFsUserCourseIsShow(courseIds, 1);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
     }
@@ -326,9 +304,8 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:putOn')")
     @Log(title = "课程批量下架", businessType = BusinessType.UPDATE)
     @PostMapping("/pullOff/{courseIds}")
-    public AjaxResult pullOff(@PathVariable Long[] courseIds)
-    {
-        fsUserCourseService.updateFsUserCourseIsShow(courseIds,0);
+    public AjaxResult pullOff(@PathVariable Long[] courseIds) {
+        fsUserCourseService.updateFsUserCourseIsShow(courseIds, 0);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
     }
@@ -336,9 +313,8 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:publicPutOff')")
     @Log(title = "课程批量下架", businessType = BusinessType.UPDATE)
     @PostMapping("/publicPutOff/{courseIds}")
-    public AjaxResult publicPutOff(@PathVariable Long[] courseIds)
-    {
-        fsUserCourseService.updateFsUserCourseIsShow(courseIds,0);
+    public AjaxResult publicPutOff(@PathVariable Long[] courseIds) {
+        fsUserCourseService.updateFsUserCourseIsShow(courseIds, 0);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
     }
@@ -349,7 +325,7 @@ public class FsUserCourseController extends BaseController
     @PreAuthorize("@ss.hasPermi('course:userCourse:editConfig')")
     @Log(title = "课程配置", businessType = BusinessType.UPDATE)
     @PostMapping("/editConfig")
-    public R editConfig(@RequestBody FsUserCourseConfigParam params){
+    public R editConfig(@RequestBody FsUserCourseConfigParam params) {
         fsUserCourseService.editConfig(params.getId(), params.getConfigJson());
         redisCacheUtil.delRedisKey("getCourseList");
         redisCacheUtil.delRedisKey("h5user:course:video:list:all");
@@ -357,4 +333,15 @@ public class FsUserCourseController extends BaseController
         redisCacheUtil.delRedisKey("cache:video");
         return R.ok();
     }
+
+    /**
+     * 同步课程模板
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:sync')")
+    @Log(title = "同步课程模板", businessType = BusinessType.UPDATE)
+    @PostMapping("/syncTemplate/{courseId}")
+    public AjaxResult syncTemplate(@PathVariable Long courseId) {
+        sopTempService.syncTemplate(courseId);
+        return toAjax(1);
+    }
 }

+ 76 - 8
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java

@@ -22,10 +22,7 @@ import com.fs.his.vo.FsStoreOrderListAndStatisticsVo;
 import com.fs.hisStore.dto.StoreOrderProductDTO;
 import com.fs.hisStore.param.FsStoreOrderParam;
 import com.fs.hisStore.service.*;
-import com.fs.hisStore.vo.FsStoreOrderErpExportVO;
-import com.fs.hisStore.vo.FsStoreOrderExportVO;
-import com.fs.hisStore.vo.FsStoreOrderItemExportVO;
-import com.fs.hisStore.vo.FsStoreOrderVO;
+import com.fs.hisStore.vo.*;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -33,10 +30,8 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import java.text.SimpleDateFormat;
+import java.util.*;
 
 @RestController
 @RequestMapping("/store/store/storeOrder")
@@ -380,4 +375,77 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         }
         return false;
     }
+
+    //订单发货批量导入
+    @Log(title = "发货同步导入", businessType = BusinessType.IMPORT)
+    @PostMapping("/importDeliveryNoteExpressWithApp")
+    public R importDeliveryNoteExpressWithApp(@RequestParam("file") MultipartFile file,@RequestParam("miniAppId") String miniAppId) {
+        // 1. 检查文件是否为空
+        if (file.isEmpty()) {
+            return R.error("上传的文件不能为空");
+        }
+        // 2. 检查文件大小
+        if (file.getSize() > MAX_FILE_SIZE) {
+            return R.error("文件大小不能超过5MB");
+        }
+        // 3. 检查文件扩展名
+        String fileName = file.getOriginalFilename();
+        if (fileName == null || !isValidExcelFile(fileName)) {
+            return R.error("请上传Excel文件(.xlsx或.xls格式)");
+        }
+
+        ExcelUtil<FsOrderDeliveryNoteDTO> util=new ExcelUtil<>(FsOrderDeliveryNoteDTO.class);
+        try {
+            List<FsOrderDeliveryNoteDTO> dtoList = util.importExcel(file.getInputStream());
+            if(!dtoList.isEmpty()){
+                if(dtoList.size() > 200){
+                    R.error("操作失败,导入数据不能大于200条!");
+                }
+                return fsStoreOrderService.importDeliveryNoteExpress(dtoList,miniAppId);
+            }else {
+                R.error("操作失败,导入数据不能小于1条!");
+            }
+        }catch (Exception e){
+            e.getStackTrace();
+        }
+        return R.ok();
+    }
+
+
+    /**
+     * 发货单导出接口
+     * **/
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:healthExportShippingOrder')")
+    @GetMapping("/healthExportShippingOrder")
+    public AjaxResult healthExportShippingOrder(FsStoreOrderParam param){
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if(fsStoreOrderService.isEntityNull(param)){
+            param = new FsStoreOrderParam();
+        }
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliverySendTimeRange())){
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        param.setIsHealth("1");
+        List<FsStoreOrderDeliveryNoteExportVO> storeOrderDeliveryNoteExportVOList=fsStoreOrderService.getDeliveryNote(param);
+        ExcelUtil<FsStoreOrderDeliveryNoteExportVO> util = new ExcelUtil<>(FsStoreOrderDeliveryNoteExportVO.class);
+        //通过商品ID获取关键字
+        String firstKeyword = storeOrderDeliveryNoteExportVOList.stream()
+                .map(FsStoreOrderDeliveryNoteExportVO::getKeyword)
+                .findFirst()
+                .orElse("无订单");
+        String fileName="077AC"+firstKeyword+new SimpleDateFormat("yyyyMMdd").format(new Date());
+        return util.exportExcel(storeOrderDeliveryNoteExportVOList, fileName);
+    }
 }

+ 23 - 0
fs-admin/src/main/java/com/fs/hisStore/task/ExpressTask.java

@@ -0,0 +1,23 @@
+package com.fs.hisStore.task;
+
+
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 物流信息定时任务
+ */
+@Slf4j
+@Component("expressTask")
+public class ExpressTask {
+
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+
+    public void syncExpressToWx(){
+        fsStoreOrderScrmService.syncExpressToWx();
+    }
+
+}

+ 10 - 0
fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java

@@ -45,6 +45,8 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
 
 import java.text.ParseException;
 import java.time.LocalTime;
@@ -168,6 +170,14 @@ public class LiveTask {
         }
     }
 
+
+    //定时任务刷新微信订单结算状态
+    public void refreshOrderSettlementStatus(){
+        liveOrderService.refreshOrderSettlementStatus();
+    }
+
+
+
     //每5分钟执行一次
     public void deliveryOp() {
         List<LiveOrder> list = liveOrderService.selectUpdateExpress();

+ 6 - 0
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -231,6 +231,12 @@ public class MallStoreTask
     }
 
 
+    //定时任务刷新微信订单结算状态
+    public void refreshOrderSettlementStatus(){
+        fsStoreOrderService.refreshOrderSettlementStatus();
+    }
+
+
     public void storeProdUpdateCostPrice()
     {
         String json=configService.selectConfigByKey("store.config");

+ 2 - 2
fs-admin/src/main/java/com/fs/live/controller/LiveAutoTaskController.java

@@ -116,9 +116,9 @@ public class LiveAutoTaskController extends BaseController
 //    @PreAuthorize("@ss.hasPermi('shop:task:edit')")
     @Log(title = "直播间自动化任务配置", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody LiveAutoTask liveAutoTask)
+    public R edit(@RequestBody LiveAutoTask liveAutoTask)
     {
-        return toAjax(liveAutoTaskService.updateLiveAutoTask(liveAutoTask));
+        return liveAutoTaskService.updateLiveAutoTask(liveAutoTask);
     }
 
     /**

+ 3 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveController.java

@@ -7,6 +7,8 @@ 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.poi.ExcelUtil;
+import com.fs.hisStore.task.LiveTask;
+import com.fs.hisStore.task.MallStoreTask;
 import com.fs.live.domain.Live;
 import com.fs.live.service.ILiveService;
 import com.fs.live.vo.LiveListVo;
@@ -14,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.text.ParseException;
 import java.util.List;
 import java.util.Map;
 

+ 12 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -5,6 +5,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.live.domain.LiveData;
+import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.vo.LiveUserFirstVo;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,6 +42,17 @@ public class LiveDataController extends BaseController {
         return getDataTable(list);
     }
 
+    /**
+     * 查询新直播数据列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:list')")
+    @PostMapping("/listLiveData")
+    public R listLiveData(@RequestBody LiveDataParam param, HttpServletRequest request)
+    {
+        startPage();
+        return liveDataService.listLiveData(param);
+    }
+
     /**
      * 查询直播数据列表
      * */

+ 102 - 9
fs-admin/src/main/java/com/fs/live/controller/LiveHealthOrderController.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
 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.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.CloudHostUtils;
@@ -23,20 +24,15 @@ import com.fs.live.param.LiveOrderParam;
 import com.fs.live.service.ILiveOrderDfService;
 import com.fs.live.service.ILiveOrderItemService;
 import com.fs.live.service.ILiveOrderService;
-import com.fs.live.vo.LiveOrderErpExportVO;
-import com.fs.live.vo.LiveOrderItemExportVO;
-import com.fs.live.vo.LiveOrderListAndStatisticsVo;
-import com.fs.live.vo.LiveOrderVO;
+import com.fs.live.vo.*;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**`
  * 订单Controller
@@ -318,9 +314,106 @@ public class LiveHealthOrderController extends BaseController {
         return util.exportExcel(list, "订单明细数据");
     }
 
+
+
+    // 允许的文件扩展名
+    private static final String[] ALLOWED_EXCEL_EXTENSIONS = {".xlsx", ".xls"};
+
+    // 最大文件大小(5MB)
+    private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+
+    /**
+     * 下载订单发货导入模板
+     */
     @GetMapping("/importDeliveryNoteExpressTemplate")
-    public AjaxResult importTemplate() {
+    public AjaxResult importDeliveryNoteExpressTemplate() {
         ExcelUtil<LiveOrderDeliveryNoteDTO> util = new ExcelUtil<>(LiveOrderDeliveryNoteDTO.class);
         return util.importTemplateExcel("订单发货导入模板");
     }
+
+    /**
+     * 订单发货批量导入
+     */
+    @Log(title = "发货同步导入", businessType = BusinessType.IMPORT)
+    @PostMapping("/importDeliveryNoteExpress")
+    public R importDeliveryNoteExpress(@RequestParam("file") MultipartFile file, @RequestParam("miniAppId") String miniAppId) {
+        // 1. 检查文件是否为空
+        if (file.isEmpty()) {
+            return R.error("上传的文件不能为空");
+        }
+        // 2. 检查文件大小
+        if (file.getSize() > MAX_FILE_SIZE) {
+            return R.error("文件大小不能超过5MB");
+        }
+        // 3. 检查文件扩展名
+        String fileName = file.getOriginalFilename();
+        if (fileName == null || !isValidExcelFile(fileName)) {
+            return R.error("请上传Excel文件(.xlsx或.xls格式)");
+        }
+
+        ExcelUtil<LiveOrderDeliveryNoteDTO> util = new ExcelUtil<>(LiveOrderDeliveryNoteDTO.class);
+        try {
+            List<LiveOrderDeliveryNoteDTO> dtoList = util.importExcel(file.getInputStream());
+            if (!dtoList.isEmpty()) {
+                if (dtoList.size() > 200) {
+                    return R.error("操作失败,导入数据不能大于200条!");
+                }
+                return liveOrderService.importDeliveryNoteExpress(dtoList,miniAppId);
+            } else {
+                return R.error("操作失败,导入数据不能小于1条!");
+            }
+        } catch (Exception e) {
+            logger.error("导入发货单失败", e);
+            return R.error("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 发货单导出接口
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveOrder:healthExportShippingOrder')")
+    @Log(title = "发货单导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/healthExportShippingOrder")
+    public AjaxResult healthExportShippingOrder(LiveOrderParam param) {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())) {
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (liveOrderService.isEntityNull(param)) {
+            param = new LiveOrderParam();
+        }
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliverySendTimeRange())) {
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        List<LiveOrderDeliveryNoteExportVO> deliveryNoteExportVOList = liveOrderService.getDeliveryNote(param);
+        ExcelUtil<LiveOrderDeliveryNoteExportVO> util = new ExcelUtil<>(LiveOrderDeliveryNoteExportVO.class);
+        //通过商品ID获取关键字
+        String firstKeyword = deliveryNoteExportVOList.stream()
+                .map(LiveOrderDeliveryNoteExportVO::getKeyword)
+                .findFirst()
+                .orElse("无订单");
+        String fileName = "077AC" + firstKeyword + new java.text.SimpleDateFormat("yyyyMMdd").format(new Date());
+        return util.exportExcel(deliveryNoteExportVOList, fileName);
+    }
+
+    /**
+     * 检查文件是否为有效的Excel文件
+     */
+    private boolean isValidExcelFile(String fileName) {
+        for (String ext : ALLOWED_EXCEL_EXTENSIONS) {
+            if (fileName.toLowerCase().endsWith(ext)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }

+ 18 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java

@@ -35,7 +35,10 @@ import com.fs.hisStore.param.*;
 import com.fs.hisStore.vo.FsStoreOrderVO;
 import com.fs.live.domain.*;
 import com.fs.live.dto.LiveOrderCustomerExportDTO;
+import com.fs.live.dto.LiveOrderDeliveryNoteDTO;
 import com.fs.live.dto.LiveOrderExpressExportDTO;
+import com.fs.live.param.LiveOrderParam;
+import com.fs.live.vo.LiveOrderDeliveryNoteExportVO;
 import com.fs.live.enums.LiveOrderCancleReason;
 import com.fs.live.param.LiveOrderScrmSetErpPhoneParam;
 import com.fs.live.service.*;
@@ -52,8 +55,10 @@ import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
@@ -151,6 +156,11 @@ public class LiveOrderController extends BaseController
         List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
         for (LiveOrderVoZm vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
         }
         return getDataTable(list);
     }
@@ -164,6 +174,14 @@ public class LiveOrderController extends BaseController
     public AjaxResult exportZm(LiveOrder liveOrder)
     {
         List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
+        for (LiveOrderVoZm vo : list){
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
+        }
         ExcelUtil<LiveOrderVoZm> util = new ExcelUtil<LiveOrderVoZm>(LiveOrderVoZm.class);
         return util.exportExcel(list, "订单数据");
     }

+ 2 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java

@@ -1,5 +1,6 @@
 package com.fs.qw.controller;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
@@ -9,6 +10,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.TimeUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.framework.web.service.TokenService;
 import com.fs.qw.vo.SortDayVo;

+ 2 - 0
fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java

@@ -27,6 +27,8 @@ public class LiveKeysConstant {
     public static final String LIVE_HOME_PAGE_CONFIG_DRAW = "live:config:%s:draw:%s"; //抽奖记录
     public static final String TOP_MSG = "topMsg"; //抽奖记录
 
+    public static final String LIVE_FLAG_CACHE = "live:flag:%s"; //直播间直播/回放状态缓存
+    public static final Integer LIVE_FLAG_CACHE_EXPIRE = 60; //直播间状态缓存过期时间(秒)
 
 
 }

+ 1 - 1
fs-company-app/src/main/resources/application.yml

@@ -6,4 +6,4 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-    active: dev-jnlzjk
+    active: druid-bjzm-test

+ 30 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsUserCoursePeriodController.java

@@ -5,6 +5,7 @@ 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.entity.SysDictData;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.CustomException;
@@ -72,6 +73,25 @@ public class FsUserCoursePeriodController extends BaseController {
         return getDataTable(list);
     }
 
+
+    /**
+     * @Description: 营期key value 值
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/18 14:59
+     */
+    @GetMapping("/listLabel")
+    public TableDataInfo listLabel(FsUserCoursePeriod fsUserCoursePeriod)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsUserCoursePeriod.setCompanyId(loginUser.getCompany().getCompanyId().toString());
+        List<SysDictData> list = fsUserCoursePeriodService.selectFsUserCoursePeriodListLabel(fsUserCoursePeriod);
+        return getDataTable(list);
+    }
+
+
     @PostMapping("/page")
     @ApiOperation("自定义查询主列表分页")
     public R pageList(@RequestBody FsUserCoursePeriod fsUserCoursePeriod)
@@ -145,6 +165,16 @@ public class FsUserCoursePeriodController extends BaseController {
         fsUserCoursePeriod.setCompanyId(loginUser.getCompany().getCompanyId().toString());
         return toAjax(fsUserCoursePeriodService.updateFsUserCoursePeriod(fsUserCoursePeriod));
     }
+    /**
+     * 修改会员营期单独注册状态
+     */
+    @PreAuthorize("@ss.hasPermi('course:period:edit')")
+    @Log(title = "会员营期", businessType = BusinessType.UPDATE)
+    @PutMapping("/editIsNeedRegisterMember")
+    public AjaxResult editIsNeedRegisterMember(@RequestBody FsUserCoursePeriod fsUserCoursePeriod)
+    {
+        return toAjax(fsUserCoursePeriodService.editIsNeedRegisterMember(fsUserCoursePeriod));
+    }
 
     /**
      * 删除会员营期

+ 2 - 2
fs-company/src/main/java/com/fs/company/controller/live/LiveAutoTaskController.java

@@ -116,9 +116,9 @@ public class LiveAutoTaskController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:task:edit')")
     @Log(title = "直播间自动化任务配置", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody LiveAutoTask liveAutoTask)
+    public R edit(@RequestBody LiveAutoTask liveAutoTask)
     {
-        return toAjax(liveAutoTaskService.updateLiveAutoTask(liveAutoTask));
+        return liveAutoTaskService.updateLiveAutoTask(liveAutoTask);
     }
 
     /**

+ 13 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveCouponController.java

@@ -73,6 +73,19 @@ public class LiveCouponController extends BaseController
         return AjaxResult.success(liveCouponService.selectLiveCouponById(couponId));
     }
 
+    /**
+     * 查询优惠券列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveCoupon:list')")
+    @GetMapping("/listOn")
+    public TableDataInfo listOn(@RequestParam("liveId") Long liveId)
+    {
+        startPage();
+        List<LiveCoupon> list = liveCouponService.listOn(liveId);
+        return getDataTable(list);
+    }
+
+
     /**
      * 新增优惠券
      */

+ 21 - 4
fs-company/src/main/java/com/fs/company/controller/live/LiveOrderController.java

@@ -28,6 +28,7 @@ import com.fs.live.service.ILiveOrderPaymentService;
 import com.fs.live.service.ILiveOrderService;
 import com.fs.live.vo.LiveGoodsVo;
 import com.fs.live.vo.LiveOrderTimeVo;
+import com.fs.live.vo.LiveOrderVoZm;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -35,6 +36,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
 
@@ -117,9 +119,15 @@ public class LiveOrderController extends BaseController
     public TableDataInfo listZm(LiveOrder liveOrder)
     {
         startPage();
-        List<LiveOrder> list = liveOrderService.selectLiveOrderList(liveOrder);
-        for (LiveOrder vo : list){
+        liveOrder.setCompanyId(SecurityUtils.getLoginUser().getUser().getCompanyId());
+        List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
+        for (LiveOrderVoZm vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
         }
         return getDataTable(list);
     }
@@ -132,8 +140,17 @@ public class LiveOrderController extends BaseController
     @GetMapping("/exportZm")
     public AjaxResult exportZm(LiveOrder liveOrder)
     {
-        List<LiveOrder> list = liveOrderService.selectLiveOrderList(liveOrder);
-        ExcelUtil<LiveOrder> util = new ExcelUtil<LiveOrder>(LiveOrder.class);
+        liveOrder.setCompanyId(SecurityUtils.getLoginUser().getUser().getCompanyId());
+        List<LiveOrderVoZm> list = liveOrderService.selectLiveOrderListZm(liveOrder);
+        for (LiveOrderVoZm vo : list){
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setCompanyUserPhone(ParseUtils.parsePhone(vo.getCompanyUserPhone()));
+            vo.setUserBindPhone(ParseUtils.parsePhone(vo.getUserBindPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
+            vo.setCostPrice(BigDecimal.ZERO);
+        }
+        ExcelUtil<LiveOrderVoZm> util = new ExcelUtil<LiveOrderVoZm>(LiveOrderVoZm.class);
         return util.exportExcel(list, "订单数据");
     }
 

+ 1 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;

+ 6 - 5
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -142,6 +142,11 @@ public class Task {
                 }
             }
         });
+        if(!liveList.isEmpty()){
+            for (Live live : liveList) {
+                liveService.updateLiveEntity(live);
+            }
+        }
         String key = "live:auto_task:";
         if (!startLiveList.isEmpty()) {
             for (Live live : startLiveList) {
@@ -182,11 +187,7 @@ public class Task {
             // 重新更新所有在直播的缓存
             liveService.asyncToCache();
         }
-        if(!liveList.isEmpty()){
-            for (Live live : liveList) {
-                liveService.updateLiveEntity(live);
-            }
-        }
+
     }
     @Scheduled(cron = "0/1 * * * * ?")
     @DistributeLock(key = "liveLotteryTask", scene = "task")

+ 8 - 8
fs-live-app/src/main/java/com/fs/live/websocket/handle/LiveChatHandler.java

@@ -71,7 +71,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
 
             if (userType == 0) {
                 // 加入房间
-                liveWatchUserService.joinWithoutLocation(liveId, userId);
+                LiveWatchUser liveWatchUser = liveWatchUserService.joinWithoutLocation(liveId, userId);
                 room.put(userId, ctx.channel());
 
                 FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
@@ -80,7 +80,6 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
                     return;
                 }
 
-                LiveWatchUserVO liveWatchUserVO = liveWatchUserService.selectWatchUserByLiveIdAndUserId(liveId, userId);
 
                 SendMsgVo sendMsgVo = new SendMsgVo();
                 sendMsgVo.setLiveId(liveId);
@@ -88,7 +87,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
                 sendMsgVo.setUserType(userType);
                 sendMsgVo.setCmd("entry");
                 sendMsgVo.setMsg("用户进入");
-                sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
+                sendMsgVo.setData(JSONObject.toJSONString(liveWatchUser));
                 sendMsgVo.setNickName(fsUser.getNickname());
                 sendMsgVo.setAvatar(fsUser.getAvatar());
 
@@ -176,8 +175,9 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
                     liveMsg.setCreateTime(new Date());
 
                     if (userType == 0) {
-                        LiveWatchUser liveWatchUser = liveWatchUserService.getByLiveIdAndUserId(msg.getLiveId(), msg.getUserId());
-                        if(liveWatchUser.getMsgStatus() == 1){
+                        Map<String, Integer> liveFlagWithCache = liveWatchUserService.getLiveFlagWithCache(liveId);
+                        LiveWatchUser liveWatchUser = liveWatchUserService.selectLiveWatchUserByFlag(msg.getLiveId(), msg.getUserId(), liveFlagWithCache.get("liveFlag"),  liveFlagWithCache.get("replayFlag"));
+                        if(liveWatchUser != null && liveWatchUser.getMsgStatus() == 1){
                             sendMessage(channelHandlerContext.channel(), JSONObject.toJSONString(R.error("你以被禁言")));
                             return;
                         }
@@ -219,14 +219,14 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
 
         if (userType == 0) {
             FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
-            liveWatchUserService.close(liveId, userId);
+            LiveWatchUser close = liveWatchUserService.close(liveId, userId);
             room.remove(userId);
 
             if (room.isEmpty()) {
                 rooms.remove(liveId);
             }
 
-            LiveWatchUserVO liveWatchUserVO = liveWatchUserService.selectWatchUserByLiveIdAndUserId(liveId, userId);
+
 
             SendMsgVo sendMsgVo = new SendMsgVo();
             sendMsgVo.setLiveId(liveId);
@@ -234,7 +234,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
             sendMsgVo.setUserType(userType);
             sendMsgVo.setCmd("out");
             sendMsgVo.setMsg("用户离开");
-            sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
+            sendMsgVo.setData(JSONObject.toJSONString(close));
             sendMsgVo.setNickName(fsUser.getNickname());
             sendMsgVo.setAvatar(fsUser.getAvatar());
 

+ 15 - 12
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -7,6 +7,8 @@ import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.exception.base.BaseException;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import com.fs.hisStore.domain.FsUserScrm;
+import com.fs.hisStore.service.IFsUserScrmService;
 import com.fs.live.config.ProductionWordFilter;
 import com.fs.live.mapper.LiveCouponMapper;
 import com.fs.live.websocket.auth.WebSocketConfigurator;
@@ -25,6 +27,7 @@ import org.springframework.stereotype.Component;
 
 import javax.websocket.*;
 import javax.websocket.server.ServerEndpoint;
+import java.io.EOFException;
 import java.io.IOException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
@@ -50,7 +53,7 @@ public class WebSocketServer {
     private final ILiveMsgService liveMsgService = SpringUtils.getBean(ILiveMsgService.class);
     private final ILiveService liveService = SpringUtils.getBean(ILiveService.class);
     private final ILiveWatchUserService liveWatchUserService = SpringUtils.getBean(ILiveWatchUserService.class);
-    private final IFsUserService fsUserService = SpringUtils.getBean(IFsUserService.class);
+    private final IFsUserScrmService fsUserService = SpringUtils.getBean(IFsUserScrmService.class);
     private final ILiveDataService liveDataService = SpringUtils.getBean(ILiveDataService.class);
     private final ProductionWordFilter productionWordFilter = SpringUtils.getBean(ProductionWordFilter.class);
     private final ILiveRedConfService liveRedConfService =  SpringUtils.getBean(ILiveRedConfService.class);
@@ -93,7 +96,7 @@ public class WebSocketServer {
 
         // 记录连接信息 管理员不记录
         if (userType == 0) {
-            FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
+            FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
             if (Objects.isNull(fsUser)) {
                 throw new BaseException("用户信息错误");
             }
@@ -131,8 +134,7 @@ public class WebSocketServer {
             if (isFirstViewer) {
                 redisCache.incr(UNIQUE_VIEWERS_KEY + liveId, 1);
             }
-            LiveWatchUser liveWatchUser = liveWatchUserService.getByLiveIdAndUserId(liveId, userId);
-            liveWatchUserVO.setMsgStatus(liveWatchUser.getMsgStatus());
+            liveWatchUserVO.setMsgStatus(liveWatchUserVO.getMsgStatus());
             SendMsgVo sendMsgVo = new SendMsgVo();
             sendMsgVo.setLiveId(liveId);
             sendMsgVo.setUserId(userId);
@@ -192,14 +194,11 @@ public class WebSocketServer {
         ConcurrentHashMap<Long, Session> room = getRoom(liveId);
         List<Session> adminRoom = getAdminRoom(liveId);
         if (userType == 0) {
-            FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
+            FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
             if (Objects.isNull(fsUser)) {
                 throw new BaseException("用户信息错误");
             }
-
-            LiveWatchUser liveWatchUserVO = liveWatchUserService.close(liveId, userId);
             room.remove(userId);
-
             if (room.isEmpty()) {
                 rooms.remove(liveId);
             }
@@ -210,7 +209,7 @@ public class WebSocketServer {
             // 从在线用户Set中移除用户ID
             String onlineUsersSetKey = ONLINE_USERS_SET_KEY + liveId;
             redisCache.redisTemplate.opsForSet().remove(onlineUsersSetKey, String.valueOf(userId));
-
+            LiveWatchUser liveWatchUserVO = liveWatchUserService.close(liveId, userId);
             SendMsgVo sendMsgVo = new SendMsgVo();
             sendMsgVo.setLiveId(liveId);
             sendMsgVo.setUserId(userId);
@@ -258,8 +257,8 @@ public class WebSocketServer {
                     liveMsg.setCreateTime(new Date());
 
                     if (userType == 0) {
-                        LiveWatchUser liveWatchUser = liveWatchUserService.getByLiveIdAndUserId(msg.getLiveId(), msg.getUserId());
-                        if(liveWatchUser.getMsgStatus() == 1){
+                        List<LiveWatchUser> liveWatchUser = liveWatchUserService.getByLiveIdAndUserId(msg.getLiveId(), msg.getUserId());
+                        if(!liveWatchUser.isEmpty() && liveWatchUser.get(0).getMsgStatus() == 1){
                             sendMessage(session, JSONObject.toJSONString(R.error("你已被禁言")));
                             return;
                         }
@@ -466,7 +465,11 @@ public class WebSocketServer {
     //错误时调用
     @OnError
     public void onError(Session session, Throwable throwable) {
-        log.error("webSocKet连接错误 msg: {}", throwable.getMessage(), throwable);
+        if (throwable instanceof EOFException) {
+            log.info("WebSocket连接被客户端正常关闭(EOF),sessionId: {}", session.getId());
+        } else {
+            log.error("WebSocket连接错误", throwable);
+        }
     }
 
     /**

+ 2 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -1072,6 +1072,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
                     setting.setLinkUrl(linkByApp.getSortLink().replaceAll("^[\\s\\u2005]+", ""));
                     setting.setAppLinkUrl(linkByApp.getAppMsgLink().replaceAll("^[\\s\\u2005]+", ""));
+                    setting.setCourseUrl(setting.getLinkImageUrl());
+                    setting.setTitle(setting.getLinkTitle());
 
                     break;
                 //自定义小程序

+ 1 - 1
fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java

@@ -53,7 +53,7 @@ public class FsCourseLink extends BaseEntity
     */
     private Long qwExternalId;
 
-    private Integer linkType; //链接类型 0:正常链接  1:应急链接  2:小程序链接
+    private Integer linkType; //链接类型 0:正常链接  1:应急链接  3:小程序链接 4:APP
 
     private Integer isRoom;//是否发群
     private String chatId;//是否发群

+ 2 - 1
fs-service/src/main/java/com/fs/course/dto/FsCourseQuestionBankImportDTO.java

@@ -30,7 +30,8 @@ public class FsCourseQuestionBankImportDTO {
     @Excel(name = "选项B(必填)")
     private String questionB;
 
-    @Excel(name = "选项C(必填)")
+    // 鹤颜堂项目有两个答案的
+    @Excel(name = "选项C(非必填)")
     private String questionC;
 
     @Excel(name = "选项D(非必填)")

+ 17 - 0
fs-service/src/main/java/com/fs/course/dto/ImportFailItemDTO.java

@@ -0,0 +1,17 @@
+package com.fs.course.dto;
+
+import lombok.Data;
+
+@Data
+public class ImportFailItemDTO {
+
+    private FsCourseQuestionBankImportDTO rowData; // 原始 Excel 行
+    private String reason; // 失败原因
+
+    public ImportFailItemDTO(FsCourseQuestionBankImportDTO rowData, String reason) {
+        this.rowData = rowData;
+        this.reason = reason;
+    }
+
+}
+

+ 62 - 0
fs-service/src/main/java/com/fs/course/dto/ImportResultDTO.java

@@ -0,0 +1,62 @@
+package com.fs.course.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ImportResultDTO {
+
+
+    private int successNum = 0;
+    private int failureNum = 0;
+
+    // 用于 message 拼接
+    private final List<String> failureMsgList = new ArrayList<>();
+
+    // 返回给前端的失败原始数据 list(你要的)
+    private final List<FsCourseQuestionBankImportDTO> failureList = new ArrayList<>();
+
+    public void addSuccess() {
+        successNum++;
+    }
+
+    public void addFailure(FsCourseQuestionBankImportDTO dto, String reason) {
+        failureNum++;
+
+        // message 用
+        failureMsgList.add("题目 " + dto.getTitle() + " 导入失败:" + reason);
+
+        // list 记录原始数据
+        failureList.add(dto);
+    }
+
+    public List<FsCourseQuestionBankImportDTO> getFailureList() {
+        return failureList;
+    }
+
+    /**
+     * 构建你要的 HTML 格式 message
+     */
+    public String buildResultMessage() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("导入完成!成功")
+                .append(successNum)
+                .append(" 条,失败")
+                .append(failureNum)
+                .append("条。");
+
+        if (!failureMsgList.isEmpty()) {
+            sb.append("<br/>");
+            int index = 1;
+            for (String msg : failureMsgList) {
+                sb.append(index++)
+                        .append("、")
+                        .append(msg)
+                        .append("<br/>");
+            }
+        }
+
+        return sb.toString();
+    }
+    }
+

+ 18 - 0
fs-service/src/main/java/com/fs/course/dto/OrderOpenIdTransDTO.java

@@ -0,0 +1,18 @@
+package com.fs.course.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class OrderOpenIdTransDTO implements Serializable {
+    /**
+     * 用户微信openId
+     * **/
+    private String openId;
+
+    /**
+     * 交易单号
+     * **/
+    private String transactionId;
+}

+ 11 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java

@@ -122,4 +122,15 @@ public interface FsUserCoursePeriodDaysMapper extends BaseMapper<FsUserCoursePer
 
     @Select("SELECT distinct period_id from fs_user_course_period_days  where day_date >=#{periodSTime} and day_date <=#{periodETime} ")
     List<Long> selectFsUserCoursePeriodDaysByTime(@Param("periodSTime") String periodSTime,@Param("periodETime") String periodETime);
+
+    /**
+     * @Description: 根据id获取当前营期的所有的大于该id数据lesson的id
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/18 11:04
+     */
+    List<Long> selectFsUserCoursePeriodDaysForLastById(FsUserCoursePeriodDays param);
+
+    List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysByCourseId(@Param("courseId") Long courseId);
 }

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

@@ -1,5 +1,6 @@
 package com.fs.course.mapper;
 
+import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.param.PeriodStatisticCountParam;
@@ -170,4 +171,6 @@ public interface FsUserCoursePeriodMapper
             "</foreach> " +
             "</script> ")
     List<Long> selectFsUserCoursePeriodListByPeriodId(@Param("periodIds") List<Long> qwUserIds,@Param("companyId") Long companyId);
+
+    List<SysDictData> selectFsUserCoursePeriodListLabel(FsUserCoursePeriod fsUserCoursePeriod);
 }

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

@@ -69,6 +69,8 @@ public class FsCourseWatchLogListParam implements Serializable {
 
     private List<Long> periodIds;//训练营期ID
 
+    private Long periodId;//训练营期ID
+
     private String customPageStr;
 
     private Long lastId;

+ 2 - 1
fs-service/src/main/java/com/fs/course/service/IFsCourseQuestionBankService.java

@@ -3,6 +3,7 @@ package com.fs.course.service;
 import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsCourseQuestionBank;
 import com.fs.course.dto.FsCourseQuestionBankImportDTO;
+import com.fs.course.dto.ImportResultDTO;
 import com.fs.course.param.FsCourseQuestionAnswerUParam;
 
 import javax.validation.constraints.Size;
@@ -73,7 +74,7 @@ public interface IFsCourseQuestionBankService
      * @param nickName 昵称
      * @return String
      */
-    String importData(List<FsCourseQuestionBankImportDTO> list, String nickName,Long userId);
+    ImportResultDTO importData(List<FsCourseQuestionBankImportDTO> list, String nickName, Long userId);
 
     /**
      * 根据ID查询题目

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

@@ -122,4 +122,5 @@ public interface IFsUserCoursePeriodDaysService extends IService<FsUserCoursePer
 
     List<Long> selectFsUserCoursePeriodDaysByTime(String periodSTime,String periodETime);
 
+        List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysByCourseId(Long courseId);
     }

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

@@ -1,5 +1,6 @@
 package com.fs.course.service;
 
+import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.param.PeriodStatisticCountParam;
 import com.fs.course.vo.FsCourseStaticsCountVO;
@@ -101,4 +102,7 @@ public interface IFsUserCoursePeriodService
 
     List<Long> selectFsUserCoursePeriodListByPeriodId(List<Long> periodIds,Long companyId);
 
+    int editIsNeedRegisterMember(FsUserCoursePeriod fsUserCoursePeriod);
+
+    List<SysDictData> selectFsUserCoursePeriodListLabel(FsUserCoursePeriod fsUserCoursePeriod);
 }

+ 6 - 9
fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java

@@ -1,10 +1,5 @@
 package com.fs.course.service;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Map;
-
 import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.param.*;
@@ -13,8 +8,11 @@ import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
 import com.fs.his.vo.OptionsVO;
 
-import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 课程Service接口
@@ -22,8 +20,7 @@ import javax.validation.constraints.NotNull;
  * @author fs
  * @date 2024-05-15
  */
-public interface IFsUserCourseService
-{
+public interface IFsUserCourseService {
     /**
      * 查询课程
      *
@@ -110,7 +107,7 @@ public interface IFsUserCourseService
 
     List<FsUserCourseListVO> getFsUserCourseList(FsUserCourseListParam param);
 
-    void  processQwSopCourseMaterialTimer();
+    void processQwSopCourseMaterialTimer();
 
     List<FsCourseListBySidebarVO> getFsCourseListBySidebar(FsCourseListBySidebarParam param);
 

+ 32 - 18
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -11,6 +11,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.dto.FsCourseQuestionBankImportDTO;
+import com.fs.course.dto.ImportResultDTO;
 import com.fs.course.mapper.*;
 import com.fs.course.param.FsCourseQuestionAnswerUParam;
 import com.fs.course.service.IFsCourseQuestionBankService;
@@ -468,43 +469,50 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
      * @return String
      */
     @Override
-    public String importData(List<FsCourseQuestionBankImportDTO> list, String nickName,Long userId) {
+    public ImportResultDTO importData(List<FsCourseQuestionBankImportDTO> list, String nickName, Long userId) {
+
         if (Objects.isNull(list) || list.isEmpty()) {
             throw new ServiceException("导入数据不能为空");
         }
 
-        ImportResult result = new ImportResult();
+        ImportResultDTO result = new ImportResultDTO();
         List<FsCourseQuestionBank> importData = new ArrayList<>();
-        Map<String, FsUserCourseCategory> categoryData = courseCategoryMapper.queryAllCategoryData();
+
+        // 分类数据判空保护
+        Map<String, FsUserCourseCategory> categoryData =
+                Optional.ofNullable(courseCategoryMapper.queryAllCategoryData())
+                        .orElse(Collections.emptyMap());
 
         for (FsCourseQuestionBankImportDTO importDTO : list) {
             try {
-                // 数据验证
                 ValidationResult validation = validateImportData(importDTO);
                 if (!validation.isValid()) {
-                    result.addFailure(importDTO.getTitle(), validation.getErrorMessage());
+                    result.addFailure(importDTO, validation.getErrorMessage());
                     continue;
                 }
 
-                // 构建题目对象
-                FsCourseQuestionBank questionBank = buildQuestionBank(importDTO, categoryData, nickName);
+                FsCourseQuestionBank questionBank =
+                        buildQuestionBank(importDTO, categoryData, nickName);
+
                 questionBank.setUserId(userId);
+
                 importData.add(questionBank);
-                result.addSuccess(importDTO.getTitle());
+                result.addSuccess();
 
             } catch (Exception e) {
-                result.addFailure(importDTO.getTitle(), "导入异常: " + e.getMessage());
+                result.addFailure(importDTO, "导入异常:" + e.getMessage());
             }
         }
 
-        // 批量保存
+        // 4. 批量插入
         if (!importData.isEmpty()) {
             fsCourseQuestionBankMapper.insertFsCourseQuestionBankBatch(importData);
         }
 
-        return result.buildResultMessage();
+        return result;
     }
 
+
     /**
      * 验证导入数据
      */
@@ -539,13 +547,13 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
     }
 
     /**
-     * 验证选项
+     * 验证选项 鹤颜堂项目允许两个答案
      */
     private ValidationResult validateOptions(FsCourseQuestionBankImportDTO importDTO) {
         if (StringUtils.isBlank(importDTO.getQuestionA()) ||
-                StringUtils.isBlank(importDTO.getQuestionB()) ||
-                StringUtils.isBlank(importDTO.getQuestionC())) {
-            return ValidationResult.fail("选项A、B、C不能为空");
+                StringUtils.isBlank(importDTO.getQuestionB())
+                ) {
+            return ValidationResult.fail("选项A、B不能为空");
         }
         return ValidationResult.success();
     }
@@ -672,7 +680,9 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         // 构建选项
         addOption(questionArray, importDTO.getQuestionA(), answerList.contains("A"), 0);
         addOption(questionArray, importDTO.getQuestionB(), answerList.contains("B"), 1);
-        addOption(questionArray, importDTO.getQuestionC(), answerList.contains("C"), 2);
+        if (StringUtils.isNotBlank(importDTO.getQuestionC())) {
+            addOption(questionArray, importDTO.getQuestionC(), answerList.contains("C"), 2);
+        }
 
         if (StringUtils.isNotBlank(importDTO.getQuestionD())) {
             addOption(questionArray, importDTO.getQuestionD(), answerList.contains("D"), 3);
@@ -703,8 +713,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         Map<String, String> questionMap = new HashMap<>();
         questionMap.put("A", importDTO.getQuestionA().trim());
         questionMap.put("B", importDTO.getQuestionB().trim());
-        questionMap.put("C", importDTO.getQuestionC().trim());
-        questionMap.put("D", importDTO.getQuestionD().trim());
+        if(StringUtils.isNotBlank(importDTO.getQuestionC())){
+            questionMap.put("C", importDTO.getQuestionC().trim());
+        }
+        if(StringUtils.isNotBlank(importDTO.getQuestionD())){
+            questionMap.put("D", importDTO.getQuestionD().trim());
+        }
 
         List<String> selectedAnswers = answerList.stream()
                 .map(questionMap::get)

+ 83 - 24
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -344,35 +344,89 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
 
     @Override
     public R updateCourseTime(UpdateCourseTimeVo vo) {
-        List<FsUserCoursePeriodDays> list = fsUserCoursePeriodDaysMapper.selectBatchIds(vo.getIds());
-        list.forEach(day -> {
+        // 批量更新
+        if(vo.getBatchUpdateSwitch()!=null && vo.getBatchUpdateSwitch()==1
+                && vo.getIds()!=null && vo.getIds().size()==1){
+            Long id=vo.getIds().get(0);
+
+            FsUserCoursePeriodDays info= fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysById(id);
+
+            if(info==null || info.getPeriodId()==null || info.getLesson()==null){
+                return R.error(400, "该课程参数异常,请联系管理员");
+            }
+            // 根据id获取当前营期的所有的大于该id数据lesson的id
+            FsUserCoursePeriodDays param = new FsUserCoursePeriodDays();
+            param.setPeriodId(info.getPeriodId());
+            param.setLesson(info.getLesson());
+            List<Long> ids = fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysForLastById(param);
+            List<FsUserCoursePeriodDays> list = fsUserCoursePeriodDaysMapper.selectBatchIds(ids);
+            list.forEach(day -> {
+
+                // 调整时间为直接接收前端传入的年月日,不使用营期的日期(2025年6月11日 10点41分)
+                day.setStartDateTime(vo.getStartTime());
+                day.setEndDateTime(vo.getEndTime1());
+
+
+                SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
+                if (ObjectUtils.isNotEmpty(config)&&config.getConfigValue().equals("1")){
+                    day.setLastJoinTime(vo.getEndTime1());
+                }else {
+                    day.setLastJoinTime(vo.getJoinTime());
+                }
+
+                // 把营期时间改成开始时间
+                day.setDayDate(vo.getStartTime().toLocalDate());
+                // 设置状态
+                LocalDateTime compareDayTime = LocalDateTime.now();
+                if(compareDayTime.isAfter(day.getStartDateTime()) && compareDayTime.isBefore(day.getEndDateTime())){
+                    day.setStatus(1);
+                } else if(compareDayTime.isBefore(day.getStartDateTime())){
+                    day.setStatus(0);
+                } else {
+                    day.setStatus(2);
+                }
+                fsUserCoursePeriodDaysMapper.updateById(day);
+
+                // 时间加一天
+                vo.setStartTime(vo.getStartTime().plusDays(1));
+                vo.setEndTime1(vo.getEndTime1().plusDays(1));
+                vo.setJoinTime(vo.getJoinTime().plusDays(1));
+            });
+
+        }else {
+            List<FsUserCoursePeriodDays> list = fsUserCoursePeriodDaysMapper.selectBatchIds(vo.getIds());
+            list.forEach(day -> {
 //            day.setStartDateTime(LocalDateTime.of(day.getDayDate(), vo.getStartTime()));
 //            day.setEndDateTime(LocalDateTime.of(day.getDayDate(), vo.getEndTime1()));
 //            day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), vo.getJoinTime()));
-            // 调整时间为直接接收前端传入的年月日,不使用营期的日期(2025年6月11日 10点41分)
-            day.setStartDateTime(vo.getStartTime());
-            day.setEndDateTime(vo.getEndTime1());
+                // 调整时间为直接接收前端传入的年月日,不使用营期的日期(2025年6月11日 10点41分)
+                day.setStartDateTime(vo.getStartTime());
+                day.setEndDateTime(vo.getEndTime1());
+
+                SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
+                if (ObjectUtils.isNotEmpty(config)&&config.getConfigValue().equals("1")){
+                    day.setLastJoinTime(vo.getEndTime1());
+                }else {
+                    day.setLastJoinTime(vo.getJoinTime());
+                }
+
+                // 把营期时间改成开始时间
+                day.setDayDate(vo.getStartTime().toLocalDate());
+                // 设置状态
+                LocalDateTime compareDayTime = LocalDateTime.now();
+                if(compareDayTime.isAfter(day.getStartDateTime()) && compareDayTime.isBefore(day.getEndDateTime())){
+                    day.setStatus(1);
+                } else if(compareDayTime.isBefore(day.getStartDateTime())){
+                    day.setStatus(0);
+                } else {
+                    day.setStatus(2);
+                }
+                fsUserCoursePeriodDaysMapper.updateById(day);
+            });
+        }
+
 
-            SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
-            if (ObjectUtils.isNotEmpty(config)&&config.getConfigValue().equals("1")){
-                day.setLastJoinTime(vo.getEndTime1());
-            }else {
-                day.setLastJoinTime(vo.getJoinTime());
-            }
 
-            // 把营期时间改成开始时间
-            day.setDayDate(vo.getStartTime().toLocalDate());
-            // 设置状态
-            LocalDateTime compareDayTime = LocalDateTime.now();
-            if(compareDayTime.isAfter(day.getStartDateTime()) && compareDayTime.isBefore(day.getEndDateTime())){
-                day.setStatus(1);
-            } else if(compareDayTime.isBefore(day.getStartDateTime())){
-                day.setStatus(0);
-            } else {
-                day.setStatus(2);
-            }
-            fsUserCoursePeriodDaysMapper.updateById(day);
-        });
         return R.ok();
     }
 
@@ -425,6 +479,11 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         return fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysByTime(periodSTime,periodETime);
     }
 
+    @Override
+    public List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysByCourseId(Long courseId) {
+        return fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysByCourseId(courseId);
+    }
+
     private static FsCourseAnalysisCountVO getCourseAnalysisCountVO(FsUserCoursePeriodDays v, Map<Long, FsCourseAnalysisCountVO> courseMap, Map<Long, FsCourseAnalysisCountVO> redPacketMap, Map<Long, FsCourseAnalysisCountVO> answerMap) {
         FsCourseAnalysisCountVO countVO = new FsCourseAnalysisCountVO();
         FsCourseAnalysisCountVO courseVO = courseMap.getOrDefault(v.getVideoId(), countVO);

+ 11 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.course.service.impl;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
@@ -310,4 +311,14 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
     public List<Long> selectFsUserCoursePeriodListByPeriodId(List<Long> periodIds, Long companyId) {
         return fsUserCoursePeriodMapper.selectFsUserCoursePeriodListByPeriodId(periodIds,companyId);
     }
+
+    @Override
+    public int editIsNeedRegisterMember(FsUserCoursePeriod fsUserCoursePeriod) {
+        return fsUserCoursePeriodMapper.updateFsUserCoursePeriod(fsUserCoursePeriod);
+    }
+
+    @Override
+    public List<SysDictData> selectFsUserCoursePeriodListLabel(FsUserCoursePeriod fsUserCoursePeriod) {
+        return fsUserCoursePeriodMapper.selectFsUserCoursePeriodListLabel(fsUserCoursePeriod);
+    }
 }

+ 36 - 15
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -26,6 +26,7 @@ import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseListParam;
+import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
@@ -128,6 +129,9 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private IFsUserIntegralLogsService userIntegralLogsService;
 
+    @Autowired
+    private IFsUserCoursePeriodDaysService fsUserCoursePeriodDaysService;
+
     private static final String realLink = "/courseH5/pages/course/learning?course=";
     public static final String shortLink = "/courseH5/pages/course/learning?s=";
 
@@ -690,24 +694,41 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Transactional(rollbackFor = Exception.class) // 显式声明事务
     public int copyFsUserCourse(Long courseId) {
         FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(courseId);
-        if(fsUserCourse != null){
-            fsUserCourse.setCourseId(null);
-            fsUserCourseService.insertFsUserCourse(fsUserCourse);
-            Long newCourseId = fsUserCourse.getCourseId();
+        try {
+            if(fsUserCourse != null){
+                fsUserCourse.setCourseId(null);
+                fsUserCourseService.insertFsUserCourse(fsUserCourse);
+                Long newCourseId = fsUserCourse.getCourseId();
 
-            if (newCourseId == null) {
-                throw new RuntimeException("课程插入失败,无法获取新课程ID");
-            }
+                if (newCourseId == null) {
+                    throw new RuntimeException("课程插入失败,无法获取新课程ID");
+                }
+
+                FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
+                fsUserCourseVideo.setCourseId(courseId);
+                List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+                if(list != null && !list.isEmpty()){
+                    for (FsUserCourseVideo courseVideo : list) {
+                        courseVideo.setVideoId(null);
+                        courseVideo.setCourseId(newCourseId);
+                        fsUserCourseVideoService.insertFsUserCourseVideo(courseVideo);
+                    }
+                }
+
+                //增加手动发课部分的复制
+                List<FsUserCoursePeriodDays> fsUserCoursePeriodDays =  fsUserCoursePeriodDaysService.selectFsUserCoursePeriodDaysByCourseId(courseId);
+                if(fsUserCoursePeriodDays != null && !fsUserCoursePeriodDays.isEmpty()){
+                    for (FsUserCoursePeriodDays periodDays : fsUserCoursePeriodDays) {
+                        periodDays.setId(null);
+                        periodDays.setCourseId(newCourseId);
+                        fsUserCoursePeriodDaysService.insertFsUserCoursePeriodDays(periodDays);
+                    }
+                }
 
-            FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
-            fsUserCourseVideo.setCourseId(courseId);
-            List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
-            for (FsUserCourseVideo courseVideo : list) {
-                courseVideo.setVideoId(null);
-                courseVideo.setCourseId(newCourseId);
-                fsUserCourseVideoService.insertFsUserCourseVideo(courseVideo);
+                return 1;
             }
-            return 1;
+        } catch (RuntimeException e) {
+            throw new RuntimeException("复制失败,请联系开发人员");
         }
 
         return 0;

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

@@ -546,8 +546,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
 
-        //服务号授权的,缺mpOpenId的重新登录
-        if (config.getMiniAppAuthType()==2 && StringUtil.strIsNullOrEmpty(fsUser.getMpOpenId())){
+        //服务号授权的,缺mpOpenId的重新登录 linkType = 4为app看课 不需要mp
+        if (param.getLinkType() != 4 && config.getMiniAppAuthType()==2 && StringUtil.strIsNullOrEmpty(fsUser.getMpOpenId())){
             return R.error(401,"授权后可继续!");
         }
 
@@ -1444,7 +1444,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             //查询是否绑定小程序
             FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
             if (fsUserWx ==null){
-                return R.error("openId参数错误,请清理缓存重新授权");
+                return R.error(401,"openId参数错误,请清理缓存重新授权");
             }else {
                 packetParam.setOpenId(fsUserWx.getOpenId());
             }

+ 6 - 1
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogStatisticsListVO.java

@@ -39,7 +39,7 @@ public class FsCourseWatchLogStatisticsListVO {
     @Excel(name = "员工名称")
     private String userName;
 
-    @Excel(name = "创建时间")
+    @Excel(name = "创建时间" ,dateFormat = "yyyy-MM-dd")
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date createTime;
 
@@ -48,18 +48,23 @@ public class FsCourseWatchLogStatisticsListVO {
     private String companyUserName;
 
     /** 发课数 */
+    @Excel(name = "发课数")
     private String  sendNumber;
 
     /** 已注册用户待看课数 */
+    @Excel(name = "已注册用户待看课数")
     private String  isUserWaitNumber;
 
     /** 未注册用户待看课数 */
+    @Excel(name = "未注册用户待看课数")
     private String  noUserWaitNumber;
 
     /** 上线率 */
+    @Excel(name = "上线率")
     private String  onLineRate;
 
     /** 完课率 */
+    @Excel(name = "完课率")
     private String  finishedRate;
 
     /** 消耗红包金额 */

+ 1 - 1
fs-service/src/main/java/com/fs/course/vo/FsUserCoursePeriodVO.java

@@ -89,5 +89,5 @@ public class FsUserCoursePeriodVO implements Serializable {
     @Excel(name = "营期线", width = 31, dateFormat = "yyyy-MM-dd")
     private Date periodLine;
     /** 是否需要单独注册会员,1-是,0-否(用于个微销售分享看课) */
-    private Integer isNeedRegisterMember;
+    private String isNeedRegisterMember;
 }

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

@@ -21,4 +21,7 @@ public class UpdateCourseTimeVo {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime joinTime;
     private LocalDate dayDate;
+
+    // 批量修改开关 0 关闭 1 开启 默认关闭 打开的话修改时间,后续的时间会一起改变
+    private Integer batchUpdateSwitch;
 }

+ 3 - 3
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -356,7 +356,7 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         order.setIntegral(totalIntegral.toString());
         order.setItemJson(JSONUtil.toJsonStr(goodsItem));
         order.setUserName(address.getRealName());
-        order.setUserAddress(address.getProvince()+address.getCity()+address.getDistrict()+address.getDetail());
+        order.setUserAddress(address.getProvince()+" "+address.getCity()+" "+address.getDistrict()+" "+address.getDetail());
         order.setUserPhone(address.getPhone());
         order.setCreateTime(new Date());
         order.setCompanyUserId(companyUserId);
@@ -855,7 +855,7 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         List<ErpOrderPayment> payments = new ArrayList<>();
         ErpOrderPayment payment = new ErpOrderPayment();
         payment.setPay_type_code("weixin");
-        payment.setPayment(order.getPayMoney().doubleValue());
+        payment.setPayment(0d);
         if (order.getPayTime() != null) {
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             String timeString = sdf.format(Date.from((order.getPayTime().atZone(ZoneId.systemDefault()).toInstant())));
@@ -935,7 +935,7 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
             JSONObject jsonObject = (JSONObject) obj;
             ErpOrderItem item = new ErpOrderItem();
             item.setOid(jsonObject.getString("goodsId"));
-            item.setItem_code(jsonObject.getString("barCode").trim());//商品编码
+            item.setItem_code(jsonObject.getString("barCode"));//商品编码
             item.setPrice(jsonObject.getString("otPrice"));//市场价
             item.setQty(jsonObject.getInteger("num"));//数量
             item.setRefund(0);

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java

@@ -25,4 +25,7 @@ public class StoreConfig implements Serializable {
     private Integer createSalesOrderType; // 订单改价方式 1 商品改价 2总价改价
     private Boolean isBrushOrders;//是否开启刷单按钮
     private Integer orderAttribution; // 下单归属 1 多销售 2单销售
+    private Boolean isWeChatShipping;//是否开启微信发货
+    private Boolean scanCodeDiscountEnabled;//是否开启扫码立减金
+    private BigDecimal scanCodeDiscountAmount;//扫码立减金额
 }

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsMenuScrm.java

@@ -33,5 +33,7 @@ public class FsMenuScrm extends BaseEntity
 
     private Integer menuType;//菜单类型
 
+    private String appId;
+
 
 }

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java

@@ -351,4 +351,6 @@ public class FsStoreOrderScrm extends BaseEntity
     private BigDecimal billPrice;
     private String erpPhone;
 
+    @TableField(exist = false)
+    private String bankTransactionId;
 }

+ 17 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -1378,4 +1378,21 @@ public interface FsStoreOrderScrmMapper
 
     @Select("select * from fs_store_order_scrm where  `status`=2 and (extend_order_id is not null and extend_order_id != '') and (delivery_id is not null and delivery_id != '')")
     List<FsStoreOrderScrm> selectShippedOrder();
+    /**
+     * 获取发货单数据
+     * @param maps 查询参数
+     * @return list
+     * **/
+    List<FsStoreOrderDeliveryNoteExportVO> getDeliveryNote(@Param("maps") FsStoreOrderParam maps);
+    /**
+     * 获取订单用户信息
+     * @param list 订单号
+     * @return lsit
+     * **/
+    List<FsStoreOrderCodeOpenIdVo> selectOrderCodeOpenIdInOrderCode(@Param("list") List<String> list);
+    /**
+     * 获取未结算订单
+     * **/
+    List<FsStoreOrderScrm> getUnsettledOrder();
+
 }

+ 7 - 6
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java

@@ -202,6 +202,12 @@ public interface FsStoreProductScrmMapper
             "<if test = 'maps.storeId != null'> " +
             "and p.store_id = #{maps.storeId}" +
             "</if>" +
+            " <if test = 'maps.cateIds != null and maps.cateIds.size() > 0'>\n" +
+            "        and p.cate_id in \n" +
+            "        <foreach collection='maps.cateIds' item='cateId' open='(' separator=',' close=')'>\n" +
+            "            #{cateId}\n" +
+            "        </foreach>\n" +
+            "    </if>"+
             "<if test = 'maps.defaultOrder != null and maps.defaultOrder==\"desc\"  '> " +
             "order by p.sort desc,product_id desc" +
             "</if>" +
@@ -217,12 +223,7 @@ public interface FsStoreProductScrmMapper
             "<if test = 'maps.salesOrder != null  and maps.salesOrder==\"asc\" '> " +
             "order by p.sales asc " +
             "</if>" +
-            " <if test = 'maps.cateIds != null and maps.cateIds.size() > 0'>\n" +
-            "        and p.cate_id in \n" +
-            "        <foreach collection='maps.cateIds' item='cateId' open='(' separator=',' close=')'>\n" +
-            "            #{cateId}\n" +
-            "        </foreach>\n" +
-            "    </if>"+
+
             "<if test = 'maps.newOrder != null and maps.newOrder==\"desc\" '> " +
             "and p.is_new =1 order by p.create_time desc  " +
             "</if>" +

+ 15 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java

@@ -39,6 +39,10 @@ import com.fs.his.vo.FsPrescribeVO;
 public interface IFsStoreOrderScrmService
 {
 
+    /**
+     * 同步物流信息到微信
+     */
+    void syncExpressToWx();
     /**
      * 查询订单
      *
@@ -321,6 +325,17 @@ public interface IFsStoreOrderScrmService
 
     R receiveWaybillPush(String body);
 
+    /**
+     * 获取发货单数据
+     * @param param 查询条件
+     * @return list
+     * **/
+    List<FsStoreOrderDeliveryNoteExportVO> getDeliveryNote(FsStoreOrderParam param);
+
+    R importDeliveryNoteExpress(List<FsOrderDeliveryNoteDTO> dtoList, String miniAppId);
+
+    void refreshOrderSettlementStatus();
+
     /**
      * 套餐包制单
      * @param companyUser 销售

+ 365 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -1,6 +1,8 @@
 package com.fs.hisStore.service.impl;
 
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*;
+import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoGetResponse;
 import cn.hutool.core.date.DateTime;
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.hutool.core.net.URLDecoder;
@@ -46,6 +48,7 @@ import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.config.WxPayProperties;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.dto.FsOrderDeliveryNoteDTO;
+import com.fs.course.dto.OrderOpenIdTransDTO;
 import com.fs.erp.domain.*;
 import com.fs.erp.dto.*;
 import com.fs.erp.dto.df.*;
@@ -102,6 +105,12 @@ import com.fs.hisStore.enums.*;
 import com.fs.hisStore.service.*;
 import com.fs.system.service.ISysConfigService;
 import com.fs.wx.miniapp.config.WxMaProperties;
+import com.fs.wx.order.domain.FsWxExpressTask;
+import com.fs.wx.order.dto.*;
+import com.fs.wx.order.mapper.FsWxExpressTaskMapper;
+import com.fs.wx.order.service.ExpressToWxHolder;
+import com.fs.wx.order.service.ExpressToWxService;
+import com.fs.wx.order.service.ShippingService;
 import com.fs.ybPay.domain.OrderResult;
 import com.fs.ybPay.domain.RefundResult;
 import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
@@ -142,6 +151,7 @@ import java.sql.Timestamp;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
@@ -361,6 +371,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
     @Autowired
     private FsStoreOrderDfMapper fsStoreOrderDfMapper;
+    @Autowired
+    private ShippingService shippingService;
+    @Autowired
+    private FsWxExpressTaskMapper fsWxExpressTaskMapper;
 
     @Autowired
     private IFsStoreCartScrmService fsStoreCartScrmService;
@@ -375,6 +389,81 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         erpServiceMap.put(5, jSTOrderService);     // 聚水潭
         erpServiceMap.put(6, k9OrderService);      // K9
     }
+    @Override
+    public void syncExpressToWx() {
+        List<FsWxExpressTask> fsWxExpressTasks = fsWxExpressTaskMapper.selectPendingData();
+        if (CollectionUtils.isEmpty(fsWxExpressTasks)) {
+            logger.info("当前没有待同步的数据!已取消");
+            return;
+        }
+
+        for (FsWxExpressTask fsWxExpressTask : fsWxExpressTasks) {
+
+            try{
+                UploadShippingInfoRequest request = new UploadShippingInfoRequest();
+
+                OrderKey orderKey = new OrderKey();
+                orderKey.setOrderNumberType(2);
+
+
+                FsUserScrm fsUser = userService.selectFsUserByUserId(fsWxExpressTask.getUserId());
+
+                ExpressToWxService service = ExpressToWxHolder.findBest(fsWxExpressTask.getType(),fsWxExpressTask.getOrderCode());
+                Asserts.notNull(service,"订单类型不被支持!");
+
+
+                orderKey.setTransactionId(service.getTransactionId());
+
+
+                String userPhone = service.getUserPhone();
+                String orderGoodsInfo = service.getOrderGoodsInfo();
+
+
+                Payer payer = new Payer();
+                if(StringUtils.isNotBlank(fsUser.getMaOpenId())){
+                    payer.setOpenid(fsUser.getMaOpenId());
+                }
+                request.setPayer(payer);
+                request.setOrderKey(orderKey);
+
+                request.setLogisticsType(1);
+                request.setDeliveryMode(1);
+
+                request.setShippingList(Collections.singletonList(ShippingItem.builder()
+                        .itemDesc(orderGoodsInfo)
+                        .expressCompany(service.getExpressCompany())
+                        .trackingNo(service.getExpressNo())
+                        .contact(Contact.builder().consignorContact(userPhone).build())
+                        .build()));
+
+                OffsetDateTime now = OffsetDateTime.now();
+                DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+                String formattedTimestamp = now.format(formatter);
+                request.setUploadTime(formattedTimestamp);
+
+
+                request.setAppid(fsWxExpressTask.getAppid());
+                WeChatApiResponse response = shippingService.uploadShippingInfo(request);
+                if(ObjectUtil.equal(response.getErrcode(),0)){
+                    fsWxExpressTask.setStatus(2);
+                } else {
+                    fsWxExpressTask.setRetryCount(fsWxExpressTask.getRetryCount() +1);
+                    fsWxExpressTask.setStatus(3);
+                    fsWxExpressTask.setData(JSON.toJSONString(request));
+                    fsWxExpressTask.setRequestBody(JSON.toJSONString(request));
+                    fsWxExpressTask.setResponseBody(JSON.toJSONString(response));
+                }
+            }catch (Exception e){
+                logger.info("该单 {} 推送到物流失败!",fsWxExpressTask);
+                fsWxExpressTask.setRetryCount(fsWxExpressTask.getRetryCount() +1);
+                fsWxExpressTask.setStatus(3);
+
+
+            }
+        }
+        fsWxExpressTaskMapper.batchUpdate(fsWxExpressTasks);
+
+    }
     /**
      * 查询订单
      *
@@ -1287,6 +1376,20 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     .templateType(TemplateListenEnum.TYPE_2.getValue())
                     .build();
             publisher.publishEvent(new TemplateEvent(this, templateBean));
+
+            List<FsStorePaymentScrm> fsStorePayments = fsStorePaymentMapper.selectFsStorePaymentByOrderId(order.getId());
+            FsStorePaymentScrm fsStorePayment = fsStorePayments.get(0);
+            FsWxExpressTask fsWxExpressTask = new FsWxExpressTask();
+            fsWxExpressTask.setUserId(order.getUserId());
+            fsWxExpressTask.setStatus(0);
+            fsWxExpressTask.setRetryCount(0);
+            fsWxExpressTask.setCreateTime(LocalDateTime.now());
+            fsWxExpressTask.setUpdateTime(LocalDateTime.now());
+            fsWxExpressTask.setOrderCode(order.getOrderCode());
+            fsWxExpressTask.setExpressCompany(express.getCode());
+            fsWxExpressTask.setExpressNo(deliveryId);
+            fsWxExpressTask.setAppid(fsStorePayment.getAppId());
+            fsWxExpressTaskMapper.insert(fsWxExpressTask);
         }
     }
 
@@ -4009,6 +4112,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 storePayment.setUserId(user.getUserId());
                 storePayment.setBusinessOrderId(order.getId().toString());
                 storePayment.setOrderId(order.getId());
+                storePayment.setAppId(fsPayConfig.getAppId() == null ? "" : fsPayConfig.getAppId());
                 fsStorePaymentMapper.insertFsStorePayment(storePayment);
 
                 if (fsPayConfig.getType().equals("hf")){
@@ -4751,6 +4855,254 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         return null;
     }
 
+    @Override
+    public List<FsStoreOrderDeliveryNoteExportVO> getDeliveryNote(FsStoreOrderParam param) {
+        return fsStoreOrderMapper.getDeliveryNote(param);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R importDeliveryNoteExpress(List<FsOrderDeliveryNoteDTO> dtoList, String appId) {
+        try {
+            StringBuilder builder = new StringBuilder();
+            //获取商城配置
+            String json = configService.selectConfigByKey("store.config");
+            StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+
+            List<FsOrderDeliveryNoteDTO> successList = new ArrayList<>(dtoList.size());
+            //提前获取所有必要数据
+            Map<String, String> expressDeliveryMap = buildExpressDeliveryMap();
+            //提取所有有效订单号
+            List<String> orderCodeList = new ArrayList<>(dtoList.size());
+            for (int i = 0; i < dtoList.size(); i++) {
+                FsOrderDeliveryNoteDTO dto = dtoList.get(i);
+                if (StringUtils.isEmpty(dto.getOrderNumber())) {
+                    builder.append("数据第").append(i + 2).append("行系统订单为空!").append(System.lineSeparator());
+                }
+                if (StringUtils.isEmpty(dto.getLogisticsCompany())) {
+                    builder.append("数据第").append(i + 2).append("行物流公司为空!").append(System.lineSeparator());
+                }
+                if (StringUtils.isEmpty(dto.getDeliveryId())) {
+                    builder.append("数据第").append(i + 2).append("行快递单号为空!").append(System.lineSeparator());
+                } else {
+                    //处理订单ID信息
+                    String originalOrderNumber = dto.getOrderNumber();
+                    String processedOrderNumber = extractNumbers(originalOrderNumber);
+                    dto.setOrderNumber(processedOrderNumber);
+                    orderCodeList.add(dto.getOrderNumber());
+                }
+            }
+
+            //批量查询订单信息
+            if (orderCodeList.isEmpty()) {
+                return R.ok(builder.toString());
+            }
+            List<FsStoreOrderCodeOpenIdVo> orderCodeOpenIdVoList = fsStoreOrderMapper.selectOrderCodeOpenIdInOrderCode(orderCodeList);
+            Map<String, OrderOpenIdTransDTO> orderMap = new HashMap<>(orderCodeOpenIdVoList.size());
+            Map<String, List<FsStoreOrderCodeOpenIdVo>> orderDetailsMap = new HashMap<>(orderCodeOpenIdVoList.size());
+
+            for (FsStoreOrderCodeOpenIdVo vo : orderCodeOpenIdVoList) {
+                orderMap.computeIfAbsent(vo.getId(), k -> {
+                    OrderOpenIdTransDTO dto = new OrderOpenIdTransDTO();
+                    dto.setOpenId(vo.getOpenId());
+                    dto.setTransactionId(vo.getOutTransId());
+                    return dto;
+                });
+
+                orderDetailsMap
+                        .computeIfAbsent(vo.getId(), k -> new ArrayList<>())
+                        .add(vo);
+            }
+            final WxMaService wxService = WxMaConfiguration.getMaService(appId);
+            String uploadTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))
+                    .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+            for (int i = 0; i < dtoList.size(); i++) {
+                FsOrderDeliveryNoteDTO dto = dtoList.get(i);
+                int rowNum = i + 2;
+                if (StringUtils.isEmpty(dto.getOrderNumber())) {
+                    continue;
+                }
+                if (StringUtils.isEmpty(dto.getDeliveryId())) {
+                    builder.append("数据第").append(rowNum).append("行快递单号为空!")
+                            .append(System.lineSeparator());
+                    continue;
+                }
+
+                if (StringUtils.isEmpty(dto.getLogisticsCompany())) {
+                    builder.append("数据第").append(rowNum).append("行快递公司编号为空!")
+                            .append(System.lineSeparator());
+                    continue;
+                }
+//                if (dto.getDeliveryStatus() == null) {
+//                    dto.setDeliveryStatus(0);
+//                }
+//                if (ObjectUtil.isNotNull(dto.getDeliveryTime())) {
+//                    dto.setDeliveryTime(parseCstToDateOnlyString(dto.getDeliveryTime()));
+//                }
+//
+//                if (ObjectUtil.isNotNull(dto.getDeliveryPayTime()) &&
+//                        !dto.getDeliveryPayTime().isEmpty()) {
+//                    dto.setDeliveryPayTime(parseCstToDateOnlyString(dto.getDeliveryPayTime()));
+//                }
+                // 验证快递公司
+                String deliverySn = expressDeliveryMap.get(dto.getLogisticsCompany());
+                if (deliverySn == null) {
+                    builder.append("数据第").append(rowNum).append("行订单号为")
+                            .append(dto.getOrderNumber()).append("物流公司名称异常")
+                            .append(System.lineSeparator());
+                    continue;
+                }
+                dto.setDeliverySn(deliverySn);
+
+                // 检查订单是否存在
+                String orderNumber = dto.getOrderNumber();
+                OrderOpenIdTransDTO orderInfo = orderMap.get(orderNumber);
+                if (orderInfo == null) {
+                    builder.append("数据第").append(rowNum).append("行订单号")
+                            .append(orderNumber).append("不存在").append(System.lineSeparator());
+                    continue;
+                }
+                //验证是否开启微信发货
+                if (config.getIsWeChatShipping() != null && config.getIsWeChatShipping()) {
+                    // 上传物流信息到微信
+                    List<FsStoreOrderCodeOpenIdVo> orderDetails = orderDetailsMap.get(orderNumber);
+                    if (uploadShippingInfoToWechat(wxService, orderInfo, orderDetails, dto, uploadTime)) {
+                        successList.add(dto);
+                    } else {
+                        builder.append("数据第").append(rowNum).append("行订单号为")
+                                .append(orderNumber).append("上传微信失败").append(System.lineSeparator());
+                    }
+                } else {
+                    successList.add(dto);
+                }
+            }
+
+            //批量更新数据
+            if (!successList.isEmpty()) {
+                batchUpdateDeliveryNotes(successList);
+            }
+
+            return R.ok(builder.toString().equals("") ? "操作成功!" : builder.toString());
+        } catch (Exception e) {
+            log.error("导入发货单快递信息失败", e);
+            return R.error("导入失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED)
+    public void refreshOrderSettlementStatus() {
+        try {
+            // 判断是否对接微信发货
+            String json = configService.selectConfigByKey("store.config");
+            StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+            logger.info("进入微信结算订单定时任务--------------->{}", "start");
+            if (config != null && config.getIsWeChatShipping() != null && config.getIsWeChatShipping()) {
+                // 获取未结算订单
+                List<FsStoreOrderScrm> orderScrmList = fsStoreOrderMapper.getUnsettledOrder();
+
+                String payConfig = configService.selectConfigByKey("store.pay");
+                JSONObject js = JSON.parseObject(payConfig);
+                String appId = js.getString("appId");
+
+                if (ObjectUtil.isNotNull(appId) && !appId.isEmpty()) {
+                    final WxMaService wxService = WxMaConfiguration.getMaService(appId);
+
+                    if (!orderScrmList.isEmpty()) {
+                        for (FsStoreOrderScrm order : orderScrmList) {
+                            WxMaOrderShippingInfoGetRequest request = new WxMaOrderShippingInfoGetRequest();
+                            request.setTransactionId(order.getBankTransactionId());
+                            WxMaOrderShippingInfoGetResponse response;
+
+                            try {
+                                response = wxService.getWxMaOrderShippingService().get(request);
+
+                                if (response.getErrCode().equals(0)) {
+                                    // 订单状态枚举:(1) 待发货;(2) 已发货;(3) 确认收货;(4) 交易完成;(5) 已退款
+                                    if (response.getOrder().getOrderState().equals(3) || response.getOrder().getOrderState().equals(4)) {
+                                        if (order.getStatus() == OrderInfoEnum.STATUS_2.getValue()) {
+                                            this.finishOrder(order.getId());
+                                        }
+                                    }
+                                    logger.info("请求信息------------------------》{}", response);
+                                }
+
+                            } catch (WxErrorException e) {
+                                logger.info("异常信息------------------------》{}", e.getMessage());
+                                continue;
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.getStackTrace();
+        } finally {
+            logger.info("进入微信结算订单定时任务--------------->{}", "end");
+        }
+    }
+
+    private void batchUpdateDeliveryNotes(List<FsOrderDeliveryNoteDTO> list) {
+        int batchSize = 500;
+        int total = list.size();
+        int batches = (total + batchSize - 1) / batchSize;
+        for (int i = 0; i < batches; i++) {
+            int start = i * batchSize;
+            int end = Math.min(start + batchSize, total);
+            List<FsOrderDeliveryNoteDTO> subList = list.subList(start, end);
+            fsStoreOrderMapper.batchUpdateInOrderCode(subList);
+        }
+    }
+
+    private boolean uploadShippingInfoToWechat(WxMaService wxService,
+                                               OrderOpenIdTransDTO orderInfo,
+                                               List<FsStoreOrderCodeOpenIdVo> orderDetails,
+                                               FsOrderDeliveryNoteDTO dto,
+                                               String uploadTime) {
+        try {
+            WxMaOrderShippingInfoUploadRequest request = new WxMaOrderShippingInfoUploadRequest();
+            OrderKeyBean orderKeyBean = new OrderKeyBean();
+            orderKeyBean.setOrderNumberType(2);
+            orderKeyBean.setTransactionId(orderInfo.getTransactionId());
+            request.setOrderKey(orderKeyBean);
+            request.setDeliveryMode(1);
+            request.setLogisticsType(1);
+            List<ShippingListBean> shippingList = new ArrayList<>(orderDetails.size());
+            ShippingListBean shippingListBean = null;
+            for (FsStoreOrderCodeOpenIdVo detail : orderDetails) {
+                if (shippingListBean == null) {
+                    shippingListBean = new ShippingListBean();
+                    shippingListBean.setTrackingNo(dto.getDeliveryId());
+                    shippingListBean.setExpressCompany(dto.getDeliverySn());
+                    JSONObject js = JSON.parseObject(detail.getJsonInfo());
+                    shippingListBean.setItemDesc(js.getString("productName"));
+                    ContactBean contactBean = new ContactBean();
+                    contactBean.setReceiverContact(detail.getPhone());
+                    shippingListBean.setContact(contactBean);
+                } else {
+                    //拼接
+//                    JSONObject js = JSON.parseObject(detail.getJsonInfo());
+//                    shippingListBean.setItemDesc(shippingListBean.getItemDesc()+"-"+js.getString("productName"));
+                    break;
+                }
+            }
+            shippingList.add(shippingListBean);
+            request.setShippingList(shippingList);
+            request.setUploadTime(uploadTime);
+            // 设置支付者信息
+            PayerBean payerBean = new PayerBean();
+            payerBean.setOpenid(orderInfo.getOpenId());
+            request.setPayer(payerBean);
+
+            // 上传物流信息
+            return wxService.getWxMaOrderShippingService().upload(request).getErrCode() == 0;
+        } catch (Exception e) {
+            log.error("上传物流信息到微信失败,订单号: {}", dto.getOrderNumber(), e);
+            return false;
+        }
+    }
+
     @Override
     public R createPackageSalesOrder(CompanyUser companyUser, String packageId, Integer orderType, Integer orderMedium) {
         FsStoreProductPackageScrm storeProductPackage = productPackageService.selectFsStoreProductPackageById(Long.parseLong(packageId));
@@ -4844,6 +5196,19 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         return map;
     }
 
+    //高性能截取订单ID
+    private static String extractNumbers(String str) {
+        if (str == null || str.isEmpty()) {
+            return "";
+        }
+        int start = 0;
+        int len = str.length();
+        while (start < len && !Character.isDigit(str.charAt(start))) {
+            start++;
+        }
+        return start < len ? str.substring(start) : "";
+    }
+
 
     @Override
     public FsStoreOrderAmountScrmStatsVo selectFsStoreOrderAmountScrmStats(FsStoreOrderAmountScrmStatsQueryDto queryDto) {

+ 2 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -788,7 +788,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
             }
         }
         return R.ok();
-        }
+    }
 
 
     @Override
@@ -890,6 +890,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
         storePayment.setOpenId(user.getMaOpenId());
         storePayment.setUserId(user.getUserId());
         storePayment.setPayMode("hf");//目前微信收款仅支持汇付
+        storePayment.setAppId(param.getAppId());
         fsStorePaymentMapper.insertFsStorePayment(storePayment);
 
         //汇付支付

+ 38 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderCodeOpenIdVo.java

@@ -0,0 +1,38 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class FsStoreOrderCodeOpenIdVo implements Serializable {
+//    /**
+//     * 订单编码
+//     * **/
+//    private String orderCode;
+
+    /**
+     * 订单id
+     * **/
+    private String id;
+
+    /**
+     * 用户openId
+     * **/
+    private String openId;
+
+    /**
+     * 用户手机号
+     * **/
+    private String phone;
+
+    /**
+     * 商品详情信息
+     * **/
+    private String jsonInfo;
+
+    /**
+     * 交易单号
+     * **/
+    private String outTransId;
+}

+ 84 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderDeliveryNoteExportVO.java

@@ -0,0 +1,84 @@
+package com.fs.hisStore.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 发货单导出(商城订单)
+ * **/
+@Data
+public class FsStoreOrderDeliveryNoteExportVO implements Serializable {
+    //系统订单号
+    @Excel(name = "原始单号",width = 20,sort = 1)
+    private String orderNumber;
+
+    @Excel(name = "收件人",width = 10,sort = 2)
+    private String recipient;
+
+    @Excel(name = "收件人手机",width = 20,sort = 3)
+    private String recipientPhone;
+
+    @Excel(name = "收件人电话",width = 20,sort = 4)
+    private String recipientTelephone;
+
+    //具体到某个街道和小区
+    @Excel(name = "收件人详细地址",width = 30,sort = 5)
+    private String recipientAddress;
+
+//    //省市区
+//    @Excel(name = "收货地址",width = 20,sort = 6)
+//    private String receivingAddress;
+
+    //编号和数量:662551*2
+    @Excel(name = "组合编号及数量",width = 20,sort = 7)
+    private String number;
+
+    //名称和数量:商品名称*2
+    @Excel(name = "组合名称及数量",width = 20,sort = 8)
+    private String nameAndNumber;
+
+    @Excel(name = "代收金额",width = 10,sort = 9)
+    private BigDecimal collectionAmount;
+
+    @Excel(name = "物流公司",width = 10,sort = 10)
+    private String logisticsCompany;
+
+    @Excel(name = "物流产品",width = 10,sort = 11)
+    private String logisticsProduct;
+
+    @Excel(name = "物流付款方式",width = 15,sort = 12)
+    private String logisticsPayMethod;
+
+    @Excel(name = "包裹数",width = 10,sort = 13)
+    private Long packageNum;
+
+    @Excel(name = "寄件人",width = 10,sort = 14)
+    private String sender;
+
+    @Excel(name = "寄件人手机",width = 20,sort = 15)
+    private String senderPhone;
+
+    @Excel(name = "寄件人电话",width = 20,sort = 16)
+    private String senderTelephone;
+
+    @Excel(name = "寄件公司",width = 10,sort = 17)
+    private String senderCompany;
+
+    //具体到某个街道和小区
+    @Excel(name = "寄件人详细地址",width = 30,sort = 18)
+    private String senderAddress;
+
+    @Excel(name = "出库仓库",width = 10,sort = 19)
+    private String outboundWarehouse;
+
+    @Excel(name = "订单付款方式",width = 10,sort = 20)
+    private String payMethod;
+
+    @Excel(name = "订单备注",width = 20,sort = 21)
+    private String orderNotes;
+
+    private String keyword;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/live/domain/LiveAutoTask.java

@@ -29,7 +29,7 @@ public class LiveAutoTask extends BaseEntity{
     @Excel(name = "任务名称")
     private String taskName;
 
-    /** 任务类型:1-定时推送卡片商品 2-定时发送红包 3-定时开启互动 */
+    /** 任务类型:1-定时推送卡片商品 2-定时发送红包 3-定时开启互动  4-抽奖 5-优惠券 6-自动上下架*/
     @Excel(name = "任务类型:1-定时推送卡片商品 2-定时发送红包 3-定时开启互动 4-抽奖")
     private Long taskType;
 

+ 3 - 1
fs-service/src/main/java/com/fs/live/domain/LiveOrder.java

@@ -1,5 +1,6 @@
 package com.fs.live.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
@@ -344,6 +345,7 @@ public class LiveOrder extends BaseEntity {
 
     private Integer price;
     private Integer cost;
-
+    @TableField(exist = false)
+    private String bankTransactionId;
 
 }

+ 4 - 1
fs-service/src/main/java/com/fs/live/dto/LiveOrderDeliveryNoteDTO.java

@@ -16,9 +16,12 @@ public class LiveOrderDeliveryNoteDTO {
     @Excel(name = "系统订单号(必填)",width = 20,sort = 1)
     private String orderNumber;
 
-    @Excel(name = "物流公司编号(必填)(SF:顺丰、EMS:邮政、ZTO:中通、JD:京东、DBL:德邦)",width = 30,sort = 2)
+//    @Excel(name = "物流公司编号(必填)(SF:顺丰、EMS:邮政、ZTO:中通、JD:京东、DBL:德邦)",width = 30,sort = 2)
     private String deliverySn;
 
+    @Excel(name = "物流公司(必填)(SF:顺丰、EMS:邮政、ZTO:中通、JD:京东、DBL:德邦)",width = 10,sort = 10)
+    private String logisticsCompany;
+
     private String deliveryName;
 
     @Excel(name = "快递单号(必填)",width = 20,sort = 3)

+ 28 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -133,10 +133,16 @@ public interface LiveMapper
     @Update("update live set global_visible = #{status} where live_id = #{liveId}")
     void updateGlobalVisible(@Param("liveId")Long liveId,@Param("status") Integer status);
 
-    @Select("select * from live where company_id = #{companyId} and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1")
+    @Select({"<script>" +
+            "select * from live where 1=1 " +
+            " <if test='companyId!=null' > and company_id = #{companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
+            " </script>"})
     List<Live> listLiveData(@Param("companyId")Long companyId);
 
-    @Select("select count(1) from live where company_id = #{companyId} and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1")
+    @Select({"<script>" +
+            "select count(1) from live where 1=1 " +
+            " <if test='companyId!=null' > and company_id = #{companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
+            " </script>"})
     int listLiveDataCount(@Param("companyId") Long companyId);
 
 
@@ -144,4 +150,24 @@ public interface LiveMapper
 
     List<Live> selectLiveShowReadyStartLiveList(@Param("companyIds") List<Long> companyIds);
 
+    @Select("select * from live where is_audit = 1 and is_del = 0 and status in (1,2,4) and live_type in (2,3) order by create_time desc")
+    List<Live> liveListAll();
+
+    /**
+     * 查询直播间是直播还是回放状态
+     * 判断标准:当前直播间开始时间 + 视频类型为1的视频时长,如果大于当前时间,返回1,否则返回0
+     * @param liveId 直播间ID
+     * @return 1表示直播中,0表示回放中
+     */
+    @Select("SELECT CASE " +
+            "WHEN l.start_time IS NOT NULL AND " +
+            "     (l.start_time + INTERVAL COALESCE(SUM(CASE WHEN lv.video_type = 1 THEN lv.duration ELSE 0 END), 0) SECOND) > NOW() " +
+            "THEN 1 " +
+            "ELSE 0 " +
+            "END AS liveFlag " +
+            "FROM live l " +
+            "LEFT JOIN live_video lv ON l.live_id = lv.live_id AND lv.video_type = 1 " +
+            "WHERE l.live_id = #{liveId} " +
+            "GROUP BY l.live_id, l.start_time")
+    Integer selectLiveFlagByLiveId(@Param("liveId") Long liveId);
 }

+ 21 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java

@@ -2,6 +2,7 @@ package com.fs.live.mapper;
 
 
 import com.fs.live.domain.LiveOrder;
+import com.fs.live.dto.LiveOrderDeliveryNoteDTO;
 import com.fs.live.param.FsMyLiveOrderQueryParam;
 import com.fs.live.param.LiveOrderParam;
 import com.fs.live.vo.*;
@@ -109,7 +110,7 @@ public interface LiveOrderMapper {
     @Select("select order_id from live_order where `status` = 2")
     List<Long> selectSyncExpressIds();
 
-    @Select("select order_id from live_order where `status` = 2 and extend_order_id is null ")
+    @Select("select order_id from live_order where `status` = 1 and extend_order_id is null ")
     List<Long> selectOrderIdByNoErp();
 
     @Select("select * from live_order where extend_order_id is not null and `status`=2")
@@ -417,7 +418,7 @@ public interface LiveOrderMapper {
 
     List<LiveOrderVoZm> selectLiveOrderListZm(LiveOrder liveOrder);
 
-    @Select(" SELECT * from live_order WHERE item_json is NULL ORDER BY id DESC  LIMIT 30")
+    @Select(" SELECT * from live_order WHERE item_json is NULL ORDER BY create_time DESC  LIMIT 30")
     List<LiveOrder> selectLiveOrderItemJson();
 
     @Update(" UPDATE live_order SET item_json=#{itemJson} WHERE order_id=#{orderId}")
@@ -425,4 +426,22 @@ public interface LiveOrderMapper {
 
     @Select("select * from live_order where  `status`=2 and (extend_order_id is not null and extend_order_id != '') and (delivery_id is not null and delivery_id != '')")
     List<LiveOrder> selectShippedOrder();
+
+    List<LiveOrder> getUnsettledOrder();
+
+
+    List<LiveOrderDeliveryNoteExportVO> getDeliveryNote(@Param("maps") LiveOrderParam param);
+
+    /**
+     * 根据订单号批量查询订单信息(包含openId和transactionId)
+     * @param list 订单号列表
+     * @return 订单信息列表
+     */
+    List<LiveOrderCodeOpenIdVo> selectLiveOrderCodeOpenIdInOrderCode(@Param("list") List<String> list);
+
+    /**
+     * 批量更新订单物流信息
+     * @param dtoList 物流信息列表
+     */
+    void batchUpdateInOrderCode(@Param("list") List<LiveOrderDeliveryNoteDTO> dtoList);
 }

+ 16 - 10
fs-service/src/main/java/com/fs/live/mapper/LiveWatchUserMapper.java

@@ -87,25 +87,31 @@ public interface LiveWatchUserMapper {
 
     /**
      * 根据直播间ID和用户ID查询观看用户信息
-     * @param liveId    直播间ID
-     * @param userId    观看用户ID
+     * @param params    参数Map,包含liveId、userId、liveFlag、replayFlag
      * @return LiveWatchUserVO
      */
-    LiveWatchUserVO selectWatchUserByLiveIdAndUserId(@Param("liveId") Long liveId, @Param("userId") Long userId);
+    LiveWatchUserVO selectWatchUserByLiveIdAndUserId(@Param("params") Map<String, Object> params);
 
 
 
     List<LiveWatchUserVO> selectOnlineUserList(LiveWatchUser param);
 
-    List<LiveWatchUser> checkOnlineNoRewardUser(@Param("liveId") Long liveId,@Param("now") Date now);
+    List<LiveWatchUser> checkOnlineNoRewardUser(@Param("params") Map<String, Object> params);
 
-    @Select("select a.*,fu.nickname as nick_name from (select lws.* from live_watch_user lws where live_id=#{liveId} and online = 0 and " +
-            "user_id in (select user_id from live_lottery_registration where live_id = #{liveId} and lottery_id=#{lotteryId} and registration_id >= " +
-            "(SELECT FLOOR(RAND() * (SELECT MAX(registration_id) FROM live_lottery_registration)))) ) a left join fs_user fu on fu.user_id = a.user_id")
-    List<LiveWatchUser> selectLiveWatchAndRegisterUser(@Param("liveId") Long liveId,@Param("lotteryId") Long lotteryId);
+    List<LiveWatchUser> selectLiveWatchAndRegisterUser(@Param("params") Map<String, Object> params);
 
-    @Select("select * from live_watch_user where live_id = #{liveId} and user_id = #{userId}")
-    LiveWatchUser selectUserByLiveIdAndUserId(@Param("liveId") long liveId,@Param("userId")  long userId);
+    List<LiveWatchUser> selectUserByLiveIdAndUserId(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据唯一索引查询:live_id, user_id, live_flag, replay_flag
+     */
+    LiveWatchUser selectByUniqueIndex(@Param("liveId") Long liveId, @Param("userId") Long userId,
+                                      @Param("liveFlag") Integer liveFlag, @Param("replayFlag") Integer replayFlag);
+
+    /**
+     * 根据唯一索引更新或插入(ON DUPLICATE KEY UPDATE)
+     */
+    int insertOrUpdateByUniqueIndex(LiveWatchUser liveWatchUser);
 
     @Select("SELECT " +
             "    SUM(CASE WHEN lwu.online = 0 and lwu.msg_status = 0 THEN 1 ELSE 0 END) AS online, " +

+ 12 - 11
fs-service/src/main/java/com/fs/live/param/LiveDataParam.java

@@ -1,6 +1,7 @@
 package com.fs.live.param;
 
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
@@ -19,31 +20,31 @@ public class LiveDataParam {
 
     private Long liveId;
 
-   /* *//** 直播名称 *//*
+
     @Excel(name = "直播名称")
     private String liveName;
 
-    *//** 直播封面 *//*
+
     @Excel(name = "直播封面")
     private String liveImgUrl;
 
-    *//** 1待直播 2直播中 3已结束 *//*
+
     @Excel(name = "1待直播 2直播中 3已结束")
     private Integer status;
 
-    *//** 开始时间 *//*
-    @JsonFormat(pattern = "yyyy-MM-dd")
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date startTime;
 
-    *//** 结束时间 *//*
-    @JsonFormat(pattern = "yyyy-MM-dd")
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date finishTime;
 
-    *//** 直播地址 *//*
+
     @Excel(name = "直播地址")
-    private String rtmpUrl;*/
+    private String rtmpUrl;
 
     /** 浏览量 */
     @Excel(name = "浏览量")
@@ -84,9 +85,9 @@ public class LiveDataParam {
 
     /** 观看时长 */
     private Integer watchDuration;
-    /** 开始时间 */
-    private Date startTime;
     /** 结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date endTime;
 
 

+ 1 - 1
fs-service/src/main/java/com/fs/live/service/ILiveAutoTaskService.java

@@ -53,7 +53,7 @@ public interface ILiveAutoTaskService {
      * @param liveAutoTask 直播间自动化任务配置
      * @return 结果
      */
-    int updateLiveAutoTask(LiveAutoTask liveAutoTask);
+    R updateLiveAutoTask(LiveAutoTask liveAutoTask);
 
     /**
      * 批量删除直播间自动化任务配置

+ 17 - 0
fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java

@@ -9,8 +9,10 @@ import com.fs.hisStore.vo.FsStoreOrderVO;
 import com.fs.live.domain.LiveAfterSales;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.dto.LiveOrderComputeDTO;
+import com.fs.live.dto.LiveOrderDeliveryNoteDTO;
 import com.fs.live.param.*;
 import com.fs.live.vo.*;
+import com.fs.live.vo.LiveOrderDeliveryNoteExportVO;
 import com.fs.store.domain.FsStoreDelivers;
 import org.springframework.web.bind.annotation.RequestBody;
 
@@ -238,4 +240,19 @@ public interface ILiveOrderService {
     R handleLiveOrderPay(LiveOrderPayParam param);
 
     List<LiveOrder> selectLiveOrderItemJson();
+
+    void refreshOrderSettlementStatus();
+
+    /**
+     * 批量导入更新直播订单发货状态
+     * @param dtoList 订单数据
+     * **/
+    R importDeliveryNoteExpress(List<LiveOrderDeliveryNoteDTO> dtoList,String appId);
+
+    /**
+     * 获取发货单数据
+     * @param param 查询条件
+     * @return list
+     * **/
+    List<LiveOrderDeliveryNoteExportVO> getDeliveryNote(LiveOrderParam param);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/live/service/ILiveService.java

@@ -1,6 +1,7 @@
 package com.fs.live.service;
 
 
+import com.fs.common.core.page.PageRequest;
 import com.fs.live.vo.LiveVo;
 import com.fs.common.core.domain.R;
 import com.fs.live.domain.Live;
@@ -189,4 +190,6 @@ public interface ILiveService
     void updateGlobalVisible(long liveId, Integer status);
 
     String getGotoWxAppLiveLink(String linkStr, String appid);
+
+    R liveListAll(PageRequest pageRequest);
 }

+ 5 - 1
fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java

@@ -73,7 +73,9 @@ public interface ILiveWatchUserService {
      */
     int deleteLiveWatchUserById(Long id);
 
-    LiveWatchUser getByLiveIdAndUserId(long liveId, long userId);
+    List<LiveWatchUser> getByLiveIdAndUserId(long liveId, long userId);
+
+    Map<String, Integer> getLiveFlagWithCache(Long liveId);
 
     LiveWatchUser join(long liveId, long userId, String location);
     LiveWatchUser joinWithoutLocation(long liveId, long userId);
@@ -121,4 +123,6 @@ public interface ILiveWatchUserService {
     void updateGlobalVisible(long liveId, int i);
 
     void updateSingleVisible(long liveId, Integer status,long userId);
+
+    LiveWatchUser selectLiveWatchUserByFlag(Long liveId, Long userId, Integer liveFlag, Integer replayFlag);
 }

+ 26 - 21
fs-service/src/main/java/com/fs/live/service/impl/LiveAutoTaskServiceImpl.java

@@ -22,6 +22,8 @@ import com.fs.live.service.ILiveGoodsService;
 import com.fs.live.vo.LiveGoodsVo;
 import com.fs.live.vo.LiveLotteryProductListVo;
 import org.checkerframework.checker.units.qual.A;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -34,6 +36,7 @@ import org.springframework.stereotype.Service;
 @Service
 public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
 
+    private static final Logger log = LoggerFactory.getLogger(LiveAutoTaskServiceImpl.class);
     @Autowired
     private LiveMapper liveMapper;
     @Autowired
@@ -249,59 +252,59 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
      * @return 结果
      */
     @Override
-    public int updateLiveAutoTask(LiveAutoTask liveAutoTask)
+    public R updateLiveAutoTask(LiveAutoTask liveAutoTask)
     {
         LiveAutoTask existTask = baseMapper.selectLiveAutoTaskById(liveAutoTask.getId());
         redisCache.redisTemplate.opsForZSet().removeRangeByScore("live:auto_task:" + existTask.getLiveId(), existTask.getAbsValue().getTime(), existTask.getAbsValue().getTime());
         if (liveAutoTask.getTaskType() == 1L) {
             // 商品
             LiveGoodsVo liveGoodsVo = goodsService.selectLiveGoodsVoByGoodsId(Long.valueOf(liveAutoTask.getContent()));
-            if(liveGoodsVo == null) return -1;
+            if(liveGoodsVo == null) return R.error("商品不存在");
             liveAutoTask.setContent(JSON.toJSONString(liveGoodsVo));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+             baseMapper.updateLiveAutoTask(liveAutoTask);
         }else if (liveAutoTask.getTaskType() == 2L) {
             // 红包
             LiveRedConf liveRedConf = liveRedConfMapper.selectLiveRedConfByRedId(Long.valueOf(liveAutoTask.getContent()));
-            if(liveRedConf == null) return -1;
-            if(liveRedConf.getRedStatus() != 0L) return -1;
+            if(liveRedConf == null) return R.error("红包不存在!");
+            if(liveRedConf.getRedStatus() != 0L) return R.error("红包已结束!");
             liveAutoTask.setContent(JSON.toJSONString(liveRedConf));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         }else if (liveAutoTask.getTaskType() == 4L) {
             // 开启抽奖
             LiveLotteryConf liveLotteryConf = liveLotteryConfMapper.selectLiveLotteryConfByLotteryId(Long.valueOf(liveAutoTask.getContent()));
-            if(liveLotteryConf == null) return -1;
-            if(!"0".equals(liveLotteryConf.getLotteryStatus())) return -1;
+            if(liveLotteryConf == null) return R.error("抽奖不存在!");
+            if(!"0".equals(liveLotteryConf.getLotteryStatus())) return R.error("抽奖未开始!");
             List<LiveLotteryProduct> prizes = lotteryProductConfMapper.selectLiveLotteryProductConfByLotteryId(liveLotteryConf.getLotteryId());
             if (prizes == null || prizes.isEmpty()) {
-                return -1;
+                return R.error("请先添加奖品");
             }
             liveAutoTask.setContent(JSON.toJSONString(liveLotteryConf));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         } else if(liveAutoTask.getTaskType() == 3L){
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         } else if( liveAutoTask.getTaskType() == 5L){
             // 自动优惠券
             LiveCoupon liveCoupon = liveCouponMapper.selectLiveCouponById(Long.valueOf(liveAutoTask.getContent()));
-            if(liveCoupon == null) return -1;
+            if(liveCoupon == null) return R.error("优惠券不存在!");
             LiveCouponIssue liveCouponIssue = liveCouponIssueMapper.selectLiveCouponIssueByCouponId(liveCoupon.getCouponId());
-            if(liveCouponIssue == null)return -1;
+            if(liveCouponIssue == null)return R.error("未发布优惠券!");
             LiveCouponIssueRelation liveCouponIssueRelation = liveCouponMapper.selectCouponRelation(liveAutoTask.getLiveId(),liveCouponIssue.getId());
-            if(liveCouponIssueRelation == null) return -1;
-            if(ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) return -1;
+            if(liveCouponIssueRelation == null) return R.error("未绑定商品,无法发布!");
+            if(ObjectUtil.isEmpty(liveCouponIssueRelation.getGoodsId())) return R.error("未绑定商品,无法发布!");
             liveCoupon.setGoodsId(liveCouponIssueRelation.getGoodsId());
             liveAutoTask.setContent(JSON.toJSONString(liveCoupon));
-            return baseMapper.updateLiveAutoTask(liveAutoTask);
+            baseMapper.updateLiveAutoTask(liveAutoTask);
         } else if (liveAutoTask.getTaskType() == 6L) {
             // 上架/下架商品
             try {
                 com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(liveAutoTask.getContent());
                 Long goodsId = jsonObject.getLong("goodsId");
                 Integer status = jsonObject.getInteger("status");
-                if (goodsId == null || status == null) return -1;
+                if (goodsId == null || status == null) return R.error("商品ID或状态为空");
 
                 // 查询商品信息
                 LiveGoodsVo liveGoodsVo = goodsService.selectLiveGoodsVoByGoodsId(goodsId);
-                if(liveGoodsVo == null) return -1;
+                if(liveGoodsVo == null) return R.error("商品不存在");
 
                 // 保存商品信息和上下架状态
                 com.alibaba.fastjson.JSONObject content = new com.alibaba.fastjson.JSONObject();
@@ -310,13 +313,15 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
                 content.put("productName", liveGoodsVo.getProductName());
                 content.put("status", status);
                 liveAutoTask.setContent(content.toJSONString());
-                return baseMapper.updateLiveAutoTask(liveAutoTask);
+                 baseMapper.updateLiveAutoTask(liveAutoTask);
             } catch (Exception e) {
-                return -1;
+                log.error("上架/下架商品自动化任务,更新异常!" + e.getMessage());
+                return R.error("上架/下架商品自动化任务,更新异常!");
             }
         } else {
-            return -1;
+            return R.error("任务类型错误");
         }
+        return R.ok("更新成功");
     }
 
     /**

+ 297 - 3
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -7,12 +7,16 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*;
+import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoGetResponse;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.util.IdUtil;
@@ -45,7 +49,10 @@ import com.fs.company.service.ICompanyDeptService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.config.cloud.CloudHostProper;
+import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.utils.OrderCodeUtils;
+import com.fs.course.dto.FsOrderDeliveryNoteDTO;
+import com.fs.course.dto.OrderOpenIdTransDTO;
 import com.fs.erp.domain.*;
 import com.fs.erp.dto.*;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
@@ -70,9 +77,7 @@ import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import com.fs.hisStore.mapper.FsUserScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
-import com.fs.hisStore.vo.FsMyStoreOrderListQueryVO;
-import com.fs.hisStore.vo.FsStoreOrderItemVO;
-import com.fs.hisStore.vo.FsStoreOrderVO;
+import com.fs.hisStore.vo.*;
 import com.fs.huifuPay.domain.HuiFuCreateOrder;
 import com.fs.huifuPay.domain.HuiFuRefundResult;
 import com.fs.huifuPay.domain.HuifuCreateOrderResult;
@@ -81,6 +86,7 @@ import com.fs.huifuPay.service.HuiFuService;
 import com.fs.live.domain.*;
 import com.fs.live.dto.LiveOrderComputeDTO;
 import com.fs.live.dto.LiveOrderCustomerExportDTO;
+import com.fs.live.dto.LiveOrderDeliveryNoteDTO;
 import com.fs.live.dto.LiveOrderItemDTO;
 import com.fs.live.mapper.*;
 import com.fs.live.param.*;
@@ -88,6 +94,8 @@ import com.fs.live.service.*;
 import com.fs.live.vo.*;
 import com.fs.store.domain.*;
 import com.fs.system.service.ISysConfigService;
+import com.fs.wx.order.domain.FsWxExpressTask;
+import com.fs.wx.order.mapper.FsWxExpressTaskMapper;
 import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
@@ -97,6 +105,7 @@ import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
 import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang.ObjectUtils;
 import org.apache.http.util.Asserts;
@@ -263,6 +272,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Autowired
     private LiveAfterSalesMapper liveAfterSalesMapper;
 
+    @Autowired
+    private FsWxExpressTaskMapper fsWxExpressTaskMapper;
+
 
     //ERP 类型到服务的映射
     private Map<Integer, IErpOrderService> erpServiceMap;
@@ -674,6 +686,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
             order.setStatus(OrderInfoEnum.STATUS_1.getValue());
             order.setPayTime(LocalDateTime.now());
+            order.setIsPay("1");
             baseMapper.updateLiveOrder(order);
             return "SUCCESS";
         }catch (Exception e){
@@ -1507,7 +1520,13 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if (!StringUtils.isEmpty(order.getExtendOrderId()) && order.getStatus() != 1) {
             return R.error("订单:" + order.getOrderCode() + ",已推送erp/订单状态不正确");
         }
+        if(order.getUserName() == null || StringUtils.isEmpty(order.getUserName())) return R.error("用户信息为空");
         ErpOrder erpOrder = getErpOrder(order);
+        if(erpOrder == null ){
+            log.info("组合码为空,订单ID:" + order.getOrderCode());
+            return R.error("组合码为空");
+        }
+
         if (erpOrderService == jSTOrderService) {
             erpOrder.setShop_code(erpConfig.getErpJstShopCode());
         }
@@ -1616,6 +1635,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         for (LiveOrderItem orderItem : orderItems) {
             FsStoreProduct cartDTO = JSONUtil.toBean(orderItem.getJsonInfo(), FsStoreProduct.class);
             ErpOrderItem item = new ErpOrderItem();
+            if(cartDTO.getBarCode() == null) return null;
             item.setItem_code(cartDTO.getBarCode() == null ? "" : cartDTO.getBarCode().trim());
             item.setPrice(cartDTO.getPrice() == null ? "" : cartDTO.getPrice().toString());
             // todo yhq 需要检查
@@ -1691,6 +1711,20 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                     .templateType(TemplateListenEnum.TYPE_2.getValue())
                     .build();
             publisher.publishEvent(new TemplateEvent(this, templateBean));
+
+            LiveOrderPayment fsStorePayment  = liveOrderPaymentMapper.selectLiveOrderLatestPayByOrderId(order.getOrderId());
+            FsWxExpressTask fsWxExpressTask = new FsWxExpressTask();
+            fsWxExpressTask.setUserId(Long.valueOf(order.getUserId()));
+            fsWxExpressTask.setStatus(0);
+            fsWxExpressTask.setRetryCount(0);
+            fsWxExpressTask.setType(1);
+            fsWxExpressTask.setCreateTime(LocalDateTime.now());
+            fsWxExpressTask.setUpdateTime(LocalDateTime.now());
+            fsWxExpressTask.setOrderCode(order.getOrderCode());
+            fsWxExpressTask.setExpressCompany(express.getCode());
+            fsWxExpressTask.setExpressNo(deliveryId);
+            fsWxExpressTask.setAppid(fsStorePayment.getAppId());
+            fsWxExpressTaskMapper.insert(fsWxExpressTask);
         }
     }
 
@@ -2820,6 +2854,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 storePayment.setOpenId(user.getRealName());
                 storePayment.setUserId(user.getUserId());
                 storePayment.setBusinessId(String.valueOf(order.getOrderId()));
+                storePayment.setAppId(fsPayConfig.getAppId() == null ? "" : fsPayConfig.getAppId());
                 liveOrderPaymentMapper.insertLiveOrderPayment(storePayment);
 
                 if (fsPayConfig.getType().equals("hf")){
@@ -2896,6 +2931,263 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         return baseMapper.selectLiveOrderItemJson();
     }
 
+    @Override
+    @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED)
+    public void refreshOrderSettlementStatus() {
+        try {
+            // 判断是否对接微信发货
+            String json = configService.selectConfigByKey("store.config");
+            StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+            log.info("进入微信结算订单定时任务--------------->{}", "start");
+            if (config != null && config.getIsWeChatShipping() != null && config.getIsWeChatShipping()) {
+                // 获取未结算订单
+                List<LiveOrder> orderScrmList = liveOrderMapper.getUnsettledOrder();
+
+                String payConfig = configService.selectConfigByKey("store.pay");
+                JSONObject js = JSON.parseObject(payConfig);
+                String appId = js.getString("appId");
+
+                if (ObjectUtil.isNotNull(appId) && !appId.isEmpty()) {
+                    final WxMaService wxService = WxMaConfiguration.getMaService(appId);
+
+                    if (!orderScrmList.isEmpty()) {
+                        for (LiveOrder order : orderScrmList) {
+                            WxMaOrderShippingInfoGetRequest request = new WxMaOrderShippingInfoGetRequest();
+                            request.setTransactionId(order.getBankTransactionId());
+                            WxMaOrderShippingInfoGetResponse response;
+
+                            try {
+                                response = wxService.getWxMaOrderShippingService().get(request);
+
+                                if (response.getErrCode().equals(0)) {
+                                    // 订单状态枚举:(1) 待发货;(2) 已发货;(3) 确认收货;(4) 交易完成;(5) 已退款
+                                    if (response.getOrder().getOrderState().equals(3) || response.getOrder().getOrderState().equals(4)) {
+                                        if (order.getStatus() == OrderInfoEnum.STATUS_2.getValue()) {
+                                            this.finishOrder(order.getOrderId());
+                                        }
+                                    }
+                                    log.info("请求信息------------------------》{}", response);
+                                }
+
+                            } catch (WxErrorException e) {
+                                log.info("异常信息------------------------》{}", e.getMessage());
+                                continue;
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.getStackTrace();
+        } finally {
+            log.info("进入微信结算订单定时任务--------------->{}", "end");
+        }
+    }
+
+
+    @Override
+    public R importDeliveryNoteExpress(List<LiveOrderDeliveryNoteDTO> dtoList,String appId) {
+        try {
+            StringBuilder builder = new StringBuilder();
+            //获取商城配置
+            String json = configService.selectConfigByKey("store.config");
+            StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+
+            List<LiveOrderDeliveryNoteDTO> successList = new ArrayList<>(dtoList.size());
+            //提前获取所有必要数据
+            Map<String, String> expressDeliveryMap = buildExpressDeliveryMap();
+            //提取所有有效订单号
+            List<String> orderCodeList = new ArrayList<>(dtoList.size());
+            for (int i = 0; i < dtoList.size(); i++) {
+                LiveOrderDeliveryNoteDTO dto = dtoList.get(i);
+                if (StringUtils.isEmpty(dto.getOrderNumber())) {
+                    builder.append("数据第").append(i + 2).append("行系统订单为空!").append(System.lineSeparator());
+                }
+                if (StringUtils.isEmpty(dto.getLogisticsCompany())) {
+                    builder.append("数据第").append(i + 2).append("行物流公司为空!").append(System.lineSeparator());
+                }
+                if (StringUtils.isEmpty(dto.getDeliveryId())) {
+                    builder.append("数据第").append(i + 2).append("行快递单号为空!").append(System.lineSeparator());
+                } else {
+                    //处理订单ID信息
+                    String originalOrderNumber = dto.getOrderNumber();
+                    String processedOrderNumber = extractNumbers(originalOrderNumber);
+                    dto.setOrderNumber(processedOrderNumber);
+                    orderCodeList.add(dto.getOrderNumber());
+                }
+            }
+
+            //批量查询订单信息
+            if (orderCodeList.isEmpty()) {
+                return R.ok(builder.toString());
+            }
+            List<LiveOrderCodeOpenIdVo> orderCodeOpenIdVoList = baseMapper.selectLiveOrderCodeOpenIdInOrderCode(orderCodeList);
+            Map<String, OrderOpenIdTransDTO> orderMap = new HashMap<>(orderCodeOpenIdVoList.size());
+            Map<String, List<LiveOrderCodeOpenIdVo>> orderDetailsMap = new HashMap<>(orderCodeOpenIdVoList.size());
+
+            for (LiveOrderCodeOpenIdVo vo : orderCodeOpenIdVoList) {
+                orderMap.computeIfAbsent(vo.getId(), k -> {
+                    OrderOpenIdTransDTO dto = new OrderOpenIdTransDTO();
+                    dto.setOpenId(vo.getOpenId());
+                    dto.setTransactionId(vo.getOutTransId());
+                    return dto;
+                });
+
+                orderDetailsMap
+                        .computeIfAbsent(vo.getId(), k -> new ArrayList<>())
+                        .add(vo);
+            }
+            final WxMaService wxService = WxMaConfiguration.getMaService(appId);
+            String uploadTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))
+                    .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+            for (int i = 0; i < dtoList.size(); i++) {
+                LiveOrderDeliveryNoteDTO dto = dtoList.get(i);
+                int rowNum = i + 2;
+                if (StringUtils.isEmpty(dto.getOrderNumber())) {
+                    continue;
+                }
+                if (StringUtils.isEmpty(dto.getDeliveryId())) {
+                    builder.append("数据第").append(rowNum).append("行快递单号为空!")
+                            .append(System.lineSeparator());
+                    continue;
+                }
+
+                if (StringUtils.isEmpty(dto.getLogisticsCompany())) {
+                    builder.append("数据第").append(rowNum).append("行快递公司编号为空!")
+                            .append(System.lineSeparator());
+                    continue;
+                }
+                // 验证快递公司
+                String deliverySn = expressDeliveryMap.get(dto.getLogisticsCompany());
+                if (deliverySn == null) {
+                    builder.append("数据第").append(rowNum).append("行订单号为")
+                            .append(dto.getOrderNumber()).append("物流公司名称异常")
+                            .append(System.lineSeparator());
+                    continue;
+                }
+                dto.setDeliverySn(deliverySn);
+
+                // 检查订单是否存在
+                String orderNumber = dto.getOrderNumber();
+                OrderOpenIdTransDTO orderInfo = orderMap.get(orderNumber);
+                if (orderInfo == null) {
+                    builder.append("数据第").append(rowNum).append("行订单号")
+                            .append(orderNumber).append("不存在").append(System.lineSeparator());
+                    continue;
+                }
+                //验证是否开启微信发货
+                if (config.getIsWeChatShipping() != null && config.getIsWeChatShipping()) {
+                    // 上传物流信息到微信
+                    List<LiveOrderCodeOpenIdVo> orderDetails = orderDetailsMap.get(orderNumber);
+                    if (uploadShippingInfoToWechat(wxService, orderInfo, orderDetails, dto, uploadTime)) {
+                        successList.add(dto);
+                    } else {
+                        builder.append("数据第").append(rowNum).append("行订单号为")
+                                .append(orderNumber).append("上传微信失败").append(System.lineSeparator());
+                    }
+                } else {
+                    successList.add(dto);
+                }
+            }
+
+            //批量更新数据
+            if (!successList.isEmpty()) {
+                batchUpdateDeliveryNotes(successList);
+            }
+
+            return R.ok(builder.toString().equals("") ? "操作成功!" : builder.toString());
+        } catch (Exception e) {
+            log.error("导入发货单快递信息失败", e);
+            return R.error("导入失败:" + e.getMessage());
+        }
+    }
+
+    private void batchUpdateDeliveryNotes(List<LiveOrderDeliveryNoteDTO> list) {
+        int batchSize = 500;
+        int total = list.size();
+        int batches = (total + batchSize - 1) / batchSize;
+        for (int i = 0; i < batches; i++) {
+            int start = i * batchSize;
+            int end = Math.min(start + batchSize, total);
+            List<LiveOrderDeliveryNoteDTO> subList = list.subList(start, end);
+            baseMapper.batchUpdateInOrderCode(subList);
+        }
+    }
+
+    private boolean uploadShippingInfoToWechat(WxMaService wxService,
+                                               OrderOpenIdTransDTO orderInfo,
+                                               List<LiveOrderCodeOpenIdVo> orderDetails,
+                                               LiveOrderDeliveryNoteDTO dto,
+                                               String uploadTime) {
+        try {
+            WxMaOrderShippingInfoUploadRequest request = new WxMaOrderShippingInfoUploadRequest();
+            OrderKeyBean orderKeyBean = new OrderKeyBean();
+            orderKeyBean.setOrderNumberType(2);
+            orderKeyBean.setTransactionId(orderInfo.getTransactionId());
+            request.setOrderKey(orderKeyBean);
+            request.setDeliveryMode(1);
+            request.setLogisticsType(1);
+            List<ShippingListBean> shippingList = new ArrayList<>(orderDetails.size());
+            ShippingListBean shippingListBean = null;
+            for (LiveOrderCodeOpenIdVo detail : orderDetails) {
+                if (shippingListBean == null) {
+                    shippingListBean = new ShippingListBean();
+                    shippingListBean.setTrackingNo(dto.getDeliveryId());
+                    shippingListBean.setExpressCompany(dto.getDeliverySn());
+                    JSONObject js = JSON.parseObject(detail.getJsonInfo());
+                    shippingListBean.setItemDesc(js.getString("productName"));
+                    ContactBean contactBean = new ContactBean();
+                    contactBean.setReceiverContact(detail.getPhone());
+                    shippingListBean.setContact(contactBean);
+                } else {
+                    break;
+                }
+            }
+            shippingList.add(shippingListBean);
+            request.setShippingList(shippingList);
+            request.setUploadTime(uploadTime);
+            // 设置支付者信息
+            PayerBean payerBean = new PayerBean();
+            payerBean.setOpenid(orderInfo.getOpenId());
+            request.setPayer(payerBean);
+
+            // 上传物流信息
+            return wxService.getWxMaOrderShippingService().upload(request).getErrCode() == 0;
+        } catch (Exception e) {
+            log.error("上传物流信息到微信失败,订单号: {}", dto.getOrderNumber(), e);
+            return false;
+        }
+    }
+
+    public Map<String, String> buildExpressDeliveryMap() {
+        Map<String, String> map = new HashMap<>();
+        map.put("SF", "顺丰");
+        map.put("EMS", "邮政");
+        map.put("ZTO", "中通");
+        map.put("JD", "京东");
+        map.put("DBL", "德邦");
+        return map;
+    }
+
+    //高性能截取订单ID
+    private static String extractNumbers(String str) {
+        if (str == null || str.isEmpty()) {
+            return "";
+        }
+        int start = 0;
+        int len = str.length();
+        while (start < len && !Character.isDigit(str.charAt(start))) {
+            start++;
+        }
+        return start < len ? str.substring(start) : "";
+    }
+
+    @Override
+    public List<LiveOrderDeliveryNoteExportVO> getDeliveryNote(LiveOrderParam param) {
+        return baseMapper.getDeliveryNote(param);
+    }
+
 
     @Override
     @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
@@ -2922,6 +3214,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if(goods == null) return R.error("当前商品不存在");
         if(fsStoreProduct == null) return R.error("店铺已下架商品,购买失败");
         if(fsStoreProduct.getIsShow() == 0 || goods.getStatus() == 0) return R.error("商品已下架,购买失败");
+        if(liveOrder.getTotalNum() == null || StringUtils.isEmpty(liveOrder.getTotalNum())) liveOrder.setTotalNum("1");
+        if(goods.getStock() == null) return R.error("直播间商品库存不足");
         if(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum())) return R.error("抱歉,这款商品已被抢光,暂时无库存~");
 
         // 更改店铺库存

+ 314 - 150
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 
+import com.fs.common.core.page.PageRequest;
 import com.fs.common.exception.base.BaseException;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.core.config.WxMaConfiguration;
@@ -31,6 +32,7 @@ import com.fs.live.utils.ProcessManager;
 import com.fs.live.vo.*;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
+import com.github.pagehelper.PageInfo;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -448,6 +450,25 @@ public class LiveServiceImpl implements ILiveService
         return "";
     }
 
+
+
+    @Override
+    public R liveListAll(PageRequest pageRequest) {
+        int start = (pageRequest.getCurrentPage() - 1) * pageRequest.getPageSize();
+        int end = pageRequest.getCurrentPage() * pageRequest.getPageSize() - 1;
+        List<Live> lives = baseMapper.liveListAll();
+
+        // 对结果进行分页处理
+        List<Live> pageLives = lives.stream()
+                .skip(start)
+                .limit(pageRequest.getPageSize())
+                .collect(Collectors.toList());
+
+        PageInfo<Live> result = new PageInfo<>(pageLives);
+        result.setTotal(lives.size());
+        return R.ok().put("data", result);
+    }
+
     /**
      * 修改直播
      *
@@ -705,7 +726,7 @@ public class LiveServiceImpl implements ILiveService
         if (deleteCount > 0) {
             return R.ok("操作成功");
         }
-        return R.error();
+        return R.error("删除失败");
     }
 
     @Override
@@ -786,24 +807,71 @@ public class LiveServiceImpl implements ILiveService
         if (exist == null) {
             return R.error("直播间不存在");
         }
-        // 复制直播间, 创建 livedata
-        // 身份认证
+        
+        // 1. 复制直播间基础信息
+        Long newLiveId = copyLiveBasicInfo(exist, live, now);
+        
+        // 2. 创建直播数据
+        createLiveData(newLiveId);
+        
+        // 3. 复制直播视频
+        copyLiveVideos(existLiveId, newLiveId, now);
+        
+        // 4. 获取所有自动化任务并按类型分组
+        List<LiveAutoTask> allTasks = liveAutoTaskService.selectLiveAutoTaskByLiveId(existLiveId);
+        Map<Long, List<LiveAutoTask>> tasksByType = allTasks.stream()
+                .collect(Collectors.groupingBy(LiveAutoTask::getTaskType));
+        
+        // 5. 复制弹幕任务(taskType=3)
+        copyBarrageTasks(tasksByType.get(3L), newLiveId, now);
+        
+        // 6. 复制红包配置及任务(taskType=2)
+        Map<Long, Long> redIdMapping = copyRedConfs(existLiveId, newLiveId, tasksByType.get(2L), now);
+        
+        // 7. 复制抽奖配置及任务(taskType=4)
+        Map<Long, Long> lotteryIdMapping = copyLotteryConfs(existLiveId, newLiveId, tasksByType.get(4L), now);
+        
+        // 8. 复制商品、商品任务、上下架任务及优惠券关系
+        Map<Long, Long> goodsIdMapping = copyGoodsAndTasks(existLiveId, newLiveId, live, 
+                tasksByType.get(1L), tasksByType.get(6L), now);
+        
+        // 9. 复制优惠券关系
+        copyCouponRelations(existLiveId, newLiveId, goodsIdMapping);
+        
+        // 10. 复制优惠券自动化任务(taskType=5)
+        copyCouponTasks(tasksByType.get(5L), newLiveId, goodsIdMapping, now);
+
+        return R.ok("复制成功");
+    }
+    
+    /**
+     * 复制直播间基础信息
+     */
+    private Long copyLiveBasicInfo(Live exist, Live live, Date now) {
         Live liveEntity = new Live();
         BeanUtils.copyBeanProp(liveEntity, exist);
         liveEntity.setLiveId(null);
         liveEntity.setCompanyId(live.getCompanyId());
         liveEntity.setCompanyUserId(live.getCompanyUserId());
-        liveEntity.setRtmpUrl( null);
-        liveEntity.setFlvHlsUrl( null);
-        liveEntity.setFinishTime( null);
-        liveEntity.setIsAudit( 0);
-        liveEntity.setStatus( 1);
-        liveEntity.setCreateTime( now);
+        liveEntity.setRtmpUrl(null);
+        liveEntity.setFlvHlsUrl(null);
+        liveEntity.setFinishTime(null);
+        liveEntity.setIsAudit(0);
+        liveEntity.setStatus(1);
+        liveEntity.setCreateTime(now);
         baseMapper.insertLive(liveEntity);
-        if(liveEntity.getLiveId() == null) throw new RuntimeException("插入直播间异常");
-        Long newLiveId = liveEntity.getLiveId();
+        if (liveEntity.getLiveId() == null) {
+            throw new RuntimeException("插入直播间异常");
+        }
+        return liveEntity.getLiveId();
+    }
+    
+    /**
+     * 创建直播数据
+     */
+    private void createLiveData(Long liveId) {
         LiveData liveData = new LiveData();
-        liveData.setLiveId(newLiveId);
+        liveData.setLiveId(liveId);
         liveData.setPageViews(0L);
         liveData.setUniqueVisitors(0L);
         liveData.setTotalViews(0L);
@@ -813,172 +881,268 @@ public class LiveServiceImpl implements ILiveService
         liveData.setFavouriteNum(0L);
         liveData.setFollowNum(0L);
         liveDataService.insertLiveData(liveData);
-        // 优惠券
-        List<LiveCouponIssueRelation> liveCouponIssueRelations = liveCouponIssueMapper.selectRelationByLiveId(existLiveId);
-        Map<Long, LiveCouponIssueRelation> collect = liveCouponIssueRelations.stream().collect(Collectors.toMap(LiveCouponIssueRelation::getGoodsId, Function.identity(), (existing, replacement) -> existing));
-        // 直播视频
+    }
+    
+    /**
+     * 复制直播视频
+     */
+    private void copyLiveVideos(Long existLiveId, Long newLiveId, Date now) {
         List<LiveVideo> liveVideos = liveVideoService.selectLiveVideosByLiveId(existLiveId);
-        if (!liveVideos.isEmpty()) {
-            for (LiveVideo liveVideo : liveVideos) {
-                LiveVideo videoEntity = new LiveVideo();
-                BeanUtils.copyBeanProp(videoEntity, liveVideo);
-                videoEntity.setVideoId(null);
-                videoEntity.setLiveId(newLiveId);
-                videoEntity.setCreateTime(now);
-                liveVideoService.insertLiveVideo(videoEntity);
-            }
+        for (LiveVideo liveVideo : liveVideos) {
+            LiveVideo videoEntity = new LiveVideo();
+            BeanUtils.copyBeanProp(videoEntity, liveVideo);
+            videoEntity.setVideoId(null);
+            videoEntity.setLiveId(newLiveId);
+            videoEntity.setCreateTime(now);
+            liveVideoService.insertLiveVideo(videoEntity);
+        }
+    }
+    
+    /**
+     * 复制弹幕任务(taskType=3)
+     */
+    private void copyBarrageTasks(List<LiveAutoTask> tasks, Long newLiveId, Date now) {
+        if (tasks == null || tasks.isEmpty()) {
+            return;
         }
-
-        // 运营自动化
-        List<LiveAutoTask> liveAutoTasksList = liveAutoTaskService.selectLiveAutoTaskByLiveId(existLiveId);
-        List<LiveAutoTask> barrageTask = liveAutoTasksList.stream().filter(liveAutoTask -> liveAutoTask.getTaskType() == 3L).collect(Collectors.toList());
-        List<LiveAutoTask> goodsTaskList = liveAutoTasksList.stream().filter(liveAutoTask -> liveAutoTask.getTaskType() == 1L).collect(Collectors.toList());
-        List<LiveAutoTask> redTaskList = liveAutoTasksList.stream().filter(liveAutoTask -> liveAutoTask.getTaskType() == 2L).collect(Collectors.toList());
-        List<LiveAutoTask> lotteryTaskList = liveAutoTasksList.stream().filter(liveAutoTask -> liveAutoTask.getTaskType() == 4L).collect(Collectors.toList());
-        List<LiveAutoTask> shelfTaskList = liveAutoTasksList.stream().filter(liveAutoTask -> liveAutoTask.getTaskType() == 6L).collect(Collectors.toList());
         List<LiveAutoTask> addList = new ArrayList<>();
-        if (!barrageTask.isEmpty()) {
-            for (LiveAutoTask liveAutoTask : barrageTask) {
-                LiveAutoTask liveAutoTaskEntity = new LiveAutoTask();
-                BeanUtils.copyBeanProp(liveAutoTaskEntity, liveAutoTask);
-                liveAutoTaskEntity.setId(null);
-                liveAutoTaskEntity.setLiveId(newLiveId);
-                liveAutoTaskEntity.setCreateTime(now);
-                liveAutoTaskEntity.setUpdateTime(now);
-                liveAutoTaskEntity.setFinishStatus(0L);
-                addList.add(liveAutoTaskEntity);
-                if (addList.size() > 100) {
-                    liveAutoTaskService.batchInsertLiveAutoTask(addList);
-                    addList.clear();
-                }
+        for (LiveAutoTask task : tasks) {
+            LiveAutoTask newTask = createAutoTaskEntity(task, newLiveId, now, null);
+            addList.add(newTask);
+            if (addList.size() >= 100) {
+                liveAutoTaskService.batchInsertLiveAutoTask(addList);
+                addList.clear();
             }
         }
         if (!addList.isEmpty()) {
             liveAutoTaskService.batchInsertLiveAutoTask(addList);
-            addList.clear();
         }
-        //直播间红包配置
+    }
+    
+    /**
+     * 复制红包配置及任务
+     */
+    private Map<Long, Long> copyRedConfs(Long existLiveId, Long newLiveId, 
+                                         List<LiveAutoTask> redTasks, Date now) {
+        Map<Long, Long> redIdMapping = new HashMap<>();
+        if (redTasks == null) {
+            redTasks = Collections.emptyList();
+        }
+        Map<Long, LiveAutoTask> redTaskMap = redTasks.stream()
+                .collect(Collectors.toMap(task -> parseIdFromContent(task.getContent(), "redId"), 
+                        Function.identity(), (existing, replacement) -> existing));
+        
         List<LiveRedConf> liveRedConfs = liveRedConfService.selectByLiveId(existLiveId);
-        if (!liveRedConfs.isEmpty()) {
-
-            for (LiveRedConf liveRedConf : liveRedConfs) {
-                LiveRedConf liveRedConfEntity = new LiveRedConf();
-                BeanUtils.copyBeanProp(liveRedConfEntity, liveRedConf);
-                liveRedConfEntity.setRedId(null);
-                liveRedConfEntity.setLiveId(newLiveId);
-                liveRedConfEntity.setRedStatus(0L);
-                liveRedConfEntity.setCreateTime(now);
-                liveRedConfEntity.setTotalSend(0L);
-                liveRedConfService.insertLiveRedConf(liveRedConfEntity);
-                LiveAutoTask liveAutoTask = redTaskList.stream().filter(item -> parseIdFromContent(item.getContent(), "redId").equals(liveRedConf.getRedId())).findFirst().orElse(null);
-                if(liveAutoTask == null) continue;
-                LiveAutoTask liveAutoTaskEntity = new LiveAutoTask();
-                BeanUtils.copyBeanProp(liveAutoTaskEntity, liveAutoTask);
-                liveAutoTaskEntity.setId(null);
-                liveAutoTaskEntity.setLiveId(newLiveId);
-                liveAutoTaskEntity.setCreateTime(now);
-                liveAutoTaskEntity.setUpdateTime(now);
-                liveAutoTaskEntity.setFinishStatus(0L);
-                liveAutoTaskEntity.setContent(JSON.toJSONString(liveRedConfEntity));
-                liveAutoTaskService.directInsertLiveAutoTask(liveAutoTaskEntity);
+        for (LiveRedConf liveRedConf : liveRedConfs) {
+            LiveRedConf newRedConf = new LiveRedConf();
+            BeanUtils.copyBeanProp(newRedConf, liveRedConf);
+            newRedConf.setRedId(null);
+            newRedConf.setLiveId(newLiveId);
+            newRedConf.setRedStatus(0L);
+            newRedConf.setCreateTime(now);
+            newRedConf.setTotalSend(0L);
+            liveRedConfService.insertLiveRedConf(newRedConf);
+            
+            redIdMapping.put(liveRedConf.getRedId(), newRedConf.getRedId());
+            
+            LiveAutoTask task = redTaskMap.get(liveRedConf.getRedId());
+            if (task != null) {
+                LiveAutoTask newTask = createAutoTaskEntity(task, newLiveId, now, 
+                        JSON.toJSONString(newRedConf));
+                liveAutoTaskService.directInsertLiveAutoTask(newTask);
             }
         }
-        // 直播间礼物配置
+        return redIdMapping;
+    }
+    
+    /**
+     * 复制抽奖配置及任务
+     */
+    private Map<Long, Long> copyLotteryConfs(Long existLiveId, Long newLiveId, 
+                                             List<LiveAutoTask> lotteryTasks, Date now) {
+        Map<Long, Long> lotteryIdMapping = new HashMap<>();
+        if (lotteryTasks == null) {
+            lotteryTasks = Collections.emptyList();
+        }
+        Map<Long, LiveAutoTask> lotteryTaskMap = lotteryTasks.stream()
+                .collect(Collectors.toMap(task -> parseIdFromContent(task.getContent(), "lotteryId"), 
+                        Function.identity(), (existing, replacement) -> existing));
+        
         List<LiveLotteryConf> liveLotteryConfs = liveLotteryConfService.selectByLiveId(existLiveId);
         if (!liveLotteryConfs.isEmpty()) {
-
-            List<Long> lotteryIds = liveLotteryConfs.stream().map(LiveLotteryConf::getLotteryId).collect(Collectors.toList());
+            List<Long> lotteryIds = liveLotteryConfs.stream()
+                    .map(LiveLotteryConf::getLotteryId).collect(Collectors.toList());
             List<LiveLotteryProductConf> products = liveLotteryProductConfMapper.selectEntityByIds(lotteryIds);
+            Map<Long, List<LiveLotteryProductConf>> productsByLotteryId = products.stream()
+                    .collect(Collectors.groupingBy(LiveLotteryProductConf::getLotteryId));
+            
             for (LiveLotteryConf liveLotteryConf : liveLotteryConfs) {
-                LiveLotteryConf liveLotteryConfEntity = new LiveLotteryConf();
-                BeanUtils.copyBeanProp(liveLotteryConfEntity, liveLotteryConf);
-                liveLotteryConfEntity.setLotteryId(null);
-                liveLotteryConfEntity.setLiveId(newLiveId);
-                liveLotteryConfEntity.setLotteryStatus(String.valueOf(0));
-                liveLotteryConfEntity.setCreateTime(now);
-                liveLotteryConfEntity.setUpdateTime(now);
-                liveLotteryConfService.insertLiveLotteryConf(liveLotteryConfEntity);
-                products.stream().filter(product -> product.getLotteryId().equals(liveLotteryConf.getLotteryId()))
-                        .forEach(product -> {
-                            product.setId(null);
-                            product.setLotteryId(liveLotteryConfEntity.getLotteryId());
-                            liveLotteryProductConfMapper.insertLiveLotteryProductConf(product);
-                        });
-                LiveAutoTask liveAutoTask = lotteryTaskList.stream().filter(item -> parseIdFromContent(item.getContent(), "lotteryId").equals(liveLotteryConf.getLotteryId())).findFirst().orElse(null);
-                if(liveAutoTask == null) continue;
-                LiveAutoTask liveAutoTaskEntity = new LiveAutoTask();
-                BeanUtils.copyBeanProp(liveAutoTaskEntity, liveAutoTask);
-                liveAutoTaskEntity.setId(null);
-                liveAutoTaskEntity.setLiveId(newLiveId);
-                liveAutoTaskEntity.setCreateTime(now);
-                liveAutoTaskEntity.setUpdateTime(now);
-                liveAutoTaskEntity.setFinishStatus(0L);
-                liveAutoTaskEntity.setContent(JSON.toJSONString(liveLotteryConfEntity));
-                liveAutoTaskService.directInsertLiveAutoTask(liveAutoTaskEntity);
+                LiveLotteryConf newLotteryConf = new LiveLotteryConf();
+                BeanUtils.copyBeanProp(newLotteryConf, liveLotteryConf);
+                newLotteryConf.setLotteryId(null);
+                newLotteryConf.setLiveId(newLiveId);
+                newLotteryConf.setLotteryStatus(String.valueOf(0));
+                newLotteryConf.setCreateTime(now);
+                newLotteryConf.setUpdateTime(now);
+                liveLotteryConfService.insertLiveLotteryConf(newLotteryConf);
+                
+                lotteryIdMapping.put(liveLotteryConf.getLotteryId(), newLotteryConf.getLotteryId());
+                
+                // 复制奖品
+                List<LiveLotteryProductConf> lotteryProducts = productsByLotteryId.get(liveLotteryConf.getLotteryId());
+                if (lotteryProducts != null) {
+                    for (LiveLotteryProductConf product : lotteryProducts) {
+                        LiveLotteryProductConf newProduct = new LiveLotteryProductConf();
+                        BeanUtils.copyBeanProp(newProduct, product);
+                        newProduct.setId(null);
+                        newProduct.setLotteryId(newLotteryConf.getLotteryId());
+                        liveLotteryProductConfMapper.insertLiveLotteryProductConf(newProduct);
+                    }
+                }
+                
+                LiveAutoTask task = lotteryTaskMap.get(liveLotteryConf.getLotteryId());
+                if (task != null) {
+                    LiveAutoTask newTask = createAutoTaskEntity(task, newLiveId, now, 
+                            JSON.toJSONString(newLotteryConf));
+                    liveAutoTaskService.directInsertLiveAutoTask(newTask);
+                }
             }
         }
+        return lotteryIdMapping;
+    }
+    
+    /**
+     * 复制商品、商品任务、上下架任务
+     */
+    private Map<Long, Long> copyGoodsAndTasks(Long existLiveId, Long newLiveId, Live live,
+                                              List<LiveAutoTask> goodsTasks, List<LiveAutoTask> shelfTasks, Date now) {
+        Map<Long, Long> goodsIdMapping = new HashMap<>();
+        if (goodsTasks == null) {
+            goodsTasks = Collections.emptyList();
+        }
+        if (shelfTasks == null) {
+            shelfTasks = Collections.emptyList();
+        }
+        
+        Map<Long, LiveAutoTask> goodsTaskMap = goodsTasks.stream()
+                .collect(Collectors.toMap(task -> parseIdFromContent(task.getContent(), "goodsId"), 
+                        Function.identity(), (existing, replacement) -> existing));
+        Map<Long, LiveAutoTask> shelfTaskMap = shelfTasks.stream()
+                .collect(Collectors.toMap(task -> parseIdFromContent(task.getContent(), "goodsId"), 
+                        Function.identity(), (existing, replacement) -> existing));
+        
         LiveGoods queryParam = new LiveGoods();
         queryParam.setLiveId(existLiveId);
-        // 直播间商品
         List<LiveGoodsVo> goodsList = liveGoodsService.selectProductListByLiveId(queryParam);
+        
         if (!goodsList.isEmpty()) {
-
-            List<Long> goodsProductIds = goodsList.stream().map(item -> item.getProductId()).collect(Collectors.toList());
-            Map<Long, FsStoreProductScrm> goodsMap = fsStoreProductScrmMapper.selectFsStoreProductByProductIds(goodsProductIds).stream().collect(Collectors.toMap(FsStoreProductScrm::getProductId, item -> item));
-
+            List<Long> goodsProductIds = goodsList.stream()
+                    .map(LiveGoodsVo::getProductId).collect(Collectors.toList());
+            Map<Long, FsStoreProductScrm> goodsMap = fsStoreProductScrmMapper
+                    .selectFsStoreProductByProductIds(goodsProductIds).stream()
+                    .collect(Collectors.toMap(FsStoreProductScrm::getProductId, Function.identity()));
+            
             for (LiveGoodsVo liveGoods : goodsList) {
-                LiveGoods liveGoodsEntity = new LiveGoods();
-                BeanUtils.copyBeanProp(liveGoodsEntity, liveGoods);
-                liveGoodsEntity.setGoodsId(null);
-                liveGoodsEntity.setLiveId(newLiveId);
-                liveGoodsEntity.setCreateTime(now);
-                liveGoodsEntity.setIsShow(false);
-                liveGoodsEntity.setCompanyId(live.getCompanyId());
-                liveGoodsEntity.setCompanyUserId(live.getCompanyUserId());
-                liveGoodsService.insertLiveGoods(liveGoodsEntity);
-                // 优惠券
-                if (collect.containsKey(liveGoods.getGoodsId())) {
-                    liveCouponIssueRelations.stream().filter(relation -> relation.getGoodsId().equals(liveGoods.getGoodsId())).findFirst().ifPresent(liveCouponIssueRelation -> liveCouponIssueRelation.setGoodsId(liveGoodsEntity.getGoodsId()));
+                LiveGoods newGoods = new LiveGoods();
+                BeanUtils.copyBeanProp(newGoods, liveGoods);
+                newGoods.setGoodsId(null);
+                newGoods.setLiveId(newLiveId);
+                newGoods.setCreateTime(now);
+                newGoods.setIsShow(false);
+                newGoods.setCompanyId(live.getCompanyId());
+                newGoods.setCompanyUserId(live.getCompanyUserId());
+                newGoods.setStock(goodsMap.containsKey(liveGoods.getProductId()) 
+                        ? goodsMap.get(liveGoods.getProductId()).getStock() : 0);
+                liveGoodsService.insertLiveGoods(newGoods);
+                
+                goodsIdMapping.put(liveGoods.getGoodsId(), newGoods.getGoodsId());
+                
+                // 复制商品推送任务(taskType=1)
+                LiveAutoTask goodsTask = goodsTaskMap.get(liveGoods.getGoodsId());
+                if (goodsTask != null) {
+                    // 使用新的goodsId创建LiveGoodsVo对象
+                    LiveGoodsVo newGoodsVo = new LiveGoodsVo();
+                    BeanUtils.copyBeanProp(newGoodsVo, liveGoods);
+                    newGoodsVo.setGoodsId(newGoods.getGoodsId());
+                    LiveAutoTask newTask = createAutoTaskEntity(goodsTask, newLiveId, now, 
+                            JSON.toJSONString(newGoodsVo));
+                    liveAutoTaskService.directInsertLiveAutoTask(newTask);
                 }
-                LiveAutoTask liveAutoTask = goodsTaskList.stream().filter(item -> parseIdFromContent(item.getContent(), "goodsId").equals(liveGoods.getGoodsId())).findFirst().orElse(null);
-                if(liveAutoTask == null) continue;
-                liveGoods.setGoodsId(liveGoodsEntity.getGoodsId());
-                LiveAutoTask liveAutoTaskEntity = new LiveAutoTask();
-                BeanUtils.copyBeanProp(liveAutoTaskEntity, liveAutoTask);
-                liveAutoTaskEntity.setId(null);
-                liveAutoTaskEntity.setLiveId(newLiveId);
-                liveGoodsEntity.setStock(goodsMap.containsKey(liveGoods.getProductId()) ? goodsMap.get(liveGoods.getProductId()).getStock() : 0);
-                liveAutoTaskEntity.setCreateTime(now);
-                liveAutoTaskEntity.setUpdateTime(now);
-                liveAutoTaskEntity.setFinishStatus(0L);
-                liveAutoTaskEntity.setContent(JSON.toJSONString(liveGoods));
-                liveAutoTaskService.directInsertLiveAutoTask(liveAutoTaskEntity);
-
-                // 处理上下架任务 taskType=6
-                LiveAutoTask shelfTask = shelfTaskList.stream().filter(item -> parseIdFromContent(item.getContent(), "goodsId").equals(liveGoods.getGoodsId())).findFirst().orElse(null);
-                if(shelfTask != null) {
-                    LiveAutoTask shelfAutoTaskEntity = new LiveAutoTask();
-                    BeanUtils.copyBeanProp(shelfAutoTaskEntity, shelfTask);
-                    shelfAutoTaskEntity.setId(null);
-                    shelfAutoTaskEntity.setLiveId(newLiveId);
-                    shelfAutoTaskEntity.setCreateTime(now);
-                    shelfAutoTaskEntity.setUpdateTime(now);
-                    shelfAutoTaskEntity.setFinishStatus(0L);
-                    // 更新content中的goodsId为新的goodsId
+                
+                // 复制上下架任务(taskType=6)
+                LiveAutoTask shelfTask = shelfTaskMap.get(liveGoods.getGoodsId());
+                if (shelfTask != null) {
                     JSONObject contentJson = JSON.parseObject(shelfTask.getContent());
-                    contentJson.put("goodsId", liveGoodsEntity.getGoodsId());
-                    shelfAutoTaskEntity.setContent(contentJson.toJSONString());
-                    liveAutoTaskService.directInsertLiveAutoTask(shelfAutoTaskEntity);
+                    contentJson.put("goodsId", newGoods.getGoodsId());
+                    LiveAutoTask newTask = createAutoTaskEntity(shelfTask, newLiveId, now, 
+                            contentJson.toJSONString());
+                    liveAutoTaskService.directInsertLiveAutoTask(newTask);
+                }
+            }
+        }
+        return goodsIdMapping;
+    }
+    
+    /**
+     * 复制优惠券关系
+     */
+    private void copyCouponRelations(Long existLiveId, Long newLiveId, Map<Long, Long> goodsIdMapping) {
+        List<LiveCouponIssueRelation> relations = liveCouponIssueMapper.selectRelationByLiveId(existLiveId);
+        for (LiveCouponIssueRelation relation : relations) {
+            LiveCouponIssueRelation newRelation = new LiveCouponIssueRelation();
+            BeanUtils.copyBeanProp(newRelation, relation);
+            newRelation.setLiveId(newLiveId);
+            newRelation.setIsShow(0);
+            // 更新goodsId映射
+            if (relation.getGoodsId() != null && goodsIdMapping.containsKey(relation.getGoodsId())) {
+                newRelation.setGoodsId(goodsIdMapping.get(relation.getGoodsId()));
+            }
+            liveCouponIssueMapper.insertLiveCouponIssueRelation(newRelation);
+        }
+    }
+    
+    /**
+     * 复制优惠券自动化任务(taskType=5)
+     */
+    private void copyCouponTasks(List<LiveAutoTask> couponTasks, Long newLiveId, 
+                                 Map<Long, Long> goodsIdMapping, Date now) {
+        if (couponTasks == null || couponTasks.isEmpty()) {
+            return;
+        }
+        for (LiveAutoTask task : couponTasks) {
+            try {
+                LiveCoupon liveCoupon = JSON.parseObject(task.getContent(), LiveCoupon.class);
+                if (liveCoupon != null && liveCoupon.getGoodsId() != null) {
+                    // 更新content中的goodsId为新的goodsId
+                    Long newGoodsId = goodsIdMapping.get(liveCoupon.getGoodsId());
+                    if (newGoodsId != null) {
+                        liveCoupon.setGoodsId(newGoodsId);
+                        LiveAutoTask newTask = createAutoTaskEntity(task, newLiveId, now, 
+                                JSON.toJSONString(liveCoupon));
+                        liveAutoTaskService.directInsertLiveAutoTask(newTask);
+                    }
                 }
+            } catch (Exception e) {
+                log.error("复制优惠券自动化任务失败,taskId: {}", task.getId(), e);
             }
         }
-        for (LiveCouponIssueRelation liveCouponIssueRelation : liveCouponIssueRelations) {
-            liveCouponIssueRelation.setLiveId(newLiveId);
-            liveCouponIssueRelation.setIsShow(0);
-            liveCouponIssueMapper.insertLiveCouponIssueRelation(liveCouponIssueRelation);
+    }
+    
+    /**
+     * 创建自动化任务实体
+     */
+    private LiveAutoTask createAutoTaskEntity(LiveAutoTask source, Long newLiveId, Date now, String content) {
+        LiveAutoTask newTask = new LiveAutoTask();
+        BeanUtils.copyBeanProp(newTask, source);
+        newTask.setId(null);
+        newTask.setLiveId(newLiveId);
+        newTask.setCreateTime(now);
+        newTask.setUpdateTime(now);
+        newTask.setFinishStatus(0L);
+        if (content != null) {
+            newTask.setContent(content);
         }
-
-        return R.ok("复制成功");
+        return newTask;
     }
 
     private Long parseIdFromContent(String content, String key) {

+ 161 - 73
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -12,6 +12,8 @@ import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import com.fs.hisStore.domain.FsUserScrm;
+import com.fs.hisStore.service.IFsUserScrmService;
 import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveVideo;
 import com.fs.live.domain.LiveWatchUser;
@@ -43,7 +45,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     @Autowired
     private RedisCache redisCache;
     @Autowired
-    private IFsUserService fsUserService;
+    private IFsUserScrmService fsUserService;
     @Autowired
     private LiveWatchUserMapper baseMapper;
     @Autowired
@@ -93,6 +95,12 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         } else if ("silenced".equals(liveWatchUser.getTabName())) {
             liveWatchUser.setMsgStatus(1);
         }
+        // 先查缓存,获取liveFlag和replayFlag
+        if (liveWatchUser != null && liveWatchUser.getLiveId() != null) {
+            Map<String, Integer> flagMap = getLiveFlagWithCache(liveWatchUser.getLiveId());
+            liveWatchUser.setLiveFlag(flagMap.get("liveFlag"));
+            liveWatchUser.setReplayFlag(flagMap.get("replayFlag"));
+        }
         return baseMapper.selectLiveWatchUserList(liveWatchUser);
     }
 
@@ -147,44 +155,79 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     }
 
     @Override
-    public LiveWatchUser getByLiveIdAndUserId(long liveId, long userId) {
-        return baseMapper.selectUserByLiveIdAndUserId(liveId, userId);
+    public List<LiveWatchUser> getByLiveIdAndUserId(long liveId, long userId) {
+        // 先查缓存,获取liveFlag和replayFlag
+        Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+        Map<String, Object> params = new HashMap<>();
+        params.put("liveId", liveId);
+        params.put("userId", userId);
+        params.put("liveFlag", flagMap.get("liveFlag"));
+        params.put("replayFlag", flagMap.get("replayFlag"));
+        return baseMapper.selectUserByLiveIdAndUserId(params);
+    }
+
+    /**
+     * 获取直播间的直播/回放状态(带缓存)
+     * @param liveId 直播间ID
+     * @return Map包含liveFlag和replayFlag
+     */
+    @Override
+    public Map<String, Integer> getLiveFlagWithCache(Long liveId) {
+        String cacheKey = String.format(LiveKeysConstant.LIVE_FLAG_CACHE, liveId);
+
+        // 先查缓存
+        Map<String, Integer> cached = redisCache.getCacheObject(cacheKey);
+        if (cached != null && cached.containsKey("liveFlag") && cached.containsKey("replayFlag")) {
+            return cached;
+        }
+
+        // 缓存不存在,查询数据库
+        Integer liveFlag = liveMapper.selectLiveFlagByLiveId(liveId);
+        if (liveFlag == null) {
+            liveFlag = 0;
+        }
+        Integer replayFlag = 1 - liveFlag; // 反数
+
+        // 构建结果
+        Map<String, Integer> result = new HashMap<>();
+        result.put("liveFlag", liveFlag);
+        result.put("replayFlag", replayFlag);
+
+        // 缓存结果,过期时间1分钟
+        redisCache.setCacheObject(cacheKey, result, LiveKeysConstant.LIVE_FLAG_CACHE_EXPIRE, TimeUnit.SECONDS);
+
+        return result;
     }
 
     @Override
     public LiveWatchUser join(long liveId, long userId, String location) {
-        LiveWatchUser liveWatchUser = getByLiveIdAndUserId(liveId, userId);
-        FsUser fsUserVO = fsUserService.selectFsUserByUserId(userId);
+        FsUserScrm fsUserVO = fsUserService.selectFsUserByUserId(userId);
         Date now = DateUtils.getNowDate();
 
         // 查询直播间信息
         Live live = liveMapper.selectLiveByLiveId(liveId);
-
-        // 判断用户进入时间:如果进入时间大于直播结束时间,说明是回放
-        boolean isReplay = false;
-        if (live != null && live.getFinishTime() != null) {
-            Date finishTime = java.sql.Timestamp.valueOf(live.getFinishTime());
-            isReplay = now.after(finishTime);
+        if (live == null) {
+            throw new RuntimeException("直播间不存在");
         }
 
-        if(liveWatchUser != null) {
+        // 获取直播/回放状态(带缓存)
+        Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+        Integer liveFlag = flagMap.get("liveFlag");
+        Integer replayFlag = flagMap.get("replayFlag");
+
+        // 使用唯一索引查询:live_id, user_id, live_flag, replay_flag
+        LiveWatchUser liveWatchUser = baseMapper.selectByUniqueIndex(liveId, userId, liveFlag, replayFlag);
+
+        if (liveWatchUser != null) {
+            // 存在则更新
             liveWatchUser.setUpdateTime(now);
             liveWatchUser.setOnline(0);
-
-            // 更新location
             if (StringUtils.isNotEmpty(location)) {
                 liveWatchUser.setLocation(location);
             }
-
-            // 更新进入标记
-            if (isReplay) {
-                liveWatchUser.setReplayFlag(1);
-            } else {
-                liveWatchUser.setLiveFlag(1);
-            }
-
             baseMapper.updateLiveWatchUser(liveWatchUser);
-        }else{
+        } else {
+            // 不存在则插入
             liveWatchUser = new LiveWatchUser();
             liveWatchUser.setLiveId(liveId);
             liveWatchUser.setUserId(userId);
@@ -192,85 +235,81 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
             liveWatchUser.setMsgStatus(0);
             liveWatchUser.setOnline(0);
             liveWatchUser.setLocation(location);
-
-            // 设置进入标记
-            if (isReplay) {
-                liveWatchUser.setReplayFlag(1);
-                liveWatchUser.setLiveFlag(0);
-            } else {
-                liveWatchUser.setLiveFlag(1);
-                liveWatchUser.setReplayFlag(0);
-            }
-
+            liveWatchUser.setLiveFlag(liveFlag);
+            liveWatchUser.setReplayFlag(replayFlag);
             liveWatchUser.setCreateTime(now);
             liveWatchUser.setUpdateTime(now);
             baseMapper.insertLiveWatchUser(liveWatchUser);
         }
+
         liveWatchUser.setAvatar(fsUserVO.getAvatar());
         liveWatchUser.setNickName(fsUserVO.getNickname());
-        String hashKey  = String.format(LiveKeysConstant.LIVE_WATCH_USERS, liveId);
+        String hashKey = String.format(LiveKeysConstant.LIVE_WATCH_USERS, liveId);
         redisCache.hashPut(hashKey, String.valueOf(userId), JSON.toJSONString(liveWatchUser));
         return liveWatchUser;
     }
 
     @Override
     public LiveWatchUser joinWithoutLocation(long liveId, long userId) {
-        LiveWatchUser liveWatchUser = getByLiveIdAndUserId(liveId, userId);
-        FsUser fsUserVO = fsUserService.selectFsUserByUserId(userId);
+        FsUserScrm fsUserVO = fsUserService.selectFsUserByUserId(userId);
         Date now = DateUtils.getNowDate();
 
         // 查询直播间信息
         Live live = liveMapper.selectLiveByLiveId(liveId);
-
-        // 判断用户进入时间:如果进入时间大于直播结束时间,说明是回放
-        boolean isReplay = false;
-        if (live != null && live.getFinishTime() != null) {
-            Date finishTime = java.sql.Timestamp.valueOf(live.getFinishTime());
-            isReplay = now.after(finishTime);
+        if (live == null) {
+            throw new RuntimeException("直播间不存在");
         }
 
-        if(liveWatchUser != null) {
-            liveWatchUser.setUpdateTime(now);
-            liveWatchUser.setOnline(0);
+        // 获取直播/回放状态(带缓存)
+        Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+        Integer liveFlag = flagMap.get("liveFlag");
+        Integer replayFlag = flagMap.get("replayFlag");
 
-            // 更新进入标记
-            if (isReplay) {
-                liveWatchUser.setReplayFlag(1);
-            } else {
-                liveWatchUser.setLiveFlag(1);
-            }
+        // 使用唯一索引查询:live_id, user_id, live_flag, replay_flag
+        LiveWatchUser liveWatchUser = baseMapper.selectByUniqueIndex(liveId, userId, liveFlag, replayFlag);
 
+        if (liveWatchUser != null) {
+            // 存在则更新
+            liveWatchUser.setUpdateTime(now);
+            liveWatchUser.setOnline(0);
             baseMapper.updateLiveWatchUser(liveWatchUser);
-        }else{
+        } else {
+            // 不存在则插入
             liveWatchUser = new LiveWatchUser();
             liveWatchUser.setLiveId(liveId);
             liveWatchUser.setUserId(userId);
             liveWatchUser.setAvatar(fsUserVO.getAvatar());
             liveWatchUser.setMsgStatus(0);
             liveWatchUser.setOnline(0);
-
-            // 设置进入标记
-            if (isReplay) {
-                liveWatchUser.setReplayFlag(1);
-                liveWatchUser.setLiveFlag(0);
-            } else {
-                liveWatchUser.setLiveFlag(1);
-                liveWatchUser.setReplayFlag(0);
-            }
-
+            liveWatchUser.setLiveFlag(liveFlag);
+            liveWatchUser.setReplayFlag(replayFlag);
             liveWatchUser.setCreateTime(now);
             liveWatchUser.setUpdateTime(now);
             baseMapper.insertLiveWatchUser(liveWatchUser);
         }
+
         liveWatchUser.setAvatar(fsUserVO.getAvatar());
         liveWatchUser.setNickName(fsUserVO.getNickname());
-        String hashKey  = String.format(LiveKeysConstant.LIVE_WATCH_USERS, liveId);
+        String hashKey = String.format(LiveKeysConstant.LIVE_WATCH_USERS, liveId);
         redisCache.hashPut(hashKey, String.valueOf(userId), JSON.toJSONString(liveWatchUser));
         return liveWatchUser;
     }
     @Override
     public LiveWatchUser close(long liveId, long userId) {
-        LiveWatchUser liveWatchUser = getByLiveIdAndUserId(liveId, userId);
+
+        // 查询直播间信息
+        Live live = liveMapper.selectLiveByLiveId(liveId);
+        if (live == null) {
+            throw new RuntimeException("直播间不存在");
+        }
+
+        // 获取直播/回放状态(带缓存)
+        Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+        Integer liveFlag = flagMap.get("liveFlag");
+        Integer replayFlag = flagMap.get("replayFlag");
+
+        // 使用唯一索引查询:live_id, user_id, live_flag, replay_flag
+        LiveWatchUser liveWatchUser = baseMapper.selectByUniqueIndex(liveId, userId, liveFlag, replayFlag);
         // 设置在线时长
         try {
             Long onlineSeconds = liveWatchUser.getOnlineSeconds();
@@ -294,6 +333,13 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
      */
     @Override
     public List<LiveWatchUserVO> selectWatchUserList(Map<String, Object> params) {
+        // 先查缓存,获取liveFlag和replayFlag
+        if (params != null && params.containsKey("liveId")) {
+            Long liveId = Long.valueOf(params.get("liveId").toString());
+            Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+            params.put("liveFlag", flagMap.get("liveFlag"));
+            params.put("replayFlag", flagMap.get("replayFlag"));
+        }
         return baseMapper.selectWatchUserListByLiveId(params);
     }
 
@@ -305,11 +351,14 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
      */
     @Override
     public int changeUserState(Long liveId, Long userId) {
-        LiveWatchUser liveWatchUser = getByLiveIdAndUserId(liveId, userId);
-        if (Objects.nonNull(liveWatchUser)) {
-            liveWatchUser.setMsgStatus(Math.abs(1 - liveWatchUser.getMsgStatus()));
-            liveWatchUser.setUpdateTime(DateUtils.getNowDate());
-            return baseMapper.updateLiveWatchUser(liveWatchUser);
+        List<LiveWatchUser> liveWatchUser = getByLiveIdAndUserId(liveId, userId);
+        if (!liveWatchUser.isEmpty()) {
+            for (LiveWatchUser watchUser : liveWatchUser) {
+                watchUser.setMsgStatus(Math.abs(1 - watchUser.getMsgStatus()));
+                watchUser.setUpdateTime(DateUtils.getNowDate());
+                baseMapper.updateLiveWatchUser(watchUser);
+            }
+            return 1;
         }
         return 0;
     }
@@ -322,22 +371,42 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
      */
     @Override
     public LiveWatchUserVO selectWatchUserByLiveIdAndUserId(Long liveId, Long userId) {
-        return baseMapper.selectWatchUserByLiveIdAndUserId(liveId, userId);
+        // 先查缓存,获取liveFlag和replayFlag
+        Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+        Map<String, Object> params = new HashMap<>();
+        params.put("liveId", liveId);
+        params.put("userId", userId);
+        params.put("liveFlag", flagMap.get("liveFlag"));
+        params.put("replayFlag", flagMap.get("replayFlag"));
+        return baseMapper.selectWatchUserByLiveIdAndUserId(params);
     }
 
     @Override
     public List<LiveWatchUserVO> selectOnlineUserList(LiveWatchUser param) {
+        // 先查缓存,获取liveFlag和replayFlag
+        if (param != null && param.getLiveId() != null) {
+            Map<String, Integer> flagMap = getLiveFlagWithCache(param.getLiveId());
+            param.setLiveFlag(flagMap.get("liveFlag"));
+            param.setReplayFlag(flagMap.get("replayFlag"));
+        }
         return baseMapper.selectOnlineUserList(param);
     }
 
     @Override
     public List<LiveWatchUser> checkOnlineNoRewardUser(Long liveId, Date now) {
-        return baseMapper.checkOnlineNoRewardUser(liveId,now);
+        // 先查缓存,获取liveFlag和replayFlag
+        Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+        Map<String, Object> params = new HashMap<>();
+        params.put("liveId", liveId);
+        params.put("now", now);
+        params.put("liveFlag", flagMap.get("liveFlag"));
+        params.put("replayFlag", flagMap.get("replayFlag"));
+        return baseMapper.checkOnlineNoRewardUser(params);
     }
 
     @Override
     public int blockUser(Long userId) {
-        FsUser fsUser = new FsUser();
+        FsUserScrm fsUser = new FsUserScrm();
         fsUser.setUserId(userId);
         fsUser.setStatus(0);
         return fsUserService.updateFsUser(fsUser);
@@ -345,7 +414,14 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
 
     @Override
     public List<LiveWatchUser> selectLiveWatchAndRegisterUser(Long liveId, Long lotteryId) {
-        return baseMapper.selectLiveWatchAndRegisterUser(liveId, lotteryId);
+        // 先查缓存,获取liveFlag和replayFlag
+        Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+        Map<String, Object> params = new HashMap<>();
+        params.put("liveId", liveId);
+        params.put("lotteryId", lotteryId);
+        params.put("liveFlag", flagMap.get("liveFlag"));
+        params.put("replayFlag", flagMap.get("replayFlag"));
+        return baseMapper.selectLiveWatchAndRegisterUser(params);
     }
 
     @Override
@@ -402,6 +478,13 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
 
     @Override
     public List<LiveWatchUserVO> dashBoardWatchUserList(Map<String, Object> param) {
+        // 先查缓存,获取liveFlag和replayFlag
+        if (param != null && param.containsKey("liveId")) {
+            Long liveId = Long.valueOf(param.get("liveId").toString());
+            Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
+            param.put("liveFlag", flagMap.get("liveFlag"));
+            param.put("replayFlag", flagMap.get("replayFlag"));
+        }
         if (param != null && param.containsKey("all") && "1".equals(param.get("all"))) {
             return baseMapper.selectWatchUserListAllByLiveId(param);
         } else {
@@ -422,4 +505,9 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         baseMapper.updateSingleVisible(liveId, status,userId);
     }
 
+    @Override
+    public LiveWatchUser selectLiveWatchUserByFlag(Long liveId, Long userId, Integer liveFlag, Integer replayFlag) {
+        return baseMapper.selectByUniqueIndex(liveId, userId, liveFlag, replayFlag);
+    }
+
 }

+ 34 - 0
fs-service/src/main/java/com/fs/live/vo/LiveOrderCodeOpenIdVo.java

@@ -0,0 +1,34 @@
+package com.fs.live.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class LiveOrderCodeOpenIdVo implements Serializable {
+    /**
+     * 订单id
+     * **/
+    private String id;
+
+    /**
+     * 用户openId
+     * **/
+    private String openId;
+
+    /**
+     * 用户手机号
+     * **/
+    private String phone;
+
+    /**
+     * 商品详情信息
+     * **/
+    private String jsonInfo;
+
+    /**
+     * 交易单号
+     * **/
+    private String outTransId;
+}
+

+ 81 - 0
fs-service/src/main/java/com/fs/live/vo/LiveOrderDeliveryNoteExportVO.java

@@ -0,0 +1,81 @@
+package com.fs.live.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 发货单导出(直播订单)
+ * **/
+@Data
+public class LiveOrderDeliveryNoteExportVO implements Serializable {
+    //系统订单号
+    @Excel(name = "原始单号",width = 20,sort = 1)
+    private String orderNumber;
+
+    @Excel(name = "收件人",width = 10,sort = 2)
+    private String recipient;
+
+    @Excel(name = "收件人手机",width = 20,sort = 3)
+    private String recipientPhone;
+
+    @Excel(name = "收件人电话",width = 20,sort = 4)
+    private String recipientTelephone;
+
+    //具体到某个街道和小区
+    @Excel(name = "收件人详细地址",width = 30,sort = 5)
+    private String recipientAddress;
+
+    //编号和数量:662551*2
+    @Excel(name = "组合编号及数量",width = 20,sort = 7)
+    private String number;
+
+    //名称和数量:商品名称*2
+    @Excel(name = "组合名称及数量",width = 20,sort = 8)
+    private String nameAndNumber;
+
+    @Excel(name = "代收金额",width = 10,sort = 9)
+    private BigDecimal collectionAmount;
+
+    @Excel(name = "物流公司",width = 10,sort = 10)
+    private String logisticsCompany;
+
+    @Excel(name = "物流产品",width = 10,sort = 11)
+    private String logisticsProduct;
+
+    @Excel(name = "物流付款方式",width = 15,sort = 12)
+    private String logisticsPayMethod;
+
+    @Excel(name = "包裹数",width = 10,sort = 13)
+    private Long packageNum;
+
+    @Excel(name = "寄件人",width = 10,sort = 14)
+    private String sender;
+
+    @Excel(name = "寄件人手机",width = 20,sort = 15)
+    private String senderPhone;
+
+    @Excel(name = "寄件人电话",width = 20,sort = 16)
+    private String senderTelephone;
+
+    @Excel(name = "寄件公司",width = 10,sort = 17)
+    private String senderCompany;
+
+    //具体到某个街道和小区
+    @Excel(name = "寄件人详细地址",width = 30,sort = 18)
+    private String senderAddress;
+
+    @Excel(name = "出库仓库",width = 10,sort = 19)
+    private String outboundWarehouse;
+
+    @Excel(name = "订单付款方式",width = 10,sort = 20)
+    private String payMethod;
+
+    @Excel(name = "订单备注",width = 20,sort = 21)
+    private String orderNotes;
+
+    private String keyword;
+}
+

+ 2 - 3
fs-service/src/main/java/com/fs/live/vo/LiveOrderVoZm.java

@@ -121,7 +121,6 @@ public class LiveOrderVoZm{
 
     /** 支付时间 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    @Excel(name = "支付时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime payTime;
 
 
@@ -391,8 +390,8 @@ public class LiveOrderVoZm{
 //    @Excel(name = "是否改价")
     private Integer isEditMoney;
 
-    /** 创建时间*/
-//    @Excel(name = "创建时间")
+    /** 创建时间 (下单时间)*/
+    @Excel(name = "下单时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 
     /** 更新时间*/

+ 141 - 0
fs-service/src/main/java/com/fs/pay/domain/PaymentMiniProgramConfig.java

@@ -0,0 +1,141 @@
+package com.fs.pay.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 多小程序支付配置对象 payment_mini_program_config
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PaymentMiniProgramConfig extends BaseEntity{
+
+    /** 主键ID */
+    private Long id;
+
+    /** 支付类型:YB-易宝,TZH-台州银行,WX-微信,HF-汇付 */
+    @Excel(name = "支付类型:YB-易宝,TZH-台州银行,WX-微信,HF-汇付")
+    private String payType;
+
+    /**
+     * 小程序名称
+     */
+    @Excel(name = "小程序名称")
+    private String appName;
+    /** 小程序appid */
+    @Excel(name = "小程序appid")
+    private String appid;
+    /**
+     * 小程序密钥
+     */
+    @Excel(name = "小程序密钥")
+    private String appSecret;
+
+    /** 易宝商户号 */
+    @Excel(name = "易宝商户号")
+    private String ybMerchantNo;
+
+    /** 易宝Key */
+    @Excel(name = "易宝Key")
+    private String ybKey;
+
+    /** 易宝回调地址 */
+    @Excel(name = "易宝回调地址")
+    private String ybNotifyUrl;
+
+    /** 台州银行商户号 */
+    @Excel(name = "台州银行商户号")
+    private String tzhMerchantNo;
+
+    /** 台州appSecret */
+    @Excel(name = "台州appSecret")
+    private String tzhAppsecret;
+
+    /** 台州私钥 */
+    @Excel(name = "台州私钥")
+    private String tzhPrivateKey;
+
+    /** 台州平台公钥 */
+    @Excel(name = "台州平台公钥")
+    private String tzhPublicKey;
+
+    /** 台州appKey */
+    @Excel(name = "台州appKey")
+    private String tzhAppkey;
+
+    /** 台州支付回调地址 */
+    @Excel(name = "台州支付回调地址")
+    private String tzhPayNotifyUrl;
+
+    /** 台州退款回调地址 */
+    @Excel(name = "台州退款回调地址")
+    private String tzhRefundNotifyUrl;
+
+    /** 台州分账回调地址 */
+    @Excel(name = "台州分账回调地址")
+    private String tzhSplitNotifyUrl;
+
+    /** 微信商户号 */
+    @Excel(name = "微信商户号")
+    private String wxMerchantNo;
+
+    /** 微信Key */
+    @Excel(name = "微信Key")
+    private String wxKey;
+    /**
+     *
+     */
+    private String wxKeyPath;
+    /**
+     * 微信回调url
+     */
+    @Excel(name = "微信回调url")
+    private String wxNotifyUrl;
+
+    /** 汇付产品号 */
+    @Excel(name = "汇付产品号")
+    private String hfProductNo;
+
+    /** 汇付系统号 */
+    @Excel(name = "汇付系统号")
+    private String hfSystemNo;
+
+    /** 汇付商户号 */
+    @Excel(name = "汇付商户号")
+    private String hfMerchantNo;
+
+    /** 汇付服务商私钥 */
+    @Excel(name = "汇付服务商私钥")
+    private String hfPrivateKey;
+
+    /** 汇付公钥 */
+    @Excel(name = "汇付公钥")
+    private String hfPublicKey;
+
+    /** 汇付支付回调地址 */
+    @Excel(name = "汇付支付回调地址")
+    private String hfPayNotifyUrl;
+
+    /** 汇付大额支付回调地址 */
+    @Excel(name = "汇付大额支付回调地址")
+    private String hfLargePayNotifyUrl;
+
+    /** 汇付退款回调地址 */
+    @Excel(name = "汇付退款回调地址")
+    private String hfRefundNotifyUrl;
+
+    /** 汇付大额退款回调地址 */
+    @Excel(name = "汇付大额退款回调地址")
+    private String hfLargeRefundNotifyUrl;
+
+    /** 状态,1启用,0禁用 */
+    @Excel(name = "状态,1启用,0禁用")
+    private Integer status;
+
+
+}

+ 70 - 0
fs-service/src/main/java/com/fs/pay/mapper/PaymentMiniProgramConfigMapper.java

@@ -0,0 +1,70 @@
+package com.fs.pay.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.pay.domain.PaymentMiniProgramConfig;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 多小程序支付配置Mapper接口
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+public interface PaymentMiniProgramConfigMapper extends BaseMapper<PaymentMiniProgramConfig>{
+    /**
+     * 查询多小程序支付配置
+     *
+     * @param id 多小程序支付配置主键
+     * @return 多小程序支付配置
+     */
+    PaymentMiniProgramConfig selectPaymentMiniProgramConfigById(String id);
+
+    /**
+     * 查询多小程序支付配置列表
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 多小程序支付配置集合
+     */
+    List<PaymentMiniProgramConfig> selectPaymentMiniProgramConfigList(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 新增多小程序支付配置
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 结果
+     */
+    int insertPaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 修改多小程序支付配置
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 结果
+     */
+    int updatePaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 删除多小程序支付配置
+     *
+     * @param id 多小程序支付配置主键
+     * @return 结果
+     */
+    int deletePaymentMiniProgramConfigById(String id);
+
+    /**
+     * 批量删除多小程序支付配置
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deletePaymentMiniProgramConfigByIds(String[] ids);
+
+    PaymentMiniProgramConfig selectPaymentConfigByAppId(@Param("appid") String appid);
+
+    @Select("select * from payment_mini_program_config where status = 1")
+    List<PaymentMiniProgramConfig> selectAll();
+
+}

+ 5 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -404,8 +404,8 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Select("SELECT id,external_user_id,name,avatar,remark,description,fs_user_id FROM  qw_external_contact " +
             " WHERE user_id = #{map.userId}   " +
             "AND corp_id =#{map.corpId} " +
-            "AND external_user_id = #{map.externalUserId}" +
-            "AND `status` != 4 AND `status` != 5" +
+            "AND external_user_id = #{map.externalUserId} " +
+            "AND `status` != 4 AND `status` != 5 " +
             "ORDER BY id desc " +
             "limit 1 ")
     QwExternalContact getQwExternalContactDetails(@Param("map")QwExternalContactHParam param);
@@ -539,4 +539,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam);
 
     void updateJoinGroup(List<Long> longs);
+
+
+    List<QwExternalContact> getGroupChatUserByChatIdAndUserName(@Param("userId")String userId,@Param("userName")String userName,@Param("corpId") String corpId,@Param("chatId") String chatId);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java

@@ -142,6 +142,13 @@ public class QwSopTempSetting implements Serializable{
             //视频ID
             private Long videoId;
 
+            //封面图片地址 app用的参数
+            private String courseUrl;
+
+            //app显示标题 app用的参数
+            private String title;
+
+
             @Override
             public Setting clone() {
                 try {

+ 2 - 1
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempMapper.java

@@ -6,6 +6,7 @@ import com.fs.common.enums.DataSourceType;
 import com.fs.sop.domain.QwSopTemp;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -70,5 +71,5 @@ public interface QwSopTempMapper extends BaseMapper<QwSopTemp> {
 
     List<QwSopTemp> listTemp();
 
-    List<QwSopTemp> selectListByIds(@Param("ids") List<String> ids);
+    List<QwSopTemp> selectListByIds(@Param("ids") Collection<String> ids);
 }

+ 3 - 1
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempRulesMapper.java

@@ -6,6 +6,7 @@ import com.fs.common.enums.DataSourceType;
 import com.fs.sop.domain.QwSopTempRules;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
 
 import java.util.List;
 
@@ -16,7 +17,8 @@ import java.util.List;
  * @date 2025-02-06
  */
 @DataSource(DataSourceType.SOP)
-public interface QwSopTempRulesMapper extends BaseMapper<QwSopTempRules>{
+@Repository
+public interface QwSopTempRulesMapper extends BaseMapper<QwSopTempRules> {
     /**
      * 查询sop任务模板规则
      *

+ 6 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopTempContentService.java

@@ -5,7 +5,9 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.sop.domain.QwSopTempContent;
 
+import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 /**
  * sop任务模板内容Service接口
@@ -84,4 +86,8 @@ public interface IQwSopTempContentService extends IService<QwSopTempContent>{
     void updateDay(QwSopTempContent content);
 
     List<QwSopTempContent> selectQwSopTempContentByTempId(String tempId);
+
+    void removeByTempIds(Collection<String> tempIds);
+
+    List<QwSopTempContent> listByTempIds(Collection<String> tempIds);
 }

+ 5 - 1
fs-service/src/main/java/com/fs/sop/service/IQwSopTempDayService.java

@@ -3,6 +3,7 @@ package com.fs.sop.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.sop.domain.QwSopTempDay;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -11,7 +12,7 @@ import java.util.List;
  * @author fs
  * @date 2025-02-06
  */
-public interface IQwSopTempDayService extends IService<QwSopTempDay>{
+public interface IQwSopTempDayService extends IService<QwSopTempDay> {
 
     void saveList(List<QwSopTempDay> list);
 
@@ -26,9 +27,12 @@ public interface IQwSopTempDayService extends IService<QwSopTempDay>{
     boolean saveOrUpdate(QwSopTempDay day);
 
     void addOrUpdateBatch(List<QwSopTempDay> days);
+
     void removeById(Long id);
 
     List<QwSopTempDay> listById(List<Long> dayIds);
 
     int getDayNumByIdLimitOne(String tempId);
+
+    void removeByTempIds(Collection<String> tempIds);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopTempRulesService.java

@@ -6,7 +6,9 @@ import com.fs.common.enums.DataSourceType;
 import com.fs.sop.domain.QwSopTempDay;
 import com.fs.sop.domain.QwSopTempRules;
 
+import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 /**
  * sop任务模板规则Service接口
@@ -88,4 +90,8 @@ public interface IQwSopTempRulesService extends IService<QwSopTempRules>{
     List<QwSopTempRules> listById(List<Long> rulesIds);
 
     void updateSiFenTemp();
+
+    List<QwSopTempRules> listByCourseId(Long courseId);
+
+    void removeByTempIds(Collection<String> tempIds);
 }

+ 5 - 3
fs-service/src/main/java/com/fs/sop/service/IQwSopTempService.java

@@ -6,6 +6,7 @@ import com.fs.sop.domain.QwSopTempDay;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.vo.QwSopTempRedPackageVo;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -15,8 +16,7 @@ import java.util.Map;
  * @author fs
  * @date 2024-09-26
  */
-public interface IQwSopTempService
-{
+public interface IQwSopTempService {
     /**
      * 查询sop模板
      *
@@ -58,7 +58,7 @@ public interface IQwSopTempService
     public int deleteQwSopTempByIds(String[] ids);
 
     /**
-     *分享sop模板
+     * 分享sop模板
      */
     public int shareQwSopTemp(QwSopShareTempParam param);
 
@@ -99,4 +99,6 @@ public interface IQwSopTempService
     void updateRedPackage(List<QwSopTempRedPackageVo> list);
 
     List<String> getSelectableRange();
+
+    void syncTemplate(Long courseId);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java

@@ -95,6 +95,7 @@ public class QwSopLogsServiceImpl extends ServiceImpl<QwSopLogsMapper, QwSopLogs
     private QwExternalContactServiceImpl qwExternalContactService;
 
     @Autowired
+    @Lazy
     private IFsCourseWatchLogService watchLogService;
 
     @Autowired

+ 1 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java

@@ -137,6 +137,7 @@ public class QwSopServiceImpl implements IQwSopService
     private IFsUserService fsUserService;
 
     @Autowired
+    @Lazy
     private IFsCourseWatchLogService fsCourseWatchLogService;
 
     @Autowired

+ 15 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempContentServiceImpl.java

@@ -5,10 +5,13 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.sop.domain.QwSopTempContent;
+import com.fs.sop.domain.QwSopTempDay;
 import com.fs.sop.mapper.QwSopTempContentMapper;
 import com.fs.sop.service.IQwSopTempContentService;
 import org.springframework.stereotype.Service;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -148,4 +151,16 @@ public class QwSopTempContentServiceImpl extends ServiceImpl<QwSopTempContentMap
     public List<QwSopTempContent> selectQwSopTempContentByTempId(String tempId) {
         return baseMapper.selectQwSopTempContentByTempId(tempId);
     }
+
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public void removeByTempIds(Collection<String> tempIds) {
+        this.remove(new QueryWrapper<QwSopTempContent>().in("temp_id", tempIds));
+    }
+
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public List<QwSopTempContent> listByTempIds(Collection<String> tempIds) {
+        return baseMapper.selectList(new QueryWrapper<QwSopTempContent>().in("temp_id", tempIds));
+    }
 }

+ 9 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempDayServiceImpl.java

@@ -1,6 +1,8 @@
 package com.fs.sop.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
@@ -11,6 +13,7 @@ import lombok.AllArgsConstructor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -83,4 +86,10 @@ public class QwSopTempDayServiceImpl extends ServiceImpl<QwSopTempDayMapper, QwS
     public int getDayNumByIdLimitOne(String tempId) {
         return  qwSopTempDayMapper.getDayNumByIdLimitOne(tempId);
     }
+
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public void removeByTempIds(Collection<String> tempIds) {
+        this.remove(new QueryWrapper<QwSopTempDay>().in("temp_id", tempIds));
+    }
 }

+ 26 - 19
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempRulesServiceImpl.java

@@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -59,8 +60,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
      * @return sop任务模板规则
      */
     @Override
-    public QwSopTempRules selectQwSopTempRulesById(String id)
-    {
+    public QwSopTempRules selectQwSopTempRulesById(String id) {
         return baseMapper.selectQwSopTempRulesById(id);
     }
 
@@ -71,8 +71,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
      * @return sop任务模板规则
      */
     @Override
-    public List<QwSopTempRules> selectQwSopTempRulesList(QwSopTempRules qwSopTempRules)
-    {
+    public List<QwSopTempRules> selectQwSopTempRulesList(QwSopTempRules qwSopTempRules) {
         return baseMapper.selectQwSopTempRulesList(qwSopTempRules);
     }
 
@@ -83,8 +82,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
      * @return 结果
      */
     @Override
-    public int insertQwSopTempRules(QwSopTempRules qwSopTempRules)
-    {
+    public int insertQwSopTempRules(QwSopTempRules qwSopTempRules) {
         return baseMapper.insertQwSopTempRules(qwSopTempRules);
     }
 
@@ -95,8 +93,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
      * @return 结果
      */
     @Override
-    public int updateQwSopTempRules(QwSopTempRules qwSopTempRules)
-    {
+    public int updateQwSopTempRules(QwSopTempRules qwSopTempRules) {
         return baseMapper.updateQwSopTempRules(qwSopTempRules);
     }
 
@@ -107,8 +104,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
      * @return 结果
      */
     @Override
-    public int deleteQwSopTempRulesByIds(String[] ids)
-    {
+    public int deleteQwSopTempRulesByIds(String[] ids) {
         return baseMapper.deleteQwSopTempRulesByIds(ids);
     }
 
@@ -119,8 +115,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
      * @return 结果
      */
     @Override
-    public int deleteQwSopTempRulesById(String id)
-    {
+    public int deleteQwSopTempRulesById(String id) {
         return baseMapper.deleteQwSopTempRulesById(id);
     }
 
@@ -140,7 +135,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
     @DataSource(DataSourceType.SOP)
     public List<QwSopTempRules> listByTempId(String id) {
         List<QwSopTempDay> dayList = qwSopTempDayService.list(new QueryWrapper<QwSopTempDay>().eq("temp_id", id).orderByAsc("day_num"));
-        if(dayList.isEmpty()) return Collections.emptyList();
+        if (dayList.isEmpty()) return Collections.emptyList();
         Map<Long, Integer> collect = dayList.stream().collect(Collectors.toMap(QwSopTempDay::getId, QwSopTempDay::getDayNum));
         List<QwSopTempRules> rulesList = list(new QueryWrapper<QwSopTempRules>().in("day_id", PubFun.listToNewList(dayList, QwSopTempDay::getId)));
         rulesList.forEach(r -> {
@@ -148,7 +143,7 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
         });
         List<QwSopTempContent> ts = qwSopTempContentService.list(new QueryWrapper<QwSopTempContent>().in("rules_id", PubFun.listToNewList(rulesList, QwSopTempRules::getId)));
         Map<Long, List<QwSopTempContent>> map = PubFun.listToMapByGroupList(ts, QwSopTempContent::getRulesId);
-        rulesList.stream().filter( e -> map.containsKey(e.getId())).forEach(e -> e.setSettingList(map.get(e.getId())));
+        rulesList.stream().filter(e -> map.containsKey(e.getId())).forEach(e -> e.setSettingList(map.get(e.getId())));
         return rulesList;
     }
 
@@ -202,9 +197,9 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
     public void updateRulesDayNumIsNull() {
         List<QwSopTempRules> rules = qwSopTempRulesMapper.rulesNull();
         log.info("时间为空的有:{}", rules.size());
-        for (QwSopTempRules rule : rules){
+        for (QwSopTempRules rule : rules) {
             QwSopTempDay day = dayMapper.info(rule.getDayId());
-            if (day!=null){
+            if (day != null) {
                 QwSopTempRules rules1 = new QwSopTempRules();
                 rules1.setDayNum(day.getDayNum());
                 rules1.setId(rule.getId());
@@ -226,14 +221,14 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
     @Override
     public void updateSiFenTemp() {
         List<QwSopTempContentVO> contents = contentMapper.updateSiFenTemp();
-        for(QwSopTempContentVO content : contents){
-            if (content.getVideoId()==null){
+        for (QwSopTempContentVO content : contents) {
+            if (content.getVideoId() == null) {
                 continue;
             }
             QwSopTempContent content1 = new QwSopTempContent();
             FsUserCourseVideo video = videoMapper.selectFsUserCourseVideoByVideoId(content.getVideoId());
             QwSopTempSetting.Content.Setting settingMap = new QwSopTempSetting.Content.Setting();
-            QwSopTempSetting.Content.Setting setting = JSON.parseObject(content.getContent(),QwSopTempSetting.Content.Setting.class);
+            QwSopTempSetting.Content.Setting setting = JSON.parseObject(content.getContent(), QwSopTempSetting.Content.Setting.class);
             settingMap.setContentType("4");
             settingMap.setMiniprogramAppid("wx73f85f8d62769119");
             settingMap.setMiniprogramPicUrl(setting.getLinkImageUrl());
@@ -245,4 +240,16 @@ public class QwSopTempRulesServiceImpl extends ServiceImpl<QwSopTempRulesMapper,
             contentMapper.updateQwSopTempContent(content1);
         }
     }
+
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public List<QwSopTempRules> listByCourseId(Long courseId) {
+        return list(new QueryWrapper<QwSopTempRules>().eq("course_id", courseId));
+    }
+
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public void removeByTempIds(Collection<String> tempIds) {
+        this.remove(new QueryWrapper<QwSopTempRules>().in("temp_id", tempIds));
+    }
 }

+ 148 - 62
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java

@@ -9,7 +9,6 @@ import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.company.domain.Company;
 import com.fs.company.service.ICompanyService;
-import com.fs.company.service.impl.CompanyServiceImpl;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsUserCourse;
@@ -30,16 +29,16 @@ import com.fs.sop.mapper.QwSopTempMapper;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.service.*;
 import com.fs.sop.vo.QwSopTempRedPackageVo;
-import com.fs.sop.vo.VoiceVo;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
 import io.netty.util.internal.StringUtil;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.ConvertUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
@@ -47,6 +46,7 @@ import java.text.SimpleDateFormat;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -59,8 +59,8 @@ import java.util.stream.Collectors;
  */
 @Service
 @AllArgsConstructor
-public class QwSopTempServiceImpl implements IQwSopTempService
-{
+@Slf4j
+public class QwSopTempServiceImpl implements IQwSopTempService {
 
     @Autowired
     private CloudHostProper cloudHostProper;
@@ -79,6 +79,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
     private final IQwSopService qwSopService;
     private final IQwUserService qwUserService;
     private final ICompanyService companyService;
+    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
     /**
      * 查询sop模板
@@ -87,7 +88,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
      * @return sop模板
      */
     @Override
-    public QwSopTemp selectQwSopTempById(String id){
+    public QwSopTemp selectQwSopTempById(String id) {
 //        QwSopTemp qwSopTemp = qwSopTempMapper.selectQwSopTempById(id);
         QwSopTemp qwSopTemp = qwSopTempMapper.selectQwSopTempById(id);
         List<QwSopTempDay> qwSopTempDays = qwSopTempDayService.listByTempId(id);
@@ -117,8 +118,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
      * @return sop模板
      */
     @Override
-    public List<QwSopTemp> selectQwSopTempList(QwSopTemp qwSopTemp)
-    {
+    public List<QwSopTemp> selectQwSopTempList(QwSopTemp qwSopTemp) {
         return qwSopTempMapper.selectQwSopTempList(qwSopTemp);
     }
 
@@ -129,7 +129,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
      * @return 结果
      */
     @Override
-    public int insertQwSopTemp(QwSopTemp qwSopTemp){
+    public int insertQwSopTemp(QwSopTemp qwSopTemp) {
         qwSopTemp.setId(UUID.randomUUID().toString());
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         qwSopTemp.setCreateTime(sdf.format(new Date()));
@@ -155,7 +155,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
      * @return 结果
      */
     @Override
-    public int updateQwSopTemp(QwSopTemp qwSopTemp){
+    public int updateQwSopTemp(QwSopTemp qwSopTemp) {
         return qwSopTempMapper.updateQwSopTemp(qwSopTemp);
     }
 
@@ -166,8 +166,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
      * @return 结果
      */
     @Override
-    public int deleteQwSopTempByIds(String[] ids)
-    {
+    public int deleteQwSopTempByIds(String[] ids) {
         return qwSopTempMapper.deleteQwSopTempByIds(ids);
     }
 
@@ -196,8 +195,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
      * @return 结果
      */
     @Override
-    public int deleteQwSopTempById(String id)
-    {
+    public int deleteQwSopTempById(String id) {
         return qwSopTempMapper.deleteQwSopTempById(id);
     }
 
@@ -214,7 +212,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
     public int update(QwSopTemp qwSopTemp) {
         QwSopTemp temp = qwSopTempMapper.selectById(qwSopTemp.getId());
         int i = qwSopTempMapper.updateById(qwSopTemp);
-        if(!Objects.equals(temp.getGap(), qwSopTemp.getGap())){
+        if (!Objects.equals(temp.getGap(), qwSopTemp.getGap())) {
             // 重新排序
             reorder(qwSopTemp.getId());
         }
@@ -233,7 +231,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         qwSopTempRulesService.removeByDayId(day.getId());
         qwSopTempContentService.removeByDayId(day.getId());
 
-        list.forEach(item-> item.setDayId(day.getId()));
+        list.forEach(item -> item.setDayId(day.getId()));
         processAndReplaceContent(list);
         QwSopTempRules rules = list.get(0);
         String tempId = rules.getTempId();
@@ -251,29 +249,29 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         reorder(day.getTempId());
         Map<String, Object> map = new HashMap<>();
         map.put("id", day.getId());
-        if(!voiceList.isEmpty()){
+        if (!voiceList.isEmpty()) {
             for (QwSopTempContent qwSopTempContent : voiceList) {
                 String content = qwSopTempContent.getContent();
                 JSONObject jsonObject = JSONObject.parseObject(content);
                 String text = jsonObject.getString("value");
                 List<QwSop> qwSopList = qwSopService.selectQwSopByTempId(tempId);//通过tempId查询出所有sop
-                if(qwSopList != null && !qwSopList.isEmpty()){
+                if (qwSopList != null && !qwSopList.isEmpty()) {
                     for (QwSop qwSop : qwSopList) {
-                        if(qwSop != null && qwSop.getQwUserIds() != null){
+                        if (qwSop != null && qwSop.getQwUserIds() != null) {
                             //查询出所有的tempContent来筛选文字
                             List<QwSopTempContent> qwSopTempContentList = qwSopTempContentService.selectQwSopTempContentByTempId(tempId);
-                            if(qwSopTempContentList != null && !qwSopTempContentList.isEmpty()){
+                            if (qwSopTempContentList != null && !qwSopTempContentList.isEmpty()) {
                                 for (QwSopTempContent qwSopTemp : qwSopTempContentList) {
-                                    if(qwSopTemp != null && qwSopTemp.getContentType() == 7){
+                                    if (qwSopTemp != null && qwSopTemp.getContentType() == 7) {
                                         String[] split = qwSop.getQwUserIds().split(",");
                                         Long[] qwUserIds = (Long[]) ConvertUtils.convert(split, Long.class);
                                         List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(qwUserIds);
-                                        if(qwUserVOS != null){
+                                        if (qwUserVOS != null) {
                                             for (QwUserVO qwUserVO : qwUserVOS) {
                                                 Long companyUserId = qwUserVO.getCompanyUserId();
-                                                QwSopTempVoice qwSopTempVoice = qwSopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId,text);
-                                                if(qwSopTempVoice == null){
-                                                    if(companyUserId != null && text != null){
+                                                QwSopTempVoice qwSopTempVoice = qwSopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId, text);
+                                                if (qwSopTempVoice == null) {
+                                                    if (companyUserId != null && text != null) {
                                                         QwSopTempVoice sopTempVoice = new QwSopTempVoice();
                                                         sopTempVoice.setCompanyUserId(companyUserId);
                                                         sopTempVoice.setVoiceTxt(text);
@@ -298,12 +296,12 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         return map;
     }
 
-    private void processAndReplaceContent(List<QwSopTempRules> tempSettings){
+    private void processAndReplaceContent(List<QwSopTempRules> tempSettings) {
         List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
         //循环天
-        tempSettings.forEach(settingList->{
+        tempSettings.forEach(settingList -> {
             //循环单日
-            settingList.getSettingList().forEach(item->{
+            settingList.getSettingList().forEach(item -> {
                 JSONObject obj = JSON.parseObject(item.getContent());
                 List<String> list = Arrays.asList("linkTitle", "linkDescribe", "desc", "nickname", "value");
                 list.stream().filter(obj::containsKey).forEach(key -> {
@@ -316,23 +314,24 @@ public class QwSopTempServiceImpl implements IQwSopTempService
 
     @Override
     public void delRules(Long id) {
-        if(id == null) return;
+        if (id == null) return;
         QwSopTempDay day = qwSopTempDayService.info(id);
-        if(day == null) return;
+        if (day == null) return;
         qwSopTempDayService.removeById(day.getId());
         qwSopTempContentService.removeByDayId(day.getId());
         reorder(day.getTempId());
     }
+
     @Override
     public QwSopTempDay selectRulesInfo(Long id) {
         QwSopTempDay day = qwSopTempDayService.info(id);
         List<QwSopTempRules> rulesList = qwSopTempRulesService.listByDayId(id);
-        if(!rulesList.isEmpty()){
+        if (!rulesList.isEmpty()) {
             List<QwSopTempContent> contentList = qwSopTempContentService.listByRulesIds(PubFun.listToNewList(rulesList, QwSopTempRules::getId));
             Map<Long, List<QwSopTempContent>> contentMap = PubFun.listToMapByGroupList(contentList, QwSopTempContent::getRulesId);
             rulesList.forEach(e -> {
                 List<QwSopTempContent> contents = contentMap.get(e.getId());
-                if (CollectionUtils.isNotEmpty(contents)){
+                if (CollectionUtils.isNotEmpty(contents)) {
                     e.setSetting(contents.stream().map(c -> JSON.parseObject(c.getContent())).collect(Collectors.toList()));
                 }
             });
@@ -353,14 +352,14 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         qwSopTemp.setId(newId);
         qwSopTempMapper.insertQwSopTemp(qwSopTemp);
         List<QwSopTempDay> dayList = qwSopTempRulesService.listByTempIdAll(oldId);
-        if(dayList.isEmpty()) return;
+        if (dayList.isEmpty()) return;
 //        List<QwSopTempRules> rulesList = qwSopTempRulesService.listByTempId(oldId);
         dayList.forEach(day -> {
             day.setTempId(newId);
-            if(day.getList() != null && !day.getList().isEmpty()){
+            if (day.getList() != null && !day.getList().isEmpty()) {
                 day.getList().forEach(e -> {
                     e.setTempId(newId);
-                    if(e.getSettingList() != null && !e.getSettingList().isEmpty()){
+                    if (e.getSettingList() != null && !e.getSettingList().isEmpty()) {
                         e.getSettingList().forEach(item -> {
                             item.setTempId(newId);
                         });
@@ -376,7 +375,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
             item.setRulesId(e.getId());
             item.setDayId(e.getDayId());
         }));
-        qwSopTempContentService.insertBatch(collect.stream().flatMap(e ->e.getSettingList().stream()).collect(Collectors.toList()));
+        qwSopTempContentService.insertBatch(collect.stream().flatMap(e -> e.getSettingList().stream()).collect(Collectors.toList()));
     }
 
     @Override
@@ -390,7 +389,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
     public void sortDay(List<SortDayVo> list) {
         Collection<QwSopTempDay> days = qwSopTempDayService.listByIds(PubFun.listToNewList(list, SortDayVo::getId));
         Map<Long, Integer> dayMap = list.stream().collect(Collectors.toMap(SortDayVo::getId, SortDayVo::getDayNum));
-        if(days.stream().anyMatch(e -> !dayMap.containsKey(e.getId()))){
+        if (days.stream().anyMatch(e -> !dayMap.containsKey(e.getId()))) {
             throw new BaseException("数据错误!");
         }
         days.forEach(day -> day.setDayNum(dayMap.get(day.getId())));
@@ -404,10 +403,10 @@ public class QwSopTempServiceImpl implements IQwSopTempService
 
 
     @DataSource(DataSourceType.SOP)
-    private void reorder(String tempId){
+    private void reorder(String tempId) {
         QwSopTemp qwSopTemp = qwSopTempMapper.selectQwSopTempById(tempId);
         List<QwSopTempDay> days = qwSopTempDayService.listByTempId(tempId);
-        if(days.isEmpty()) return;
+        if (days.isEmpty()) return;
         for (int i = 0; i < days.size(); i++) {
             QwSopTempDay entity = days.get(i);
             entity.setSorts(i);
@@ -418,7 +417,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
 
     @Override
     public void createSopTempRules(QwSopTemp temp) {
-        if(temp.getTime() == null){
+        if (temp.getTime() == null) {
             return;
         }
 
@@ -436,11 +435,11 @@ public class QwSopTempServiceImpl implements IQwSopTempService
             day.setSorts(day.getDayNum());
             day.setList(new ArrayList<>());
             List<String> timeList = new ArrayList<>();
-            if(temp.getTimeList() != null){
+            if (temp.getTimeList() != null) {
                 timeList = JSON.parseArray(JSON.toJSONString(temp.getTimeList()), String.class);
             }
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
-            if(temp.getTime() != null){
+            if (temp.getTime() != null) {
                 timeList.add(0, temp.getTime().format(formatter));
             }
             AtomicInteger sorts = new AtomicInteger(0);
@@ -448,15 +447,15 @@ public class QwSopTempServiceImpl implements IQwSopTempService
                 QwSopTempRules rules = new QwSopTempRules();
                 rules.setTempId(temp.getId());
                 rules.setName(day.getName());
-                if (temp.getOpenOfficial().equals("1")){
+                if (temp.getOpenOfficial().equals("1")) {
                     rules.setIsOfficial(sorts.get() == 0 ? "1" : "0");
-                }else {
+                } else {
                     rules.setIsOfficial("0");
                 }
 
-                if (day.getDayNum()==1 && sorts.get() == 0 && temp.getOpenOfficial().equals("1")){
+                if (day.getDayNum() == 1 && sorts.get() == 0 && temp.getOpenOfficial().equals("1")) {
                     rules.setTime("01:05");
-                }else {
+                } else {
                     rules.setTime(time);
                 }
 
@@ -475,10 +474,10 @@ public class QwSopTempServiceImpl implements IQwSopTempService
                 setting.setMiniprogramTitle(e.getTitle());
 
                 //用课节图片做封面
-                if("今正科技".equals(cloudHostProper.getCompanyName())){
-                    setting.setMiniprogramPicUrl(!StringUtil.isNullOrEmpty(e.getThumbnail())?e.getThumbnail():fsUserCourse.getImgUrl());
-                    setting.setLinkImageUrl(!StringUtil.isNullOrEmpty(e.getThumbnail())?e.getThumbnail():fsUserCourse.getImgUrl());
-                }else {
+                if ("今正科技".equals(cloudHostProper.getCompanyName())) {
+                    setting.setMiniprogramPicUrl(!StringUtil.isNullOrEmpty(e.getThumbnail()) ? e.getThumbnail() : fsUserCourse.getImgUrl());
+                    setting.setLinkImageUrl(!StringUtil.isNullOrEmpty(e.getThumbnail()) ? e.getThumbnail() : fsUserCourse.getImgUrl());
+                } else {
                     setting.setMiniprogramPicUrl(fsUserCourse.getImgUrl());
                     setting.setLinkImageUrl(fsUserCourse.getImgUrl());
 
@@ -489,7 +488,7 @@ public class QwSopTempServiceImpl implements IQwSopTempService
                 setting.setContentType("4");
                 content.setContent(JSON.toJSONString(setting));
                 content.setIsBindUrl(1);
-                List<QwSopTempContent> qwSopTempContents = new ArrayList<>() ;
+                List<QwSopTempContent> qwSopTempContents = new ArrayList<>();
                 qwSopTempContents.add(content);
                 if (sorts.get() == 0 && !StringUtil.isNullOrEmpty(temp.getModeContent())) {
                     QwSopTempContent content2 = new QwSopTempContent();
@@ -501,13 +500,13 @@ public class QwSopTempServiceImpl implements IQwSopTempService
                     content2.setContent(JSON.toJSONString(setting2));
                     qwSopTempContents.add(content2);
                 }
-                if (sorts.get() > 0){
+                if (sorts.get() > 0) {
 
                     QwSopTempContent content3 = new QwSopTempContent();
                     content3.setTempId(temp.getId());
                     content3.setContentType(3);
                     QwSopTempSetting2.Content.Setting setting3 = new QwSopTempSetting2.Content.Setting();
-                    setting3.setValue(temp.getTimeDesc().get(sorts.get()-1));
+                    setting3.setValue(temp.getTimeDesc().get(sorts.get() - 1));
                     setting3.setContentType("1");
                     content3.setContent(JSON.toJSONString(setting3));
                     qwSopTempContents.add(content3);
@@ -515,11 +514,11 @@ public class QwSopTempServiceImpl implements IQwSopTempService
                 }
 
                 rules.setSorts(sorts.getAndIncrement());
-                if(rules.getSorts() == 0){
+                if (rules.getSorts() == 0) {
                     rules.setCourseType(0);
-                }else if(rules.getSorts() == 1){
+                } else if (rules.getSorts() == 1) {
                     rules.setCourseType(1);
-                }else{
+                } else {
                     rules.setCourseType(4);
                 }
                 rules.setList(qwSopTempContents);
@@ -540,10 +539,10 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         CourseConfig courseConfig = JSON.parseObject(sysConfig.getConfigValue(), CourseConfig.class);
         List<Long> videoIdList = PubFun.listToNewList(ruleList, QwSopTempRules::getVideoId);
         Map<Long, Optional<BigDecimal>> redMap;
-        if(!videoIdList.isEmpty()){
+        if (!videoIdList.isEmpty()) {
             List<FsUserCourseVideoRedPackage> redPackageList = fsUserCourseVideoRedPackageService.listByCompanyIdAndVideoIds(temp.getCompanyId(), videoIdList);
             redMap = redPackageList.stream().collect(Collectors.groupingBy(FsUserCourseVideoRedPackage::getVideoId, Collectors.mapping(FsUserCourseVideoRedPackage::getRedPacketMoney, Collectors.reducing((e1, e2) -> e1))));
-        }else{
+        } else {
             redMap = new HashMap<>();
         }
 
@@ -562,15 +561,15 @@ public class QwSopTempServiceImpl implements IQwSopTempService
     @Override
     public List<QwSopTempRedPackageVo> redList(String id) {
         List<QwSopTempDay> dayList = qwSopTempRulesService.listByTempIdAll(id);
-        if(CollectionUtils.isEmpty(dayList)){
+        if (CollectionUtils.isEmpty(dayList)) {
             return Collections.emptyList();
         }
         List<QwSopTempRules> rules = dayList.stream()
-                .filter(e->e!= null && e.getList()!=null)
+                .filter(e -> e != null && e.getList() != null)
                 .flatMap(e -> e.getList().stream())
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
-        if(rules.isEmpty()){
+        if (rules.isEmpty()) {
             return Collections.emptyList();
         }
         List<FsUserCourseVideoRedPackage> redPackageList = fsUserCourseVideoRedPackageService.listByRuleIds(PubFun.listToNewList(rules, QwSopTempRules::getId));
@@ -608,14 +607,101 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         }).collect(Collectors.toList());
         fsUserCourseVideoRedPackageService.batchSaveFsUserCourseVideoRedPackage(redPackage);
     }
+
     @Override
     public List<String> getSelectableRange() {
         SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("course.config");
         CourseConfig courseConfig = JSON.parseObject(sysConfig.getConfigValue(), CourseConfig.class);
         List<CourseConfig.DisabledTimeVo> disabledTimeList = courseConfig.getDisabledTimeList();
-        if(disabledTimeList == null){
+        if (disabledTimeList == null) {
             return Collections.emptyList();
         }
         return TimeCalculator.calculateAvailableTimes(disabledTimeList);
     }
+
+
+    @Override
+    public void syncTemplate(Long courseId) {
+        // 根据courseId查询所有相关的sop模板规则
+        List<QwSopTempRules> rulesList = qwSopTempRulesService.listByCourseId(courseId);
+        if (CollectionUtils.isEmpty(rulesList)) {
+            return;
+        }
+
+        // 获取这些规则关联的模板ID集合
+        Set<String> tempIds = rulesList.stream()
+                .map(QwSopTempRules::getTempId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        // 查询相关联的sop模板
+        if (CollectionUtils.isEmpty(tempIds)) {
+            return;
+        }
+        List<QwSopTemp> tempList = qwSopTempMapper.selectListByIds(tempIds);
+        tempList = tempList.stream().filter(f -> Objects.equals(f.getStatus(), "1")).collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(tempList)) {
+            return;
+        }
+        List<QwSopTempContent> contentList = qwSopTempContentService.listByTempIds(tempIds);
+
+        CountDownLatch latch = new CountDownLatch(tempList.size());
+        // 对每个模板执行同步操作
+        for (QwSopTemp temp : tempList) {
+            // 构造timeList timeDesc time
+            rulesList.stream().filter(e -> e.getTempId().equals(temp.getId())).findFirst()
+                    .ifPresent(first -> temp.setTime(LocalTime.parse(first.getTime() + ":00")));
+
+            temp.setTimeList(rulesList.stream()
+                    .filter(e -> e.getTempId().equals(temp.getId()) && Objects.equals(e.getIsOfficial(), "0")
+                            && Objects.equals(e.getName(), "第1天")).map(QwSopTempRules::getTime)
+                    .collect(Collectors.toList()));
+            // 过滤并找到 dayId 最小的元素
+            Optional<QwSopTempContent> minDayEntity = contentList.stream()
+                    // 1. 过滤条件:tempId匹配 + isBindUrl为null
+                    .filter(e -> e.getTempId().equals(temp.getId())
+                            && Objects.isNull(e.getIsBindUrl()))
+                    // 2. 按 dayId 升序排序,取第一个(最小)
+                    .min(Comparator.comparingLong(QwSopTempContent::getDayId)); // 若dayId是Long,用comparingLong
+
+            if (minDayEntity.isPresent()) {
+                QwSopTempContent qwSopTempContent = minDayEntity.get();
+                temp.setTimeDesc(contentList.stream().filter(e -> e.getTempId().equals(temp.getId())
+                                && Objects.isNull(e.getIsBindUrl())
+                                && Objects.equals(qwSopTempContent.getDayId(), e.getDayId())
+                        )
+                        .map(m -> JSONObject.parseObject(m.getContent()).getString("value")).collect(Collectors.toList()));
+            }
+
+            // 插入课程id
+            temp.setCourseId(courseId);
+            temp.setOpenOfficial("1");
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            temp.setCreateTime(sdf.format(new Date()));
+            temp.setId(UUID.randomUUID().toString());
+            qwSopTempMapper.insertQwSopTemp(temp);
+            // 重新生成该模板的规则和内容
+            threadPoolTaskExecutor.execute(() -> {
+                try {
+                    createSopTempRules(temp);
+                } catch (Exception e) {
+                    log.error("同步模板规则和内容失败", e);
+                } finally {
+                    latch.countDown();
+                }
+            });
+
+        }
+
+        try {
+            latch.await();
+            qwSopTempMapper.deleteQwSopTempByIds(tempIds.toArray(new String[0]));
+            qwSopTempDayService.removeByTempIds(tempIds);
+            qwSopTempRulesService.removeByTempIds(tempIds);
+            qwSopTempContentService.removeByTempIds(tempIds);
+        } catch (InterruptedException e) {
+            log.error("等待线程执行完成时被中断", e);
+        }
+    }
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/statis/dto/ModifyMoreDTO.java

@@ -26,4 +26,6 @@ public class ModifyMoreDTO implements Serializable {
      * 公司id
      */
     private String companyIds;
+
+    private String isAudit;
 }

+ 104 - 0
fs-service/src/main/java/com/fs/wx/order/domain/FsWxExpressTask.java

@@ -0,0 +1,104 @@
+package com.fs.wx.order.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 微信同步发货信息定时任务表
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class FsWxExpressTask {
+
+    /**
+     * 任务ID,唯一标识
+     */
+    private Long id;
+
+    /**
+     * 订单code
+     */
+    private String orderCode;
+
+    /**
+     * 支付单号
+     */
+    private String payCode;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 消息内容,JSON格式。
+     */
+    private String data;
+
+    /**
+     * 任务状态:0=待执行, 1=执行中, 2=执行成功, 3=执行失败, 4=已取消
+     */
+    private Integer status;
+
+    /**
+     * 当前重试次数
+     */
+    private Integer retryCount;
+
+    /**
+     * 最大重试次数
+     */
+    private Integer maxRetries;
+
+    /**
+     * 请求参数(JSON格式,主要记录 access_token 获取方式)
+     */
+    private String requestParams;
+
+    /**
+     * 完整的请求体 (JSON格式)
+     */
+    private String requestBody;
+
+    /**
+     * API 响应结果 (JSON格式)
+     */
+    private String responseBody;
+
+    /**
+     * 错误信息 (如果执行失败)
+     */
+    private String errorMessage;
+
+    /**
+     * 任务创建时间
+     */
+    private LocalDateTime createTime; // 使用LocalDateTime对应timestamp
+
+    /**
+     * 最后更新时间
+     */
+    private LocalDateTime updateTime; // 使用LocalDateTime对应timestamp
+
+
+    /**
+     * 快递公司
+     */
+    private String expressCompany;
+
+    /**
+     * 快递编号
+     */
+    private String expressNo;
+
+    private Integer type;
+
+    /**
+     * 小程序ID
+     */
+    private String appid;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wx/order/dto/Contact.java

@@ -0,0 +1,21 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Contact {
+    // 根据实际需要添加更多联系人字段
+    @JsonProperty("consignor_contact")
+    private String consignorContact; // 发货人联系方式 (示例字段)
+
+    // 可以添加收货人联系方式等
+    // @JsonProperty("receiver_contact")
+    // private String receiverContact;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/wx/order/dto/OrderKey.java

@@ -0,0 +1,27 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderKey {
+
+    @JsonProperty("order_number_type")
+    private Integer orderNumberType;
+
+    @JsonProperty("transaction_id")
+    private String transactionId;
+
+    @JsonProperty("mchid")
+    private String mchId;
+
+    @JsonProperty("out_trade_no")
+    private String outTradeNo;
+}

+ 77 - 0
fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryRequest.java

@@ -0,0 +1,77 @@
+package com.fs.wx.order.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderQueryRequest {
+
+    /**
+     * 聚合账户 (Required)
+     */
+    private String account;
+
+    /**
+     * 下游订单号 (Required if upOrderId is null)
+     */
+    private String lowOrderId;
+
+    /**
+     * 通莞订单号 (Required if lowOrderId is null)
+     */
+    private String upOrderId;
+
+    /**
+     * 值为"Y"时,接口返回优惠信息字段 (Optional)
+     */
+    private String extendInfo;
+
+    /**
+     * 值为"1"时,接口返回易宝专业版分账订单详情 (Optional)
+     */
+    private String isExtend;
+
+    /**
+     * 值为"1"时,接口返回SAAS分账订单详情 (Optional)
+     */
+    private String isNeedUpInfo;
+
+    /**
+     * 值为"1"时,接口返回渠道商户订单号字段 (Optional)
+     */
+    private String isNeedChannelMchOrderId;
+
+    /**
+     * 值为"1"时,返回因公付金额信息,仅支付宝服务商渠道支持 (Optional)
+     */
+    private String isNeedEnterprisePayInfo;
+
+    /**
+     * 签名 (Required)
+     * Note: This will be calculated and set by the service.
+     */
+    private String sign;
+
+    /**
+     * Helper method to get parameters for signing.
+     * Excludes the 'sign' field itself.
+     * Returns sorted map to ensure consistent order for signing.
+     *
+     * @return A map of non-null parameters sorted by key.
+     */
+    public Map<String, String> toSignMap() {
+       Map<String,String> sign = new HashMap<>();
+        sign.put("account",account);
+        sign.put("upOrderId",upOrderId);
+        sign.put("isNeedUpInfo", isNeedUpInfo);
+        return sign;
+    }
+}

+ 236 - 0
fs-service/src/main/java/com/fs/wx/order/dto/OrderQueryResponse.java

@@ -0,0 +1,236 @@
+package com.fs.wx.order.dto;
+
+import cn.hutool.core.annotation.Alias;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class OrderQueryResponse {
+
+    /**
+     * 100:成功,101:失败 (Required)
+     */
+    private Integer status;
+
+    /**
+     * 消息描述 (Required)
+     */
+    private String message;
+
+    /**
+     * 上游订单号 (Required)
+     */
+    private String channelOrderId;
+
+    /**
+     * 通莞订单号 (Required)
+     */
+    private String upOrderId;
+
+    /**
+     * 默认传null (Required) - Although API doc says required, it seems informational.
+     */
+    private String payoffType;
+
+    /**
+     * 支付时间 (Required)
+     */
+    private String payTime;
+
+    /**
+     * WX: openid; Alipay: account name (Required)
+     * Mapped from both "openId" and "openid" in JSON
+     */
+    @Alias("openid") // Instruct Hutool to map "openid" to this field as well
+    private String openId;
+
+    /**
+     * 签名 (Required) - For response validation
+     */
+    private String sign;
+
+    /**
+     * 结算渠道编号 (Required)
+     */
+    private String settlementChannel;
+
+    /**
+     * 下游订单号 (Required)
+     */
+    private String lowOrderId;
+
+    /**
+     * 支付金额,单位元 (Required)
+     */
+    private String payMoney; // Keep as String as per API doc, can be converted later
+
+    /**
+     * 支付方式 0:WX, 1:ZFB, 2:UnionPay QR, 6:LongPay, 8:BestPay, H:Digital Currency (Required)
+     */
+    private String payType;
+
+    /**
+     * 0:Success, 1:Fail, 2:Revoked, 4:Pending, 5:Refunded, 6:Partial Refund (Required)
+     */
+    private String state;
+
+    /**
+     * 订单备注 (Optional)
+     */
+    private String attach;
+
+    /**
+     * 聚合账户 (Required)
+     */
+    private String account;
+
+    /**
+     * 支付方式例:WX、ZFB、YZF、LZF、YLZF (Required)
+     */
+    private String channelId;
+
+    /**
+     * 渠道优惠金额 JSON String, e.g., {"discountAmt":"100"} (Unit: Fen) (Optional)
+     */
+    private String discountInfo;
+
+    /**
+     * 扩展信息 JSON String (Optional, if requested)
+     * Need to be parsed into an object if needed.
+     */
+    private String extendInfo; // Raw JSON string
+
+    /**
+     * 易宝/SAAS分账详情 JSON Object (Optional, if requested)
+     */
+    private JSONObject extend; // Parsed JSON Object
+
+    /**
+     * SAAS分账订单详情 JSON Object (Optional, if requested)
+     */
+    private JSONObject upInfo; // Parsed JSON Object
+
+    /**
+     * 渠道商户订单号 (Optional, if requested)
+     */
+    private String channelMchOrderId;
+
+    /**
+     * 订单管控状态 FROZEN/UN_FROZEN (Optional, if requested via isNeedUpInfo=1)
+     */
+    private String fundControlCsStatus;
+
+    /**
+     * 管控订单解冻时间 yyyy-mm-dd hh:mm:ss (Optional, if requested via isNeedUpInfo=1 and status is UN_FROZEN)
+     */
+    private String csUnFrozenCompleteDate;
+
+    /**
+     * 因公付金额信息 JSON String, e.g., {"invoiceAmount":"0.01","isUseEnterprisePay":false} (Optional, if requested)
+     */
+    private String enterprisePayInfo; // Raw JSON String
+
+    // --- Helper methods to access parsed nested JSON data ---
+
+    /**
+     * Gets parsed DiscountInfo object from discountInfo string.
+     * @return DiscountInfo object or null if parsing fails or discountInfo is null/empty.
+     */
+    public DiscountInfo getParsedDiscountInfo() {
+        if (JSONUtil.isJson(this.discountInfo)) {
+            try {
+                return JSONUtil.toBean(this.discountInfo, DiscountInfo.class);
+            } catch (Exception e) {
+                // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+     /**
+     * Gets parsed EnterprisePayInfo object from enterprisePayInfo string.
+     * @return EnterprisePayInfo object or null if parsing fails or enterprisePayInfo is null/empty.
+     */
+    public EnterprisePayInfo getParsedEnterprisePayInfo() {
+         if (JSONUtil.isJson(this.enterprisePayInfo)) {
+            try {
+                return JSONUtil.toBean(this.enterprisePayInfo, EnterprisePayInfo.class);
+            } catch (Exception e) {
+                 // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets parsed ExtendInfo object from extendInfo string.
+     * @return JSONObject or null if parsing fails or extendInfo is null/empty.
+     */
+    public JSONObject getParsedExtendInfo() {
+        if (JSONUtil.isJson(this.extendInfo)) {
+            try {
+                return JSONUtil.parseObj(this.extendInfo);
+            } catch (Exception e) {
+                 // Log parsing error if needed
+                return null;
+            }
+        }
+        return null;
+    }
+
+    // --- Nested DTOs for parsed JSON strings ---
+
+    @Data
+    public static class DiscountInfo {
+        /**
+         * Discount amount in Fen (分)
+         */
+        private String discountAmt; // Keep as String as per API, or use Integer/Long
+
+        public BigDecimal getDiscountAmtYuan() {
+            if (discountAmt != null) {
+                try {
+                    // Assuming discountAmt is in Fen (cents)
+                    return new BigDecimal(discountAmt).divide(new BigDecimal("100"));
+                } catch (NumberFormatException e) {
+                    return null; // Or handle error appropriately
+                }
+            }
+            return null;
+        }
+    }
+
+    @Data
+    public static class EnterprisePayInfo {
+        private String invoiceAmount; // Amount in Yuan (元)
+        private Boolean isUseEnterprisePay;
+
+        public BigDecimal getInvoiceAmountValue() {
+             if (invoiceAmount != null) {
+                 try {
+                     return new BigDecimal(invoiceAmount);
+                 } catch (NumberFormatException e) {
+                     return null; // Or handle error appropriately
+                 }
+             }
+             return null;
+         }
+    }
+
+    /**
+     * Validates the response signature.
+     * IMPORTANT: Implement the actual signature validation logic based on Tongguan's specification.
+     * @param secretKey The secret key.
+     * @return true if the signature is valid, false otherwise.
+     */
+    public boolean isResponseSignValid(String secretKey) {
+
+        System.err.println("WARN: Response signature validation is not implemented!");
+        return true;
+    }
+}

+ 17 - 0
fs-service/src/main/java/com/fs/wx/order/dto/Payer.java

@@ -0,0 +1,17 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Payer {
+
+    @JsonProperty("openid")
+    private String openid;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/wx/order/dto/ShippingItem.java

@@ -0,0 +1,26 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShippingItem {
+
+    @JsonProperty("tracking_no")
+    private String trackingNo;
+
+    @JsonProperty("express_company")
+    private String expressCompany;
+
+    @JsonProperty("item_desc")
+    private String itemDesc;
+
+    @JsonProperty("contact")
+    private Contact contact;
+}

+ 42 - 0
fs-service/src/main/java/com/fs/wx/order/dto/UploadShippingInfoRequest.java

@@ -0,0 +1,42 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UploadShippingInfoRequest {
+
+    @JsonProperty("order_key")
+    private OrderKey orderKey;
+
+    @JsonProperty("logistics_type")
+    private Integer logisticsType;
+
+    @JsonProperty("delivery_mode")
+    private Integer deliveryMode;
+
+    @JsonProperty("is_all_delivered")
+    private Boolean isAllDelivered;
+
+    @JsonProperty("shipping_list")
+    private List<ShippingItem> shippingList;
+
+    @JsonProperty("upload_time")
+    private String uploadTime;
+
+    @JsonProperty("payer")
+    private Payer payer;
+
+    @JsonIgnore
+    private String appid;
+
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiConfig.java

@@ -0,0 +1,21 @@
+package com.fs.wx.order.dto;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Getter
+public class WeChatApiConfig {
+
+    @Value("${wechat.api.base-url}")
+    private String baseUrl;
+
+    @Value("${wechat.api.upload-shipping-info}")
+    private String uploadShippingInfoPath;
+
+
+    public String getUploadShippingInfoUrl() {
+        return baseUrl + uploadShippingInfoPath;
+    }
+}

+ 20 - 0
fs-service/src/main/java/com/fs/wx/order/dto/WeChatApiResponse.java

@@ -0,0 +1,20 @@
+package com.fs.wx.order.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class WeChatApiResponse {
+
+    @JsonProperty("errcode")
+    private Integer errcode;
+
+    @JsonProperty("errmsg")
+    private String errmsg;
+
+    public boolean isSuccess() {
+        return errcode != null && errcode == 0;
+    }
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است