Преглед на файлове

Merge branch '企微聊天' of http://1.14.104.71:10880/root/ylrz_his_scrm_java into 企微聊天

15376779826 преди 1 седмица
родител
ревизия
910c4da952
променени са 100 файла, в които са добавени 3565 реда и са изтрити 441 реда
  1. 1 1
      deploy.sh
  2. 6 0
      fs-admin/Dockerfile
  3. 103 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java
  4. 18 6
      fs-admin/src/main/java/com/fs/course/controller/FsCourseAnswerLogsController.java
  5. 38 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java
  6. 24 0
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  7. 56 18
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java
  8. 53 2
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  9. 27 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  10. 6 6
      fs-admin/src/main/java/com/fs/his/controller/FsAdvController.java
  11. 85 82
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  12. 21 0
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  13. 107 0
      fs-admin/src/main/java/com/fs/his/controller/MerchantAppConfigController.java
  14. 63 8
      fs-admin/src/main/java/com/fs/his/task/Task.java
  15. 63 3
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  16. 45 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  17. 79 7
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  18. 1 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java
  19. 9 9
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreScrmController.java
  20. 18 1
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  21. 22 1
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  22. 54 0
      fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java
  23. 18 1
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  24. 3 3
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  25. 29 1
      fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java
  26. 55 0
      fs-admin/src/main/java/com/fs/live/controller/OrderController.java
  27. 30 6
      fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java
  28. 52 9
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  29. 107 24
      fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java
  30. 111 0
      fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java
  31. 8 1
      fs-admin/src/main/java/com/fs/web/controller/common/CommonController.java
  32. 129 7
      fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java
  33. 10 0
      fs-admin/src/main/java/com/fs/web/controller/system/SysUserController.java
  34. 1 1
      fs-admin/src/main/resources/application.yml
  35. 6 0
      fs-common-api/Dockerfile
  36. 24 0
      fs-common-api/pom.xml
  37. 1 1
      fs-common-api/src/main/resources/application.yml
  38. 6 0
      fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java
  39. 10 0
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java
  40. 44 0
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  41. 6 0
      fs-company/Dockerfile
  42. 51 4
      fs-company/src/main/java/com/fs/company/controller/company/CompanyLoginController.java
  43. 116 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyRedPacketBalanceLogsController.java
  44. 8 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  45. 25 1
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java
  46. 10 4
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  47. 77 1
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  48. 19 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  49. 10 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwTagGroupController.java
  50. 1 1
      fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java
  51. 284 0
      fs-company/src/main/java/com/fs/framework/service/CompanyLoginService.java
  52. 2 1
      fs-company/src/main/resources/application.yml
  53. 2 1
      fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java
  54. 207 0
      fs-framework/src/main/java/com/fs/framework/web/service/SysLoginService.java
  55. 12 0
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  56. 0 1
      fs-live-app/src/main/java/com/fs/framework/aspectj/LiveWatchUserAspect.java
  57. 1 1
      fs-live-app/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java
  58. 3 2
      fs-live-app/src/main/java/com/fs/live/controller/LiveController.java
  59. 0 2
      fs-live-app/src/main/java/com/fs/live/controller/LiveDataController.java
  60. 12 11
      fs-live-app/src/main/java/com/fs/live/websocket/handle/LiveChatHandler.java
  61. 249 57
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  62. 69 31
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  63. 6 5
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  64. 34 0
      fs-qwhook/src/main/java/com/fs/app/controller/CommonController.java
  65. 49 0
      fs-service/src/main/java/com/fs/common/service/WechatLoginService.java
  66. 46 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceLogs.java
  67. 10 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  68. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  69. 64 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceLogsMapper.java
  70. 1 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  71. 64 0
      fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceLogsService.java
  72. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  73. 99 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceLogsServiceImpl.java
  74. 14 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  75. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  76. 25 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java
  77. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsCoursePlaySourceConfig.java
  78. 2 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseWatchLog.java
  79. 1 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  80. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  81. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseFinishTempMapper.java
  82. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCoursePlaySourceConfigMapper.java
  83. 10 3
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  84. 6 2
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  85. 2 5
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  86. 30 2
      fs-service/src/main/java/com/fs/course/param/FsCourseAnswerLogsParam.java
  87. 3 0
      fs-service/src/main/java/com/fs/course/param/FsCoursePlaySourceConfigEditParam.java
  88. 37 1
      fs-service/src/main/java/com/fs/course/param/FsCourseRedPacketLogParam.java
  89. 29 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  90. 5 0
      fs-service/src/main/java/com/fs/course/param/PeriodStatisticCountParam.java
  91. 7 0
      fs-service/src/main/java/com/fs/course/service/IFsCoursePlaySourceConfigService.java
  92. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCoursePlaySourceConfigServiceImpl.java
  93. 1 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseProductOrderServiceImpl.java
  94. 19 5
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  95. 19 6
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  96. 2 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyBindServiceImpl.java
  97. 13 6
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  98. 1 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  99. 1 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java
  100. 224 77
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

+ 1 - 1
deploy.sh

@@ -87,7 +87,7 @@ deploy_jar() {
     echo "启动服务..."
     ssh -f "$REMOTE_USER@$remote_host" \
     "cd $REMOTE_BASE_DIR/$remote_dir && \
-     nohup java -jar  -Dfile.encoding=UTF-8 $app_name.jar --spring.profiles.active=druid-bjzm --server.port=7114  \
+     nohup java -jar -Xms8g -Xmx8g -XX:+UseG1GC -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 $app_name.jar --spring.profiles.active=druid-bjzm --server.port=7114  \
      >> $app_name.log 2>&1 &"
 
     # 检查进程是否启动成功

+ 6 - 0
fs-admin/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-admin.jar fs-admin.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-admin.jar"]
+# 执行启动命令java -jar

+ 103 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java

@@ -0,0 +1,103 @@
+package com.fs.company.controller;
+
+import java.util.List;
+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.company.domain.CompanyRedPacketBalanceLogs;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 企业红包余额记录Controller
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+@RestController
+@RequestMapping("/company/companyRedPacketBalanceLogs")
+public class CompanyRedPacketBalanceLogsController extends BaseController
+{
+    @Autowired
+    private ICompanyRedPacketBalanceLogsService companyRedPacketBalanceLogsService;
+
+    /**
+     * 查询企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        startPage();
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:export')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        ExcelUtil<CompanyRedPacketBalanceLogs> util = new ExcelUtil<CompanyRedPacketBalanceLogs>(CompanyRedPacketBalanceLogs.class);
+        return util.exportExcel(list, "企业红包余额记录数据");
+    }
+
+    /**
+     * 获取企业红包余额记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:query')")
+    @GetMapping(value = "/{logsId}")
+    public AjaxResult getInfo(@PathVariable("logsId") Long logsId)
+    {
+        return AjaxResult.success(companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsByLogsId(logsId));
+    }
+
+    /**
+     * 新增企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:add')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.insertCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs));
+    }
+
+    /**
+     * 修改企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:edit')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.updateCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs));
+    }
+
+    /**
+     * 删除企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:remove')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logsIds}")
+    public AjaxResult remove(@PathVariable Long[] logsIds)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.deleteCompanyRedPacketBalanceLogsByLogsIds(logsIds));
+    }
+}

+ 18 - 6
fs-admin/src/main/java/com/fs/course/controller/FsCourseAnswerLogsController.java

@@ -3,6 +3,7 @@ package com.fs.course.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
@@ -10,6 +11,8 @@ import com.fs.course.domain.FsCourseAnswerLogs;
 import com.fs.course.param.FsCourseAnswerLogsParam;
 import com.fs.course.service.IFsCourseAnswerLogsService;
 import com.fs.course.vo.FsCourseAnswerLogsListVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -35,17 +38,26 @@ public class FsCourseAnswerLogsController extends BaseController
      * 查询答题日志列表
      */
     @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(FsCourseAnswerLogsParam param)
+    @PostMapping("/list")
+    public R list(@RequestBody  FsCourseAnswerLogsParam param)
     {
-        startPage();
+        if(param.getPageNum() == null) {
+            param.setPageNum(1);
+        }
+        if(param.getPageSize() == null) {
+            param.setPageSize(10);
+        }
 
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
             param.setPhone(encryptPhone(param.getPhoneMk()));
         }
 
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+
         List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
-        return getDataTable(list);
+
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**
@@ -53,8 +65,8 @@ public class FsCourseAnswerLogsController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:export')")
     @Log(title = "答题日志", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsCourseAnswerLogsParam param)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsCourseAnswerLogsParam param)
     {
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
             param.setPhone(encryptPhone(param.getPhoneMk()));

+ 38 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java

@@ -6,14 +6,22 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.service.impl.CompanyUserServiceImpl;
+import com.fs.company.vo.DocCompanyUserVO;
 import com.fs.course.domain.FsCourseFinishTemp;
 import com.fs.course.service.IFsCourseFinishTempService;
 import com.fs.course.vo.FsCourseFinishTempListVO;
+import com.fs.sop.domain.QwSopTemp;
+import com.fs.voice.utils.StringUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 完课模板Controller
@@ -27,6 +35,8 @@ public class FsCourseFinishTempController extends BaseController
 {
     @Autowired
     private IFsCourseFinishTempService fsCourseFinishTempService;
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
 
     /**
      * 查询完课模板列表
@@ -37,6 +47,34 @@ public class FsCourseFinishTempController extends BaseController
     {
         startPage();
         List<FsCourseFinishTempListVO> list = fsCourseFinishTempService.selectFsCourseFinishTempListVO(fsCourseFinishTemp);
+        // 收集所有需要查询的用户ID
+        Set<Long> userIds = list.stream()
+                .map(FsCourseFinishTempListVO::getCreateBy)
+                .filter(str -> !StringUtil.strIsNullOrEmpty(str)) // 取反,保留非空值
+                .map(Long::valueOf)
+                .collect(Collectors.toSet());
+
+        if (!userIds.isEmpty()){
+            // 批量查询用户信息
+            Map<Long, DocCompanyUserVO> userMap = companyUserService
+                    .selectDocCompanyUserListByUserIds(userIds)
+                    .stream()
+                    .collect(Collectors.toMap(DocCompanyUserVO::getUserId, Function.identity()));
+
+
+            list.forEach(item->{
+
+                if (!StringUtil.strIsNullOrEmpty(item.getCreateBy())) {
+                    DocCompanyUserVO user = userMap.get(Long.valueOf(item.getCreateBy()));
+                    if (user != null) {
+                        item.setCreateByName(user.getNickName());
+                        item.setCreateByDeptName(user.getDeptName());
+                    }
+                }
+
+            });
+        }
+
         return getDataTable(list);
     }
 

+ 24 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
@@ -134,6 +135,29 @@ public class FsCoursePlaySourceConfigController extends BaseController {
         return AjaxResult.success();
     }
 
+    @PreAuthorize("@ss.hasPermi('course:playSourceConfig:bind')")
+    @Log(title = "绑定支付配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateBindConfig")
+    public AjaxResult updateBindConfig(@RequestBody FsCoursePlaySourceConfigEditParam param) {
+
+        FsCoursePlaySourceConfig update =new FsCoursePlaySourceConfig();
+        update.setId(param.getId());
+        update.setMerchantConfigId(param.getMerchantConfigId());
+        fsCoursePlaySourceConfigService.updateById(update);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:playSourceConfig:unbind')")
+    @Log(title = "解绑支付配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateUnbindConfig")
+    public AjaxResult updateUnbindConfig(@RequestBody FsCoursePlaySourceConfigEditParam param) {
+        LambdaUpdateWrapper<FsCoursePlaySourceConfig> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(FsCoursePlaySourceConfig::getId, param.getId())
+                .set(FsCoursePlaySourceConfig::getMerchantConfigId, null);
+        fsCoursePlaySourceConfigService.update(updateWrapper);
+        return AjaxResult.success();
+    }
+
     @PreAuthorize("@ss.hasPermi('course:playSourceConfig:remove')")
     @Log(title = "点播播放源配置", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")

+ 56 - 18
fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java

@@ -1,32 +1,27 @@
 package com.fs.course.controller;
 
-import java.util.ArrayList;
-import java.util.List;
-
+import java.util.*;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.config.CourseConfig;
+import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.param.FsCourseRedPacketLogParam;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.vo.FsCourseRedPacketLogListPVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.system.service.ISysConfigService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 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 org.springframework.web.bind.annotation.*;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -50,6 +45,9 @@ public class FsCourseRedPacketLogController extends BaseController
 {
     @Autowired
     private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
+
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
     @Autowired
     FsUserCourseMapper fsUserCourseMapper;
     @Autowired
@@ -62,21 +60,58 @@ public class FsCourseRedPacketLogController extends BaseController
      * 查询短链课程看课记录列表
      */
     @PreAuthorize("@ss.hasPermi('course:courseRedPacketLog:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(FsCourseRedPacketLogParam fsCourseRedPacketLog)
+    @PostMapping("/list")
+    public R list(@RequestBody FsCourseRedPacketLogParam fsCourseRedPacketLog)
     {
-        startPage();
+        if(fsCourseRedPacketLog.getPageNum() == null) {
+            fsCourseRedPacketLog.setPageNum(1);
+        }
+        if(fsCourseRedPacketLog.getPageSize() == null) {
+            fsCourseRedPacketLog.setPageSize(10);
+        }
+
 
         if (fsCourseRedPacketLog.getPhoneMk() != null && fsCourseRedPacketLog.getPhoneMk() != "") {
             fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
         }
+        PageHelper.startPage(fsCourseRedPacketLog.getPageNum(), fsCourseRedPacketLog.getPageSize());
 
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
+            fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
+        }
+        return R.ok().put("data",new PageInfo<>(list));
+    }
+
+    /**
+     * 查询短链课程看课记录列表分页
+     * 与上面请求方式不同,list集合数据在get传输掉包
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseRedPacketLog:pageList')")
+    @PostMapping("/pageList")
+    public R pageList(@RequestBody FsCourseRedPacketLogParam fsCourseRedPacketLog)
+    {
+
+        if (fsCourseRedPacketLog.getPhoneMk() != null && fsCourseRedPacketLog.getPhoneMk() != "") {
+            fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
+        }
 
+        PageHelper.startPage(fsCourseRedPacketLog.getPageNum(), fsCourseRedPacketLog.getPageSize());
+
+        List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
+        for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
-        return getDataTable(list);
+
+        return R.ok().put("data", new PageInfo<>(list));
     }
 
     /**
@@ -84,15 +119,18 @@ public class FsCourseRedPacketLogController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:courseRedPacketLog:export')")
     @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsCourseRedPacketLogParam fsCourseRedPacketLog)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsCourseRedPacketLogParam fsCourseRedPacketLog)
     {
         if (fsCourseRedPacketLog.getPhoneMk()!=null&&fsCourseRedPacketLog.getPhoneMk()!=""){
             fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
         }
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
-
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
         ExcelUtil<FsCourseRedPacketLogListPVO> util = new ExcelUtil<FsCourseRedPacketLogListPVO>(FsCourseRedPacketLogListPVO.class);

+ 53 - 2
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java

@@ -2,8 +2,12 @@ package com.fs.course.controller;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
+import cn.hutool.core.collection.CollectionUtil;
 import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.param.FsCourseOverParam;
@@ -14,6 +18,8 @@ import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.service.IQwWatchLogService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -56,10 +62,29 @@ public class FsCourseWatchLogController extends BaseController
     public TableDataInfo list(FsCourseWatchLogListParam param)
     {
         startPage();
+        if(CollectionUtil.isNotEmpty(param.getUserIds())){
+            param.setUserIds(param.getUserIds().stream()
+                    .filter(userId -> userId != null && userId.startsWith("user_"))
+                    .map(userId -> userId.substring(5))
+                    .collect(Collectors.toList())
+            );
+        }
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         return getDataTable(list);
     }
 
+    /**
+     * 查询短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:pageList')")
+    @PostMapping("/pageList")
+    public R pageList(@RequestBody FsCourseWatchLogListParam param)
+    {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
     @GetMapping("/qwWatchLogAllStatisticsList")
     public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
     {
@@ -109,13 +134,39 @@ public class FsCourseWatchLogController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 会员看课统计导出
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsExport')")
+    @Log(title = "会员看课统计导出", businessType = BusinessType.EXPORT)
+    @PostMapping("/statisticsExport")
+    public AjaxResult statisticsExport(@RequestBody FsCourseWatchLogStatisticsListParam param)
+    {
+
+        // 如果看指定用户就不用设置公司
+        if(param.getCompanyId() == null){
+            if(param.getUserId() == null) {
+                throw new CustomException("查看公司或者用户必填!");
+            }
+        }
+        if (param.getSTime()==null||param.getETime()==null){
+            throw new CustomException("必须选择开始时间和结束时间!");
+        }
+
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONew(param);
+
+        ExcelUtil<FsCourseWatchLogStatisticsListVO> util = new ExcelUtil<FsCourseWatchLogStatisticsListVO>(FsCourseWatchLogStatisticsListVO.class);
+        return util.exportExcel(list, "会员看课统计");
+    }
+
+
     /**
      * 导出短链课程看课记录列表
      */
     @PreAuthorize("@ss.hasPermi('course:courseWatchLog:export')")
     @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsCourseWatchLogListParam param)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsCourseWatchLogListParam param)
     {
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);

+ 27 - 0
fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java

@@ -89,6 +89,33 @@ public class QwFsCourseWatchLogController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 会员看课统计导出
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsExport')")
+    @Log(title = "企微看课统计导出", businessType = BusinessType.EXPORT)
+    @PostMapping("/statisticsExport")
+    public AjaxResult statisticsExport(@RequestBody FsCourseWatchLogStatisticsListParam param)
+    {
+
+        if (param.getSTime()==null||param.getETime()==null){
+            throw new CustomException("必须选择开始时间和结束时间!");
+        }
+
+        param.setSendType(2); //企微
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
+        if("济南联志健康".equals(signProjectName)){
+            FsCourseWatchLogStatisticsListVO totalData = fsCourseWatchLogService.getTotalDataAddItem(param);
+            list.add(totalData);
+        }
+
+        ExcelUtil<FsCourseWatchLogStatisticsListVO> util = new ExcelUtil<FsCourseWatchLogStatisticsListVO>(FsCourseWatchLogStatisticsListVO.class);
+
+        return util.exportExcel(list, "企微看课统计");
+    }
+
+
+
     @GetMapping("/getSignProjectName")
     public R getSignProjectName(){
         return R.ok().put("signProjectName", signProjectName);

+ 6 - 6
fs-admin/src/main/java/com/fs/his/controller/FsAdvController.java

@@ -40,7 +40,7 @@ public class FsAdvController extends BaseController
     /**
      * 查询广告列表
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:list')")
+    @PreAuthorize("@ss.hasPermi('store:adv:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsAdv fsAdv)
     {
@@ -52,7 +52,7 @@ public class FsAdvController extends BaseController
     /**
      * 导出广告列表
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:export')")
+    @PreAuthorize("@ss.hasPermi('store:adv:export')")
     @Log(title = "广告", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(FsAdv fsAdv)
@@ -65,7 +65,7 @@ public class FsAdvController extends BaseController
     /**
      * 获取广告详细信息
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:query')")
+    @PreAuthorize("@ss.hasPermi('store:adv:query')")
     @GetMapping(value = "/{advId}")
     public AjaxResult getInfo(@PathVariable("advId") String advId)
     {
@@ -76,7 +76,7 @@ public class FsAdvController extends BaseController
     /**
      * 新增广告
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:add')")
+    @PreAuthorize("@ss.hasPermi('store:adv:add')")
     @Log(title = "广告", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody FsAdv fsAdv)
@@ -91,7 +91,7 @@ public class FsAdvController extends BaseController
     /**
      * 修改广告
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:edit')")
+    @PreAuthorize("@ss.hasPermi('store:adv:edit')")
     @Log(title = "广告", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsAdv fsAdv)
@@ -105,7 +105,7 @@ public class FsAdvController extends BaseController
     /**
      * 删除广告
      */
-    @PreAuthorize("@ss.hasPermi('his:adv:remove')")
+    @PreAuthorize("@ss.hasPermi('store:adv:remove')")
     @Log(title = "广告", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{advIds}")
     public AjaxResult remove(@PathVariable String[] advIds)

+ 85 - 82
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -7,6 +7,8 @@ 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.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.CloudHostUtils;
@@ -22,6 +24,7 @@ import com.fs.his.param.BatchSetErpOrderParam;
 import com.fs.his.vo.*;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.*;
+import com.fs.system.service.ISysRoleService;
 import com.fs.utils.OrderContextHolder;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -81,6 +84,7 @@ public class FsIntegralOrderController extends BaseController
     public TableDataInfo list(FsIntegralOrderParam fsIntegralOrder)
     {
         startPage();
+        List<FsIntegralOrderListVO> list = new ArrayList<>();
         if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
             if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
@@ -89,82 +93,109 @@ public class FsIntegralOrderController extends BaseController
             }
 
             // 金牛明医项目:支持多个订单ID查询
-            if (fsIntegralOrder.getOrderCodes() != null && !fsIntegralOrder.getOrderCodes().isEmpty()) {
-                // 如果传了orderIds参数,使用新的查询逻辑
-                List<FsIntegralOrder> orders = fsIntegralOrderService.selectFsIntegralOrderByOrderIdsv2(fsIntegralOrder.getOrderCodes());
-                List<FsIntegralOrderListVO> list = orders.stream()
-                    .map(this::convertOrderToListVO)
-                    .collect(Collectors.toList());
-                for (FsIntegralOrderListVO vo : list) {
-                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
-                }
-                return getDataTable(list);
-            } else {
-                // 原有逻辑:单个orderId或其他条件查询
-                List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
-                for (FsIntegralOrderListVO vo : list) {
-                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
-                }
-                return getDataTable(list);
-            }
+//            if (fsIntegralOrder.getOrderCodes() != null && !fsIntegralOrder.getOrderCodes().isEmpty()) {
+//                // 如果传了orderIds参数,使用新的查询逻辑
+//                List<FsIntegralOrder> orders = fsIntegralOrderService.selectFsIntegralOrderByOrderIdsv2(fsIntegralOrder.getOrderCodes());
+//                List<FsIntegralOrderListVO> list = orders.stream()
+//                    .map(this::convertOrderToListVO)
+//                    .collect(Collectors.toList());
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            } else {
+//                // 原有逻辑:单个orderId或其他条件查询
+//                List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            }
+            list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
         }
-        List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
-        for (FsIntegralOrderListVO vo : list) {
-            vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+        SysRole sysRole = isCheckPermission();
+        if (sysRole != null && !(sysRole.getIsCheckPhone()==1)) {
+            for (FsIntegralOrderListVO vo : list) {
+                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+            }
         }
         return getDataTable(list);
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private SysRole isCheckPermission() {
+        SysRole sysRole = new SysRole();
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            sysRole.setIsCheckPhone(1);
+            sysRole.setIsCheckAddress(1);
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (roles != null && !roles.isEmpty()) {
+                Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+                return sysRoleService.getIsCheckPermission(roleIds);
+            }
+        }
+        return sysRole;
+    }
+
     /**
      * 导出积分商品订单列表
      */
     @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(FsIntegralOrder fsIntegralOrder)
-    {
-        if (CloudHostUtils.hasCloudHostName("金牛明医")&&fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals(6)) {
+    public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
+        List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
-            FsIntegralOrderParam param = new FsIntegralOrderParam();
-            BeanUtil.copyProperties(fsIntegralOrder, param);
-            param.setStatus("1");
-            param.setIsPush(0);
-            List<FsIntegralOrderListVO> fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(param);
-
-            // 处理商品名称:解析item_json并设置goodsName
-            for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
-                if (StringUtils.isNotEmpty(vo.getItemJson())) {
-                    try {
-                        // 尝试解析JSON格式的商品信息
-                        if (vo.getItemJson().startsWith("[")) {
-                            // 数组格式,取第一个商品
-                            com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(vo.getItemJson());
-                            if (jsonArray != null && !jsonArray.isEmpty()) {
-                                com.alibaba.fastjson.JSONObject goods = jsonArray.getJSONObject(0);
-                                if (goods != null && goods.getString("goodsName") != null) {
-                                    vo.setGoodsName(goods.getString("goodsName"));
-                                }
-                            }
-                        } else if (vo.getItemJson().startsWith("{")) {
-                            // 对象格式
-                            com.alibaba.fastjson.JSONObject goods = com.alibaba.fastjson.JSONObject.parseObject(vo.getItemJson());
+            if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
+                fsIntegralOrder.setStatus("1");
+                fsIntegralOrder.setIsPush(0);
+            }
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        // 处理商品名称:解析item_json并设置goodsName
+        for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
+            if (StringUtils.isNotEmpty(vo.getItemJson())) {
+                try {
+                    // 尝试解析JSON格式的商品信息
+                    if (vo.getItemJson().startsWith("[")) {
+                        // 数组格式,取第一个商品
+                        com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(vo.getItemJson());
+                        if (jsonArray != null && !jsonArray.isEmpty()) {
+                            com.alibaba.fastjson.JSONObject goods = jsonArray.getJSONObject(0);
                             if (goods != null && goods.getString("goodsName") != null) {
                                 vo.setGoodsName(goods.getString("goodsName"));
                             }
                         }
-                    } catch (Exception e) {
-                        // 解析失败时保持goodsName为空,避免导出出错
-                        log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
+                    } else if (vo.getItemJson().startsWith("{")) {
+                        // 对象格式
+                        com.alibaba.fastjson.JSONObject goods = com.alibaba.fastjson.JSONObject.parseObject(vo.getItemJson());
+                        if (goods != null && goods.getString("goodsName") != null) {
+                            vo.setGoodsName(goods.getString("goodsName"));
+                        }
                     }
+                } catch (Exception e) {
+                    // 解析失败时保持goodsName为空,避免导出出错
+                    log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
                 }
-                // 解密手机号
+            }
+            if (!(sysRole.getIsCheckPhone()==1)){
+                // 加密手机号
                 vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
             }
 
-            ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
-            return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
         }
-        return fsIntegralOrderService.export(fsIntegralOrder);
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
     }
     /**
      * 发货
@@ -460,32 +491,4 @@ public class FsIntegralOrderController extends BaseController
         return df;
     }
 
-    /**
-     * 将 FsIntegralOrder 转换为 FsIntegralOrderListVO
-     * @param order 积分订单实体
-     * @return 转换后的VO对象
-     */
-    private FsIntegralOrderListVO convertOrderToListVO(FsIntegralOrder order) {
-        FsIntegralOrderListVO vo = new FsIntegralOrderListVO();
-        vo.setOrderId(order.getOrderId());
-        vo.setOrderCode(order.getOrderCode());
-        vo.setUserId(order.getUserId());
-        vo.setUserName(order.getUserName());
-        vo.setUserPhone(order.getUserPhone());
-        vo.setUserAddress(order.getUserAddress());
-        vo.setItemJson(order.getItemJson());
-        vo.setIntegral(order.getIntegral());
-        vo.setStatus(order.getStatus() != null ? order.getStatus().toString() : null);
-        vo.setDeliveryCode(order.getDeliveryCode());
-        vo.setDeliveryName(order.getDeliveryName());
-        vo.setDeliverySn(order.getDeliverySn());
-        vo.setDeliveryTime(order.getDeliveryTime());
-        vo.setCreateTime(order.getCreateTime());
-        vo.setQwUserId(order.getQwUserId());
-        vo.setCompanyUserId(order.getCompanyUserId());
-        vo.setCompanyId(order.getCompanyId());
-        vo.setPayMoney(order.getPayMoney());
-        vo.setLoginAccount(order.getLoginAccount());
-        return vo;
-    }
 }

+ 21 - 0
fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java

@@ -47,6 +47,7 @@ import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -73,6 +74,7 @@ import static com.fs.his.utils.PhoneUtil.*;
  */
 @RestController
 @RequestMapping("/his/storeOrder")
+@Slf4j
 public class FsStoreOrderController extends BaseController
 {
     @Autowired
@@ -421,6 +423,14 @@ public class FsStoreOrderController extends BaseController
             tuiMoneyLogs=moneyLogsService.selectCompanyStoreOrderMoneyLogsList(moneyLogsMap);
         }
         if ((CloudHostUtils.hasCloudHostName("金牛明医"))){
+            if (order.getStatus() == 2 ){
+                FsUserInfoCollectionAndStoreOrderVo infoVo = fsPackageOrderService.selectInformationCollectionByStoreOrderId(orderId);
+                if (infoVo!=null) {
+                    if (infoVo.getDoctorType2Confirm() == null || infoVo.getDoctorType2Confirm()!=1) {
+                        order.setStatus(7);
+                    }
+                }
+            }
             return R.ok().put("data",order).put("tuiMoneyLogs",tuiMoneyLogs).put("isUpdateRefund",1).put("isUpdatePayRemain",1);
         } else {
             return R.ok().put("data",order).put("tuiMoneyLogs",tuiMoneyLogs).put("isUpdateRefund",0).put("isUpdatePayRemain",0);
@@ -482,6 +492,17 @@ public class FsStoreOrderController extends BaseController
     {
         AjaxResult error = moneyCheck(fsStoreOrder);
         if (error != null) return error;
+        try {
+            FsUserInfoCollectionAndStoreOrderVo infoVo = fsPackageOrderService.selectInformationCollectionByStoreOrderId(fsStoreOrder.getOrderId());
+            if (infoVo != null) {
+                Integer doctorType2Confirm = infoVo.getDoctorType2Confirm();
+                if (doctorType2Confirm == null || doctorType2Confirm != 1) {
+                    return AjaxResult.error("信息采集订单药师未确认,不能修改状态!");
+                }
+            }
+        } catch (Exception e) {
+            log.error("信息采集查询错误,orderId: " + fsStoreOrder.getOrderId() + ",信息:{}"+ e);
+        }
         return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrder));
     }
 

+ 107 - 0
fs-admin/src/main/java/com/fs/his/controller/MerchantAppConfigController.java

@@ -0,0 +1,107 @@
+package com.fs.his.controller;
+
+import java.util.List;
+
+import com.fs.his.domain.MerchantAppConfig;
+import com.fs.his.service.IMerchantAppConfigService;
+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.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 商户应用配置Controller
+ *
+ * @author fs
+ * @date 2025-12-05
+ */
+@RestController
+@RequestMapping("/his/merchantAppConfig")
+public class MerchantAppConfigController extends BaseController
+{
+    @Autowired
+    private IMerchantAppConfigService merchantAppConfigService;
+
+    /**
+     * 查询商户应用配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(MerchantAppConfig merchantAppConfig)
+    {
+        startPage();
+        List<MerchantAppConfig> list = merchantAppConfigService.selectMerchantAppConfigList(merchantAppConfig);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出商户应用配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:export')")
+    @Log(title = "商户应用配置", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(MerchantAppConfig merchantAppConfig)
+    {
+        List<MerchantAppConfig> list = merchantAppConfigService.selectMerchantAppConfigList(merchantAppConfig);
+        ExcelUtil<MerchantAppConfig> util = new ExcelUtil<MerchantAppConfig>(MerchantAppConfig.class);
+        return util.exportExcel(list, "商户应用配置数据");
+    }
+
+    /**
+     * 获取商户应用配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(merchantAppConfigService.selectMerchantAppConfigById(id));
+    }
+
+    /**
+     * 新增商户应用配置
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:add')")
+    @Log(title = "商户应用配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody MerchantAppConfig merchantAppConfig)
+    {
+        merchantAppConfig.setCreatedBy(getUsername());
+        merchantAppConfig.setUpdatedBy(getUsername());
+        return toAjax(merchantAppConfigService.insertMerchantAppConfig(merchantAppConfig));
+    }
+
+    /**
+     * 修改商户应用配置
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:edit')")
+    @Log(title = "商户应用配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody MerchantAppConfig merchantAppConfig)
+    {
+        merchantAppConfig.setUpdatedBy(getUsername());
+        return toAjax(merchantAppConfigService.updateMerchantAppConfig(merchantAppConfig));
+    }
+
+    /**
+     * 删除商户应用配置
+     */
+    @PreAuthorize("@ss.hasPermi('his:merchantAppConfig:remove')")
+    @Log(title = "商户应用配置", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long ids)
+    {
+        return toAjax(merchantAppConfigService.deleteMerchantAppConfigById(ids));
+    }
+}

+ 63 - 8
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -3,6 +3,7 @@ package com.fs.his.task;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -33,12 +34,10 @@ import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.dto.ErpOrderResponse;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
-import com.fs.fastGpt.domain.FastGptEventTokenLog;
-import com.fs.fastGpt.domain.FastGptPushTokenTotal;
-import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
-import com.fs.fastGpt.domain.FastgptEventLogTotal;
+import com.fs.fastGpt.domain.*;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
 import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.fastGpt.service.AiHookService;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.fastgptApi.util.AudioUtils;
 import com.fs.fastgptApi.vo.AudioVO;
@@ -67,7 +66,9 @@ import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.*;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSopTempVoice;
@@ -225,6 +226,53 @@ public class Task {
     @Autowired
     private FsIntegralOrderMapper fsIntegralOrderMapper;
 
+    private final String DELAY_MSG = "delayMsg";
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private AiHookService aiHookService;
+    @Autowired
+    private IFsUserInformationCollectionService fsUserInformationCollectionService;
+
+
+    /**
+     * 定时任务,处理ai禁止回复之后的消息
+     */
+    public void forbidTimeMsgTask() {
+        Map<String, Object> cacheMap = redisCache.hGetAll(DELAY_MSG);
+        for (String key : cacheMap.keySet()) {
+            String value = (String) cacheMap.get(key);
+            //获取sessionId
+            Long sessionId = Long.parseLong(key);
+            try {
+                Thread.sleep(800);
+            } catch (Exception e) {
+                log.error("定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
+            }
+            try {
+                if (value != null && !value.isEmpty()) {
+                    FastGptChatSession chatSession = fastGptChatSessionMapper.selectFastGptChatSessionBySessionId(sessionId);
+                    Long qwUserId = chatSession.getQwUserId();
+                    QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+                    String uid = qwUser.getUid();
+
+                    JSONObject jsonObject = JSONObject.parseObject(value);
+                    String content = jsonObject.getString("content");
+                    Long sender = jsonObject.getLong("sender");
+                    Integer type = jsonObject.getInteger("type");
+
+                    aiHookService.qwHookNotifyAiReply(qwUserId,sender,content,uid,type);
+                    //删除已经处理的缓存
+                    redisCache.hDel(DELAY_MSG,key);
+                }
+            } catch (Exception e) {
+                log.error("个人定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
+            }
+        }
+    }
+
     /**
      * sop任务token消耗统计
      */
@@ -708,14 +756,14 @@ public class Task {
         //查询没有物流字段但是创建过的,如果有则把待发货状态改成待收货
         List<FsIntegralOrderDf> integralDf = integralOrderDfMapper.selectByIsPush();
         if (integralDf.isEmpty()) {
-            log.info("⏹️ 没有需要推送的订单明细,流程结束");
+            log.info("⏹️ 没有需要发货的积分订单,流程结束");
             return;
         }
-        log.info("📊 查询到 {} 条未查询的订单明细", integralDf.size());
+        log.info("📊 查询到 {} 条需要发货的积分订单", integralDf.size());
         //只判断类型,给个对象
         OrderContextHolder.setIntegralOrder(new FsIntegralOrder());
         for (FsIntegralOrderDf df : integralDf) {
-            log.info("🔄 开始处理订单明细,订单号: {}, orderId: {}", df.getOrderCode(), df.getOrderId());
+            log.info("🔄 开始处理需要发货的积分订单,订单号: {}, orderId: {}", df.getOrderCode(), df.getOrderId());
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
             request.setCode(df.getOrderCode());
             erpOrderService.getOrder(request);
@@ -740,7 +788,7 @@ public class Task {
             FsStoreOrder order1 = new FsStoreOrder();
             order1.setDeliverySn(currentOrder.getDeliverySn());
             order1.setOrderCode(currentOrder.getOrderCode());
-
+            order1.setOrderId(currentOrder.getOrderId());
             // 异步执行,使用局部变量副本
             CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                 OrderContextHolder.setIntegralOrder(currentOrder);
@@ -1764,5 +1812,12 @@ public class Task {
         allFutures.join(); // 等待所有任务完成
     }
 
+    /**
+     * 金牛:信息采集超过一小时未确认
+     */
+    public void informationDoctorConfirm(){
+        fsUserInformationCollectionService.autoInformationDoctorConfirm();
+    }
+
 
 }

+ 63 - 3
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -1,5 +1,6 @@
 package com.fs.hisStore.controller;
 
+import cn.hutool.core.bean.BeanUtil;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -27,12 +28,18 @@ import com.fs.hisStore.service.IFsStoreAfterSalesScrmService;
 import com.fs.hisStore.service.IFsStoreAfterSalesStatusScrmService;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.vo.FsStoreAfterSalesVO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportRefundZMVO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportZMVO;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * 售后记录Controller
@@ -57,6 +64,10 @@ public class FsStoreAfterSalesScrmController extends BaseController
 
     @Autowired
     private TokenService tokenService;
+
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     /**
      * 查询售后记录列表
      */
@@ -84,16 +95,65 @@ public class FsStoreAfterSalesScrmController extends BaseController
             fsStoreAfterSales.setBeginTime(null);
             fsStoreAfterSales.setEndTime(null);
         }
-        Boolean a = fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales);
-        if (fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales)){
+//        Boolean a = fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales);
+        if (fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales) && Objects.isNull(fsStoreAfterSales.getParams())){
             return AjaxResult.error("请筛选数据导出");
         }
+
         List<FsStoreAfterSalesVO> list = fsStoreAfterSalesService.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportRefundZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportRefundZMVO zmvo = new FsStoreOrderItemExportRefundZMVO();
+                        try {
+                            zmvo.setOrderCode(vo.getOrderCode());
+                            zmvo.setStatus(vo.getOrderStatus().toString());
+                            zmvo.setUserId(vo.getUserId());
+                            zmvo.setProductName(vo.getProductName());
+                            zmvo.setBarCode(vo.getProductBarCode());
+                            zmvo.setSku(vo.getSku());
+                            zmvo.setNum(vo.getNum());
+                            zmvo.setPrice(vo.getPrice());
+                            zmvo.setCost(vo.getCost());
+//                            zmvo.setFPrice("");
+                            zmvo.setPayMoney(vo.getPayMoney());
+                            zmvo.setPayPostage(vo.getTotalPostage());
+                            zmvo.setCateName(vo.getCateName());
+                            zmvo.setRealName(vo.getUserName());
+                            zmvo.setUserPhone(vo.getUserPhone());
+                            zmvo.setUserAddress(vo.getUserAddress());
+                            zmvo.setCreateTime(vo.getCreateTime());
+                            zmvo.setPayTime(vo.getOrderPayTime());
+                            zmvo.setDeliverySn(vo.getOrderDeliverySn());
+                            zmvo.setDeliveryName(vo.getOrderDeliveryName());
+                            zmvo.setDeliveryId(vo.getOrderDeliveryId());
+                            zmvo.setCompanyName(vo.getCompanyName());
+                            zmvo.setCompanyUserNickName(vo.getCompanyUserNickName());
+                            zmvo.setRefundTime(vo.getCreateTime());
+//                            zmvo.setAfterSalesNumber
+                            zmvo.setRefundMoney(vo.getRefundAmount());
+                            zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
+
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            for (FsStoreOrderItemExportRefundZMVO vo : zmvoList){
+                vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            }
+            ExcelUtil<FsStoreOrderItemExportRefundZMVO> util = new ExcelUtil<FsStoreOrderItemExportRefundZMVO>(FsStoreOrderItemExportRefundZMVO.class);
+            return util.exportExcel(zmvoList, "退款订单导出");
+        }
         for (FsStoreAfterSalesVO vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
         }
         ExcelUtil<FsStoreAfterSalesVO> util = new ExcelUtil<FsStoreAfterSalesVO>(FsStoreAfterSalesVO.class);
-        return util.exportExcel(list, "storeAfterSales");
+        return util.exportExcel(list, "退款订单导出");
     }
     /**
      * 获取售后记录详细信息

+ 45 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java

@@ -28,6 +28,7 @@ import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
@@ -35,6 +36,7 @@ import org.springframework.web.multipart.MultipartFile;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.stream.Collectors;
 
 @RestController
 @RequestMapping("/store/store/storeOrder")
@@ -71,6 +73,9 @@ public class FsStoreHealthOrderScrmController extends BaseController {
     // 最大文件大小(5MB)
     private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
 
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     /**
      * 查询健康商城订单列表
      */
@@ -112,7 +117,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     }
                 }
                 //
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
+                if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") && (vo.getCost() !=null && vo.getTotalNum() != null))) {
                     vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);
@@ -342,6 +347,45 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         }
         param.setIsHealth("1");
         List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportZMVO zmvo = new FsStoreOrderItemExportZMVO();
+                        try {
+                            BeanUtil.copyProperties( vo,zmvo);
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            //对手机号脱敏
+            if (zmvoList != null) {
+                    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+                    for (FsStoreOrderItemExportZMVO vo : zmvoList) {
+                        if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                            try {
+                                StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                                BeanUtil.copyProperties(orderProductDTO, vo);
+                            } catch (Exception e) {
+                            }
+                        }
+                        if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
+                            vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
+                        } else {
+                            vo.setPayPostage(BigDecimal.ZERO);
+                            vo.setCost(BigDecimal.ZERO);
+                            vo.setFPrice(BigDecimal.ZERO);
+                            vo.setBarCode("");
+                            vo.setCateName("");
+                            vo.setBankTransactionId("");
+                        }
+                    }
+                }
+                ExcelUtil<FsStoreOrderItemExportZMVO> util = new ExcelUtil<FsStoreOrderItemExportZMVO>(FsStoreOrderItemExportZMVO.class);
+                return util.exportExcel(zmvoList, "订单明细数据");
+        }
         //对手机号脱敏
         if (list != null) {
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());

+ 79 - 7
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -8,6 +8,8 @@ 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.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
@@ -53,11 +55,13 @@ import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -137,6 +141,9 @@ public class FsStoreOrderScrmController extends BaseController {
     @Autowired
     private IFsStoreOrderLogsScrmService fsStoreOrderLogsService;
 
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     private IErpOrderService getErpService(){
         //判断是否开启erp
         IErpOrderService erpOrderService = null;
@@ -336,21 +343,25 @@ public class FsStoreOrderScrmController extends BaseController {
         }
         param.setNotHealth(1);
         List<FsStoreOrderErpExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
+
         //对手机号脱敏
         if (list != null) {
-            //获取当前账号角色权限
-            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            SysRole sysRole = isCheckPermission();
 
             for (FsStoreOrderErpExportVO vo : list) {
-                if (vo.getPhone() != null) {
+                if (vo.getPhone() != null && sysRole.getIsCheckPhone() != 1) {
                     vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
                 }
-                if (vo.getUserPhone() != null) {
+                if (vo.getUserPhone() != null && sysRole.getIsCheckPhone() != 1) {
                     vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
                 }
-                if (vo.getUserAddress()!=null){
+                if (vo.getUserAddress()!=null && sysRole.getIsCheckAddress() != 1){
                     vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
                 }
+                //商品明细
+                String orderItem = orderItemService.selectFsStoreOrderItemByOrderId(vo.getId());
+                vo.setOrderItem(orderItem);
+
             }
         }
         String filter = param.getFilter();
@@ -373,6 +384,25 @@ public class FsStoreOrderScrmController extends BaseController {
         }
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private SysRole isCheckPermission() {
+        SysRole sysRole = new SysRole();
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            sysRole.setIsCheckPhone(1);
+            sysRole.setIsCheckAddress(1);
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (roles != null && !roles.isEmpty()) {
+                Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+                return sysRoleService.getIsCheckPermission(roleIds);
+            }
+        }
+        return sysRole;
+    }
+
 
     /**
      * 导出订单列表(明文)
@@ -449,14 +479,15 @@ public class FsStoreOrderScrmController extends BaseController {
         List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
         //对手机号脱敏
         if (list != null) {
+            SysRole sysRole = isCheckPermission();
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
 
             for (FsStoreOrderItemExportVO vo : list) {
-                if (vo.getUserPhone() != null) {
+                if (vo.getUserPhone() != null && sysRole.getIsCheckPhone() != 1) {
                     String phone = vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{1})", "$1****$2");
                     vo.setUserPhone(phone);
                 }
-                if (vo.getUserAddress()!=null){
+                if (vo.getUserAddress()!=null && sysRole.getIsCheckAddress() != 1){
                     vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
                 }
                 if (!StringUtils.isEmpty(vo.getJsonInfo())) {
@@ -511,6 +542,47 @@ public class FsStoreOrderScrmController extends BaseController {
         }
         param.setNotHealth(1);
         List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportZMVO zmvo = new FsStoreOrderItemExportZMVO();
+                        try {
+                            BeanUtil.copyProperties( vo,zmvo);
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            //对手机号脱敏
+            if (zmvoList != null) {
+                LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+                for (FsStoreOrderItemExportZMVO vo : zmvoList) {
+                    if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                        try {
+                            StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                            BeanUtil.copyProperties(orderProductDTO, vo);
+                        } catch (Exception e) {
+                        }
+                    }
+                    //
+                    if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
+                        vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
+                    } else {
+                        vo.setPayPostage(BigDecimal.ZERO);
+                        vo.setCost(BigDecimal.ZERO);
+                        vo.setFPrice(BigDecimal.ZERO);
+                        vo.setBarCode("");
+                        vo.setCateName("");
+                        vo.setBankTransactionId("");
+                    }
+                }
+            }
+            ExcelUtil<FsStoreOrderItemExportZMVO> util = new ExcelUtil<FsStoreOrderItemExportZMVO>(FsStoreOrderItemExportZMVO.class);
+            return util.exportExcel(zmvoList, "订单明细数据");
+        }
+
         //对手机号脱敏
         if (list != null) {
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());

+ 1 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java

@@ -223,6 +223,7 @@ public class FsStorePaymentScrmController extends BaseController
                 request.setOrdAmt(payment.getPayMoney().toString());
                 request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
                 request.setReqSeqId("refund-"+payment.getPayCode());
+                request.setAppId(payment.getAppId());
                 Map<String, Object> extendInfoMap = new HashMap<>();
                 extendInfoMap.put("org_party_order_id", payment.getBankSerialNo());
                 request.setExtendInfo(extendInfoMap);

+ 9 - 9
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreScrmController.java

@@ -37,7 +37,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 查询店铺管理列表
      */
-    @PreAuthorize("@ss.hasPermi('his:store:list')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsStoreScrm fsStore)
     {
@@ -52,7 +52,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 导出店铺管理列表
      */
-    @PreAuthorize("@ss.hasPermi('his:store:export')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:export')")
     @Log(title = "店铺管理", businessType = BusinessType.EXPORT,logParam = {"店铺","导出店铺信息"},isStoreLog = true)
     @GetMapping("/export")
     public AjaxResult export(FsStoreScrm fsStore)
@@ -69,7 +69,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 获取店铺管理详细信息
      */
-    @PreAuthorize("@ss.hasPermi('his:store:query')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:query')")
     @GetMapping(value = "/{storeId}")
     public AjaxResult getInfo(@PathVariable("storeId") Long storeId)
     {
@@ -81,7 +81,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 新增店铺管理
      */
-    @PreAuthorize("@ss.hasPermi('his:store:add')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:add')")
     @Log(title = "店铺管理", businessType = BusinessType.INSERT,logParam = {"店铺","新增店铺信息"},isStoreLog = true)
     @PostMapping
     public AjaxResult add(@RequestBody FsStoreScrm fsStore)
@@ -93,7 +93,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 修改店铺管理
      */
-    @PreAuthorize("@ss.hasPermi('his:store:edit')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:edit')")
     @Log(title = "店铺管理", businessType = BusinessType.UPDATE,logParam = {"店铺","修改店铺信息"},isStoreLog = true)
     @PutMapping
     public AjaxResult edit(@RequestBody FsStoreScrm fsStore)
@@ -108,7 +108,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 删除店铺管理
      */
-    @PreAuthorize("@ss.hasPermi('his:store:remove')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:remove')")
     @Log(title = "店铺管理", businessType = BusinessType.DELETE,logParam = {"店铺","删除店铺信息"},isStoreLog = true)
 	@DeleteMapping("/{storeIds}")
     public AjaxResult remove(@PathVariable Long[] storeIds)
@@ -119,7 +119,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 店铺审核
      */
-    @PreAuthorize("@ss.hasPermi('his:store:audit')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:audit')")
     @Log(title = "店铺审核", businessType = BusinessType.AUDIT,logParam = {"店铺","店铺审核"},isStoreLog = true
     ,logParamExpression = "#p0.getIsAudit()==1?new String[]{'店铺','店铺审核通过'}: new String[]{'店铺','店铺审核退回'}")
     @PutMapping("/audit")
@@ -131,7 +131,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 重置店铺密码
      * */
-    @PreAuthorize("@ss.hasPermi('his:store:refresh')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:refresh')")
     @Log(title = "店铺管理", businessType = BusinessType.UPDATE,logParam = {"店铺","重置店铺密码"},isStoreLog = true)
     @PutMapping("/refresh/{storeId}")
     public AjaxResult refresh(@PathVariable Long storeId)
@@ -143,7 +143,7 @@ public class FsStoreScrmController extends BaseController
     /**
      * 店铺审核日志
      * */
-    @PreAuthorize("@ss.hasPermi('his:store:auditLog')")
+    @PreAuthorize("@ss.hasPermi('store:his:store:auditLog')")
     @GetMapping("/auditLog/{storeId}")
     public R auditLog(@PathVariable Long storeId){
         return R.ok().put("auditLog",storeAuditLogUtil.selectOperLogByMainId(storeId));

+ 18 - 1
fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java

@@ -12,6 +12,7 @@ import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
+import com.fs.erp.service.FsJstAftersalePushScrmService;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.dto.ExpressInfoDTO;
@@ -160,6 +161,14 @@ public class LiveTask {
     @Autowired
     private IFsStoreOrderScrmService orderService;
 
+    @Autowired
+    private FsJstAftersalePushScrmService fsJstAftersalePushScrmService;
+
+    // 聚水潭 推送售后信息
+    public void pushJst(){
+        fsJstAftersalePushScrmService.pushJst();
+    }
+
 
     // 订单银行回调数据丢失补偿
     public void recoveryBankOrder() {
@@ -198,8 +207,16 @@ public class LiveTask {
         if (ids.size() > 50) {
             ids = ids.subList(0, 50);
         }
+//        liveOrderService.batchUpdateTimeIds(ids);
+        // 单个异常影响全部,跳过异常单子
         for (Long id : ids) {
-            liveOrderService.createOmsOrder(id);
+            try {
+                liveOrderService.createOmsOrder(id);
+            } catch (Exception e) {
+                log.error("创建直播oms订单失败:"+id);
+                log.error("创建直播oms订单失败:"+e.getMessage());
+            }
+
         }
     }
 

+ 22 - 1
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -3,7 +3,9 @@ package com.fs.hisStore.task;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -32,6 +34,7 @@ import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
+import com.fs.live.domain.LiveOrder;
 import com.fs.pay.pay.dto.OrderQueryDTO;
 import com.fs.pay.service.IPayService;
 import com.fs.store.config.StoreConfig;
@@ -49,6 +52,7 @@ import java.math.BigDecimal;
 import java.text.ParseException;
 import java.time.LocalTime;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
@@ -168,8 +172,18 @@ public class MallStoreTask
         if (!ids.isEmpty() && ids.size() > 50) {
             ids = ids.subList(0, 50);
         }
+        // 单个异常影响全部,跳过异常单子
         for (Long id : ids) {
-            fsStoreOrderService.createOmsOrder(id);
+            try {
+                R omsOrder = fsStoreOrderService.createOmsOrder(id);
+                if ("500".equals(omsOrder.get("code"))) {
+
+                }
+            } catch (Exception e) {
+                log.error("创建商城oms订单失败:"+id);
+                log.error("创建商城oms订单失败:"+e.getMessage());
+            }
+
         }
     }
 
@@ -210,6 +224,13 @@ public class MallStoreTask
         if (list != null && list.size() > 50) {
             list = list.subList(0, 50);
         }
+        Date nowDate = DateUtils.getNowDate();
+        for (FsStoreOrderScrm order : list) {
+            order.setUpdateTime(nowDate);
+        }
+        if (list!= null && !list.isEmpty()){
+            fsStoreOrderMapper.batchUpdateTime(list);
+        }
         for (FsStoreOrderScrm order : list){
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
             request.setCode(order.getExtendOrderId());

+ 54 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java

@@ -16,6 +16,7 @@ import com.fs.his.domain.FsStoreAfterSalesLogs;
 import com.fs.his.domain.FsUser;
 import com.fs.his.enums.FsStoreAfterSalesStatusEnum;
 import com.fs.his.service.IFsUserService;
+import com.fs.hisStore.vo.FsStoreOrderItemExportRefundZMVO;
 import com.fs.live.domain.LiveAfterSales;
 import com.fs.live.domain.LiveAfterSalesItem;
 import com.fs.live.domain.LiveAfterSalesLogs;
@@ -31,11 +32,13 @@ import com.fs.live.service.ILiveOrderService;
 import com.fs.live.vo.LiveAfterSalesVo;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.ParseException;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 售后记录Controller
@@ -60,6 +63,9 @@ public class LiveAfterSalesController extends BaseController
     private IFsUserService userService;
     @Autowired
     private ILiveOrderService orderService;
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
 
     /**
      * 获取售后记录详细信息
@@ -105,6 +111,53 @@ public class LiveAfterSalesController extends BaseController
         PageHelper.clearPage();
         PageHelper.startPage(1, 10000, "");
         List<LiveAfterSalesVo> list = liveAfterSalesService.selectLiveAfterSalesVoList(liveAfterSales);
+        if("北京卓美".equals(signProjectName)){
+            List<FsStoreOrderItemExportRefundZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportRefundZMVO zmvo = new FsStoreOrderItemExportRefundZMVO();
+                        try {
+                            zmvo.setOrderCode(vo.getOrderCode());
+                            zmvo.setStatus(vo.getOrderStatus().toString());
+                            zmvo.setUserId(vo.getUserId());
+                            zmvo.setProductName(vo.getProductName());
+                            zmvo.setBarCode(vo.getProductBarCode());
+                            zmvo.setSku(vo.getSku());
+                            zmvo.setNum(vo.getNum());
+                            zmvo.setPrice(vo.getPrice());
+                            zmvo.setCost(vo.getCost());
+//                            zmvo.setFPrice("");
+                            zmvo.setPayMoney(vo.getPayMoney());
+                            zmvo.setPayPostage(vo.getTotalPostage());
+                            zmvo.setCateName(vo.getCateName());
+                            zmvo.setRealName(vo.getUserName());
+                            zmvo.setUserPhone(vo.getUserPhone());
+                            zmvo.setUserAddress(vo.getUserAddress());
+                            zmvo.setCreateTime(vo.getCreateTime());
+                            zmvo.setPayTime(vo.getOrderPayTime());
+                            zmvo.setDeliverySn(vo.getOrderDeliverySn());
+                            zmvo.setDeliveryName(vo.getOrderDeliveryName());
+                            zmvo.setDeliveryId(vo.getOrderDeliveryId());
+                            zmvo.setCompanyName(vo.getCompanyName());
+                            zmvo.setCompanyUserNickName(vo.getCompanyUserNickName());
+                            zmvo.setRefundTime(vo.getCreateTime());
+//                            zmvo.setAfterSalesNumber
+                            zmvo.setRefundMoney(vo.getRefundAmount());
+                            zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
+                        } catch (Exception e) {
+                            // 处理异常
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            for (FsStoreOrderItemExportRefundZMVO vo : zmvoList){
+                vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            }
+            ExcelUtil<FsStoreOrderItemExportRefundZMVO> util = new ExcelUtil<FsStoreOrderItemExportRefundZMVO>(FsStoreOrderItemExportRefundZMVO.class);
+            return util.exportExcel(zmvoList, "退款订单导出");
+        }
         for (LiveAfterSalesVo liveAfterSalesVo : list) {
             liveAfterSalesVo.setUserPhone(liveAfterSalesVo.getUserPhone() == null ? "" : liveAfterSalesVo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
             liveAfterSalesVo.setPhoneNumber(liveAfterSalesVo.getPhoneNumber() == null ? "" : liveAfterSalesVo.getPhoneNumber().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
@@ -142,6 +195,7 @@ public class LiveAfterSalesController extends BaseController
         logs.setOperator(loginUser.getUser().getNickName());
         logs.setStoreAfterSalesId(liveAfterSales.getId());
         logs.setChangeMessage(FsStoreAfterSalesStatusEnum.STATUS_2.getDesc());
+        liveAfterSales.setStatus(FsStoreAfterSalesStatusEnum.STATUS_2.getValue());
         liveAfterSalesLogsService.insertLiveAfterSalesLogs(logs);
         return toAjax(liveAfterSalesService.updateLiveAfterSales(liveAfterSales));
     }

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

@@ -4,14 +4,19 @@ 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.framework.web.service.TokenService;
 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;
+import com.hc.openapi.tool.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -28,10 +33,14 @@ import java.util.Map;
  */
 @RestController
 @RequestMapping("/live/live")
+@Slf4j
 public class LiveController extends BaseController {
     @Autowired
     private ILiveService liveService;
 
+    @Autowired
+    private TokenService tokenService;
+
 
     /**
      * 查询直播列表
@@ -63,7 +72,7 @@ public class LiveController extends BaseController {
     @PreAuthorize("@ss.hasPermi('live:live:query')")
     @GetMapping(value = "/{liveId}")
     public AjaxResult getInfo(@PathVariable("liveId") Long liveId) {
-        return AjaxResult.success(liveService.selectLiveByLiveId(liveId));
+        return AjaxResult.success(liveService.selectLiveDbByLiveId(liveId));
     }
 
     /**
@@ -83,6 +92,8 @@ public class LiveController extends BaseController {
     @Log(title = "直播", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody Live live) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
         return toAjax(liveService.updateLive(live));
     }
 
@@ -93,6 +104,8 @@ public class LiveController extends BaseController {
     @Log(title = "直播", businessType = BusinessType.DELETE)
     @DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
         return toAjax(liveService.deleteLiveByLiveIds(liveIds, new Live()));
     }
 
@@ -116,6 +129,8 @@ public class LiveController extends BaseController {
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleShelfOrUn")
     public R handleShelfOrUn(@RequestBody LiveListVo listVo) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleShelfOrUnAdmin(listVo);
     }
 
@@ -125,6 +140,8 @@ public class LiveController extends BaseController {
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleDeleteSelected")
     public R handleDeleteSelected(@RequestBody LiveListVo listVo) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleDeleteSelectedAdmin(listVo);
     }
     /**

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

@@ -133,7 +133,7 @@ public class LiveDataController extends BaseController {
     @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
     @GetMapping("/getLiveUserDetailListBySql")
     public R getLiveUserDetailListBySql(@RequestParam Long liveId) {
-        return liveDataService.getLiveUserDetailListBySql(liveId);
+        return liveDataService.getLiveUserDetailListBySql(liveId,null,null);
     }
 
     /**
@@ -167,11 +167,11 @@ public class LiveDataController extends BaseController {
     @Log(title = "直播间用户详情", businessType = BusinessType.EXPORT)
     @GetMapping("/exportLiveUserDetail")
     public AjaxResult exportLiveUserDetail(@RequestParam Long liveId) {
-        List<LiveUserDetailExportVO> list = liveDataService.exportLiveUserDetail(liveId);
+        List<LiveUserDetailExportVO> list = liveDataService.exportLiveUserDetail(liveId,null,null);
         if (list == null || list.isEmpty()) {
             return AjaxResult.error("未找到用户详情数据");
         }
-        
+
         ExcelUtil<LiveUserDetailExportVO> util = new ExcelUtil<>(LiveUserDetailExportVO.class);
         return util.exportExcel(list, "直播间用户详情数据");
     }

+ 29 - 1
fs-admin/src/main/java/com/fs/live/controller/LiveOrderController.java

@@ -38,6 +38,7 @@ import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.IFsExpressScrmService;
 import com.fs.hisStore.task.ExpressTask;
 import com.fs.hisStore.task.LiveTask;
+import com.fs.hisStore.vo.FsStoreOrderItemExportZMVO;
 import com.fs.hisStore.vo.FsStoreOrderVO;
 import com.fs.live.domain.*;
 import com.fs.live.dto.LiveOrderCustomerExportDTO;
@@ -242,7 +243,34 @@ public class LiveOrderController extends BaseController
         return util.exportExcel(list, "订单数据");
     }
 
-
+    /**
+     * 导出订单列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveOrder:export')")
+    @Log(title = "订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportZmNew")
+    public AjaxResult exportZmNew(LiveOrder liveOrder){
+        List<FsStoreOrderItemExportZMVO> list = liveOrderService.selectLiveOrderListZmNew(liveOrder);
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        for (FsStoreOrderItemExportZMVO 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()));
+            // 财务独特字段
+            if (loginUser.getPermissions().contains("live:liveOrder:finance") || loginUser.getPermissions().contains("*:*:*")) {
+//                vo.setCostPrice(vo.getCost());
+                vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getNum())));
+            } else {
+                vo.setCost(BigDecimal.ZERO);
+                vo.setFPrice(BigDecimal.ZERO);
+                vo.setBankTransactionId("");
+            }
+//            vo.setCost(vo.getCostPrice());
+        }
+        ExcelUtil<FsStoreOrderItemExportZMVO> util = new ExcelUtil<FsStoreOrderItemExportZMVO>(FsStoreOrderItemExportZMVO.class);
+        return util.exportExcel(list, "订单数据");
+    }
     /**
      * 获取订单详细信息
      */

+ 55 - 0
fs-admin/src/main/java/com/fs/live/controller/OrderController.java

@@ -0,0 +1,55 @@
+package com.fs.live.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ParseUtils;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.hisStore.service.IMergedOrderService;
+import com.fs.live.param.MergedOrderQueryParam;
+import com.fs.live.vo.MergedOrderVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 合并订单Controller
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+@Api("合并订单管理")
+@RestController
+@RequestMapping("/order")
+public class OrderController extends BaseController
+{
+    @Autowired
+    private IMergedOrderService mergedOrderService;
+
+    /**
+     * 查询合并订单列表
+     */
+    @ApiOperation("查询合并订单列表")
+    @GetMapping("/list")
+    public TableDataInfo list(MergedOrderQueryParam param)
+    {
+        startPage();
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        for (MergedOrderVO vo : list) {
+            vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setPhone(ParseUtils.parsePhone(vo.getPhone()));
+            vo.setSalesPhone(ParseUtils.parsePhone(vo.getSalesPhone()));
+            vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+        }
+        return getDataTable(list);
+    }
+}

+ 30 - 6
fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java

@@ -21,12 +21,15 @@ import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopEditQwUserParam;
 import com.fs.sop.service.IQwSopService;
 import com.fs.sop.vo.SopVoiceListVo;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
@@ -53,16 +56,37 @@ public class QwSopController extends BaseController
     @Autowired
     private FsUserCourseVideoMapper fsUserCourseVideoMapper;
 
+
     /**
      * 查询企微sop列表
      */
     @PreAuthorize("@ss.hasPermi('qw:sop:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(QwSop qwSop)
+    @PostMapping("/list")
+    public R list(@RequestBody  QwSop qwSop)
     {
-        startPage();
+        List<String> userIds = qwSop.getUserIds();
+        if (userIds != null && !userIds.isEmpty()) {
+            List<Long> longs = qwUserService.selectQwUserListByCompanyUserIdS(userIds);
+            if (longs != null && !longs.isEmpty()) {
+                qwSop.getQwUserIdList().addAll(longs);
+            }else {
+                return R.ok().put("data",new ArrayList<>());
+            }
+        }
+
+
+        if(qwSop.getPageNum() == null) {
+            qwSop.setPageNum(1);
+        }
+        if(qwSop.getPageSize() == null) {
+            qwSop.setPageSize(10);
+        }
+
+        PageHelper.startPage(qwSop.getPageNum(), qwSop.getPageSize());
+
         List<QwSop> list = qwSopService.selectQwSopList(qwSop);
-        return getDataTable(list);
+
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**
@@ -104,8 +128,8 @@ public class QwSopController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('qw:sop:export')")
     @Log(title = "企微sop", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(QwSop qwSop)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody QwSop qwSop)
     {
         List<QwSop> list = qwSopService.selectQwSopList(qwSop);
         ExcelUtil<QwSop> util = new ExcelUtil<QwSop>(QwSop.class);

+ 52 - 9
fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java

@@ -12,6 +12,8 @@ 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.company.service.impl.CompanyUserServiceImpl;
+import com.fs.company.vo.DocCompanyUserVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.qw.vo.SortDayVo;
 import com.fs.sop.domain.QwSopTemp;
@@ -19,14 +21,16 @@ import com.fs.sop.domain.QwSopTempDay;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.service.IQwSopTempService;
 import com.fs.sop.vo.UpdateRedVo;
+import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -43,17 +47,56 @@ public class QwSopTempController extends BaseController
     private IQwSopTempService qwSopTempService;
     @Autowired
     private TokenService tokenService;
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
     /**
      * 查询sop模板列表
      */
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(QwSopTemp qwSopTemp)
+    @PostMapping("/list")
+    public R list(@RequestBody QwSopTemp qwSopTemp)
     {
-        startPage();
+
 //        List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
         List<QwSopTemp> list = qwSopTempService.selectQwSopTempListNew(qwSopTemp);
-        return getDataTable(list);
+        // 收集所有需要查询的用户ID
+        Set<Long> userIds = list.stream()
+                .map(QwSopTemp::getCreateBy)
+                .filter(str -> !StringUtil.strIsNullOrEmpty(str)) // 取反,保留非空值
+                .map(Long::valueOf)
+                .collect(Collectors.toSet());
+
+        if (!userIds.isEmpty()){
+            // 批量查询用户信息
+            Map<Long, DocCompanyUserVO> userMap = companyUserService
+                    .selectDocCompanyUserListByUserIds(userIds)
+                    .stream()
+                    .collect(Collectors.toMap(DocCompanyUserVO::getUserId, Function.identity()));
+
+
+            list.forEach(item->{
+
+                if (!StringUtil.strIsNullOrEmpty(item.getCreateBy())) {
+                    DocCompanyUserVO user = userMap.get(Long.valueOf(item.getCreateBy()));
+                    if (user != null) {
+                        item.setCreateByName(user.getNickName());
+                        item.setCreateByDeptName(user.getDeptName());
+                    }
+                }
+
+            });
+        }
+
+        if(qwSopTemp.getPageNum() == null) {
+            qwSopTemp.setPageNum(1);
+        }
+        if(qwSopTemp.getPageSize() == null) {
+            qwSopTemp.setPageSize(10);
+        }
+
+        PageHelper.startPage(qwSopTemp.getPageNum(), qwSopTemp.getPageSize());
+
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**
@@ -61,8 +104,8 @@ public class QwSopTempController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:export')")
     @Log(title = "sop模板", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(QwSopTemp qwSopTemp)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody QwSopTemp qwSopTemp)
     {
         List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
         ExcelUtil<QwSopTemp> util = new ExcelUtil<QwSopTemp>(QwSopTemp.class);

+ 107 - 24
fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java

@@ -4,6 +4,7 @@ import com.fs.course.service.IFsUserCourseService;
 import com.fs.qw.domain.QwIpadServerLog;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.param.QwMandatoryRegistrParam;
 import com.fs.qw.service.*;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.fs.sop.service.impl.QwSopServiceImpl;
@@ -15,10 +16,14 @@ import com.fs.wxwork.service.WxWorkService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.time.Duration;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 @Component("qwTask")
 public class qwTask {
@@ -73,6 +78,10 @@ public class qwTask {
     private WxWorkService wxWorkService;
 
 
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
+
     //正在使用
     public void qwExternalContact()
     {
@@ -240,37 +249,41 @@ public class qwTask {
      * 定时清除 占着茅坑不拉屎的ipad 账号
      */
     public void selectQwUserByUnbindIpad(){
+
+        //查询所有状态为 绑定了AI主机的
         List<QwUser> list = qwUserMapper.selectQwUserByTest();
         for (QwUser qwUser : list) {
             try {
-                Integer serverStatus = qwUser.getServerStatus();
+
                 Long serverId = qwUser.getServerId();
-                if (serverStatus==0){
-                    System.out.println("不需要解绑");
-                }
+
                 if (serverId==null){
                     System.out.println("serverId不存在");
+                }else {
+                    //没绑定销售 或者 已经离职
+                    if (qwUser.getStatus()==0 || qwUser.getIsDel()==2){
+
+                        updateIpadStatus(qwUser,serverId);
+                    }
+
+                    //绑定了销售-也绑定了ipad,但是长时间离线的(离线状态,无操作超过2天的,也自动解绑)
+                    if(qwUser.getCreateTime()!=null){
+                        Date createTime = qwUser.getCreateTime();
+                        Integer serverStatus = qwUser.getServerStatus();
+                        Integer ipadStatus = qwUser.getIpadStatus();
+
+                        boolean result = isCreateTimeMoreThanDaysWithOptional(createTime, 2);
+                        //大于2天 ,绑定了ipad,离线
+                        if(result && serverStatus==1 && ipadStatus==0){
+                            updateIpadStatus(qwUser,serverId);
+
+                        }
+                    }
+
+
                 }
-                QwUser u = new QwUser();
-                u.setId(qwUser.getId());
-                u.setServerId(null);
-                u.setServerStatus(0);
-                qwUserMapper.updateQwUser(u);
-                ipadServerService.addServer(serverId);
-                QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
-                qwIpadServerLog.setType(2);
-                qwIpadServerLog.setTilie("解绑");
-                qwIpadServerLog.setServerId(serverId);
-                qwIpadServerLog.setQwUserId(qwUser.getId());
-                qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
-                qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
-                qwIpadServerLog.setCreateTime(new Date());
-                qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
-                qwIpadServerUserService.deleteQwIpadServerUserByQwUserId(qwUser.getId());
-                WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
-                wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
-                wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
-                updateIpadStatus(qwUser.getId(),0);
+
+
             } catch (Exception e) {
                 System.out.println("解绑ipad报错"+e);
 
@@ -278,6 +291,41 @@ public class qwTask {
         }
     }
 
+    public void updateIpadStatus(QwUser qwUser,Long serverId){
+        QwUser u = new QwUser();
+        u.setId(qwUser.getId());
+        u.setServerId(null);
+        u.setServerStatus(0);
+        qwUserMapper.updateQwUser(u);
+        ipadServerService.addServer(serverId);
+        QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
+        qwIpadServerLog.setType(2);
+        qwIpadServerLog.setTilie("解绑");
+        qwIpadServerLog.setServerId(serverId);
+        qwIpadServerLog.setQwUserId(qwUser.getId());
+        qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
+        qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
+        qwIpadServerLog.setCreateTime(new Date());
+        qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
+        qwIpadServerUserService.deleteQwIpadServerUserByQwUserId(qwUser.getId());
+        WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+        wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
+        wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
+        updateIpadStatus(qwUser.getId(),0);
+    }
+
+    public static boolean isCreateTimeMoreThanDaysWithOptional(Date createTime, int days) {
+        return Optional.ofNullable(createTime)
+                .map(time -> {
+                    LocalDateTime createDateTime = time.toInstant()
+                            .atZone(ZoneId.systemDefault())
+                            .toLocalDateTime();
+                    LocalDateTime now = LocalDateTime.now();
+                    Duration duration = Duration.between(createDateTime, now);
+                    return duration.toDays() > days;
+                })
+                .orElse(false); // 为null时返回false,可根据需求调整
+    }
 
     void updateIpadStatus(Long id ,Integer status){
         QwUser u = new QwUser();
@@ -286,4 +334,39 @@ public class qwTask {
         qwUserMapper.updateQwUser(u);
     }
 
+
+    /**
+     * 强制注册-2025-11-12 之后的 更新
+     */
+    public void QwExternalContactMandatoryRegistration(){
+        try {
+//            List<String> longs = qwExternalContactService.selectQwExternalContactMandatoryRegistration();
+            List<String> longs = iQwCompanyService.selectQwCompanyListFormCorpId();
+            longs.forEach(item->{
+                List<QwMandatoryRegistrParam> registrParamList = qwExternalContactService.selectQwExternalContactMandatoryRegistrationByIds(String.valueOf(item));
+
+                batchUpdateQwExternalContactMandatoryRegistration(registrParamList);
+
+            });
+        }catch (Exception e){
+
+        }
+
+    }
+
+
+    private void batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> registrParamList) {
+        // 定义批量插入的大小
+        int batchSize = 300;
+
+        // 循环处理外部用户 ID,每次处理批量大小的子集
+        for (int i = 0; i < registrParamList.size(); i += batchSize) {
+
+            int endIndex = Math.min(i + batchSize, registrParamList.size());
+            List<QwMandatoryRegistrParam> batchList = registrParamList.subList(i, endIndex);  // 获取当前批次的子集
+
+            qwExternalContactService.batchUpdateQwExternalContactMandatoryRegistration(batchList);
+
+        }
+    }
 }

+ 111 - 0
fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java

@@ -0,0 +1,111 @@
+package com.fs.task;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.fs.live.domain.LiveMiniprogramSubNotifyTask;
+import com.fs.live.mapper.LiveMiniprogramSubNotifyTaskMapper;
+import com.fs.store.enums.MiniAppNotifyTaskStatusEnum;
+import com.fs.store.service.IWechatMiniProgrService;
+import com.fs.store.dto.ClientCredGrantReqDTO;
+import com.fs.store.dto.MiniGramSubsMsgResultDTO;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.dto.WeXinAccessTokenDTO;
+import com.fs.wx.miniapp.config.WxMaProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 小程序订阅通知定时任务
+ */
+@Service("miniProgramSubTask")
+@Slf4j
+@RequiredArgsConstructor
+public class MiniProgramSubTask {
+    private final IWechatMiniProgrService wechatMiniProgrService;
+
+    private final LiveMiniprogramSubNotifyTaskMapper notifyTaskMapper;
+
+    private WxMaProperties.Config config = null;
+
+    @Autowired
+    public void setConfig(WxMaProperties properties) {
+        if(ObjectUtil.isNotNull(properties)){
+            this.config = properties.getConfigs().get(0);
+        }
+    }
+
+
+    /**
+     * 小程序订阅通知
+     */
+    public void notifyMiniLiveAppSub(){
+        log.info("小程序直播订阅通知定时任务");
+        // 先获取所有可用待处理任务
+        List<LiveMiniprogramSubNotifyTask> pendingData = notifyTaskMapper.selectLivePendingData();
+        if(CollectionUtils.isEmpty(pendingData)){
+            log.info("小程序直播阅通知定时任务, 无待处理数据");
+            return;
+        }
+        LocalDateTime now = LocalDateTime.now();
+        for (LiveMiniprogramSubNotifyTask pendingDatum : pendingData) {
+
+            if(pendingDatum.getUpdateTime().isAfter(now)) continue;
+
+            pendingDatum.setUpdateTime(LocalDateTime.now());
+
+            ClientCredGrantReqDTO clientCredGrantReqDTO = new ClientCredGrantReqDTO();
+            clientCredGrantReqDTO.setAppid(config.getAppid());
+            clientCredGrantReqDTO.setSecret(config.getSecret());
+            clientCredGrantReqDTO.setGrant_type("client_credential");
+
+            try{
+                // 获取accessToken
+                WeXinAccessTokenDTO stableToken = wechatMiniProgrService
+                        .getStableToken(clientCredGrantReqDTO);
+
+                String accessToken = stableToken.getAccessToken();
+
+                // 调用微信小程序订阅通知
+                TemplateMessageSendRequestDTO sendRequestDTO = new TemplateMessageSendRequestDTO();
+                sendRequestDTO.setTemplate_id(pendingDatum.getTemplateId());
+                sendRequestDTO.setTouser(pendingDatum.getTouser());
+                sendRequestDTO.setPage(pendingDatum.getPage());
+                TypeReference<Map<String, TemplateMessageSendRequestDTO.TemplateDataValue>> typeReference = new TypeReference<Map<String, TemplateMessageSendRequestDTO.TemplateDataValue>>() {};
+                sendRequestDTO.setData(JSON.parseObject(pendingDatum.getData(),typeReference));
+                MiniGramSubsMsgResultDTO miniGramSubsMsgResultDTO = wechatMiniProgrService.sendSubscribeMsg(accessToken, sendRequestDTO);
+                pendingDatum.setRequestBody(JSON.toJSONString(sendRequestDTO));
+                pendingDatum.setResponseBody(JSON.toJSONString(miniGramSubsMsgResultDTO));
+
+                // 如果推送消息成功
+                if(miniGramSubsMsgResultDTO.getErrcode() == 0){
+                    pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.SUCCESS.getValue());
+                } else {
+                    // 更新任务状态为执行失败
+                    pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.FAILED.getValue());
+                    pendingDatum.setErrorMessage(JSON.toJSONString(miniGramSubsMsgResultDTO));
+                    pendingDatum.setRetryCount(pendingDatum.getRetryCount() +1);
+                }
+            }catch (Throwable e){
+                // 更新任务状态为执行失败
+                pendingDatum.setStatus(MiniAppNotifyTaskStatusEnum.FAILED.getValue());
+                pendingDatum.setErrorMessage(ExceptionUtils.getStackTrace(e));
+                pendingDatum.setRetryCount(pendingDatum.getRetryCount() +1);
+                log.error("小程序直播订阅通知定时任务异常: {}", ExceptionUtils.getStackTrace(e));
+            }
+        }
+
+        if(CollectionUtils.isNotEmpty(pendingData)){
+            notifyTaskMapper.updateBatchById(pendingData);
+        }
+
+    }
+}

+ 8 - 1
fs-admin/src/main/java/com/fs/web/controller/common/CommonController.java

@@ -12,12 +12,12 @@ import com.fs.framework.config.ServerConfig;
 import com.fs.his.domain.FsExportTask;
 import com.fs.his.service.IFsExportTaskService;
 import com.fs.im.service.OpenIMService;
+import com.fs.qw.service.IQwUserService;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 
 import com.fs.web.vo.WangUploadVO;
 
-import com.huaweicloud.sdk.vod.v1.model.BaseInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -72,6 +72,9 @@ public class CommonController
     public RedisTemplate redisTemplate;
 
     org.slf4j.Logger logger= LoggerFactory.getLogger(getClass());
+    @Autowired
+    private IQwUserService qwUserService;
+
     @GetMapping(value = "common/getTask/{taskId}")
     public R getTask(@PathVariable("taskId") Long taskId)
     {
@@ -305,4 +308,8 @@ public class CommonController
 
     }
 
+    @PostMapping("/common/unbindQwUserByServerIds")
+    public R unbindQwUserByServerIds(@RequestBody List<String> serverIds){
+        return qwUserService.unbindQwUserByServerIds(serverIds);
+    }
 }

+ 129 - 7
fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java

@@ -1,22 +1,28 @@
 package com.fs.web.controller.system;
 
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.PatternUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.system.service.ISysRoleService;
+import com.fs.system.service.ISysUserService;
 import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.web.bind.annotation.*;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.entity.SysMenu;
@@ -33,7 +39,7 @@ import com.fs.system.service.ISysMenuService;
 
  */
 @RestController
-
+@Slf4j
 public class SysLoginController
 {
     @Autowired
@@ -54,6 +60,18 @@ public class SysLoginController
     @Autowired
     private MedicalMallConfig medicalMallConfig;
 
+    @Autowired
+    RedisCache redisCache;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private UserDetailsService userDetailsService;
+
     /**
      * 登录方法
      *
@@ -141,4 +159,108 @@ public class SysLoginController
         List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
         return AjaxResult.success(menuService.buildMenus(menus));
     }
+
+    @PostMapping("/checkIsNeedCheck")
+    public boolean checkIsNeedCheck(@RequestBody LoginBody loginBody)
+    {
+//        return  false;
+        return loginService.checkIsNeedCheck(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
+    }
+
+    @PostMapping("/checkCode")
+    public AjaxResult checkCode(@RequestBody Map<String,String> map)
+    {
+        String phone = map.get("phone");
+        String code = map.get("code");
+        String smsKey = "doctorLogin:sms:" + map.get("phone");
+        String smsCode = redisCache.getCacheObject(smsKey);
+
+        if (smsCode == null) {
+            throw new ServiceException("验证码已过期,请重新发送");
+        } else {
+            String string = redisCache.getCacheObject("doctorLogin:sms:" + phone).toString();
+            if (!string.equals(code)){
+                throw new ServiceException("验证码错误");
+            }else{
+                redisCache.deleteObject("doctorLogin:sms:" + phone);
+                List<SysUser> sysUsers = userService.selectUserByPhone(phone);
+                if(sysUsers.size()>1){
+                    throw new ServiceException("此电话号码绑定了多个医生,请核实");
+                }
+                SysUser sysUser = sysUsers.get(0);
+                String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+                String username = sysUser.getUserName();
+
+                // 调 UserDetailsServiceImpl.loadUserByUsername 获取完整 LoginUser
+                sysUser.setLoginIp(ipAddr);
+                sysUser.setLoginDate(new Date());
+                userService.updateUserProfile(sysUser);
+                LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+                String token = tokenService.createToken(loginUser);
+                AjaxResult ajax = AjaxResult.success();
+                ajax.put(Constants.TOKEN, token);
+                return ajax;
+            }
+        }
+    }
+
+    @GetMapping("/checkWechatScan")
+    public AjaxResult checkWechatScan(@RequestParam String ticket)
+    {
+        String status = redisCache.getCacheObject("wechat:scan:" + ticket);
+        if ("ok".equals(status)) {
+            String username = redisCache.getCacheObject("login:ticket:" + ticket);
+            redisCache.deleteObject("login:ticket:" + ticket);
+            redisCache.deleteObject("wechat:scan:" + ticket);
+            SysUser sysUser = userService.selectUserByUserName(username);
+
+            String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+            String loginIp = sysUser.getLoginIp();
+            if (com.fs.common.utils.StringUtils.isEmpty(loginIp)) {
+                sysUser.setLoginIp(ipAddr.trim());
+            } else {
+                List<String> ipList = Arrays.stream(loginIp.split(","))
+                        .map(String::trim)       // 去掉前后空格
+                        .filter(s -> !s.isEmpty())
+                        .distinct()              // 去重
+                        .collect(Collectors.toList());
+
+                String newIp = ipAddr.trim();
+                if (!ipList.contains(newIp)) {
+                    ipList.add(newIp);
+                }
+
+                sysUser.setLoginIp(String.join(",", ipList));
+            }
+
+            sysUser.setLoginDate(new Date());
+            userService.updateUserProfile(sysUser);
+            LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+            String token = tokenService.createToken(loginUser);
+            if (token != null){
+                return AjaxResult.success(Constants.TOKEN, token);
+            }
+            return AjaxResult.success("waiting");
+        }else if (com.fs.common.utils.StringUtils.isNotEmpty(status)&&status.startsWith("error:")) {
+            // 把错误返回给前端
+            throw new ServiceException(status);
+        }
+        return null;
+    }
+
+    @PostMapping("/getWechatQrCode")
+    public AjaxResult getWechatQrCode(@RequestBody Map<String,String> params) throws Exception {
+        Map<String,String> qr = loginService.getWechatQrCode(params.get("username"));
+        return AjaxResult.success(qr);
+    }
+    @GetMapping("/callback")
+    public String wechatCallback(@RequestParam String code, @RequestParam String state) {
+        try {
+            log.info("触发回调");
+            loginService.handleCallback(code, state);
+            return "success"; // 微信要求返回内容,显示给用户即可
+        } catch (Exception e) {
+            return "error";
+        }
+    }
 }

+ 10 - 0
fs-admin/src/main/java/com/fs/web/controller/system/SysUserController.java

@@ -2,6 +2,7 @@ package com.fs.web.controller.system;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 import com.fs.common.constant.HttpStatus;
@@ -10,6 +11,7 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.PageDomain;
 import com.fs.common.core.page.TableSupport;
 import com.fs.common.utils.ServletUtils;
+import io.swagger.annotations.ApiOperation;
 import org.apache.commons.lang3.ArrayUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -263,4 +265,12 @@ public class SysUserController extends BaseController
         Boolean isAdmin= SecurityUtils.isAdmin(loginUser.getUser().getUserId());
         return R.ok().put("data",isAdmin);
     }
+
+    @PreAuthorize("@ss.hasPermi('system:user:unBind')")
+    @ApiOperation("解绑微信")
+    @PostMapping("/unBind")
+    public AjaxResult unBind(@RequestBody Map<String, String> userIdMap){
+        String userId = userIdMap.get("userId");
+        return toAjax(userService.unBind(userId));
+    }
 }

+ 1 - 1
fs-admin/src/main/resources/application.yml

@@ -4,7 +4,7 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-bjzm-test
+    active: druid-ylrz
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz-test

+ 6 - 0
fs-common-api/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-common-api.jar fs-common-api.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-common-api.jar"]
+# 执行启动命令java -jar

+ 24 - 0
fs-common-api/pom.xml

@@ -118,6 +118,30 @@
                     <warName>${project.artifactId}</warName>
                 </configuration>
             </plugin>
+<!--            <plugin>-->
+<!--                <groupId>com.spotify</groupId>-->
+<!--                <artifactId>dockerfile-maven-plugin</artifactId>-->
+<!--                <version>1.4.13</version>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <id>harbor</id>-->
+<!--                        <goals>-->
+<!--                            <goal>build</goal>-->
+<!--                            <goal>push</goal>-->
+<!--                        </goals>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--                <configuration>-->
+<!--                    <repository>ylrz-docker.tencentcloudcr.com/ylrz/sub1/repo</repository>-->
+<!--                    <tag>${project.version}</tag>-->
+<!--                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>-->
+<!--                    <username>100044034444</username>-->
+<!--                    <password>eyJhbGciOiJSUzI1NiIsImtpZCI6Ilg0R1k6SDU1TjpCQU9SOk5GVk86RkdSWDpYTjM1OjZGQUM6S1BBWjo0TE4yOkhDSUQ6UTJDRzpSN1NHIn0.eyJvd25lclVpbiI6IjEwMDAzNDY4ODY0OCIsIm9wZXJhdG9yVWluIjoiMTAwMDQ0MDM0NDQ0IiwidG9rZW5JZCI6ImQ0ZnZja2o4a2NnbnFjaTVvanQwIiwiZXhwIjoyMDc5MDYyMzU0LCJuYmYiOjE3NjM3MDIzNTQsImlhdCI6MTc2MzcwMjM1NH0.PhNxx6pBQ-ItrNlSs_gojvXeHghhYqDqxh8nLUIuhBeRzAgmVnY8F3bFPVgbHGydQNxvgyqLYv3nRIE1j020LGgzUetF5b-NBqSWYMiXfu6uZNWctRkwm5hdlWBrMlV8k8zGxY4ZDGUNEG0ksrk7kk3UZ-lHj4ButI2GIEhTx0lQEPHjhEY0xuteocJVYMHdVUqF-Bc5Jr0nvbwxUbmCGakN1VszxBoMpI-zA2O8anMvYq8h7EqOJLU4dlBVcsbkz-4sMi97Xev-mcGh7THbEGWoRWGWNSa4QwsdXEXS5-mhfrvOw6FGuuiIeEQvcuR8zDztDzSApl4ko57Yat-AQQ</password>-->
+<!--                    <buildArgs>-->
+<!--                        <JAR_FILE>${project.artifactId}.jar</JAR_FILE>-->
+<!--                    </buildArgs>-->
+<!--                </configuration>-->
+<!--            </plugin>-->
         </plugins>
         <finalName>${project.artifactId}</finalName>
     </build>

+ 1 - 1
fs-common-api/src/main/resources/application.yml

@@ -5,4 +5,4 @@ server:
 spring:
   profiles:
 #    active: dev
-    active: druid-myhk-test
+    active: druid-ylrz

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

@@ -30,5 +30,11 @@ public class LiveKeysConstant {
     public static final String LIVE_FLAG_CACHE = "live:flag:%s"; //直播间直播/回放状态缓存
     public static final Integer LIVE_FLAG_CACHE_EXPIRE = 300; //直播间状态缓存过期时间(秒)
 
+    public static final String LIVE_DATA_CACHE = "live:data:%s"; //直播间数据缓存
+    public static final Integer LIVE_DATA_CACHE_EXPIRE = 300; //直播间数据缓存过期时间(秒)
+
+    public static final String PRODUCT_DETAIL_CACHE = "product:detail:%s"; //商品详情缓存
+    public static final Integer PRODUCT_DETAIL_CACHE_EXPIRE = 300; //商品详情缓存过期时间(秒)
+
 
 }

+ 10 - 0
fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java

@@ -103,6 +103,16 @@ public class SysUser extends BaseEntity
     @Excel(name = "角色名称")
     private List<String> roleName;
 
+    private String unionId;
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
     public SysUser()
     {
 

+ 44 - 0
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -451,4 +451,48 @@ public class RedisCache
     public Long size(String key) {
         return redisTemplate.opsForHash().size(key);
     }
+
+    // ========== 新增Hash操作方法 ==========
+    /**
+     * 存储Hash结构:field-value
+     */
+    public <T> void hPut(String mainKey, String field, T value) {
+        redisTemplate.opsForHash().put(mainKey, field, value);
+    }
+
+    /**
+     * 获取Hash中指定field的值
+     */
+    public <T> T hGet(String mainKey, String field) {
+        return (T) redisTemplate.opsForHash().get(mainKey, field);
+    }
+
+    /**
+     * 获取Hash中所有field-value(方便批量读取)
+     */
+    public Map<String, Object> hGetAll(String mainKey) {
+        return (Map<String, Object>) redisTemplate.opsForHash().entries(mainKey);
+    }
+
+    /**
+     * 删除Hash中指定field
+     */
+    public void hDel(String mainKey, Object... fields) {
+        redisTemplate.opsForHash().delete(mainKey, (Object[]) fields);
+    }
+
+    public boolean hasKey(String key) {
+        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
+    }
+
+    // 判断集合中是否存在元素
+    public boolean sIsMember(String key, Object value) {
+        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));
+    }
+
+    // 添加元素到集合
+    public void sAdd(String key, Object value) {
+        redisTemplate.opsForSet().add(key, value);
+    }
+
 }

+ 6 - 0
fs-company/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-company.jar fs-company.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-company.jar"]
+# 执行启动命令java -jar

+ 51 - 4
fs-company/src/main/java/com/fs/company/controller/company/CompanyLoginController.java

@@ -4,11 +4,18 @@ import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.exception.user.CaptchaException;
+import com.fs.common.exception.user.CaptchaExpireException;
+import com.fs.common.exception.user.UserPasswordNotMatchException;
+import com.fs.common.utils.MessageUtils;
 import com.fs.common.utils.PatternUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.company.domain.CompanyMenu;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyMenuService;
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.framework.security.LoginBody;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.CompanyLoginService;
@@ -16,13 +23,16 @@ import com.fs.framework.service.CompanyPermissionService;
 import com.fs.framework.service.TokenService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
 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.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -31,6 +41,7 @@ import java.util.Set;
 
  */
 @RestController
+@Slf4j
 public class CompanyLoginController
 {
     @Autowired
@@ -133,5 +144,41 @@ public class CompanyLoginController
         return AjaxResult.success(false);
     }
 
+    @PostMapping("/checkIsNeedCheck")
+    public boolean checkIsNeedCheck(@RequestBody LoginBody loginBody)
+    {
+//        return false;
+        return loginService.checkIsNeedCheck(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
+    }
+
+    @PostMapping("/getWechatQrCode")
+    public AjaxResult getWechatQrCode(@RequestBody Map<String,String> params) throws Exception {
+        Map<String,String> qr = loginService.getWechatQrCode(params.get("username"));
+        return AjaxResult.success(qr);
+    }
+
+    @GetMapping("/checkWechatScan")
+    public AjaxResult checkWechatScan(@RequestParam String ticket) {
+        //log.info("触发轮询");
+        String token = loginService.checkWechatScan(ticket);
+        Map<String, String> stringStringMap = Collections.singletonMap(Constants.TOKEN, token);
+        if (token != null){
+            return AjaxResult.success(Constants.TOKEN, token);
+        }
+        return AjaxResult.success("waiting");
+    }
+
+    @GetMapping("/callback")
+    public String wechatCallback(@RequestParam String code, @RequestParam String state) {
+        try {
+            log.info("触发回调");
+            loginService.handleCallback(code, state);
+            return "success"; // 微信要求返回内容,显示给用户即可
+        } catch (Exception e) {
+            return "error";
+        }
+    }
+
+
 }
 

+ 116 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyRedPacketBalanceLogsController.java

@@ -0,0 +1,116 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.annotation.RepeatSubmit;
+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.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyRecharge;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+import com.fs.company.param.CompanyRechargeParam;
+import com.fs.company.service.ICompanyRechargeService;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
+import com.fs.company.service.impl.CompanyServiceImpl;
+import com.fs.core.utils.OrderCodeUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/company/companyRedPacketBalanceLogs")
+public class CompanyRedPacketBalanceLogsController extends BaseController {
+
+    @Autowired
+    private ICompanyRedPacketBalanceLogsService companyRedPacketBalanceLogsService;
+    @Autowired
+    private CompanyServiceImpl companyService;
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private ICompanyRechargeService rechargeService;
+
+    /**
+     * 查询企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyRedPacketBalanceLogs.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:export')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyRedPacketBalanceLogs.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        ExcelUtil<CompanyRedPacketBalanceLogs> util = new ExcelUtil<CompanyRedPacketBalanceLogs>(CompanyRedPacketBalanceLogs.class);
+        return util.exportExcel(list, "企业红包余额记录数据");
+    }
+
+    /**
+     * 点击红包充值按钮,获取企业红包余额等信息
+     */
+    @GetMapping("/redBalance")
+    public R getCompanyRedPacketBalance(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Company  company=companyService.selectCompanyById(loginUser.getCompany().getCompanyId());
+        return R.ok().put("data",company);
+    }
+
+    /**
+     * @Description: 红包充值功能 因公司余额的变动关联的地方太多,现在把充值红包的金额单独提出来 不合计到公司余额中
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/3 11:08
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRecharge:Recharge')")
+    @Log(title = "红包充值", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/redRecharge")
+    @Transactional
+    @RepeatSubmit
+    public R redRecharge(@RequestBody CompanyRechargeParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        CompanyRecharge redRecharge=new CompanyRecharge();
+        String orderSn =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(orderSn)){
+            return R.error("订单生成失败,请重试");
+        }
+        redRecharge.setRechargeNo(orderSn);
+        redRecharge.setCompanyId(param.getCompanyId());
+        redRecharge.setMoney(param.getMoney());
+        redRecharge.setCreateUserId(loginUser.getUser().getUserId());
+        redRecharge.setIsAudit(0);
+        redRecharge.setStatus(1);
+        redRecharge.setRemark(param.getRemark());
+        redRecharge.setPayType(3);
+        redRecharge.setBusinessType(1);// 红包充值
+        rechargeService.insertCompanyRecharge(redRecharge);
+        return R.ok("提交成功,等待审核");
+
+    }
+
+}

+ 8 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -869,4 +869,12 @@ public class CompanyUserController extends BaseController {
         List<com.fs.hisStore.domain.FsUserScrm> userList = companyUserService.selectBoundFsUsersByCompanyUserId(companyUserId);
         return R.ok().put("data", userList);
     }
+
+    @PreAuthorize("@ss.hasPermi('company:user:unBind')")
+    @ApiOperation("解绑微信")
+    @PostMapping("/unBind")
+    public AjaxResult unBind(@RequestBody Map<String, String> userIdMap){
+        String userId = userIdMap.get("userId");
+        return toAjax(companyUserService.unBind(userId));
+    }
 }

+ 25 - 1
fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java

@@ -13,10 +13,12 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseRedPacketLog;
+import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.param.FsCourseRedPacketLogParam;
 import com.fs.course.service.IFsCourseRedPacketLogService;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.vo.FsCourseRedPacketLogListPVO;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
@@ -51,6 +53,9 @@ public class FsCourseRedPacketLogController extends BaseController
     private TokenService tokenService;
     @Autowired
     private ISysConfigService configService;
+
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
     /**
      * 查询短链课程看课记录列表
      */
@@ -73,6 +78,10 @@ public class FsCourseRedPacketLogController extends BaseController
 
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
 
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
@@ -96,6 +105,10 @@ public class FsCourseRedPacketLogController extends BaseController
 
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
         return getDataTable(list);
@@ -115,6 +128,10 @@ public class FsCourseRedPacketLogController extends BaseController
 
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVONew(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
         return getDataTable(list);
@@ -136,7 +153,10 @@ public class FsCourseRedPacketLogController extends BaseController
         }
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
-
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
         ExcelUtil<FsCourseRedPacketLogListPVO> util = new ExcelUtil<FsCourseRedPacketLogListPVO>(FsCourseRedPacketLogListPVO.class);
@@ -158,6 +178,10 @@ public class FsCourseRedPacketLogController extends BaseController
         }
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {
+            if (ObjectUtil.isNotEmpty(fsCourseRedPacketLogListPVO.getPeriodId())){
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(fsCourseRedPacketLogListPVO.getPeriodId().longValue());
+                fsCourseRedPacketLogListPVO.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
         ExcelUtil<FsCourseRedPacketLogListPVO> util = new ExcelUtil<FsCourseRedPacketLogListPVO>(FsCourseRedPacketLogListPVO.class);

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

@@ -15,9 +15,17 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveCompanyCode;
+import com.fs.live.domain.LiveOrder;
+import com.fs.live.domain.LiveOrderPayment;
+import com.fs.live.mapper.LiveOrderMapper;
+import com.fs.live.mapper.LiveOrderPaymentMapper;
 import com.fs.live.service.ILiveCompanyCodeService;
+import com.fs.live.service.ILiveOrderService;
 import com.fs.live.service.ILiveService;
 import com.fs.live.vo.LiveListVo;
 import com.fs.system.oss.OSSFactory;
@@ -29,10 +37,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.text.SimpleDateFormat;
+import java.util.*;
 
 /**
  * 直播Controller

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

@@ -7,6 +7,7 @@ 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.company.domain.CompanyUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
@@ -14,12 +15,14 @@ import com.fs.live.domain.LiveData;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.vo.ColumnsConfigVo;
+import com.fs.live.vo.LiveUserDetailExportVO;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -38,6 +41,79 @@ public class LiveDataController extends BaseController
     @Autowired
     private TokenService tokenService;
 
+    /**
+     * 查询直播间详情数据(SQL方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveDataDetailBySql")
+    public R getLiveDataDetailBySql(@RequestParam Long liveId) {
+        return liveDataService.getLiveDataDetailBySql(liveId);
+    }
+
+    /**
+     * 查询直播间用户详情列表(SQL方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveUserDetailListBySql")
+    public R getLiveUserDetailListBySql(@RequestParam Long liveId, HttpServletRequest request) {
+        CompanyUser user = tokenService.getLoginUser(request).getUser();
+        if ("00".equals(user.getUserType())) {
+            return liveDataService.getLiveUserDetailListBySql(liveId,user.getCompanyId(),null);
+        }
+        return liveDataService.getLiveUserDetailListBySql(liveId,user.getCompanyId(),user.getUserId());
+    }
+
+    /**
+     * 查询直播间详情数据(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveDataDetailByServer")
+    public R getLiveDataDetailByServer(@RequestParam Long liveId) {
+        return liveDataService.getLiveDataDetailByServer(liveId);
+    }
+
+    /**
+     * 查询直播间用户详情列表(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveUserDetailListByServer")
+    public R getLiveUserDetailListByServer(@RequestParam Long liveId) {
+        return liveDataService.getLiveUserDetailListByServer(liveId);
+    }
+
+
+    /**
+     * 导出直播间用户详情数据
+     * @param liveId 直播间ID
+     * @return Excel文件
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:export')")
+    @Log(title = "直播间用户详情", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportLiveUserDetail")
+    public AjaxResult exportLiveUserDetail(@RequestParam Long liveId, HttpServletRequest request) {
+        CompanyUser user = tokenService.getLoginUser(request).getUser();
+        List<LiveUserDetailExportVO> list = new ArrayList<>();
+        if ("00".equals(user.getUserType())) {
+            liveDataService.getLiveUserDetailListBySql(liveId, user.getCompanyId(), null);
+        } else {
+            list = liveDataService.exportLiveUserDetail(liveId,user.getCompanyId(),user.getUserId());
+        }
+        if (list == null || list.isEmpty()) {
+            return AjaxResult.error("未找到用户详情数据");
+        }
+
+        ExcelUtil<LiveUserDetailExportVO> util = new ExcelUtil<>(LiveUserDetailExportVO.class);
+        return util.exportExcel(list, "直播间用户详情数据");
+    }
+
     /**
      * 直播数据页面卡片数据
      */
@@ -67,7 +143,7 @@ public class LiveDataController extends BaseController
     @PostMapping("/listLiveData")
     public R listLiveData(@RequestBody LiveDataParam param, HttpServletRequest request)
     {
-        param.setCompanyId(tokenService.getLoginUser(request).getUser().getCompanyId());
+//        param.setCompanyId(tokenService.getLoginUser(request).getUser().getCompanyId());
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
         return liveDataService.listLiveData(param);
     }

+ 19 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -7,6 +7,7 @@ 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.DictUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
@@ -113,6 +114,10 @@ public class QwExternalContactController extends BaseController
 
         startPage();
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+        if(StringUtils.isNotEmpty(qwExternalContact.getStatuses())){
+            String[] split = qwExternalContact.getStatuses().split(",");
+            qwExternalContact.setStatusCondition(split);
+        }
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
         list.forEach(item->{
 
@@ -343,6 +348,10 @@ public class QwExternalContactController extends BaseController
         List<QwContactWay> wayList = qwContactWayService.selectQwContactWayList(qwContactWay);
 
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+        if(StringUtils.isNotEmpty(qwExternalContact.getStatuses())){
+            String[] split = qwExternalContact.getStatuses().split(",");
+            qwExternalContact.setStatusCondition(split);
+        }
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
         list.forEach(item->{
 
@@ -362,6 +371,10 @@ public class QwExternalContactController extends BaseController
 
             if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
                 item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
+
+            }
+            if (item.getStatus()!=null){
+                item.setStatusName(DictUtils.getDictLabel("sys_qw_external_contact_status", String.valueOf(item.getStatus())));
             }
         });
         ExcelUtil<QwExternalContactVO> util = new ExcelUtil<QwExternalContactVO>(QwExternalContactVO.class);
@@ -449,7 +462,12 @@ public class QwExternalContactController extends BaseController
     @Log(title = "添加标签", businessType = BusinessType.UPDATE)
     @PostMapping("/addTag")
     public R addTag(@RequestBody QwExternalContactAddTagParam Param) throws JSONException {
-
+        if(Param.isFilter()){
+            Param.setUserIds(getList(Param.getAddType(), Param.getParam()));
+        }
+        if(Param.getUserIds() == null || Param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
         return qwExternalContactService.addUserTag(Param);
     }
 

+ 10 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwTagGroupController.java

@@ -59,6 +59,16 @@ public class QwTagGroupController extends BaseController
         List<QwTagGroupListVO> list = qwTagGroupService.selectQwTagGroupListVO(qwTagGroup);
         return getDataTable(list);
     }
+    /**
+     * 所有标签列表 模糊查询标签名称
+     */
+    @GetMapping("/allListPage")
+    public TableDataInfo allListPage(QwTagGroup qwTagGroup)
+    {
+        startPage();
+        List<QwTagGroupListVO> list = qwTagGroupService.selectQwTagGroupListVOPage(qwTagGroup);
+        return getDataTable(list);
+    }
     @PreAuthorize("@ss.hasPermi('qw:tagGroup:sync')")
     @Log(title = "同步标签", businessType = BusinessType.INSERT)
     @PostMapping("/syncTag/{corpId}")

+ 1 - 1
fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -100,7 +100,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 // 过滤请求
                 .authorizeRequests()
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/chat/upload/**","/login", "/register", "/captchaImage").anonymous()
+                .antMatchers("/chat/upload/**","/login", "/register", "/captchaImage","/checkIsNeedCheck","/getWechatQrCode","/checkWechatScan","/callback").anonymous()
                 .antMatchers(
                         HttpMethod.GET,
                         "/",

+ 284 - 0
fs-company/src/main/java/com/fs/framework/service/CompanyLoginService.java

@@ -1,26 +1,41 @@
 package com.fs.framework.service;
 
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.exception.user.CaptchaException;
 import com.fs.common.exception.user.CaptchaExpireException;
 import com.fs.common.exception.user.UserPasswordNotMatchException;
+import com.fs.common.service.WechatLoginService;
 import com.fs.common.utils.MessageUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.framework.manager.AsyncManager;
 import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.framework.security.LoginUser;
 import com.fs.his.domain.StoreLoginUser;
 import com.fs.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 登录校验方法
@@ -28,6 +43,7 @@ import java.util.concurrent.TimeUnit;
  
  */
 @Component
+@Slf4j
 public class CompanyLoginService
 {
     @Autowired
@@ -39,6 +55,24 @@ public class CompanyLoginService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Value("${wechat.company.appid:#{null}}")
+    private String appId;
+    @Value("${wechat.company.secret:#{null}}")
+    private String secret;
+    @Value("${wechat.company.redirectUri:#{null}}")
+    private String redirectUri;
+    @Value("${wechat.isNeedScan:false}")
+    private Boolean isNeedScan;
+
+    @Autowired
+    private WechatLoginService wechatLoginService;
+
+    @Autowired
+    private UserDetailsService userDetailsService;
+
     /**
      * 登录验证
      *
@@ -85,10 +119,260 @@ public class CompanyLoginService
             }
         }
         LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        //查询当前登录用户信息
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(loginUser.getUser().getUserId());
         AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUser().getCompanyId(),username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
         redisCache.setCacheObject("companyId:"+loginUser.getUser().getUserId(),loginUser.getUser().getCompanyId(),604800, TimeUnit.SECONDS);
+        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()).split(",")[0].trim();
+        log.info("销售用户{}正常登录获取到的ip地址{}", loginUser.getUser().getUserId(), ipAddr);
+
+        companyUser.setUserId(loginUser.getUser().getUserId());
+        String loginIp = companyUser.getLoginIp();
+        List<String> ipList = new ArrayList<>();
+        if (StringUtils.isNotEmpty(loginIp)) {
+            String[] ips = loginIp.split(",");
+            for (String ip : ips) {
+                ip = ip.trim();                  // 去掉前后空格
+                if (!ip.isEmpty()) {
+                    ipList.add(ip);              // 先加入已有 IP
+                }
+            }
+        }
+
+        ipList.add(ipAddr);
+        List<String> distinctList = ipList.stream()
+                .map(String::trim)       // 再次确保去掉空格
+                .distinct()
+                .collect(Collectors.toList());
+        companyUser.setLoginIp(String.join(",", distinctList));
+
+        companyUser.setLoginDate(new Date());
+        companyUserService.updateCompanyUser(companyUser);
         // 生成token
         return tokenService.createToken(loginUser);
     }
 
+
+    public boolean checkIsNeedCheck(String username, String password, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = redisCache.getCacheObject(verifyKey);
+        //redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+            throw new CaptchaException();
+        }
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        //查询当前登录用户信息
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(loginUser.getUser().getUserId());
+
+        Long[] userIds = new Long[]{2020L};
+        for (Long userId : userIds) {
+            if (userId.equals(companyUser.getUserId())) {
+                return false;
+            }
+        }
+
+        // 判断是否开启了扫码配置
+        if (ObjectUtil.isEmpty(isNeedScan) || !isNeedScan){
+            return false;
+        }
+
+        //true → 要发短信验证码再登录
+        //false → 直接登录
+        return needCheck(companyUser);
+    }
+
+    public boolean needCheck(CompanyUser companyUser) {
+        // 1. 校验 IP
+        if (!checkIp(companyUser)) {
+            // IP 不一致
+            return true;
+        }
+
+        // 2. 校验是否首次登录
+        if (checkIsFirstLogin(companyUser)) {
+            return true;
+        }
+
+        // 3. 校验上次登录时间是否在五天前
+        if (checkIsLoginTime(companyUser)) {
+            return true;
+        }
+
+        // 4. 检查是否在设置的某一天
+        /*if (checkIsSpecialDay(new Date())) {
+            return true;
+        }*/
+        if (haveUnionId(companyUser)){
+            return true;
+        }
+
+        return false;
+    }
+
+    public boolean checkIp(CompanyUser companyUser) {
+        // 获取当前 IP
+        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()).split(",")[0].trim();
+
+        // 获取已记录的登录 IP
+        String lastLoginIp = companyUser.getLoginIp();
+
+        if (StringUtils.isNotEmpty(lastLoginIp)) {
+            List<String> ipList = Arrays.stream(lastLoginIp.split(","))
+                    .map(String::trim)
+                    .filter(s -> !s.isEmpty())
+                    .distinct()
+                    .collect(Collectors.toList());
+
+            return ipList.contains(ipAddr);
+        }
+        return false;
+    }
+
+    //检查是否第一次登录
+    public boolean checkIsFirstLogin(CompanyUser companyUser){
+        // 获取上次登录 IP
+        String lastLoginIp = companyUser.getLoginIp();
+        if (StringUtils.isEmpty(lastLoginIp)||companyUser.getLoginDate()==null){
+            return true;
+        }
+        return false;
+    }
+    public boolean checkIsLoginTime(CompanyUser companyUser) {
+        // 获取上次登录时间
+        Date loginDate = companyUser.getLoginDate();
+        if (loginDate == null) {
+            // 没有登录记录,直接返回 true(需要处理)
+            return true;
+        }
+
+        // 当前时间
+        Date now = new Date();
+
+        // 计算两个时间的毫秒差
+        long diff = now.getTime() - loginDate.getTime();
+
+        // 5天 = 5 * 24 * 60 * 60 * 1000 毫秒
+        long fiveDays = 5L * 24 * 60 * 60 * 1000;
+
+        return diff >= fiveDays;
+    }
+
+    public boolean haveUnionId( CompanyUser companyUser){
+        if (StringUtils.isEmpty(companyUser.getUnionId())){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 获取微信登录二维码参数
+     * @param username 当前登录用户名
+     * @return 二维码参数
+     */
+    public Map<String, String> getWechatQrCode(String username) throws Exception {
+        // 生成 loginTicket
+        String ticket = UUID.randomUUID().toString();
+        redisCache.setCacheObject("login:ticket:" + ticket, username, 60, TimeUnit.SECONDS);
+
+        return wechatLoginService.getQrCode(ticket,appId,secret,redirectUri); // 返回二维码参数
+    }
+
+    public String checkWechatScan(String ticket) {
+        String status = redisCache.getCacheObject("wechat:scan:" + ticket);
+        if ("ok".equals(status)) {
+            String username = redisCache.getCacheObject("login:ticket:" + ticket);
+            redisCache.deleteObject("login:ticket:" + ticket);
+            redisCache.deleteObject("wechat:scan:" + ticket);
+            CompanyUser companyUser = companyUserService.selectUserByUserName(username);
+
+            // 调 UserDetailsServiceImpl.loadUserByUsername 获取完整 LoginUser
+            LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+            companyUser.setUserId(loginUser.getUser().getUserId());
+            String loginIp = companyUser.getLoginIp();
+            String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()).split(",")[0].trim();
+            log.info("销售用户{}扫码验证过后登录获取到的ip地址{}", loginUser.getUser().getUserId(), ipAddr);
+            List<String> ipList = new ArrayList<>();
+            if (StringUtils.isNotEmpty(loginIp)) {
+                String[] ips = loginIp.split(",");
+                for (String ip : ips) {
+                    ip = ip.trim();
+                    if (!ip.isEmpty()) {
+                        ipList.add(ip);
+                    }
+                }
+            }
+            ipList.add(ipAddr);
+            List<String> distinctList = ipList.stream()
+                    .map(String::trim)       // 再次确保去掉空格
+                    .distinct()
+                    .collect(Collectors.toList());
+            companyUser.setLoginIp(String.join(",", distinctList));
+            companyUser.setLoginDate(new Date());
+            companyUserService.updateCompanyUser(companyUser);
+            return tokenService.createToken(loginUser);
+        }else if (StringUtils.isNotEmpty(status)&&status.startsWith("error:")) {
+            // 把错误返回给前端
+            throw new ServiceException(status);
+        }
+        return null;
+    }
+    /**
+     * 微信扫码回调
+     */
+    public void handleCallback(String code, String ticket) {
+        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId
+                + "&secret=" + secret
+                + "&code=" + code
+                + "&grant_type=authorization_code";
+
+        JSONObject json = JSON.parseObject(HttpUtil.get(url));
+        String unionid = json.getString("unionid");
+        if (unionid == null) throw new ServiceException("微信授权失败");
+
+        String username = redisCache.getCacheObject("login:ticket:" + ticket);
+        if (username == null) throw new ServiceException("ticket无效或过期");
+        CompanyUser companyUser = companyUserService.selectUserByUserName(username);
+        if (companyUser == null) throw new ServiceException("用户不存在");
+        if (companyUser.getUnionId() == null || companyUser.getUnionId().isEmpty()) {
+            // 如果用户没有绑定 unionid,则绑定当前扫码用户的 unionid
+            companyUser.setUnionId(unionid);
+            companyUserService.updateCompanyUser(companyUser);
+        } else if (!companyUser.getUnionId().equals(unionid)) {
+            // 如果用户已绑定 unionid,但与扫码用户不一致,则拒绝登录
+            redisCache.setCacheObject("wechat:scan:" + ticket, "error:账号与绑定用户不匹配", 30, TimeUnit.SECONDS);
+            return;
+        }
+
+        redisCache.setCacheObject("wechat:scan:" + ticket, "ok", 30, TimeUnit.SECONDS);
+    }
+
 }

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

@@ -3,11 +3,12 @@ server:
 # Spring配置
 spring:
   profiles:
+    active: druid-ylrz
 #    active: druid-jnsyj-test
 #    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt
-    active: druid-bjzm-test
+#    active: druid-bjzm-test
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft

+ 2 - 1
fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -97,7 +97,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 // 过滤请求
                 .authorizeRequests()
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/login", "/register", "/captchaImage").anonymous()
+                .antMatchers("/login", "/register", "/captchaImage","/getWechatQrCode","/checkWechatScan","/callback","/checkIsNeedCheck").anonymous()
                 .antMatchers("/app/common/test").anonymous()
                 .antMatchers("/ad/adDyApi/authorized").anonymous()
                 .antMatchers(
@@ -137,6 +137,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/common/uploadWang**").anonymous()
                 .antMatchers("/common/download**").anonymous()
                 .antMatchers("/common/download/resource**").anonymous()
+                .antMatchers("/common/unbindQwUserByServerIds").anonymous()
                 .antMatchers("/swagger-ui.html").anonymous()
                 .antMatchers("/swagger-resources/**").anonymous()
                 .antMatchers("/webjars/**").anonymous()

+ 207 - 0
fs-framework/src/main/java/com/fs/framework/web/service/SysLoginService.java

@@ -1,7 +1,14 @@
 package com.fs.framework.web.service;
 
 import javax.annotation.Resource;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.service.WechatLoginService;
+import com.fs.common.utils.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -24,6 +31,9 @@ import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysUserService;
 
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
 /**
  * 登录校验方法
  * 
@@ -46,6 +56,17 @@ public class SysLoginService
 
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private WechatLoginService wechatLoginService;
+
+    @Value("${wechat.admin.appid:#{null}}")
+    private String appId;
+    @Value("${wechat.admin.secret:#{null}}")
+    private String secret;
+    @Value("${wechat.admin.redirectUri:#{null}}")
+    private String redirectUri;
+    @Value("${wechat.isNeedScan:false}")
+    private Boolean isNeedScan;
 
     /**
      * 登录验证
@@ -122,8 +143,194 @@ public class SysLoginService
      */
     public void recordLoginInfo(SysUser user)
     {
+        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+        String loginIp = user.getLoginIp();
+        if (com.fs.common.utils.StringUtils.isEmpty(loginIp)) {
+            user.setLoginIp(ipAddr);
+        } else {
+            List<String> ipList = new ArrayList<>(Arrays.asList(loginIp.split(",")));
+            if (!ipList.contains(ipAddr)) {
+                ipList.add(ipAddr);
+                user.setLoginIp(String.join(",", ipList));
+            }
+        }
         user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
         user.setLoginDate(DateUtils.getNowDate());
         userService.updateUserProfile(user);
     }
+
+
+    public boolean checkIsNeedCheck(String username, String password, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = redisCache.getCacheObject(verifyKey);
+        //redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+            throw new CaptchaException();
+        }
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        //查询当前登录用户信息
+        SysUser sysUser = userService.selectUserById(loginUser.getUserId());
+        Long[] userIds = new Long[]{236L, 246L, 247L, 253L,119L};
+        for (Long userId : userIds) {
+            if (userId.equals(sysUser.getUserId())){
+                return false;
+            }
+        }
+
+        // 判断是否开启了扫码配置
+        if (ObjectUtil.isEmpty(isNeedScan) || !isNeedScan){
+            return false;
+        }
+
+        //true → 要发短信验证码再登录
+        //false → 直接登录
+        return needCheck(sysUser);
+    }
+    public boolean needCheck(SysUser sysUser) {
+
+
+        // 1. 校验 IP
+        if (!checkIp(sysUser)) {
+            // IP 不一致
+            return true;
+        }
+
+        // 2. 校验是否首次登录
+        if (checkIsFirstLogin(sysUser)) {
+            return true;
+        }
+
+        // 3. 校验上次登录时间是否在五天前
+        if (checkIsLoginTime(sysUser)) {
+            return true;
+        }
+
+        // 4. 检查是否在设置的某一天
+//        if (checkIsSpecialDay(new Date())) {
+//            return true;
+//        }
+        if (haveUnionId(sysUser)){
+            return true;
+        }
+
+        return false;
+    }
+    public boolean haveUnionId( SysUser sysUser){
+        if (StringUtils.isEmpty(sysUser.getUnionId())){
+            return true;
+        }
+        return false;
+    }
+    public boolean checkIp(SysUser sysUser){
+        // 获取当前 IP
+        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+        // 获取已记录的登录 IP
+        String lastLoginIp = sysUser.getLoginIp();
+
+        if (StringUtils.isNotEmpty(lastLoginIp)) {
+            List<String> ipList = Arrays.asList(lastLoginIp.split(","));
+            return ipList.contains(ipAddr);
+        }
+        return false;
+    }
+    //检查是否第一次登录
+    public boolean checkIsFirstLogin(SysUser sysUser){
+        // 获取上次登录 IP
+        String lastLoginIp = sysUser.getLoginIp();
+        if (StringUtils.isEmpty(lastLoginIp)||sysUser.getLoginDate()==null){
+            return true;
+        }
+        return false;
+    }
+    public boolean checkIsLoginTime(SysUser sysUser) {
+        // 获取上次登录时间
+        Date loginDate = sysUser.getLoginDate();
+        if (loginDate == null) {
+            // 没有登录记录,直接返回 true(需要处理)
+            return true;
+        }
+
+        // 当前时间
+        Date now = new Date();
+
+        // 计算两个时间的毫秒差
+        long diff = now.getTime() - loginDate.getTime();
+
+        // 5天 = 5 * 24 * 60 * 60 * 1000 毫秒
+        long fiveDays = 5L * 24 * 60 * 60 * 1000;
+
+        return diff >= fiveDays;
+    }
+
+    /**
+     * 获取微信登录二维码参数
+     * @param account 当前登录用户名
+     * @return 二维码参数
+     */
+    public Map<String, String> getWechatQrCode(String account) throws Exception {
+        // 生成 loginTicket
+        String ticket = UUID.randomUUID().toString();
+        redisCache.setCacheObject("login:ticket:" + ticket, account, 60, TimeUnit.SECONDS);
+
+        return wechatLoginService.getQrCode(ticket,appId,secret,redirectUri); // 返回二维码参数
+    }
+
+    /**
+     * 微信扫码回调
+     */
+    public void handleCallback(String code, String ticket) {
+        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId
+                + "&secret=" + secret
+                + "&code=" + code
+                + "&grant_type=authorization_code";
+
+        JSONObject json = JSON.parseObject(cn.hutool.http.HttpUtil.get(url));
+        String unionid = json.getString("unionid");
+        if (unionid == null) throw new ServiceException("微信授权失败");
+
+        String username = redisCache.getCacheObject("login:ticket:" + ticket);
+        if (username == null) throw new ServiceException("ticket无效或过期");
+        SysUser sysUser = userService.selectUserByUserName(username);
+        if (sysUser == null) throw new ServiceException("用户不存在");
+        if (sysUser.getUnionId() == null || sysUser.getUnionId().isEmpty()) {
+            // 如果用户没有绑定 unionid,则绑定当前扫码用户的 unionid
+            sysUser.setUnionId(unionid);
+            userService.updateUserProfile(sysUser);
+        } else if (!sysUser.getUnionId().equals(unionid)) {
+            // 如果用户已绑定 unionid,但与扫码用户不一致,则拒绝登录
+            redisCache.setCacheObject("wechat:scan:" + ticket, "error:账号与绑定用户不匹配", 30, TimeUnit.SECONDS);
+            return;
+        }
+
+        redisCache.setCacheObject("wechat:scan:" + ticket, "ok", 30, TimeUnit.SECONDS);
+    }
 }

+ 12 - 0
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -273,6 +273,18 @@ public class SendMsg {
                     try {
                         List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
                         asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(settings, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId());
+
+                        //app文本消息
+                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "11".equals(e.getContentType())).collect(Collectors.toList());
+
+                        if (!settings.isEmpty()){
+                            asyncSopTestService.asyncSendMsgBySopAppTxtNormalIM(settings, qwSopLogs.getCorpId(),qwUser.getCompanyUserId(),qwSopLogs.getFsUserId());
+                        }
+                        //app语音消息
+                        settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "12".equals(e.getContentType())).collect(Collectors.toList());
+                        if (!settings.isEmpty()){
+                            asyncSopTestService.asyncSendMsgBySopAppMP3NormalIM(settings, qwSopLogs.getCorpId(),qwUser.getCompanyUserId(),qwSopLogs.getFsUserId());
+                        }
                     } catch (Exception e) {
                         log.error("推送APP失败", e);
                     }

+ 0 - 1
fs-live-app/src/main/java/com/fs/framework/aspectj/LiveWatchUserAspect.java

@@ -33,7 +33,6 @@ public class LiveWatchUserAspect {
         try {
             String methodName = joinPoint.getSignature().getName();
             Object[] args = joinPoint.getArgs();
-            log.info("直播观看用户数据发生变化,方法: {}, 参数: {}", methodName, Arrays.toString(args));
             // 提取liveId并处理缓存更新
             Set<Long> liveIds = extractLiveIds(methodName, args);
             for (Long liveId : liveIds) {

+ 1 - 1
fs-live-app/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java

@@ -73,7 +73,7 @@ public class RateLimiterAspect
             {
                 throw new ServiceException("访问过于频繁,请稍后再试");
             }
-            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
+
         }
         catch (ServiceException e)
         {

+ 3 - 2
fs-live-app/src/main/java/com/fs/live/controller/LiveController.java

@@ -77,7 +77,7 @@ public class LiveController {
 		live.setLiveId(Long.valueOf(params.get("stream_id")));
 		live.setStatus(3);
 		live.setFinishTime(LocalDateTime.now());
-		liveService.updateLiveEntity(live);
+//		liveService.updateLiveEntity(live);
 		return R.ok();
 //		{app=200149.push.tlivecloud.com, appid=1319721001, appname=live, channel_id=673,
 //				errcode=1, errmsg=The push client actively stopped the push, event_time=1755571239,
@@ -120,12 +120,13 @@ public class LiveController {
 
 	@PostMapping("/videoUpload")
 	public R videoUpload(HttpServletRequest request, @RequestBody  Map<String, Object> params) {
+		String videoUrl = "https://bjzmkytcpv.ylrzcloud.com/";
 		log.info("请求参数:{}", params);
 		if(!params.containsKey("WorkflowExecution")) return R.error("参数错误");
 
 		LinkedHashMap<String,Object> result = (LinkedHashMap<String,Object>) params.get("WorkflowExecution");
 		String string = result.get("Object").toString();
-		videoService.updateFinishStatus("https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/" + string.replace(".mp4", ".m3u8"));
+		videoService.updateFinishStatus(videoUrl + string.replace(".mp4", "-1080.m3u8"));
 
 		return R.ok();
 //		{app=200149.push.tlivecloud.com, appid=1319721001, appname=live, channel_id=673,

+ 0 - 2
fs-live-app/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -15,7 +15,6 @@ public class LiveDataController extends BaseController {
 
     @Autowired
     private RedisCache redisCache;
-
     /**
      * 点赞
      * */
@@ -23,7 +22,6 @@ public class LiveDataController extends BaseController {
     public R like(@PathVariable("liveId") Long liveId) {
         //直播间总点赞数
         Long increment = redisCache.incr("live:like:" + liveId, 1);
-
         return R.ok().put("like",increment);
     }
 }

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

@@ -3,6 +3,8 @@ package com.fs.live.websocket.handle;
 import com.alibaba.fastjson.JSONObject;
 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.websocket.bean.SendMsgVo;
 import com.fs.live.websocket.constant.AttrConstant;
 import com.fs.common.core.domain.R;
@@ -41,7 +43,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
     private final static ILiveService liveService = SpringUtils.getBean(ILiveService.class);
     private final static ILiveWatchUserService liveWatchUserService = SpringUtils.getBean(ILiveWatchUserService.class);
     private final static ILiveMsgService liveMsgService = SpringUtils.getBean(ILiveMsgService.class);
-    private final static IFsUserService fsUserService = SpringUtils.getBean(IFsUserService.class);
+    private final static IFsUserScrmService fsUserService = SpringUtils.getBean(IFsUserScrmService.class);
 
     /**
      * 处理握手
@@ -51,7 +53,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
      */
     @Override
     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
-        log.debug("事件");
+
         // 处理 WebSocket 握手完成事件
         if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
             Long userId = ctx.channel().attr(AttrConstant.ATTR_USER_ID).get();
@@ -70,11 +72,12 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
             roomGroup.add(ctx.channel());
 
             if (userType == 0) {
+
+
+                FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
                 // 加入房间
-                LiveWatchUser liveWatchUser = liveWatchUserService.joinWithoutLocation(liveId, userId);
+                LiveWatchUser liveWatchUser = liveWatchUserService.joinWithoutLocation(fsUser,liveId, userId);
                 room.put(userId, ctx.channel());
-
-                FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
                 if (Objects.isNull(fsUser)) {
                     ctx.channel().writeAndFlush(new TextWebSocketFrame("Error: 用户信息错误")).addListener(ChannelFutureListener.CLOSE);
                     return;
@@ -97,7 +100,6 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
                 adminRoom.add(ctx.channel());
             }
 
-            log.debug("加入webSocket liveId: {}, userId: {}, 直播间人数: {}", liveId, userId, room.size());
         }
     }
 
@@ -154,7 +156,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
      */
     @Override
     protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
-        log.debug("接收到消息 data: {}", textWebSocketFrame.text());
+
         Long liveId = channelHandlerContext.channel().attr(AttrConstant.ATTR_LIVE_ID).get();
         Long userType = channelHandlerContext.channel().attr(AttrConstant.ATTR_USER_TYPE).get();
 
@@ -204,7 +206,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
      */
     @Override
     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
-        log.debug("断开连接");
+
         Long userId = ctx.channel().attr(AttrConstant.ATTR_USER_ID).get();
         Long liveId = ctx.channel().attr(AttrConstant.ATTR_LIVE_ID).get();
         Long userType = ctx.channel().attr(AttrConstant.ATTR_USER_TYPE).get();
@@ -218,8 +220,8 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
         ChannelGroup roomGroup = getRoomGroup(liveId);
 
         if (userType == 0) {
-            FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
-            LiveWatchUser close = liveWatchUserService.close(liveId, userId);
+            FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
+            LiveWatchUser close = liveWatchUserService.close(fsUser,liveId, userId);
             room.remove(userId);
 
             if (room.isEmpty()) {
@@ -251,7 +253,6 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
             roomGroups.remove(liveId);
         }
 
-        log.debug("断开webSocket liveId: {}, userId: {}, 直播间人数: {}", liveId, userId, room.size());
 
     }
 

+ 249 - 57
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -22,6 +22,7 @@ import com.fs.live.service.*;
 import com.fs.live.vo.LiveGoodsVo;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.time.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
@@ -30,10 +31,9 @@ import javax.websocket.server.ServerEndpoint;
 import java.io.EOFException;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import static com.fs.common.constant.LiveKeysConstant.*;
 
@@ -49,6 +49,16 @@ public class WebSocketServer {
     private final static ConcurrentHashMap<Long, ConcurrentHashMap<Long, Session>> rooms = new ConcurrentHashMap<>();
     // 管理端连接
     private final static ConcurrentHashMap<Long, CopyOnWriteArrayList<Session>> adminRooms = new ConcurrentHashMap<>();
+
+    // Session发送锁,避免同一会话并发发送消息
+    private final static ConcurrentHashMap<String, Lock> sessionLocks = new ConcurrentHashMap<>();
+    // 心跳超时缓存:key=sessionId,value=最后心跳时间戳
+    private final static ConcurrentHashMap<String, Long> heartbeatCache = new ConcurrentHashMap<>();
+    // 心跳超时时间(毫秒):3分钟无心跳则认为超时
+    private final static long HEARTBEAT_TIMEOUT = 3 * 60 * 1000;
+    // admin房间消息发送线程池(单线程,保证串行化)
+    private final static ConcurrentHashMap<Long, ExecutorService> adminExecutors = new ConcurrentHashMap<>();
+
     private final RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
     private final ILiveMsgService liveMsgService = SpringUtils.getBean(ILiveMsgService.class);
     private final ILiveService liveService = SpringUtils.getBean(ILiveService.class);
@@ -62,6 +72,7 @@ public class WebSocketServer {
     private final ILiveUserFirstEntryService liveUserFirstEntryService =  SpringUtils.getBean(ILiveUserFirstEntryService.class);
     private final ILiveCouponIssueService liveCouponIssueService =  SpringUtils.getBean(ILiveCouponIssueService.class);
     private final LiveCouponMapper liveCouponMapper = SpringUtils.getBean(LiveCouponMapper.class);
+    private static Random random = new Random();
 
     // 直播间在线用户缓存
 //    private static final ConcurrentHashMap<Long, Integer> liveOnlineUsers = new ConcurrentHashMap<>();
@@ -101,7 +112,7 @@ public class WebSocketServer {
                 throw new BaseException("用户信息错误");
             }
 
-            LiveWatchUser liveWatchUserVO = liveWatchUserService.join(liveId, userId, location);
+            LiveWatchUser liveWatchUserVO = liveWatchUserService.join(fsUser,liveId, userId, location);
             room.put(userId, session);
             // 直播间浏览量 +1
             redisCache.incr(PAGE_VIEWS_KEY + liveId, 1);
@@ -135,17 +146,19 @@ public class WebSocketServer {
                 redisCache.incr(UNIQUE_VIEWERS_KEY + liveId, 1);
             }
             liveWatchUserVO.setMsgStatus(liveWatchUserVO.getMsgStatus());
-            SendMsgVo sendMsgVo = new SendMsgVo();
-            sendMsgVo.setLiveId(liveId);
-            sendMsgVo.setUserId(userId);
-            sendMsgVo.setUserType(userType);
-            sendMsgVo.setCmd("entry");
-            sendMsgVo.setMsg("用户进入");
-            sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
-            sendMsgVo.setNickName(fsUser.getNickname());
-            sendMsgVo.setAvatar(fsUser.getAvatar());
-            // 广播连接消息
-            broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+            if (1 == random.nextInt(10)) {
+                SendMsgVo sendMsgVo = new SendMsgVo();
+                sendMsgVo.setLiveId(liveId);
+                sendMsgVo.setUserId(userId);
+                sendMsgVo.setUserType(userType);
+                sendMsgVo.setCmd("entry");
+                sendMsgVo.setMsg("用户进入");
+                sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
+                sendMsgVo.setNickName(fsUser.getNickname());
+                sendMsgVo.setAvatar(fsUser.getAvatar());
+                // 广播连接消息
+                broadcastWebMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+            }
 
             LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, userId);
             if (liveUserFirstEntry != null) {
@@ -177,9 +190,15 @@ public class WebSocketServer {
 
         } else {
             adminRoom.add(session);
+            // 为admin房间创建单线程执行器,保证串行化发送
+            adminExecutors.computeIfAbsent(liveId, k -> Executors.newSingleThreadExecutor());
         }
 
-        log.debug("加入webSocket liveId: {}, userId: {}, 直播间人数: {}, 管理端人数: {}", liveId, userId, room.size(), adminRoom.size());
+        // 初始化Session锁
+        sessionLocks.putIfAbsent(session.getId(), new ReentrantLock());
+        // 初始化心跳时间
+        heartbeatCache.put(session.getId(), System.currentTimeMillis());
+
     }
 
     //关闭连接时调用
@@ -209,24 +228,38 @@ 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);
-            sendMsgVo.setUserType(userType);
-            sendMsgVo.setCmd("out");
-            sendMsgVo.setMsg("用户离开");
-            sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
-            sendMsgVo.setNickName(fsUser.getNickname());
-            sendMsgVo.setAvatar(fsUser.getAvatar());
-
-            // 广播离开消息
-            broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+            LiveWatchUser liveWatchUserVO = liveWatchUserService.close(fsUser,liveId, userId);
+
+
+            // 广播离开消息 添加一个概率问题 摇塞子,1-4 当为1的时候广播消息
+            if (1 == new Random().nextInt(10)) {
+                SendMsgVo sendMsgVo = new SendMsgVo();
+                sendMsgVo.setLiveId(liveId);
+                sendMsgVo.setUserId(userId);
+                sendMsgVo.setUserType(userType);
+                sendMsgVo.setCmd("out");
+                sendMsgVo.setMsg("用户离开");
+                sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
+                sendMsgVo.setNickName(fsUser.getNickname());
+                sendMsgVo.setAvatar(fsUser.getAvatar());
+                broadcastWebMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+            }
+
         } else {
             adminRoom.remove(session);
+            // 如果admin房间为空,关闭并清理执行器
+            if (adminRoom.isEmpty()) {
+                ExecutorService executor = adminExecutors.remove(liveId);
+                if (executor != null) {
+                    executor.shutdown();
+                }
+                adminRooms.remove(liveId);
+            }
         }
 
-        log.debug("离开webSocket liveId: {}, userId: {}, 直播间人数: {}, 管理端人数: {}", liveId, userId, room.size(), adminRoom.size());
+        // 清理Session相关资源
+        heartbeatCache.remove(session.getId());
+        sessionLocks.remove(session.getId());
     }
 
     //收到客户端信息
@@ -243,6 +276,8 @@ public class WebSocketServer {
         try {
             switch (msg.getCmd()) {
                 case "heartbeat":
+                    // 更新心跳时间
+                    heartbeatCache.put(session.getId(), System.currentTimeMillis());
                     sendMessage(session, JSONObject.toJSONString(R.ok().put("data", msg)));
                     break;
                 case "sendMsg":
@@ -436,7 +471,6 @@ public class WebSocketServer {
      * 处理红包变动消息
      */
     private void processRed(Long liveId, SendMsgVo msg) {
-        log.debug("redData: {}", msg);
         JSONObject jsonObject = JSON.parseObject(msg.getData());
         Integer status = jsonObject.getInteger("status");
         msg.setStatus( status);
@@ -452,7 +486,6 @@ public class WebSocketServer {
      * 处理抽奖变动消息
      */
     private void processLottery(Long liveId, SendMsgVo msg) {
-        log.debug("lotteryData: {}", msg);
         JSONObject jsonObject = JSON.parseObject(msg.getData());
         Integer status = jsonObject.getInteger("status");
         msg.setStatus( status);
@@ -471,12 +504,7 @@ public class WebSocketServer {
         try {
             this.onClose(session);
         } catch (Exception e) {
-            log.error("webSocket 错误 onError", e);
-        }
-        if (throwable instanceof EOFException) {
-            log.info("WebSocket连接被客户端正常关闭(EOF),sessionId: {}", session.getId());
-        } else {
-            log.error("WebSocket连接错误", throwable);
+            log.error("webSocket 错误处理失败", e);
         }
     }
 
@@ -498,12 +526,36 @@ public class WebSocketServer {
         return adminRooms.computeIfAbsent(liveId, k -> new CopyOnWriteArrayList<>());
     }
 
-    //发送消息
+    //发送消息(带锁机制,避免并发发送)
     public void sendMessage(Session session, String message) throws IOException {
-        session.getAsyncRemote().sendText(message);
+        if (session == null || !session.isOpen()) {
+            return;
+        }
+
+        // 获取Session锁
+        Lock lock = sessionLocks.get(session.getId());
+        if (lock == null) {
+            // 如果锁不存在,创建一个新锁
+            lock = sessionLocks.computeIfAbsent(session.getId(), k -> new ReentrantLock());
+        }
+
+        // 使用锁保证同一Session的消息串行发送
+        lock.lock();
+        try {
+            if (session.isOpen()) {
+                session.getAsyncRemote().sendText(message);
+            }
+        } finally {
+            lock.unlock();
+        }
     }
 
     public void sendIntegralMessage(Long liveId, Long userId,Long scoreAmount) {
+        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
+        Session session = room.get(userId);
+        if (session == null || !session.isOpen()) {
+            return;
+        }
         SendMsgVo sendMsgVo = new SendMsgVo();
         sendMsgVo.setLiveId(liveId);
         sendMsgVo.setUserId(userId);
@@ -511,13 +563,19 @@ public class WebSocketServer {
         sendMsgVo.setCmd("Integral");
         sendMsgVo.setMsg("恭喜你成功获得观看奖励:" + scoreAmount + "芳华币");
         sendMsgVo.setData(String.valueOf(scoreAmount));
-        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
-        Session session = room.get(userId);
+
         if(Objects.isNull( session)) return;
         session.getAsyncRemote().sendText(JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
     }
 
     private void sendBlockMessage(Long liveId, Long userId) {
+
+        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
+        Session session = room.get(userId);
+        if (session == null || !session.isOpen()) {
+            return;
+        }
+
         SendMsgVo sendMsgVo = new SendMsgVo();
         sendMsgVo.setLiveId(liveId);
         sendMsgVo.setUserId(userId);
@@ -525,8 +583,7 @@ public class WebSocketServer {
         sendMsgVo.setCmd("blockUser");
         sendMsgVo.setMsg("账号已被停用");
         sendMsgVo.setData(null);
-        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
-        Session session = room.get(userId);
+
         if(Objects.isNull( session)) return;
         session.getAsyncRemote().sendText(JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
     }
@@ -536,20 +593,53 @@ public class WebSocketServer {
      * @param liveId   直播间ID
      * @param message  消息内容
      */
-    public void broadcastMessage(Long liveId, String message) {
+    public void broadcastWebMessage(Long liveId, String message) {
         ConcurrentHashMap<Long, Session> room = getRoom(liveId);
-        List<Session> adminRoom = getAdminRoom(liveId);
 
+        // 普通用户房间:并行发送
         room.forEach((k, v) -> {
             if (v.isOpen()) {
-                sendWithRetry(v,message,7);
+                sendWithRetry(v,message,1);
             }
         });
-        adminRoom.forEach(v -> {
+    }
+
+    /**
+     * 广播消息
+     * @param liveId   直播间ID
+     * @param message  消息内容
+     */
+    public void broadcastMessage(Long liveId, String message) {
+        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
+        List<Session> adminRoom = getAdminRoom(liveId);
+
+        // 普通用户房间:并行发送
+        room.forEach((k, v) -> {
             if (v.isOpen()) {
-                sendWithRetry(v,message,7);
+                sendWithRetry(v,message,1);
             }
         });
+
+        // admin房间:串行发送,使用单线程执行器
+        if (!adminRoom.isEmpty()) {
+            ExecutorService executor = adminExecutors.get(liveId);
+            if (executor != null && !executor.isShutdown()) {
+                executor.submit(() -> {
+                    for (Session session : adminRoom) {
+                        if (session.isOpen()) {
+                            sendWithRetry(session, message, 1);
+                        }
+                    }
+                });
+            } else {
+                // 如果执行器不存在或已关闭,直接发送
+                adminRoom.forEach(v -> {
+                    if (v.isOpen()) {
+                        sendWithRetry(v, message, 1);
+                    }
+                });
+            }
+        }
     }
 
     public void removeLikeCountCache(Long liveId) {
@@ -569,7 +659,6 @@ public class WebSocketServer {
                 String valueStr = cacheObject.toString().trim();
                 current = Integer.parseInt(valueStr);
             } catch (NumberFormatException e) {
-                log.error("点赞数格式错误,liveId: {}, value: {}", liveId, cacheObject, e);
                 continue;
             }
             Integer last = lastLikeCountCache.getOrDefault(liveId, 0);
@@ -586,6 +675,97 @@ public class WebSocketServer {
     }
 
 
+    @Scheduled(fixedRate = 2000)// 每2秒执行一次
+    public void broadcastUserNumMessage() {
+        // 遍历每个直播间
+        for (Map.Entry<Long, ConcurrentHashMap<Long, Session>> entry : rooms.entrySet()) {
+            Long liveId = entry.getKey();
+            ConcurrentHashMap<Long, Session> room = entry.getValue();
+
+            // 统计当前直播间的在线人数
+            int onlineCount = room.size();
+
+            // 构造消息
+            SendMsgVo sendMsgVo = new SendMsgVo();
+            sendMsgVo.setLiveId(liveId);
+            sendMsgVo.setCmd("userCount");
+            sendMsgVo.setData(String.valueOf(onlineCount));
+
+            // 广播当前直播间的在线人数
+            broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+        }
+    }
+
+    /**
+     * 定时清理无效会话(每分钟执行一次)
+     * 检查心跳超时的会话并关闭
+     */
+    @Scheduled(fixedRate = 60000) // 每分钟执行一次
+    public void cleanInactiveSessions() {
+        long currentTime = System.currentTimeMillis();
+        int cleanedCount = 0;
+
+        // 遍历所有直播间
+        for (Map.Entry<Long, ConcurrentHashMap<Long, Session>> roomEntry : rooms.entrySet()) {
+            Long liveId = roomEntry.getKey();
+            ConcurrentHashMap<Long, Session> room = roomEntry.getValue();
+
+            // 检查普通用户会话
+            List<Long> toRemove = new ArrayList<>();
+            room.forEach((userId, session) -> {
+                Long lastHeartbeat = heartbeatCache.get(session.getId());
+                if (lastHeartbeat != null && (currentTime - lastHeartbeat) > HEARTBEAT_TIMEOUT) {
+                    toRemove.add(userId);
+                    try {
+                        if (session.isOpen()) {
+                            session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "心跳超时"));
+                        }
+                    } catch (Exception e) {
+                        log.error("关闭超时会话失败: sessionId={}, liveId={}, userId={}",
+                                session.getId(), liveId, userId, e);
+                    }
+                }
+            });
+
+            // 移除超时的会话
+            toRemove.forEach(room::remove);
+            cleanedCount += toRemove.size();
+        }
+
+        // 检查admin房间
+        for (Map.Entry<Long, CopyOnWriteArrayList<Session>> adminEntry : adminRooms.entrySet()) {
+            Long liveId = adminEntry.getKey();
+            CopyOnWriteArrayList<Session> adminRoom = adminEntry.getValue();
+
+            List<Session> toRemoveAdmin = new ArrayList<>();
+            for (Session session : adminRoom) {
+                Long lastHeartbeat = heartbeatCache.get(session.getId());
+                if (lastHeartbeat != null && (currentTime - lastHeartbeat) > HEARTBEAT_TIMEOUT) {
+                    toRemoveAdmin.add(session);
+                    try {
+                        if (session.isOpen()) {
+                            session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "心跳超时"));
+                        }
+                    } catch (Exception e) {
+                        log.error("关闭admin超时会话失败: sessionId={}, liveId={}",
+                                session.getId(), liveId, e);
+                    }
+                }
+            }
+
+            // 移除超时的admin会话
+            toRemoveAdmin.forEach(adminRoom::remove);
+            cleanedCount += toRemoveAdmin.size();
+        }
+
+        if (cleanedCount > 0) {
+            if (random.nextInt(10) == 1) {
+                log.info("已清理 {} 个无效会话", cleanedCount);
+            }
+        }
+    }
+
+
     /**
      * 广播点赞消息
      * @param liveId   直播间ID
@@ -595,18 +775,21 @@ public class WebSocketServer {
         ConcurrentHashMap<Long, Session> room = getRoom(liveId);
         room.forEach((k, v) -> {
             if (v.isOpen()) {
-                sendWithRetry(v,message,7);
+                sendWithRetry(v,message,1);
             }
         });
     }
 
     private void sendWithRetry(Session session, String message, int maxRetries) {
+        if (session == null || !session.isOpen()) {
+            return;
+        }
+
         int attempts = 0;
         while (attempts < maxRetries) {
             try {
-                if(session.isOpen()) {
-                    session.getAsyncRemote().sendText(message);
-                }
+                // 使用带锁的sendMessage方法,避免并发发送
+                sendMessage(session, message);
                 return;  // 发送成功,退出
             } catch (Exception e) {
                 if (e.getMessage() != null && e.getMessage().contains("TEXT_FULL_WRITING")) {
@@ -618,11 +801,15 @@ public class WebSocketServer {
                         break;
                     }
                 } else {
-                    throw e;
+                    log.error("发送消息失败: sessionId={}, error={}", session.getId(), e.getMessage(), e);
+                    break;
                 }
             }
         }
-        log.info("超过重试次数, 消息 {}",message);
+
+        if (attempts >= maxRetries) {
+            log.warn("超过重试次数({}),放弃发送消息: sessionId={}", maxRetries, session.getId());
+        }
     }
 
 
@@ -684,6 +871,9 @@ public class WebSocketServer {
                 }
                 LiveCouponIssue liveCouponIssue = liveCouponIssueService.selectLiveCouponIssueByCouponId(liveCoupon.getCouponId());
                 LiveCouponIssueRelation relation = liveCouponMapper.selectCouponRelation(task.getLiveId(), liveCouponIssue.getId());
+                if (liveCoupon != null) {
+                    redisCache.setCacheObject(String.format(LiveKeysConstant.LIVE_COUPON_NUM , liveCouponIssue.getId()), liveCouponIssue.getRemainCount().intValue(), 30, TimeUnit.MINUTES);
+                }
                 HashMap<String, Object> data = new HashMap<>();
                 data.put("liveId", task.getLiveId());
                 data.put("couponIssueId", liveCouponIssue.getId());
@@ -728,4 +918,6 @@ public class WebSocketServer {
         String key = "live:auto_task:";
         redisCache.redisTemplate.opsForZSet().removeRangeByScore(key + liveId, data, data);
     }
+
 }
+

+ 69 - 31
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -46,13 +46,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import com.fs.app.task.qwTask;
 
+import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 @Api("公共接口")
 @RestController
@@ -164,36 +163,39 @@ public class CommonController {
             List<QwUser> list = qwUserMapper.selectQwUserByTest();
             for (QwUser qwUser : list) {
                 try {
-                    Integer serverStatus = qwUser.getServerStatus();
-                    Long serverId = qwUser.getServerId();
-                    if (serverStatus==0){
-                        log.error("不需要解绑");
-                    }
+
+                     Long serverId = qwUser.getServerId();
+
                     if (serverId==null){
-                        log.error("serverId不存在");
+                        System.out.println("serverId不存在");
+                    }else {
+                        //没绑定销售 或者 已经离职
+                        if (qwUser.getStatus()==0 || qwUser.getIsDel()==2){
+
+                            updateIpadStatus(qwUser,serverId);
+                        }
+
+                        //绑定了销售-也绑定了ipad,但是长时间离线的(离线状态,无操作超过2天的,也自动解绑)
+                        if(qwUser.getUpdateTime()!=null){
+                            Date createTime = qwUser.getUpdateTime();
+                            Integer serverStatus = qwUser.getServerStatus();
+                            Integer ipadStatus = qwUser.getIpadStatus();
+
+                            boolean result = isCreateTimeMoreThanDaysWithOptional(createTime, 2);
+                            //大于2天 ,绑定了ipad,离线
+                            if(result && serverStatus==1 && ipadStatus==0){
+                                updateIpadStatus(qwUser,serverId);
+
+                            }
+                        }
+
+
                     }
-                    QwUser u = new QwUser();
-                    u.setId(qwUser.getId());
-                    u.setServerId(null);
-                    u.setServerStatus(0);
-                    qwUserMapper.updateQwUser(u);
-                    ipadServerService.addServer(serverId);
-                    QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
-                    qwIpadServerLog.setType(2);
-                    qwIpadServerLog.setTilie("解绑");
-                    qwIpadServerLog.setServerId(serverId);
-                    qwIpadServerLog.setQwUserId(qwUser.getId());
-                    qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
-                    qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
-                    qwIpadServerLog.setCreateTime(new Date());
-                    qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
-                    qwIpadServerUserService.deleteQwIpadServerUserByQwUserId(qwUser.getId());
-                    WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
-                    wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
-                    wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
-                    updateIpadStatus(qwUser.getId(),0);
+
+
                 } catch (Exception e) {
-                    log.error("解绑ipad报错",e);
+                    System.out.println("解绑ipad报错"+e);
+
                 }
             }
         } catch (Exception e) {
@@ -203,6 +205,42 @@ public class CommonController {
     }
 
 
+    public void updateIpadStatus(QwUser qwUser,Long serverId){
+        QwUser u = new QwUser();
+        u.setId(qwUser.getId());
+        u.setServerId(null);
+        u.setServerStatus(0);
+        qwUserMapper.updateQwUser(u);
+        ipadServerService.addServer(serverId);
+        QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
+        qwIpadServerLog.setType(2);
+        qwIpadServerLog.setTilie("解绑");
+        qwIpadServerLog.setServerId(serverId);
+        qwIpadServerLog.setQwUserId(qwUser.getId());
+        qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
+        qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
+        qwIpadServerLog.setCreateTime(new Date());
+        qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
+        qwIpadServerUserService.deleteQwIpadServerUserByQwUserId(qwUser.getId());
+        WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+        wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
+        wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
+        updateIpadStatus(qwUser.getId(),0);
+    }
+
+    public static boolean isCreateTimeMoreThanDaysWithOptional(Date createTime, int days) {
+        return Optional.ofNullable(createTime)
+                .map(time -> {
+                    LocalDateTime createDateTime = time.toInstant()
+                            .atZone(ZoneId.systemDefault())
+                            .toLocalDateTime();
+                    LocalDateTime now = LocalDateTime.now();
+                    Duration duration = Duration.between(createDateTime, now);
+                    return duration.toDays() > days;
+                })
+                .orElse(false); // 为null时返回false,可根据需求调整
+    }
+
     void updateIpadStatus(Long id ,Integer status){
         QwUser u = new QwUser();
         u.setId(id);

+ 6 - 5
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -1022,14 +1022,14 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                 GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
                                 if (vo != null && vo.getId() != null) {
                                     sopLogs.setFsUserId(vo.getFsUserId());
-                                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo);
+                                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo,2);
                                 }
                             });
                         } catch (Exception e) {
                             log.error("群聊创建看课记录失败!", e);
                         }
                     } else {
-                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo,2);
                     }
 
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
@@ -1066,7 +1066,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     break;
                 //app
                 case "9":
-                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo,1);
 
                     QwCreateLinkByAppVO linkByApp = createLinkByApp(setting, logVo, sendTime, courseId, videoId,
                             qwUserId, companyUserId, companyId, externalId,sopLogs.getCorpId(),qwUserName);
@@ -1079,7 +1079,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     break;
                 //自定义小程序
                 case "10":
-                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo,2);
 
                     Optional<Company> matchedCompany = companies.stream()
                             .filter(company -> String.valueOf(company.getCompanyId()).equals(companyId))
@@ -1480,7 +1480,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private void addWatchLogIfNeeded(QwSopLogs sopLogs, Long videoId, Long courseId,
                                      Date sendTime, String qwUserId, String companyUserId,
-                                     String companyId, String externalId,SopUserLogsVo logsVo) {
+                                     String companyId, String externalId,SopUserLogsVo logsVo,Integer watchType) {
         FsCourseWatchLog watchLog = new FsCourseWatchLog();
         watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
         watchLog.setQwExternalContactId(externalId != null ? Long.valueOf(externalId) : null);
@@ -1495,6 +1495,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         watchLog.setUpdateTime(new Date());
         watchLog.setLogType(3);
         watchLog.setUserId(sopLogs.getFsUserId());
+        watchLog.setWatchType(watchType);
         watchLog.setCampPeriodTime(convertStringToDate(logsVo.getStartTime(),"yyyy-MM-dd"));
         enqueueWatchLog(watchLog);
     }

+ 34 - 0
fs-qwhook/src/main/java/com/fs/app/controller/CommonController.java

@@ -17,6 +17,7 @@ import com.fs.his.service.IFsAppVersionService;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwConfigSignatureParam;
+import com.fs.qw.param.QwMandatoryRegistrParam;
 import com.fs.qw.service.*;
 import com.fs.qw.vo.QwHookAuthVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
@@ -34,6 +35,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @Api("公共接口")
@@ -93,6 +96,37 @@ public class CommonController {
     @Autowired
     private RocketMQTemplate rocketMQTemplate;
 
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+
+    @GetMapping("/MandatoryRegistration")
+    public R MandatoryRegistration() throws Exception {
+        List<String> longs = qwExternalContactService.selectQwExternalContactMandatoryRegistration();
+        longs.forEach(item->{
+            List<QwMandatoryRegistrParam> registrParamList = qwExternalContactService.selectQwExternalContactMandatoryRegistrationByIds(String.valueOf(item));
+
+            batchUpdateQwExternalContactMandatoryRegistration(registrParamList);
+
+        });
+        return R.ok();
+    }
+
+    private void batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> registrParamList) {
+        // 定义批量插入的大小
+        int batchSize = 300;
+
+        // 循环处理外部用户 ID,每次处理批量大小的子集
+        for (int i = 0; i < registrParamList.size(); i += batchSize) {
+
+            int endIndex = Math.min(i + batchSize, registrParamList.size());
+            List<QwMandatoryRegistrParam> batchList = registrParamList.subList(i, endIndex);  // 获取当前批次的子集
+
+            qwExternalContactService.batchUpdateQwExternalContactMandatoryRegistration(batchList);
+
+        }
+    }
+
     @PostMapping("/qwHookNotify")
     public R qwHookNotify(@RequestBody String body) {
         QwHookVO vo= JSONUtil.toBean(body,QwHookVO.class);

+ 49 - 0
fs-service/src/main/java/com/fs/common/service/WechatLoginService.java

@@ -0,0 +1,49 @@
+package com.fs.common.service;
+
+import com.fs.common.core.redis.RedisCache;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class WechatLoginService {
+
+    @Autowired
+    private RedisCache redisCache;
+    /**
+     * 获取二维码参数
+     */
+    public Map<String, String> getQrCode(String ticket,String appId,String secret,String redirectUri) throws UnsupportedEncodingException {
+        String username = redisCache.getCacheObject("login:ticket:" + ticket);
+        if (username == null) throw new RuntimeException("ticket无效或过期");
+
+        Map<String, String> data = new HashMap<>();
+        data.put("appid", appId);
+        data.put("scope", "snsapi_login");
+        data.put("state", ticket);
+        data.put("redirect_uri", redirectUri);
+
+        // 拼接完整的微信扫码 URL
+        String url = "https://open.weixin.qq.com/connect/qrconnect?appid=" + appId
+                + "&redirect_uri=" + URLEncoder.encode(redirectUri, StandardCharsets.UTF_8.name())
+                + "&response_type=code"
+                + "&scope=snsapi_login"
+                + "&state=" + ticket
+                + "#wechat_redirect";
+        data.put("url", url);
+        log.info("url{}",url);
+        return data;
+    }
+
+
+
+
+
+}

+ 46 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceLogs.java

@@ -0,0 +1,46 @@
+package com.fs.company.domain;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 企业红包余额记录对象 company_red_packet_balance_logs
+ *
+ * @author fs
+ * @date 2025-11-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyRedPacketBalanceLogs extends BaseEntity{
+
+    /** ID */
+    private Long logsId;
+
+    /** 企业ID */
+    @Excel(name = "企业ID")
+    private Long companyId;
+
+    // 企业名称
+    private String companyName;
+
+    /** 金额 */
+    @Excel(name = "金额")
+    private BigDecimal money;
+
+    /** 余额 */
+    @Excel(name = "余额")
+    private BigDecimal balance;
+
+    /** 类型 字典字段 */
+    @Excel(name = "类型")
+    private Integer logsType;
+
+    /** 是否处理状态(0-初始化,1-已同步) */
+    private Long status;
+
+
+}

+ 10 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyUser.java

@@ -94,6 +94,16 @@ public class CompanyUser extends BaseEntity
     /** 医生id */
     private Long doctorId;
 
+    private String unionId;
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
     public String getIdCard() {
         return idCard;
     }

+ 3 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java

@@ -84,6 +84,9 @@ public interface CompanyRechargeMapper
             "<if test = 'maps.payType != null  '> " +
             "and r.pay_type = #{maps.payType}" +
             "</if>" +
+            "<if test = 'maps.businessType != null  '> " +
+            "and r.business_type = #{maps.businessType}" +
+            "</if>" +
             "<if test= 'maps.params != null and maps.params !=\"\"'>"+
             "<if test = 'maps.params.beginTime != null and maps.params.beginTime != \"\" '> " +
             "and date_format(r.pay_time,'%y%m%d') &gt;= date_format(#{maps.params.beginTime},'%y%m%d') " +

+ 64 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceLogsMapper.java

@@ -0,0 +1,64 @@
+package com.fs.company.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+
+/**
+ * 企业红包余额记录Mapper接口
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+public interface CompanyRedPacketBalanceLogsMapper extends BaseMapper<CompanyRedPacketBalanceLogs>{
+    /**
+     * 查询企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 企业红包余额记录
+     */
+    CompanyRedPacketBalanceLogs selectCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+
+    /**
+     * 查询企业红包余额记录列表
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 企业红包余额记录集合
+     */
+    List<CompanyRedPacketBalanceLogs> selectCompanyRedPacketBalanceLogsList(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 新增企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int insertCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 修改企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int updateCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 删除企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+
+    /**
+     * 批量删除企业红包余额记录
+     * 
+     * @param logsIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsIds(Long[] logsIds);
+
+    Company getCompanyRedPacketBalance(Long companyId);
+}

+ 1 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -351,4 +351,5 @@ public interface CompanyUserMapper
      */
     List<com.fs.hisStore.domain.FsUserScrm> selectBoundFsUsersByCompanyUserId(@Param("companyUserId") Long companyUserId);
 
+    int unBind(String userId);
 }

+ 64 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceLogsService.java

@@ -0,0 +1,64 @@
+package com.fs.company.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+
+/**
+ * 企业红包余额记录Service接口
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+public interface ICompanyRedPacketBalanceLogsService extends IService<CompanyRedPacketBalanceLogs>{
+    /**
+     * 查询企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 企业红包余额记录
+     */
+    CompanyRedPacketBalanceLogs selectCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+
+    /**
+     * 查询企业红包余额记录列表
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 企业红包余额记录集合
+     */
+    List<CompanyRedPacketBalanceLogs> selectCompanyRedPacketBalanceLogsList(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 新增企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int insertCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 修改企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int updateCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 批量删除企业红包余额记录
+     * 
+     * @param logsIds 需要删除的企业红包余额记录主键集合
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsIds(Long[] logsIds);
+
+    /**
+     * 删除企业红包余额记录信息
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+
+    Company getCompanyRedPacketBalance(Long companyId);
+}

+ 2 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java

@@ -264,4 +264,6 @@ public interface ICompanyUserService {
      * @return 绑定的用户列表
      */
     List<com.fs.hisStore.domain.FsUserScrm> selectBoundFsUsersByCompanyUserId(Long companyUserId);
+
+    int unBind(String userId);
 }

+ 99 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceLogsServiceImpl.java

@@ -0,0 +1,99 @@
+package com.fs.company.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.company.domain.Company;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.company.mapper.CompanyRedPacketBalanceLogsMapper;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
+
+/**
+ * 企业红包余额记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+@Service
+public class CompanyRedPacketBalanceLogsServiceImpl extends ServiceImpl<CompanyRedPacketBalanceLogsMapper, CompanyRedPacketBalanceLogs> implements ICompanyRedPacketBalanceLogsService {
+
+    /**
+     * 查询企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 企业红包余额记录
+     */
+    @Override
+    public CompanyRedPacketBalanceLogs selectCompanyRedPacketBalanceLogsByLogsId(Long logsId)
+    {
+        return baseMapper.selectCompanyRedPacketBalanceLogsByLogsId(logsId);
+    }
+
+    /**
+     * 查询企业红包余额记录列表
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 企业红包余额记录
+     */
+    @Override
+    public List<CompanyRedPacketBalanceLogs> selectCompanyRedPacketBalanceLogsList(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return baseMapper.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+    }
+
+    /**
+     * 新增企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    @Override
+    public int insertCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        companyRedPacketBalanceLogs.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs);
+    }
+
+    /**
+     * 修改企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    @Override
+    public int updateCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return baseMapper.updateCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs);
+    }
+
+    /**
+     * 批量删除企业红包余额记录
+     * 
+     * @param logsIds 需要删除的企业红包余额记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyRedPacketBalanceLogsByLogsIds(Long[] logsIds)
+    {
+        return baseMapper.deleteCompanyRedPacketBalanceLogsByLogsIds(logsIds);
+    }
+
+    /**
+     * 删除企业红包余额记录信息
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyRedPacketBalanceLogsByLogsId(Long logsId)
+    {
+        return baseMapper.deleteCompanyRedPacketBalanceLogsByLogsId(logsId);
+    }
+
+    @Override
+    public Company getCompanyRedPacketBalance(Long companyId) {
+        return baseMapper.getCompanyRedPacketBalance(companyId);
+    }
+}

+ 14 - 5
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -124,6 +124,9 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private LiveOrderMapper liveOrderMapper;
 
+    @Autowired
+    private CompanyRedPacketBalanceLogsMapper companyRedPacketBalanceLogsMapper;
+
 
     @Override
     public List<CompanyVO> liveShowList(CompanyParam param) {
@@ -200,6 +203,11 @@ public class CompanyServiceImpl implements ICompanyService
             List<CompanyMiniapp> miniApp = companyMiniappService.getMiniAppListByCompanyList(Collections.singletonList(company.getCompanyId()));
             company.setMiniAppMaster(GET_MINI_APP_STR.apply(0, miniApp));
             company.setMiniAppServer(GET_MINI_APP_STR.apply(1, miniApp));
+
+            String redPackageMoney = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY+company.getCompanyId());
+            if(redPackageMoney!=null){
+                company.setRedPackageMoney(new BigDecimal(redPackageMoney));
+            }
         }
         return company;
     }
@@ -336,6 +344,9 @@ public class CompanyServiceImpl implements ICompanyService
             company.setUserId(user.getUserId());
             companyMapper.updateCompany(company);
             bindMiniApp(company);
+            // 红包余额缓存
+            String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + company.getCompanyId();
+            redisCache.setCacheObject(companyMoneyKey, new BigDecimal(0.00).toString());
             return R.ok();
         }
         else
@@ -1526,18 +1537,16 @@ public class CompanyServiceImpl implements ICompanyService
     @Override
     public void asyncRecordBalanceLog(Long companyId, BigDecimal money,Integer logType, BigDecimal balance, String remark) {
         try {
-            CompanyMoneyLogs log = new CompanyMoneyLogs();
+            CompanyRedPacketBalanceLogs log = new CompanyRedPacketBalanceLogs();
             log.setCompanyId(companyId);
             log.setRemark(remark);
             log.setMoney(money);
             log.setLogsType(logType); // 同步余额
             log.setBalance(balance);
             log.setCreateTime(new Date());
-            moneyLogsMapper.insertCompanyMoneyLogs(log);
-            logger.info("异步登记余额日志成功 - 公司ID: {}, 金额: {}, 余额: {}, 备注: {}",
-                    companyId, money, balance, remark);
+            companyRedPacketBalanceLogsMapper.insertCompanyRedPacketBalanceLogs(log);
         } catch (Exception e) {
-            logger.error("异步登记余额日志失败 - 公司ID: {}, 金额: {}, 余额: {}, 备注: {}",
+            logger.error("异步登记红包余额日志失败 - 公司ID: {}, 金额: {}, 余额: {}, 备注: {}",
                     companyId, money, balance, remark, e);
         }
     }

+ 5 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -1096,4 +1096,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public List<com.fs.hisStore.domain.FsUserScrm> selectBoundFsUsersByCompanyUserId(Long companyUserId) {
         return companyUserMapper.selectBoundFsUsersByCompanyUserId(companyUserId);
     }
+
+    @Override
+    public int unBind(String userId) {
+        return companyUserMapper.unBind(userId);
+    }
 }

+ 25 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java

@@ -5,6 +5,7 @@ import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -66,4 +67,28 @@ public class FsCourseFinishTemp extends BaseEntity
      */
     @TableField(exist = false)
     private List<Long> ids;
+
+    @TableField(exist = false)
+    private List<String> userIds = new ArrayList<>();
+
+    public List<String> getUserIds() {
+        if (userIds == null || userIds.isEmpty()) {
+            return userIds;
+        }
+
+        // 直接在原始列表上修改
+        for (int i = 0; i < userIds.size(); i++) {
+            String id = userIds.get(i);
+            if (id != null) {
+                if (id.startsWith("dept_")) {
+                    userIds.set(i, id.substring(5));
+                } else if (id.startsWith("company_")) {
+                    userIds.set(i, id.substring(8));
+                } else if (id.startsWith("user_")) {
+                    userIds.set(i, id.substring(5));
+                }
+            }
+        }
+        return userIds;
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/domain/FsCoursePlaySourceConfig.java

@@ -104,4 +104,9 @@ public class FsCoursePlaySourceConfig {
      * 小程序状态:0正常,1半封禁,2封禁
      */
     private Integer status;
+
+    /**
+     * 商户支付配置id
+     */
+    private Long merchantConfigId;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseWatchLog.java

@@ -91,4 +91,6 @@ public class FsCourseWatchLog extends BaseEntity
     /** im发送消息详情id */
     private Long imMsgSendDetailId;
 
+    private Integer watchType;//看课方式:1 app  2 小程序
+
 }

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

@@ -120,4 +120,5 @@ public class FsUserCourseVideo extends BaseEntity
 
     private Long listingEndTime;//商品结束售卖时间
 
+    private Integer isSpeed; // 是否启用倍速 0:否 1:是
 }

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

@@ -65,6 +65,12 @@ public interface FsCourseAnswerLogsMapper
             "            <if test=\"map.createTime != null \"> and Date(cal.create_time) = #{map.createTime}</if>\n" +
             "<if test=\"map.sTime != null \">  and DATE(cal.create_time) &gt;= DATE(#{map.sTime})</if>\n" +
             "<if test=\"map.eTime != null \">  and DATE(cal.create_time) &lt;= DATE(#{map.eTime})</if>\n" +
+            "            <if test=\"map.companyUserIds != null and !map.companyUserIds.isEmpty()\">\n" +
+            "                AND cal.company_user_id IN\n" +
+            "                <foreach collection='map.companyUserIds' item='item' open='(' separator=',' close=')'>\n" +
+            "                    #{item}\n" +
+            "                </foreach>\n" +
+            "            </if>" +
             "        </where>  " +
             "order by cal.log_id desc  " +
             " </script>")

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

@@ -87,6 +87,12 @@ public interface FsCourseFinishTempMapper
             "<if test = ' maps.status !=null '> " +
             "and t.status = #{maps.status} " +
             "</if>" +
+            "            <if test=\"userIds != null and !userIds.isEmpty()\">\n" +
+            "                AND create_by IN\n" +
+            "                <foreach collection='userIds' item='item' open='(' separator=',' close=')'>\n" +
+            "                    #{item}\n" +
+            "                </foreach>\n" +
+            "            </if>" +
             " order by t.id desc  "+
             "</script>"})
     List<FsCourseFinishTempListVO> selectFsCourseFinishTempListVO(@Param("maps") FsCourseFinishTemp fsCourseFinishTemp);

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

@@ -14,4 +14,6 @@ public interface FsCoursePlaySourceConfigMapper extends BaseMapper<FsCoursePlayS
      * 查询点播配置列表
      */
     List<FsCoursePlaySourceConfigVO> selectCoursePlaySourceConfigVOListByMap(@Param("params") Map<String, Object> params);
+
+    FsCoursePlaySourceConfig selectCoursePlaySourceConfigByAppId(String appId);
 }

+ 10 - 3
fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java

@@ -107,13 +107,13 @@ public interface FsCourseRedPacketLogMapper
     List<FsCourseRedPacketLogListPVO> selectRedPacketLogListVO(@Param("maps") FsCourseRedPacketLogParam param);
 
     @Select({"<script> " +
-            "select l.*,v.title,u.nick_name as fsNickName,u.avatar as fsAvatar,u.phone,cu.nick_name company_user_name,c.company_name,qu.qw_user_name,fuc.course_name,u.phone as phoneNumber   from fs_course_red_packet_log l  \n" +
+            "select l.*,v.title,u.nick_name as fsNickName,u.avatar as fsAvatar,u.phone,cu.nick_name company_user_name,c.company_name,qu.qw_user_name,fuc.course_name,u.phone as phoneNumber,cu.dept_id   from fs_course_red_packet_log l  \n" +
             "left join fs_user_course_video v on v.video_id = l.video_id \n" +
             "left join fs_user u on u.user_id = l.user_id \n" +
             "left join fs_user_course fuc on fuc.course_id = l.course_id \n" +
             "left join company_user cu on cu.user_id=l.company_user_id \n" +
-            "left join company c on c.company_id=l.company_id \n" +
-            "LEFT JOIN qw_user qu on qu.id= l.qw_user_id  " +
+            "left join company c on c.company_id=cu.company_id \n" +
+            "LEFT JOIN qw_user qu on qu.id= l.qw_user_id  \n" +
             "where 1=1   " +
             "<if test = ' maps.userId !=null '> and l.user_id = #{maps.userId} </if>" +
             "<if test = ' maps.watchLogId !=null '> and l.watch_log_id = #{maps.watchLogId} </if>" +
@@ -123,11 +123,18 @@ public interface FsCourseRedPacketLogMapper
             "<if test = ' maps.nickName !=null '> and u.nick_name  like concat('%', #{maps.nickName}, '%') </if>" +
             "<if test = ' maps.courseId !=null '> and l.course_id = #{maps.courseId} </if>" +
             "<if test = ' maps.videoId !=null '> and l.video_id = #{maps.videoId} </if>" +
+            "<if test = ' maps.periodId !=null '> and l.period_id = #{maps.periodId} </if>" +
             "<if test = ' maps.status !=null '> and l.status = #{maps.status} </if>" +
             "<if test = \"maps.phone !=null and maps.phone != '' \"> and u.phone = #{maps.phone} </if>" +
             "<if test = ' maps.qwUserId !=null '> and l.qw_user_id = #{maps.qwUserId} </if>" +
             "<if test=\"maps.sTime != null \">  and DATE(l.create_time) &gt;= DATE(#{maps.sTime})</if>\n" +
             "<if test=\"maps.eTime != null \">  and DATE(l.create_time) &lt;= DATE(#{maps.eTime})</if>\n" +
+            "<if test=\"maps.userIds != null and maps.userIds.size() > 0\">\n" +
+            "                AND l.company_user_id IN\n" +
+            "                <foreach collection=\"maps.userIds\" open=\"(\" close=\")\" separator=\",\" item=\"item\">\n" +
+            "                    ${item}\n" +
+            "                </foreach>\n" +
+            "            </if>" +
             " order by l.log_id desc  "+
             "</script>"})
     List<FsCourseRedPacketLogListPVO> selectFsCourseRedPacketLogListVO(@Param("maps")FsCourseRedPacketLogParam fsCourseRedPacketLog);

+ 6 - 2
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -265,10 +265,10 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "       and  send_type= #{sendType} " +
             "</if>\n" +
             "<if test= 'sTime != null '> " +
-            "       and DATE(o.create_time) &gt;= DATE(#{sTime})\n" +
+            "       and o.create_time &gt;= #{sTime}\n" +
             "</if>\n" +
             "<if test='eTime != null '> " +
-            "      and DATE(o.create_time) &lt;= DATE(#{eTime})\n" +
+            "      and o.create_time &lt; DATE_ADD(#{eTime}, INTERVAL 1 DAY)\n" +
             "</if>" +
             "<if test ='sendType != 1 and nickName !=null and nickName!=\"\"'>\n" +
             "   and qu.qw_user_name like concat( #{nickName}, '%')\n" +
@@ -295,6 +295,10 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "</script>"})
     List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVO(FsCourseWatchLogStatisticsListParam param);
 
+    List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVO_COUNT(FsCourseWatchLogStatisticsListParam param);
+
+
+
     @Select({"<script> " +
             " \tselect \n" +
             "\t'总合计' as qw_user_name,\n" +

+ 2 - 5
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java

@@ -2,7 +2,6 @@ package com.fs.course.mapper;
 
 import java.util.List;
 
-import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserCourseVideoRedPackage;
 import com.fs.course.param.FsUserCourseVideoParam;
 import org.apache.ibatis.annotations.Delete;
@@ -10,8 +9,6 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
-import javax.validation.constraints.NotNull;
-
 /**
  * 课程公司红包Mapper接口
  *
@@ -107,12 +104,12 @@ public interface FsUserCourseVideoRedPackageMapper
     int updateBatchDelFlag(@Param("ids") Long [] ids, @Param("delFlag") Integer delFlag);
 
     @Delete("<script>" +
-            "DELETE FROM fs_user_course_video_red_package WHERE video_id IN " +
+            "DELETE FROM fs_user_course_video_red_package WHERE period_id IN " +
             "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
             "#{id}" +
             "</foreach>" +
             "</script>")
-    int deleteBatchByVideoIds(@Param("ids") Long[] ids);
+    int deleteBatchByPeriodIds(@Param("ids") Long[] ids);
 
 
     Integer selectRedPacketByCompanyCount(@Param("videoId") Long videoId,@Param("companyId") Long companyId, @Param("periodId") Long periodId);

+ 30 - 2
fs-service/src/main/java/com/fs/course/param/FsCourseAnswerLogsParam.java

@@ -1,10 +1,13 @@
 package com.fs.course.param;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Set;
 
 @Data
@@ -33,8 +36,8 @@ public class FsCourseAnswerLogsParam  extends BaseEntity  {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date sTime;
 
-    private Long pageNum;
-    private Long pageSize;
+    private Integer pageNum;
+    private Integer pageSize;
 
     /**
      * 用户(昵称_id组合)
@@ -45,4 +48,29 @@ public class FsCourseAnswerLogsParam  extends BaseEntity  {
      */
     private Set<Long> userIds;
 
+    @TableField(exist = false)
+    private List<String> companyUserIds=new ArrayList<>();
+
+
+    public List<String> getCompanyUserIds() {
+        if (companyUserIds == null || companyUserIds.isEmpty()) {
+            return companyUserIds;
+        }
+
+        // 直接在原始列表上修改
+        for (int i = 0; i < companyUserIds.size(); i++) {
+            String id = companyUserIds.get(i);
+            if (id != null) {
+                if (id.startsWith("dept_")) {
+                    companyUserIds.set(i, id.substring(5));
+                } else if (id.startsWith("company_")) {
+                    companyUserIds.set(i, id.substring(8));
+                } else if (id.startsWith("user_")) {
+                    companyUserIds.set(i, id.substring(5));
+                }
+            }
+        }
+        return companyUserIds;
+    }
+
 }

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

@@ -55,4 +55,7 @@ public class FsCoursePlaySourceConfigEditParam {
 
     @ApiModelProperty("小程序状态:0正常,1半封禁,2封禁")
     private Integer status;
+
+    @ApiModelProperty("商户支付配置id")
+    private Long merchantConfigId;
 }

+ 37 - 1
fs-service/src/main/java/com/fs/course/param/FsCourseRedPacketLogParam.java

@@ -3,10 +3,13 @@ package com.fs.course.param;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
+import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
 
 @Data
-public class FsCourseRedPacketLogParam {
+public class FsCourseRedPacketLogParam implements Serializable {
 
     private Long userId;
 
@@ -20,6 +23,11 @@ public class FsCourseRedPacketLogParam {
 
     private Long videoId;
 
+    /**
+     * 营期id
+     */
+    private Long periodId;
+
     private Long status;
 
     private String phone;
@@ -34,4 +42,32 @@ public class FsCourseRedPacketLogParam {
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date sTime;
+
+    private Integer pageNum;
+    private Integer pageSize;
+
+    private List<String> userIds;
+
+    public List<String> getUserIds() {
+        if (userIds == null || userIds.isEmpty()) {
+            return userIds;
+        }
+
+        return userIds.stream()
+                .map(id -> {
+                    if (id == null) {
+                        return null;
+                    }
+                    // 去除前缀
+                    if (id.startsWith("dept_")) {
+                        return "0";
+                    } else if (id.startsWith("company_")) {
+                        return "0";
+                    } else if (id.startsWith("user_")) {
+                        return id.substring(5);
+                    }
+                    return "0";
+                })
+                .collect(Collectors.toList());
+    }
 }

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

@@ -6,6 +6,7 @@ import lombok.Data;
 import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Data
 public class FsCourseWatchLogListParam implements Serializable {
@@ -107,4 +108,32 @@ public class FsCourseWatchLogListParam implements Serializable {
     private Long deptId;
     private List<Long> deptIds;
     private String ids;
+
+    private Integer pageNum;
+    private Integer pageSize;
+    private List<String> userIds;
+    private Integer watchType;
+
+    public List<String> getUserIds() {
+        if (userIds == null || userIds.isEmpty()) {
+            return userIds;
+        }
+
+        return userIds.stream()
+                .map(id -> {
+                    if (id == null) {
+                        return null;
+                    }
+                    // 去除前缀
+                    if (id.startsWith("dept_")) {
+                        return "0";
+                    } else if (id.startsWith("company_")) {
+                        return "0";
+                    } else if (id.startsWith("user_")) {
+                        return id.substring(5);
+                    }
+                    return "0";
+                })
+                .collect(Collectors.toList());
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/param/PeriodStatisticCountParam.java

@@ -18,6 +18,11 @@ public class PeriodStatisticCountParam implements Serializable {
     @ApiModelProperty(value = "营期id")
     private Long periodId;
 
+    /**
+     * 营期名称
+     */
+    private String periodName;
+
     /**
      * 训练营Id
      */

+ 7 - 0
fs-service/src/main/java/com/fs/course/service/IFsCoursePlaySourceConfigService.java

@@ -15,4 +15,11 @@ public interface IFsCoursePlaySourceConfigService extends IService<FsCoursePlayS
     List<FsCoursePlaySourceConfigVO> selectCoursePlaySourceConfigVOListByMap(Map<String, Object> params);
 
     List<FsCoursePlaySourceConfig> selectByAppIds(List<String> miniAppList);
+
+    /**
+     * 根据appId查询
+     * @param appId
+     * @return
+     */
+    FsCoursePlaySourceConfig selectCoursePlaySourceConfigByAppId(String appId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCoursePlaySourceConfigServiceImpl.java

@@ -30,4 +30,9 @@ public class FsCoursePlaySourceConfigServiceImpl extends ServiceImpl<FsCoursePla
     public List<FsCoursePlaySourceConfig> selectByAppIds(List<String> miniAppList) {
         return baseMapper.selectList(new QueryWrapper<FsCoursePlaySourceConfig>().in("appid", miniAppList));
     }
+
+    @Override
+    public FsCoursePlaySourceConfig selectCoursePlaySourceConfigByAppId(String appId) {
+        return baseMapper.selectCoursePlaySourceConfigByAppId(appId);
+    }
 }

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

@@ -680,6 +680,7 @@ public class FsCourseProductOrderServiceImpl extends ServiceImpl<FsCourseProduct
                     request.setOrdAmt(payment.getPayMoney().toString());
                     request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
                     request.setReqSeqId("refund-"+payment.getPayCode());
+                    request.setAppId(payment.getAppId());
                     Map<String, Object> extendInfoMap = new HashMap<>();
                     extendInfoMap.put("org_req_seq_id", "product-"+payment.getPayCode());
                     request.setExtendInfo(extendInfoMap);

+ 19 - 5
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -3,16 +3,16 @@ package com.fs.course.service.impl;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.cache.ICompanyDeptCacheService;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.mapper.CompanyMapper;
@@ -62,6 +62,8 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
 
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private ICompanyDeptCacheService companyDeptCacheService;
     /**
      * 查询短链课程看课记录
      *
@@ -162,7 +164,19 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
 
     @Override
     public List<FsCourseRedPacketLogListPVO> selectFsCourseRedPacketLogListVO(FsCourseRedPacketLogParam fsCourseRedPacketLog) {
-        return fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
+        List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
+        // 应用到每个对象
+        if(CollectionUtil.isNotEmpty(list)){
+            list.forEach(item -> {
+                if(item.getDeptId() != null) {
+                    String deptNameById = companyDeptCacheService.getDeptNameById(item.getDeptId());
+                    if(StringUtils.isNotBlank(deptNameById)) {
+                        item.setDeptName(deptNameById);
+                    }
+                }
+            });
+        }
+        return list;
     }
 
     @Override

+ 19 - 6
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.json.JSONUtil;
@@ -13,6 +14,7 @@ import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DictUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyDeptCacheService;
 import com.fs.company.cache.ICompanyUserCacheService;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
@@ -138,6 +140,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private FsTagUpdateService fsTagUpdateService;
 
+    @Autowired
+    private ICompanyDeptCacheService companyDeptCacheService;
+
     /**
      * 查询短链课程看课记录
      *
@@ -677,12 +682,20 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 ));
 
         // 遍历并赋值
-        fsCourseWatchLogListVOS.forEach(vo -> {
-            String periodName = periodIdToNameMap.get(vo.getPeriodId());
-            if (periodName != null) {
-                vo.setPeriodIdName(periodName);
-            }
-        });
+        if(CollectionUtil.isNotEmpty(fsCourseWatchLogListVOS)){
+            fsCourseWatchLogListVOS.forEach(vo -> {
+                String periodName = periodIdToNameMap.get(vo.getPeriodId());
+                if (periodName != null) {
+                    vo.setPeriodIdName(periodName);
+                }
+                if(vo.getDeptId() != null) {
+                    String deptNameById = companyDeptCacheService.getDeptNameById(vo.getDeptId());
+                    if(com.fs.common.utils.StringUtils.isNotBlank(deptNameById)) {
+                        vo.setDeptName(deptNameById);
+                    }
+                }
+            });
+        }
 
         return fsCourseWatchLogListVOS;
     }

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

@@ -152,8 +152,8 @@ public class FsUserCompanyBindServiceImpl extends ServiceImpl<FsUserCompanyBindM
                 }
             }
             if (qwExternalContact.getUserRepeat() == 0) {
-                Integer i = baseMapper.selectCount(new QueryWrapper<FsUserCompanyBind>().eq("fs_user_id", fsUserId).ne("company_user_id", companyUserId));
-                if (i > 0) {
+                Integer i = baseMapper.selectCount(new QueryWrapper<FsUserCompanyBind>().eq("fs_user_id", fsUserId));
+                if (i > 1) {
                     qwExternalContact.setUserRepeat(1);
                     qwExternalContactMapper.updateById(qwExternalContact);
                 }

+ 13 - 6
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -198,11 +198,13 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
             day.setVideoId(e);
             day.setCreateTime(new Date());
             // 默认开启今天及以后的两天
-            LocalDate compareDay = LocalDate.now().plusDays(1);
-            if(day.getDayDate().isBefore(compareDay)){
+            LocalDateTime compareDayTime = LocalDateTime.now();
+            if(compareDayTime.isAfter(day.getStartDateTime()) && compareDayTime.isBefore(day.getEndDateTime())){
                 day.setStatus(1);
-            } else {
+            } else if(compareDayTime.isBefore(day.getStartDateTime())){
                 day.setStatus(0);
+            } else {
+                day.setStatus(2);
             }
             return day;
         }).collect(Collectors.toList());
@@ -326,17 +328,20 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         LocalDateTime tempStartDateTime = source.getStartDateTime();
         LocalDateTime tempEndDateTime = source.getEndDateTime();
         LocalDateTime tempLastJoinTime = source.getLastJoinTime();
+        Integer status = source.getStatus();
 
         // 将目标数据赋给源
         source.setDayDate(target.getDayDate());
         source.setLesson(target.getLesson());
         source.setStartDateTime(target.getStartDateTime());
         source.setEndDateTime(target.getEndDateTime());
+        source.setStatus(target.getStatus());
         source.setLastJoinTime(target.getLastJoinTime());
 
         // 将保存的源数据赋给目标
         target.setDayDate(tempDayDate);
         target.setLesson(tempLesson);
+        target.setStatus(status);
         target.setStartDateTime(tempStartDateTime);
         target.setEndDateTime(tempEndDateTime);
         target.setLastJoinTime(tempLastJoinTime);
@@ -440,11 +445,13 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         day.setEndDateTime(LocalDateTime.of(day.getDayDate(), day.getEndDateTime().toLocalTime()));
         day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), day.getLastJoinTime().toLocalTime()));
         // 默认开启今天及以后的两天,为进行中
-        LocalDate compareDay = LocalDate.now().plusDays(1);
-        if(day.getDayDate().isBefore(compareDay)){
+        LocalDateTime compareDayTime = LocalDateTime.now();
+        if(compareDayTime.isAfter(day.getStartDateTime()) && compareDayTime.isBefore(day.getEndDateTime())){
             day.setStatus(1);
-        } else {
+        } else if(compareDayTime.isBefore(day.getStartDateTime())){
             day.setStatus(0);
+        } else {
+            day.setStatus(2);
         }
         updateById(day);
         return R.ok();

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

@@ -169,13 +169,12 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
         }
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
-        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
             //删除红包记录(修改状态)
             //fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
             //直接删除
-            fsUserCourseVideoRedPackageMapper.deleteBatchByVideoIds(videoIds.toArray(new Long[0]));
+            fsUserCourseVideoRedPackageMapper.deleteBatchByPeriodIds(periodIds);
         }
         return flag;
     }

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

@@ -97,13 +97,12 @@ public class FsUserCourseTrainingCampServiceImpl extends ServiceImpl<FsUserCours
         }
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
-        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
             //删除红包记录(修改状态)
             //fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
             //直接删除
-            fsUserCourseVideoRedPackageMapper.deleteBatchByVideoIds(videoIds.toArray(new Long[0]));
+            fsUserCourseVideoRedPackageMapper.deleteBatchByPeriodIds(set.toArray(new Long[0]));
         }
     }
 

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

@@ -300,8 +300,10 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             fsUserCourseVideo.setListingStartTime(null);
             fsUserCourseVideo.setListingEndTime(null);
         }
-        String videoRedisKey = "h5user:video:duration:" + fsUserCourseVideo.getVideoId();
-        redisCache.setCacheObject(videoRedisKey, fsUserCourseVideo.getDuration());
+        String videoRedisKey1 = "h5user:video:duration:" + fsUserCourseVideo.getVideoId();
+        redisCache.setCacheObject(videoRedisKey1, fsUserCourseVideo.getDuration());
+        String videoRedisKey2 = "h5wxuser:video:duration:" + fsUserCourseVideo.getVideoId();
+        redisCache.setCacheObject(videoRedisKey2, fsUserCourseVideo.getDuration());
         return fsUserCourseVideoMapper.updateFsUserCourseVideo(fsUserCourseVideo);
     }
 
@@ -1009,6 +1011,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         log.setQwUserId(Long.valueOf(param.getQwUserId()));
         log.setCreateTime(new Date());
         log.setLogType(3);
+        log.setWatchType(2);
         logger.info("【群聊生成看课记录】:{}", param);
         courseWatchLogMapper.insertFsCourseWatchLog(log);
     }
@@ -1218,10 +1221,17 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 return R.error("公司不存在");
             }
 
+            // 实际流量传输消耗是大于文件的,添加倍数计算流量消耗 配置 course.data.usage.multiple
+            BigDecimal multiple = new BigDecimal("1"); // 默认一倍
+            String config=configService.selectConfigByKey("course.data.usage.multiple");
+            if(StringUtils.isNotEmpty(config)){
+                multiple=new BigDecimal(config);
+            }
+
             // 计算流量
             BigDecimal result = param.getBufferRate().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
             BigDecimal longAsBigDecimal = BigDecimal.valueOf(video.getFileSize());
-            long roundedResult = result.multiply(longAsBigDecimal).setScale(0, RoundingMode.HALF_UP).longValue();
+            long roundedResult = result.multiply(longAsBigDecimal).multiply(multiple).setScale(0, RoundingMode.HALF_UP).longValue();
             trafficLog.setInternetTraffic(roundedResult);
             // 获取课程所属项目id
             FsUserCourse fsUserCourse = fsUserCourseMapper.selectFsUserCourseByCourseId(param.getCourseId());
@@ -1731,41 +1741,166 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     private R sendRedPacketRewardToUser(FsCourseSendRewardUParam param, FsCourseWatchLog log, CourseConfig config, WxSendRedPacketParam packetParam, BigDecimal amount) {
 
 
-        // 发送红包
-        R sendRedPacket = paymentService.sendRedPacket(packetParam);
-        if (sendRedPacket.get("code").equals(200)) {
-            FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-            TransferBillsResult transferBillsResult;
-            if (sendRedPacket.get("isNew").equals(1)) {
-                transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
-                redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
-                redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+        // 自动看课红包余额字段实时监控  xgb 1111
+        if("1".equals(config.getIsRedPackageBalanceDeduction())) {
+            // 新增的有设计文档 可以找 xgb 要
+            // ===================== 20251022 xgb 修改 本次修改目的为了实时扣减公司余额=====================
+            // 1 使用redis缓存加锁 预扣减余额 红包发送失败 恢复redis缓存余额,如果回滚失败登记异常记录表 定时任务重新回滚余额
+            // 2 另起定时任务 同步缓存余额到redis中
+            // 3 注意!!!!! 启动系统时查询公司账户余额(这个时候要保证余额正确)启动会自动保存到redis缓存中
+            // 注意!!!!! 打开这个开关前记得检测redis缓存余额是否正确 若不正确 修改数据库字段red_package_money,删除redis缓存,重启系统,
+
+
+            // 预设值异常对象
+            BalanceRollbackError balanceRollbackError = new BalanceRollbackError();
+            balanceRollbackError.setCompanyId(packetParam.getCompanyId());
+            balanceRollbackError.setUserId(param.getUserId());
+            balanceRollbackError.setLogId(log.getLogId());
+            balanceRollbackError.setVideoId(log.getVideoId());
+            balanceRollbackError.setStatus(0);
+            balanceRollbackError.setMoney(amount);
+
+            if (packetParam.getCompanyId() == null) {
+                logger.error("发送红包参数错误,公司不能为空,异常请求参数{}", packetParam);
+                return R.error("发送红包失败,请联系管理员");
+            }
+            String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
+
+            // 第一次加锁:预扣减余额
+            RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + packetParam.getCompanyId());
+            boolean lockAcquired = false;
+            BigDecimal newMoney;
+            try {
+                if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
+                    lockAcquired = true;
+                    BigDecimal originalMoney;
+                    // 获取当前余额
+                    String moneyStr = redisCache.getCacheObject(companyMoneyKey);
+                    if (StringUtils.isNotEmpty(moneyStr)) {
+                        originalMoney = new BigDecimal(moneyStr);
+                    } else {
+                        // 缓存没有值,重启系统恢复redis数据 保证数据正确性
+                        logger.error("发送红包获取redis余额缓存异常,异常请求参数{}", packetParam);
+                        return R.error("系统异常,请稍后重试");
+                    }
+
+                    if (originalMoney.compareTo(BigDecimal.ZERO) < 0) {
+                        logger.error("服务商余额不足,异常请求参数{}", packetParam);
+                        return R.error("服务商余额不足,请联系群主服务器充值!");
+                    }
+
+                    // 预扣减金额
+                    newMoney = originalMoney.subtract(amount);
+                    redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+                } else {
+                    logger.error("获取redis锁失败,异常请求参数{}", packetParam);
+                    return R.error("系统繁忙,请稍后重试");
+                }
+            } catch (Exception e) {
+                logger.error("预扣减余额失败: 异常请求参数{},异常信息{}", packetParam, e.getMessage(), e);
+                return R.error("系统异常,请稍后重试");
+            } finally {
+                // 只有在成功获取锁的情况下才释放锁
+                if (lockAcquired && lock1.isHeldByCurrentThread()) {
+                    try {
+                        lock1.unlock();
+                    } catch (IllegalMonitorStateException e) {
+                        logger.warn("尝试释放非当前线程持有的锁: companyId={}", packetParam.getCompanyId());
+                    }
+                }
+            }
+
+            // 调用第三方接口(锁外操作)
+            R sendRedPacket;
+            try {
+                sendRedPacket= paymentService.sendRedPacket(packetParam);
+            } catch (Exception e) {
+                logger.error("红包发送异常: 异常请求参数{}",packetParam, e);
+                // 异常时回滚余额
+
+                rollbackBalance(balanceRollbackError);
+                return R.error("奖励发送失败,请联系客服");
+            }
+
+            if (sendRedPacket.get("code").equals(200)) {
+                FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                TransferBillsResult transferBillsResult;
+                if (sendRedPacket.get("isNew").equals(1)){
+                    transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
+                    redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+                    redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                }else {
+                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                }
+                // 添加红包记录
+                redPacketLog.setCourseId(param.getCourseId());
+                redPacketLog.setCompanyId(param.getCompanyId());
+                redPacketLog.setUserId(param.getUserId());
+                redPacketLog.setVideoId(param.getVideoId());
+                redPacketLog.setStatus(0);
+                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+                redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                redPacketLog.setCreateTime(new Date());
+                redPacketLog.setAmount(amount);
+                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+                redPacketLog.setPeriodId(param.getPeriodId());
+
+                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+                // 更新观看记录的奖励类型
+                log.setRewardType(config.getRewardType());
+                courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+                // 异步登记余额扣减日志
+                BigDecimal money=amount.multiply(BigDecimal.valueOf(-1));
+                companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包");
+//            redisCache.setCacheObject("h5user:redPacket:"+param.getUserId(),LocalDateTime.now().toString());
+
+                return sendRedPacket;
             } else {
-                redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                // 登记回滚流水表
+                rollbackBalance(balanceRollbackError);
+                return R.error("奖励发送失败,请联系客服");
             }
-            // 添加红包记录
-            redPacketLog.setCourseId(param.getCourseId());
-            redPacketLog.setCompanyId(param.getCompanyId());
-            redPacketLog.setUserId(param.getUserId());
-            redPacketLog.setVideoId(param.getVideoId());
-            redPacketLog.setStatus(0);
-            redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
-            redPacketLog.setCompanyUserId(param.getCompanyUserId());
-            redPacketLog.setCreateTime(new Date());
-            redPacketLog.setAmount(amount);
-            redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
-            redPacketLog.setPeriodId(param.getPeriodId());
 
-            redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-            // 更新观看记录的奖励类型
-            log.setRewardType(config.getRewardType());
-            courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+
+        }else {
+            // 发送红包
+            R sendRedPacket = paymentService.sendRedPacket(packetParam);
+            if (sendRedPacket.get("code").equals(200)) {
+                FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                TransferBillsResult transferBillsResult;
+                if (sendRedPacket.get("isNew").equals(1)){
+                    transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
+                    redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+                    redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                }else {
+                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                }
+                // 添加红包记录
+                redPacketLog.setCourseId(param.getCourseId());
+                redPacketLog.setCompanyId(param.getCompanyId());
+                redPacketLog.setUserId(param.getUserId());
+                redPacketLog.setVideoId(param.getVideoId());
+                redPacketLog.setStatus(0);
+                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+                redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                redPacketLog.setCreateTime(new Date());
+                redPacketLog.setAmount(amount);
+                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+                redPacketLog.setPeriodId(param.getPeriodId());
+
+                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+                // 更新观看记录的奖励类型
+                log.setRewardType(config.getRewardType());
+                courseWatchLogMapper.updateFsCourseWatchLog(log);
 
 //            redisCache.setCacheObject("h5user:redPacket:"+param.getUserId(),LocalDateTime.now().toString());
 
-            return sendRedPacket;
-        } else {
-            return R.error("奖励发送失败,请联系客服");
+                return sendRedPacket;
+            } else {
+                return R.error("奖励发送失败,请联系客服");
+            }
         }
     }
 
@@ -1977,7 +2112,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                     // 异步登记余额扣减日志
                     BigDecimal money = amount.multiply(BigDecimal.valueOf(-1));
                     companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包");
-                    // 发送成功,记录日志等操作
+
                     return sendRedPacket;
 
 
@@ -1995,44 +2130,49 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                     return R.error("服务商余额不足,请联系群主服务器充值!");
                 }
 
-                // 发送红包
-                R sendRedPacket = paymentService.sendRedPacket(packetParam);
-                if (sendRedPacket.get("code").equals(200)) {
-                    FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-                    TransferBillsResult transferBillsResult;
-                    if (sendRedPacket.get("isNew").equals(1)) {
-                        transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
-                        redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
-                        redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
-                        redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
-                    } else {
-                        redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-                        redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
-                    }
-                    // 添加红包记录
-                    redPacketLog.setCourseId(param.getCourseId());
-                    redPacketLog.setCompanyId(param.getCompanyId());
-                    redPacketLog.setUserId(param.getUserId());
-                    redPacketLog.setVideoId(param.getVideoId());
-                    redPacketLog.setStatus(0);
-                    redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
-                    redPacketLog.setCompanyUserId(param.getCompanyUserId());
-                    redPacketLog.setCreateTime(new Date());
-                    redPacketLog.setAmount(amount);
-                    redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
-                    redPacketLog.setPeriodId(param.getPeriodId());
-                    redPacketLog.setAppId(param.getAppId());
-
-                    redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+             try{
+                 // 发送红包
+                 R sendRedPacket = paymentService.sendRedPacket(packetParam);
+                 if (sendRedPacket.get("code").equals(200)) {
+                     FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                     TransferBillsResult transferBillsResult;
+                     if (sendRedPacket.get("isNew").equals(1)) {
+                         transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
+                         redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+                         redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                         redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+                     } else {
+                         redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                         redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                     }
+                     // 添加红包记录
+                     redPacketLog.setCourseId(param.getCourseId());
+                     redPacketLog.setCompanyId(param.getCompanyId());
+                     redPacketLog.setUserId(param.getUserId());
+                     redPacketLog.setVideoId(param.getVideoId());
+                     redPacketLog.setStatus(0);
+                     redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+                     redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                     redPacketLog.setCreateTime(new Date());
+                     redPacketLog.setAmount(amount);
+                     redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+                     redPacketLog.setPeriodId(param.getPeriodId());
+                     redPacketLog.setAppId(param.getAppId());
+
+                     redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+                     // 更新观看记录的奖励类型
+                     log.setRewardType(config.getRewardType());
+                     courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+                     return sendRedPacket;
+                 } else {
+                     return R.error("奖励发送失败,请联系客服");
+                 }
+             }catch (Exception e){
+                 return R.error("发放奖励失败,请联系客服");
+             }
 
-                    // 更新观看记录的奖励类型
-                    log.setRewardType(config.getRewardType());
-                    courseWatchLogMapper.updateFsCourseWatchLog(log);
-
-                    return sendRedPacket;
-                } else {
-                    return R.error("奖励发送失败,请联系客服");
-                }
             }
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
@@ -2885,7 +3025,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
 
         //看课记录
-        addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), param.getFsUserId(), qwUser, param.getExternalUserId());
+        addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), param.getFsUserId(), qwUser, param.getExternalUserId(),2);
 
         //生成小程序链接
         String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(), 2, null, 0);
@@ -2928,7 +3068,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             domainName = config.getRealLinkDomainName();
         }
 
-        addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), param.getFsUserId(), qwUser, param.getExternalUserId());
+        addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), param.getFsUserId(), qwUser, param.getExternalUserId(),2);
 
         String linkByCartLink = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(), 1, domainName, 0);
 
@@ -2946,7 +3086,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
 
     //插入观看记录
     private void addWatchLogIfNeeded(Long videoId, Long courseId,
-                                     Long fsUserId, QwUser qwUser, Long externalId) {
+                                     Long fsUserId, QwUser qwUser, Long externalId,Integer watchType) {
 
         try {
 
@@ -2962,7 +3102,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             watchLog.setCreateTime(new Date());
             watchLog.setUpdateTime(new Date());
             watchLog.setLogType(3);
-
+            watchLog.setWatchType(watchType);
             if (fsUserId == null) {
                 fsUserId = 0L;
             }
@@ -3481,10 +3621,17 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 return R.error("视频不存在");
             }
 
+            // 实际流量传输消耗是大于文件的,添加倍数计算流量消耗 配置 course.data.usage.multiple
+            BigDecimal multiple = new BigDecimal("1"); // 默认一倍
+            String config=configService.selectConfigByKey("course.data.usage.multiple");
+            if(config!= null){
+                multiple=new BigDecimal(config);
+            }
+
             // 计算流量
             BigDecimal result = param.getBufferRate().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
             BigDecimal longAsBigDecimal = BigDecimal.valueOf(video.getFileSize());
-            long roundedResult = result.multiply(longAsBigDecimal).setScale(0, RoundingMode.HALF_UP).longValue();
+            long roundedResult = result.multiply(longAsBigDecimal).multiply(multiple).setScale(0, RoundingMode.HALF_UP).longValue();
             trafficLog.setInternetTraffic(roundedResult);
 
             //扣除流量
@@ -3923,7 +4070,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             }
 
             //看课记录
-            addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), qwExternalContact.getFsUserId(), qwUser, qwExternalContact.getId());
+            addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), qwExternalContact.getFsUserId(), qwUser, qwExternalContact.getId(),2);
 
             //生成小程序链接
             String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, qwExternalContact.getId(),2,null, 0);
@@ -3937,7 +4084,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
 
             contacts.forEach(contact -> {
                 //看课记录
-                addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), contact.getFsUserId(), qwUser, contact.getId());
+                addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), contact.getFsUserId(), qwUser, contact.getId(),2);
             });
 
             //生成小程序链接

Някои файлове не бяха показани, защото твърде много файлове са промени