Browse Source

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java
#	fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
#	fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
#	fs-service/src/main/java/com/fs/course/param/FsCourseRedPacketLogParam.java
#	fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
#	fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
peicj 4 days ago
parent
commit
8e512adabe
100 changed files with 2861 additions and 274 deletions
  1. 103 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java
  2. 18 6
      fs-admin/src/main/java/com/fs/course/controller/FsCourseAnswerLogsController.java
  3. 38 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java
  4. 12 4
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java
  5. 96 17
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  6. 45 4
      fs-admin/src/main/java/com/fs/his/task/Task.java
  7. 3 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  8. 34 7
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  9. 9 0
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  10. 8 2
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  11. 3 0
      fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java
  12. 4 4
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  13. 3 3
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  14. 30 6
      fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java
  15. 52 9
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  16. 111 0
      fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java
  17. 8 1
      fs-admin/src/main/java/com/fs/web/controller/common/CommonController.java
  18. 129 7
      fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java
  19. 10 0
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java
  20. 33 0
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  21. 51 4
      fs-company/src/main/java/com/fs/company/controller/company/CompanyLoginController.java
  22. 116 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyRedPacketBalanceLogsController.java
  23. 10 4
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  24. 69 1
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  25. 11 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  26. 10 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwTagGroupController.java
  27. 1 1
      fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java
  28. 257 0
      fs-company/src/main/java/com/fs/framework/service/CompanyLoginService.java
  29. 2 1
      fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java
  30. 196 0
      fs-framework/src/main/java/com/fs/framework/web/service/SysLoginService.java
  31. 2 1
      fs-live-app/src/main/java/com/fs/live/controller/LiveController.java
  32. 6 3
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  33. 49 0
      fs-service/src/main/java/com/fs/common/service/WechatLoginService.java
  34. 46 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceLogs.java
  35. 10 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  36. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  37. 64 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceLogsMapper.java
  38. 64 0
      fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceLogsService.java
  39. 99 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceLogsServiceImpl.java
  40. 13 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  41. 25 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java
  42. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  43. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseFinishTempMapper.java
  44. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  45. 30 2
      fs-service/src/main/java/com/fs/course/param/FsCourseAnswerLogsParam.java
  46. 213 69
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  47. 5 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseFinishTempListVO.java
  48. 28 22
      fs-service/src/main/java/com/fs/course/vo/FsCourseRedPacketLogListPVO.java
  49. 75 10
      fs-service/src/main/java/com/fs/erp/service/impl/FsJstAftersalePushScrmServiceImpl.java
  50. 13 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java
  51. 46 6
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  52. 3 0
      fs-service/src/main/java/com/fs/his/domain/FsUserInformationCollection.java
  53. 8 6
      fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java
  54. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserWxMapper.java
  55. 3 0
      fs-service/src/main/java/com/fs/his/param/FsIntegralOrderParam.java
  56. 3 2
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  57. 1 1
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java
  58. 20 6
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  59. 1 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java
  60. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  61. 2 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  62. 3 3
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  63. 2 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderItemScrmService.java
  64. 1 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  65. 10 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderItemScrmServiceImpl.java
  66. 12 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  67. 2 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderErpExportVO.java
  68. 1 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderExportVO.java
  69. 8 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java
  70. 1 1
      fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java
  71. 46 7
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  72. 2 1
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  73. 2 2
      fs-service/src/main/java/com/fs/live/service/ILiveDataService.java
  74. 12 17
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  75. 4 4
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  76. 26 13
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  77. 10 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  78. 1 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  79. 1 0
      fs-service/src/main/java/com/fs/live/vo/FsMyLiveOrderListQueryVO.java
  80. 2 0
      fs-service/src/main/java/com/fs/live/vo/LiveDataDetailVo.java
  81. 2 0
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java
  82. 2 0
      fs-service/src/main/java/com/fs/live/vo/ProductSalesVo.java
  83. 2 0
      fs-service/src/main/java/com/fs/qw/mapper/QwIpadServerUserMapper.java
  84. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  85. 15 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  86. 6 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactAddTagParam.java
  87. 8 0
      fs-service/src/main/java/com/fs/qw/param/QwTagParam.java
  88. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwIpadServerUserService.java
  89. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwTagGroupService.java
  90. 4 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  91. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwIpadServerUserServiceImpl.java
  92. 14 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java
  93. 58 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  94. 4 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  95. 32 1
      fs-service/src/main/java/com/fs/sop/domain/QwSop.java
  96. 27 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java
  97. 43 0
      fs-service/src/main/java/com/fs/store/dto/ClientCredGrantReqDTO.java
  98. 52 0
      fs-service/src/main/java/com/fs/store/dto/MiniGramSubsMsgResultDTO.java
  99. 68 0
      fs-service/src/main/java/com/fs/store/dto/TemplateMessageSendRequestDTO.java
  100. 36 0
      fs-service/src/main/java/com/fs/store/dto/WeXinAccessTokenDTO.java

+ 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);
     }
 

+ 12 - 4
fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java

@@ -55,20 +55,28 @@ 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) {
 
             fsCourseRedPacketLogListPVO.setPhone(PhoneUtil.decryptAutoPhoneMk(fsCourseRedPacketLogListPVO.getPhone()));
         }
-        return getDataTable(list);
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**

+ 96 - 17
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;
@@ -29,6 +32,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
+import lombok.extern.slf4j.Slf4j;
 
 import java.text.ParseException;
 import java.time.LocalDateTime;
@@ -45,6 +49,7 @@ import static com.fs.his.utils.PhoneUtil.decryptPhone;
  * @author fs
  * @date 2023-11-02
  */
+@Slf4j
 @RestController
 @RequestMapping("/his/integralOrder")
 public class FsIntegralOrderController extends BaseController
@@ -78,44 +83,118 @@ 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")) {
                 fsIntegralOrder.setStatus("1");
                 fsIntegralOrder.setIsPush(0);
             }
-            List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+
+            // 金牛明医项目:支持多个订单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);
+//            }
+            list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        if (sysRole != null && !(sysRole.getIsCheckPhone()==1)) {
             for (FsIntegralOrderListVO vo : list) {
                 vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
             }
-            return getDataTable(list);
-        }
-        List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
-        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);
-            ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
-            return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
+            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"));
+                            }
+                        }
+                    } 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()));
+            }
+
         }
-        return fsIntegralOrderService.export(fsIntegralOrder);
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
     }
     /**
      * 发货

+ 45 - 4
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,46 @@ public class Task {
     @Autowired
     private FsIntegralOrderMapper fsIntegralOrderMapper;
 
+    private final String DELAY_MSG = "delayMsg";
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private AiHookService aiHookService;
+
+
+    /**
+     * 定时任务,处理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 {
+                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消耗统计
      */

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

@@ -133,6 +133,9 @@ public class FsStoreAfterSalesScrmController extends BaseController
 //                            zmvo.setAfterSalesNumber
                             zmvo.setRefundMoney(vo.getRefundAmount());
                             zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
+
                         } catch (Exception e) {
                             // 处理异常
                             e.printStackTrace();

+ 34 - 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,6 +55,7 @@ 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;
@@ -340,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();
@@ -377,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;
+    }
+
 
     /**
      * 导出订单列表(明文)
@@ -453,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())) {

+ 9 - 0
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() {

+ 8 - 2
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -3,6 +3,7 @@ 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;
@@ -174,7 +175,10 @@ public class MallStoreTask
         // 单个异常影响全部,跳过异常单子
         for (Long id : ids) {
             try {
-                fsStoreOrderService.createOmsOrder(id);
+                R omsOrder = fsStoreOrderService.createOmsOrder(id);
+                if ("500".equals(omsOrder.get("code"))) {
+
+                }
             } catch (Exception e) {
                 log.error("创建商城oms订单失败:"+id);
                 log.error("创建商城oms订单失败:"+e.getMessage());
@@ -224,7 +228,9 @@ public class MallStoreTask
         for (FsStoreOrderScrm order : list) {
             order.setUpdateTime(nowDate);
         }
-        fsStoreOrderMapper.batchUpdateTime(list);
+        if (list!= null && !list.isEmpty()){
+            fsStoreOrderMapper.batchUpdateTime(list);
+        }
         for (FsStoreOrderScrm order : list){
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
             request.setCode(order.getExtendOrderId());

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

@@ -143,6 +143,8 @@ public class LiveAfterSalesController extends BaseController
 //                            zmvo.setAfterSalesNumber
                             zmvo.setRefundMoney(vo.getRefundAmount());
                             zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
                         } catch (Exception e) {
                             // 处理异常
                             e.printStackTrace();
@@ -193,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));
     }

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

@@ -93,7 +93,7 @@ public class LiveController extends BaseController {
     @PutMapping
     public AjaxResult edit(@RequestBody Live live) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
         return toAjax(liveService.updateLive(live));
     }
 
@@ -105,7 +105,7 @@ public class LiveController extends BaseController {
     @DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
         return toAjax(liveService.deleteLiveByLiveIds(liveIds, new Live()));
     }
 
@@ -130,7 +130,7 @@ public class LiveController extends BaseController {
     @PostMapping("/handleShelfOrUn")
     public R handleShelfOrUn(@RequestBody LiveListVo listVo) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleShelfOrUnAdmin(listVo);
     }
 
@@ -141,7 +141,7 @@ public class LiveController extends BaseController {
     @PostMapping("/handleDeleteSelected")
     public R handleDeleteSelected(@RequestBody LiveListVo listVo) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
+        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, "直播间用户详情数据");
     }

+ 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);

+ 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-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()
     {
 

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

@@ -451,4 +451,37 @@ 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));
+    }
 }

+ 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("提交成功,等待审核");
+
+    }
+
+}

+ 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

+ 69 - 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,6 +15,7 @@ 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;
@@ -38,6 +40,72 @@ 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();
+
+        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 = 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 +135,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);
     }

+ 11 - 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;
@@ -370,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);
@@ -457,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,
                         "/",

+ 257 - 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}")
+    private String appId;
+    @Value("${wechat.company.secret}")
+    private String secret;
+    @Value("${wechat.company.redirectUri}")
+    private String redirectUri;
+    @Value("${wechat.isNeedScan}")
+    private Boolean isNeedScan;
+
+    @Autowired
+    private WechatLoginService wechatLoginService;
+
+    @Autowired
+    private UserDetailsService userDetailsService;
+
     /**
      * 登录验证
      *
@@ -91,4 +125,227 @@ public class CompanyLoginService
         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-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()

+ 196 - 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}")
+    private String appId;
+    @Value("${wechat.admin.secret}")
+    private String secret;
+    @Value("${wechat.admin.redirectUri}")
+    private String redirectUri;
+    @Value("${wechat.isNeedScan}")
+    private Boolean isNeedScan;
 
     /**
      * 登录验证
@@ -126,4 +147,179 @@ public class SysLoginService
         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);
+    }
 }

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

@@ -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,

+ 6 - 3
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -146,7 +146,7 @@ public class WebSocketServer {
                 redisCache.incr(UNIQUE_VIEWERS_KEY + liveId, 1);
             }
             liveWatchUserVO.setMsgStatus(liveWatchUserVO.getMsgStatus());
-            if (1 == random.nextInt(4)) {
+            if (1 == random.nextInt(10)) {
                 SendMsgVo sendMsgVo = new SendMsgVo();
                 sendMsgVo.setLiveId(liveId);
                 sendMsgVo.setUserId(userId);
@@ -232,7 +232,7 @@ public class WebSocketServer {
 
 
             // 广播离开消息 添加一个概率问题 摇塞子,1-4 当为1的时候广播消息
-            if (1 == new Random().nextInt(4)) {
+            if (1 == new Random().nextInt(10)) {
                 SendMsgVo sendMsgVo = new SendMsgVo();
                 sendMsgVo.setLiveId(liveId);
                 sendMsgVo.setUserId(userId);
@@ -759,7 +759,7 @@ public class WebSocketServer {
         ConcurrentHashMap<Long, Session> room = getRoom(liveId);
         room.forEach((k, v) -> {
             if (v.isOpen()) {
-                sendWithRetry(v,message,7);
+                sendWithRetry(v,message,1);
             }
         });
     }
@@ -855,6 +855,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());

+ 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);
+}

+ 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);
+}

+ 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);
+    }
+}

+ 13 - 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,10 @@ public class CompanyServiceImpl implements ICompanyService
             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;
     }
 
@@ -335,6 +342,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
@@ -1525,18 +1535,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);
         }
     }

+ 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;
+    }
 }

+ 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);

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

@@ -107,7 +107,7 @@ 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,cu.dept_id   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,cd.dept_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" +

+ 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;
+    }
+
 }

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

@@ -1206,10 +1206,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());
@@ -1719,41 +1726,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("奖励发送失败,请联系客服");
+            }
         }
     }
 
@@ -1965,7 +2097,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                     // 异步登记余额扣减日志
                     BigDecimal money = amount.multiply(BigDecimal.valueOf(-1));
                     companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包");
-                    // 发送成功,记录日志等操作
+
                     return sendRedPacket;
 
 
@@ -1983,44 +2115,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);
-
-                    // 更新观看记录的奖励类型
-                    log.setRewardType(config.getRewardType());
-                    courseWatchLogMapper.updateFsCourseWatchLog(log);
+             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("发放奖励失败,请联系客服");
+             }
 
-                    return sendRedPacket;
-                } else {
-                    return R.error("奖励发送失败,请联系客服");
-                }
             }
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
@@ -3469,10 +3606,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);
 
             //扣除流量

+ 5 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseFinishTempListVO.java

@@ -42,6 +42,7 @@ public class FsCourseFinishTempListVO implements Serializable
     private Long courseId;
 
     private String courseName;
+    private String createBy;
 
     private String videoName;
 
@@ -64,4 +65,8 @@ public class FsCourseFinishTempListVO implements Serializable
     private Date updateTime;
 
     private Integer isAllCompanyUser;
+
+    private String createByName;
+
+    private String createByDeptName;
 }

+ 28 - 22
fs-service/src/main/java/com/fs/course/vo/FsCourseRedPacketLogListPVO.java

@@ -28,24 +28,31 @@ public class FsCourseRedPacketLogListPVO extends BaseEntity
 
     private String fsAvatar;
 
+    @Excel(name = "课程id")
+    private String courseId;
     @Excel(name = "课程名称")
     private String courseName;
 
-    @Excel(name = "小节名称")
-    private String videoName;
+    @Excel(name = "小节id")
+    private String videoId;
 
-    @Excel(name = "记录类型" ,dictType = "sys_course_watch_log_type")
-    private Integer logType;
+    @Excel(name = "小节名称")
+    private String title;
 
-    @Excel(name = "企微外部联系人id")
-    private String qwExternalContactId;
+    @Excel(name = "小节名称")
+    private String videoName;
 
 
-    @Excel(name = "播放时长")
-    private String duration;
+    @Excel(name = "电话")
+    private String phone;
+    /** 转帐金额 */
+    @Excel(name = "转帐金额")
+    private BigDecimal amount;
 
+    @Excel(name = "状态",dictType = "sys_course_red_packet_status")
+    private Integer status;//状态 0 发送中  1  已发送
 
-    @Excel(name = "分享人企微userId")
+    @Excel(name = "分享人企微Id")
     private String qwUserId;
     /**
      * 企业微信员工名称
@@ -56,6 +63,10 @@ public class FsCourseRedPacketLogListPVO extends BaseEntity
     @Excel(name = "所属销售")
     private String companyUserName;
 
+
+    @Excel(name = "所属部门")
+    private String deptName;
+
     @Excel(name = "所属团队")
     private String companyName;
 
@@ -75,22 +86,17 @@ public class FsCourseRedPacketLogListPVO extends BaseEntity
     @Excel(name = "批次编号")
     private String outBatchNo;
 
-    @Excel(name = "课程id")
-    private String courseId;
-    @Excel(name = "课程id")
-    private String videoId;
 
-    @Excel(name = "小节名称")
-    private String title;
 
-    @Excel(name = "电话")
-    private String phone;
-    /** 转帐金额 */
-    @Excel(name = "转帐金额")
-    private BigDecimal amount;
+    @Excel(name = "记录类型" ,dictType = "sys_course_watch_log_type_new")
+    private Integer logType;
 
-    @Excel(name = "状态",dictType = "sys_course_red_packet_status")
-    private Integer status;//状态 0 发送中  1  已发送
+    @Excel(name = "企微外部联系人id")
+    private String qwExternalContactId;
+
+
+    @Excel(name = "播放时长")
+    private String duration;
 
     private Long deptId;
     private Long companyId;

+ 75 - 10
fs-service/src/main/java/com/fs/erp/service/impl/FsJstAftersalePushScrmServiceImpl.java

@@ -22,6 +22,12 @@ import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.mapper.FsStoreAfterSalesScrmMapper;
 import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
+import com.fs.live.domain.LiveOrder;
+import com.fs.live.domain.LiveOrderItem;
+import com.fs.live.mapper.LiveOrderMapper;
+import com.fs.live.mapper.LiveOrderPaymentMapper;
+import com.fs.live.service.ILiveOrderItemService;
+import com.fs.live.service.ILiveOrderPaymentService;
 import com.fs.ybPay.dto.RefundOrderDTO;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.util.Asserts;
@@ -45,6 +51,13 @@ public class FsJstAftersalePushScrmServiceImpl implements FsJstAftersalePushScrm
 
     @Autowired
     private FsStoreOrderScrmMapper fsStoreOrderMapper;
+    @Autowired
+    private LiveOrderMapper liveOrderMapper;
+
+    @Autowired
+    private ILiveOrderItemService liveOrderItemService;
+    @Autowired
+    private LiveOrderPaymentMapper liveOrderPaymentMapper;
 
     @Autowired
     private IFsStoreOrderItemScrmService storeOrderItemService;
@@ -65,21 +78,37 @@ public class FsJstAftersalePushScrmServiceImpl implements FsJstAftersalePushScrm
                 log.info("获取记录{} 锁失败!",item.getId());
                 continue;
             }
-
+            item.setRetryCount(item.getRetryCount()+1);
             FsStoreOrderScrm fsStoreOrder = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(item.getOrderId());
+            LiveOrder liveOrder = null;
+            // 拆开,现在分为商城订单和直播订单
+            if (fsStoreOrder == null) {
+                liveOrder = liveOrderMapper.selectLiveOrderByOrderCode(item.getOrderId());
+                if (liveOrder == null) {
+                    item.setErrorMessage("该订单未找到!");
+                    item.setTaskStatus(TaskStatusEnum.FAILED.getCode());
+                    log.info("该订单未找到!");
+                    fsJstAftersalePushMapper.update(item);
+                    continue;
+                }
+            }
 
-            item.setRetryCount(item.getRetryCount()+1);
 
-            if(fsStoreOrder == null){
-                item.setErrorMessage("该订单未找到!");
-                item.setTaskStatus(TaskStatusEnum.FAILED.getCode());
-                log.info("该订单未找到!");
-                fsJstAftersalePushMapper.update(item);
+            RefundOrderDTO dto;
+            try {
+                if(fsStoreOrder != null){
+                    dto = getAfterSaleDTO(item, fsStoreOrder);
+                }else {
+                    dto = getAfterSaleLiveDTO(item, liveOrder);
+                }
+            } catch (Exception e) {
+                log.error("订单售后创建售后信息失败:{}" ,e.getMessage());
                 continue;
             }
-            Asserts.notNull(fsStoreOrder,"该订单未找到!");
-            RefundOrderDTO dto;
-            dto = getAfterSaleDTO(item, fsStoreOrder);
+
+
+
+
             // 买家已经申请,等待卖家同意
             if(StringUtils.equals(AfterSalesOrderStatusEnum.WAIT_SELLER_AGREE.getIndex().toString()
                     ,item.getType())){
@@ -173,4 +202,40 @@ public class FsJstAftersalePushScrmServiceImpl implements FsJstAftersalePushScrm
         dto.setItems(refundItemDTOS);
         return dto;
     }
+
+    private RefundOrderDTO getAfterSaleLiveDTO(FsJstAftersalePush item, LiveOrder fsStoreOrder) {
+        RefundOrderDTO dto = new RefundOrderDTO();
+        AfterSalesOrderStatusEnum statusEnum = AfterSalesOrderStatusEnum.getByIndex(Integer.valueOf(item.getType()));
+
+        dto.setShopStatus(statusEnum.getCode());
+        dto.setQuestionType("可更新");
+        dto.setOuterAsId(item.getAfterSaleId());
+        dto.setRemark("用户退款");
+        dto.setType("仅退款");
+
+        dto.setShopId(Long.parseLong(shopId));
+        dto.setTotalAmount(fsStoreOrder.getTotalPrice());
+        dto.setSoId(item.getOrderId());
+        dto.setRefund(fsStoreOrder.getPayPrice());
+
+
+        FsStoreOrderItemScrm itemMap=new FsStoreOrderItemScrm();
+//        itemMap.setOrderId(fsStoreOrder.getId());
+        itemMap.setOrderId(fsStoreOrder.getOrderId());
+        List<LiveOrderItem> orderItems=liveOrderItemService.selectCheckedByOrderId(fsStoreOrder.getOrderId());
+        List<RefundItemDTO> refundItemDTOS=new ArrayList<>();
+
+        for(LiveOrderItem orderItem: orderItems) {
+            FsStoreCartDTO cartDTO = JSONUtil.toBean(orderItem.getJsonInfo(), FsStoreCartDTO.class);
+
+            RefundItemDTO itemDTO = new RefundItemDTO();
+            itemDTO.setSkuId(cartDTO.getBarCode());
+            itemDTO.setQty(cartDTO.getNum());
+            itemDTO.setAmount(cartDTO.getPrice());
+            itemDTO.setType("退货");
+            refundItemDTOS.add(itemDTO);
+        }
+        dto.setItems(refundItemDTOS);
+        return dto;
+    }
 }

+ 13 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java

@@ -4,6 +4,8 @@ import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.sql.Time;
+
 /**
  * 应用对象 fastgpt_role
  *
@@ -65,4 +67,15 @@ public class FastGptRole extends BaseEntity
     private String channelType;
 
     private Integer logistics;
+
+    //回复禁止起始时间
+    private Time forbidSendStart;
+
+    //回复禁止结束时间
+    private Time forbidSendEnd;
+
+    /**
+     * 是否禁止时段回复 0是不开启禁止  1是开启禁止 默认为1
+     */
+    private Integer forbidStatus;
 }

+ 46 - 6
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -66,14 +66,17 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.Nullable;
+import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.lang.reflect.Field;
+import java.sql.Time;
 import java.time.DayOfWeek;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -169,6 +172,8 @@ public class AiHookServiceImpl implements AiHookService {
     private static final String AI_REPLY = "AI_REPLY:";
     private static final String AI_REPLY_TAG = "AI_REPLY_TAG:";
 
+    private final String DELAY_MSG = "delayMsg";
+
 
     /** Ai半小时未回复提醒 **/
     /**
@@ -479,6 +484,39 @@ public class AiHookServiceImpl implements AiHookService {
                 }
             }
 
+            //规定时间不发送内容
+            if(role.getForbidStatus() == null){
+                role.setForbidStatus(1);
+            }
+
+            if(role.getForbidStatus() == 1){
+                Time forbidSendStart = role.getForbidSendStart()==null? Time.valueOf("00:00:00") :role.getForbidSendStart();
+                Time forbidSendEnd = role.getForbidSendEnd()==null? Time.valueOf("04:59:59") :role.getForbidSendEnd();
+                //设置规定时间不发送消息
+                Time now = Time.valueOf(LocalDateTime.now().toLocalTime());
+                // 判断当前时间是否在禁止发送时间段内
+                if (now.after(forbidSendStart) && now.before(forbidSendEnd)) {
+                    String sessionId = String.valueOf(fastGptChatSession.getSessionId());
+                    log.info("当前时间 {} 在禁止发送时间段内 ({} - {}),会话id:{},跳过回复", now, forbidSendStart, forbidSendEnd,fastGptChatSession.getSessionId());
+
+                    // HSET命令是原子的,多个线程同时写不同field不会互相覆盖;同field会覆盖(符合需求)
+                    JSONObject jsonObject = new JSONObject();
+                    jsonObject.put("content",contentEmj);
+                    jsonObject.put("sender",sender);
+                    jsonObject.put("type",type);
+                    String objectString = jsonObject.toString();
+
+                    redisCache.hPut(DELAY_MSG, sessionId, objectString);
+
+                    // 4. 确保主Key有8小时过期时间(只在首次设置时生效,避免重复刷新)
+                    if (!redisCache.hasKey(DELAY_MSG)) {
+                        redisCache.expire(DELAY_MSG, 8, TimeUnit.HOURS);
+                    }
+
+                    return R.ok();
+                }
+            }
+
 
             //判断是否转人工
             if (fastGptChatSession.getIsArtificial()==1){
@@ -549,7 +587,7 @@ public class AiHookServiceImpl implements AiHookService {
                 //从fastgpt_chat_artificial_words表中查询所有转人工文本
                 List<FastgptChatArtificialWords> chatArtificialWords = qwExternalContactMapper.selectChatGptChatArtificialWords();
                 List<String> collect = chatArtificialWords.stream().map(m -> m.getContent()).collect(Collectors.toList());
-                if (collect.stream().anyMatch(contentKh::contains)){
+                if (collect.stream().anyMatch(content::contains)){
                     log.info("触发关键词:"+role.getRoleId()+":"+qwExternalContacts.getName());
                     notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," 触发关键词",qwExternalContacts.getId(),sender);
                     return R.ok();
@@ -1157,7 +1195,7 @@ public class AiHookServiceImpl implements AiHookService {
     }
 
 
-    private void saveQwUserMsg(FastGptChatSession fastGptChatSession,Integer sendType,String content) {
+    private void saveQwUserMsg(FastGptChatSession fastGptChatSession,Integer sendType,String content,QwUser sendUser) {
         content = replaceWxEmo(content);
         if(content.isEmpty()){
             return;
@@ -1168,6 +1206,7 @@ public class AiHookServiceImpl implements AiHookService {
         fastGptChatMsgAi.setRoleId(Long.parseLong(fastGptChatSession.getKfId()));
         fastGptChatMsgAi.setSendType(sendType);
         fastGptChatMsgAi.setCompanyId(fastGptChatSession.getCompanyId());
+        fastGptChatMsgAi.setCompanyUserId(sendUser.getCompanyUserId());
         fastGptChatMsgAi.setUserId(fastGptChatSession.getUserId());
         fastGptChatMsgAi.setUserType(1);
         fastGptChatMsgAi.setMsgType(1);
@@ -1972,7 +2011,7 @@ public class AiHookServiceImpl implements AiHookService {
             }
             FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), sendUser.getId());
             if (fastGptChatSession!=null){
-                saveQwUserMsg(fastGptChatSession,2,count);
+                saveQwUserMsg(fastGptChatSession,2,count,sendUser);
                 // 客服进行回复后就转人工10分钟
                 if(type == 1){
                     Calendar calendar = Calendar.getInstance();
@@ -2012,9 +2051,10 @@ public class AiHookServiceImpl implements AiHookService {
                         fastGptChatSession.setCompanyId(sendUser.getCompanyId());
                         fastGptChatSession.setLastTime(new Date());
                         fastGptChatSession.setIsReply(0);
+                        fastGptChatSession.setUserId(String.valueOf(sender));
                         fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
                         addUserSex(qwExternalContacts);
-                        saveQwUserMsg(fastGptChatSession,2,count);
+                        saveQwUserMsg(fastGptChatSession,2,count,sendUser);
                     }
                 }
             }
@@ -2036,7 +2076,7 @@ public class AiHookServiceImpl implements AiHookService {
             }
             FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), sendUser.getId());
             if (fastGptChatSession!=null){
-                saveQwUserMsg(fastGptChatSession,2,count);
+                saveQwUserMsg(fastGptChatSession,2,count,sendUser);
             }else {
 
                 if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
@@ -2059,7 +2099,7 @@ public class AiHookServiceImpl implements AiHookService {
                         fastGptChatSession.setIsReply(0);
                         fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
                         addUserSex(qwExternalContacts);
-                        saveQwUserMsg(fastGptChatSession,2,count);
+                        saveQwUserMsg(fastGptChatSession,2,count,sendUser);
                     }
                 }
             }

+ 3 - 0
fs-service/src/main/java/com/fs/his/domain/FsUserInformationCollection.java

@@ -108,4 +108,7 @@ public class FsUserInformationCollection extends BaseEntity{
     //药师签名
     private String doctorType2Sign;
 
+    //药品订单id
+    private Long storeOrderId;
+
 }

+ 8 - 6
fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java

@@ -199,12 +199,14 @@ public interface FsStorePaymentMapper
             " LEFT JOIN fs_store s ON s.store_id=sp.store_id " +
             " left join fs_store_order fso on fso.order_code = sp.business_code " +
             " left join fs_inquiry_order fio on fio.order_sn = sp.business_code " +
-            "LEFT JOIN company c ON \n" +
-            "    ( c.company_id = fso.company_id) OR\n" +
-            "    ( c.company_id = fio.company_id)\n" +
-            "LEFT JOIN company_user cu ON \n" +
-            "    ( cu.user_id = fso.company_user_id) OR\n" +
-            "    (cu.user_id = fio.company_user_id) " +
+            " left join company c on c.company_id = sp.company_id " +
+            " left join company_user cu on cu.user_id = sp.company_user_id " +
+//            "LEFT JOIN company c ON \n" +
+//            "    ( c.company_id = fso.company_id) OR\n" +
+//            "    ( c.company_id = fio.company_id)\n" +
+//            "LEFT JOIN company_user cu ON \n" +
+//            "    ( cu.user_id = fso.company_user_id) OR\n" +
+//            "    (cu.user_id = fio.company_user_id) " +
             "LEFT JOIN fs_course_play_source_config csc ON csc.appid = sp.app_id" +
             " where 1=1 " +
             "            <if test=\"maps.payCode != null  and maps.payCode != ''\"> and sp.pay_code = #{maps.payCode}</if>\n" +

+ 3 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserWxMapper.java

@@ -2,6 +2,7 @@ package com.fs.his.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.his.domain.FsUserWx;
+import org.apache.ibatis.annotations.Param;
 
 public interface FsUserWxMapper extends BaseMapper<FsUserWx> {
 
@@ -10,4 +11,6 @@ public interface FsUserWxMapper extends BaseMapper<FsUserWx> {
      * @param wx    配置信息
      */
     void insertOrUpdateByUniqueKey(FsUserWx wx);
+
+    FsUserWx getFsUserWcByUserIdAndAppId(@Param("fsUserId") Long userId,@Param("appId") String appId );
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/param/FsIntegralOrderParam.java

@@ -7,6 +7,7 @@ import lombok.Data;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class FsIntegralOrderParam {
@@ -17,6 +18,8 @@ public class FsIntegralOrderParam {
     @Excel(name = "订单编号")
     private String orderCode;
 
+    private List<String> orderCodes;
+
     /** 用户id */
     @Excel(name = "用户id")
     private Long userId;

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

@@ -210,10 +210,11 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         FsIntegralOrder order = fsIntegralOrderMapper.selectById(fsIntegralOrder.getOrderId());
         //当前状态
         Integer status = order.getStatus();
-        if (status.equals(2)){
+        Integer newStatus = fsIntegralOrder.getStatus();
+        if (!status.equals(newStatus) && status.equals(2)){
             throw new CustomException("积分订单无法修改为待支付状态");
         }
-        if (status.equals(2)&&StrUtil.isBlank(fsIntegralOrder.getDeliverySn())) {
+        if (!status.equals(newStatus) && newStatus.equals(2)&&StrUtil.isBlank(fsIntegralOrder.getDeliverySn())) {
             throw new CustomException("修改为待发货时,请填写物流单号");
         }
         //其他逻辑,目前暂定这个

+ 1 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java

@@ -762,7 +762,7 @@ public class FsStoreAfterSalesServiceImpl implements IFsStoreAfterSalesService {
         fsStoreAfterSalesLogsMapper.insertFsStoreAfterSalesLogs(logs);
         fsStoreOrderLogsService.create(order.getOrderId(), FsStoreOrderLogEnum.REFUND_ORDER_APPLY.getValue(),
                 FsStoreOrderLogEnum.REFUND_ORDER_APPLY.getDesc());
-        if (order.getExtendOrderId() != null) {
+        if (order.getExtendOrderId() != null && !"".equals(order.getExtendOrderId())) {
             ErpRefundUpdateRequest request = new ErpRefundUpdateRequest();
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());

+ 20 - 6
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -24,6 +24,7 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.FsStoreOrderStatisticsVO;
 import com.fs.company.vo.FsStoreProductStatisticsVO;
+import com.fs.config.ai.AiHostProper;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.core.config.WxPayProperties;
 import com.fs.core.utils.OrderCodeUtils;
@@ -265,6 +266,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
     @Autowired
     private ApplicationEventPublisher publisher;
 
+    @Autowired
+    AiHostProper aiHostProper;
+
     @Autowired
     private IFsUserWatchService fsUserWatchService;
     @Autowired
@@ -1051,7 +1055,7 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         if (fsStoreOrderMapper.insertFsStoreOrder(order) > 0) {
             if(CloudHostUtils.hasCloudHostName("金牛明医")){
                 //信息采集 发送药师im
-                doctorSendIm(packageOrder);
+                doctorSendIm(packageOrder,order.getOrderId());
             }
             if (packageOrder.getCycle() >= followRate) {
                 FsFollow fsFollow = new FsFollow();
@@ -1137,7 +1141,7 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         return R.ok();
     }
 
-    private void doctorSendIm(FsPackageOrder packageOrder) {
+    private void doctorSendIm(FsPackageOrder packageOrder,Long storeOrderId) {
         try {
             //信息采集 发送药师im
             FsUserInformationCollection fsUserInformationCollectionParam = new FsUserInformationCollection();
@@ -1148,6 +1152,11 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
             if (!fsUserInformationCollections.isEmpty()) {
                 for (FsUserInformationCollection collection : fsUserInformationCollections) {
                     openIMService.sendUserInformation(collection.getUserId(),collection.getDoctorType2Id(),collection.getId());
+                    //保存id 到信息采集表
+                    FsUserInformationCollection saveParam = new FsUserInformationCollection();
+                    saveParam.setId(collection.getId());
+                    saveParam.setStoreOrderId(storeOrderId);
+                    fsUserInformationCollectionMapper.updateFsUserInformationCollection(saveParam);
                 }
             }
         } catch (Exception e) {
@@ -2194,8 +2203,13 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
                                 }
                                 //app派件通知
                                 if (dto.getState().equals("2") && (dto.getStateEx().equals("211"))) {
-                                    //ai向客户发送发货物流信息
-                                    requestExpressInfo(order.getOrderId());
+                                    try {
+                                        //ai向客户发送发货物流信息
+                                        requestExpressInfo(order.getOrderId(),aiHostProper.getIpadUrl());
+                                    }catch (Exception e){
+                                        log.error("app派件通知推送失败:{}", e.getMessage());
+                                    }
+
                                 }
                                 //app派件通知
                                 if (dto.getState().equals("2") && (dto.getStateEx().equals("202"))) {
@@ -2267,8 +2281,8 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         return fsStoreOrderMapper.selectFsStoreOrderListByDeliverySn(logisticCode);
     }
 
-    public static R requestExpressInfo(Long orderId){
-        String fileUrl = "http://ipad.cdwjyyh.com/msg/sendExpressInfo/" + orderId;
+    public static R requestExpressInfo(Long orderId,String ipadUrl){
+        String fileUrl = ipadUrl + "/msg/sendExpressInfo/" + orderId;
 
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             HttpGet httpGet = new HttpGet(fileUrl);

+ 1 - 0
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java

@@ -84,4 +84,5 @@ public class FsIntegralOrderListVO {
     private String erpPhone;
 
     private String loginAccount;
+    private String goodsName;
 }

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java

@@ -97,7 +97,7 @@ public interface FsStoreAfterSalesScrmMapper
             " left join fs_user u on s.user_id=u.user_id " +
             " left join company c on c.company_id=s.company_id " +
             " left join company_user cu on cu.user_id=s.company_user_id " +
-            " left join fs_store_payment_scrm fsps on fsps.business_order_id = s.id and fsps.status in (-1,1) " +
+            " left join fs_store_payment_scrm fsps on fsps.business_order_id = o.id and fsps.status in (-1,1) " +
             " where 1=1 " +
             "<if test = 'maps.status != null    '> " +
             "and s.status = #{maps.status} " +

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -284,4 +284,6 @@ public interface FsStoreOrderItemScrmMapper
 
 
     List<FsStoreOrderItemVO> selectFsStoreOrderItemListByOrderIds(@Param("orderIds")List<Long> orderIds);
+
+    String selectFsStoreOrderItemByOrderId(@Param("orderId") Long orderId);
 }

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

@@ -1283,9 +1283,9 @@ public interface FsStoreOrderScrmMapper
             "        </if>" +
             "<if test=\"maps.status == 6\">" +
             "            AND o.`status` = 1" +
-            "            AND (" +
-            "            o.store_id IN (SELECT store_id FROM fs_store WHERE delivery_type=2 OR delivery_type=1)" +
-            "            )" +
+//            "            AND (" +
+//            "            o.store_id IN (SELECT store_id FROM fs_store WHERE delivery_type=2 OR delivery_type=1)" +
+//            "            )" +
             "            AND (o.extend_order_id IS NULL OR o.extend_order_id = '')" +
             "        </if>" +
             "<if test = 'maps.deliveryStatus != null    '> " +

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

@@ -75,4 +75,6 @@ public interface IFsStoreOrderItemScrmService
     int updateFsStoreOrderCode(Long orderId, String orderCode);
 
     List<FsStoreOrderItemListDVO> selectFsStoreOrderItemListDVOByOrderId(Long orderId);
+
+    String selectFsStoreOrderItemByOrderId(Long orderId);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -393,6 +393,7 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         request.setOid(order.getOrderCode());
         request.setRefund_state(1);
         request.setStoreAfterSalesId(storeAfterSales.getId());
+        request.setOrderStatus(orderStatus);
         if (StringUtils.isNotBlank(order.getExtendOrderId())){
             BaseResponse response=erpOrderService.refundUpdateScrm(request);
             if(response.getSuccess()){

+ 10 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderItemScrmServiceImpl.java

@@ -126,4 +126,14 @@ public class FsStoreOrderItemScrmServiceImpl implements IFsStoreOrderItemScrmSer
     public List<FsStoreOrderItemListDVO> selectFsStoreOrderItemListDVOByOrderId(Long orderId) {
         return fsStoreOrderItemMapper.selectFsStoreOrderItemListDVOByOrderId(orderId);
     }
+
+    /**
+     * 根据订单id查询订单详情汇总
+     * @param orderId
+     * @return
+     */
+    @Override
+    public String selectFsStoreOrderItemByOrderId(Long orderId) {
+        return fsStoreOrderItemMapper.selectFsStoreOrderItemByOrderId(orderId);
+    }
 }

+ 12 - 3
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -2239,11 +2239,19 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
         erpOrder.setDetails(details);
         erpOrder.setReceiver_name(order.getRealName());
-        if (order.getUserPhone().length() > 11) {
-            erpOrder.setReceiver_phone(order.getUserPhone());
+        //2025.6.27 金牛要求erp推送电话可以设置默认 不影响其他推送
+        String phone = null;
+        if (CloudHostUtils.hasCloudHostName("康年堂") && StringUtils.isNotBlank(order.getErpPhone())) {
+            phone = order.getErpPhone();
         } else {
-            erpOrder.setReceiver_mobile(order.getUserPhone());
+            phone = order.getUserPhone();
         }
+        if (phone.length() > 11) {
+            erpOrder.setReceiver_phone(phone);
+        } else {
+            erpOrder.setReceiver_mobile(phone);
+        }
+
         String[] address = order.getUserAddress().split(" ");
         erpOrder.setReceiver_province(address[0]);
         erpOrder.setReceiver_city(address[1]);
@@ -2386,6 +2394,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
+            request.setOrderStatus(order.getStatus());
             //BaseResponse response=erpOrderService.refundUpdate(request);
 //            if(response.getSuccess()){
 //            }

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderErpExportVO.java

@@ -21,4 +21,6 @@ public class FsStoreOrderErpExportVO extends FsStoreOrderExportVO
     private String erpPhone;
     @Excel(name = "ERP账户",sort = 2)
     private String erpAccount;
+    @Excel(name = "商品明细")
+    private String orderItem;
 }

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderExportVO.java

@@ -119,7 +119,7 @@ public class FsStoreOrderExportVO implements Serializable
     private Date payTime;
 
     /** 支付方式 */
-    @Excel(name = "支付方式")
+    @Excel(name = "支付方式",dictType = "store_pay_type")
     private String payType;
 
     /** 订单状态(-1 : 申请退款 -2 : 退货成功 0:待发货;1:待收货;2:已收货;3:已完成;-1:已退款) */

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java

@@ -130,4 +130,12 @@ public class FsStoreOrderItemExportRefundZMVO implements Serializable  {
     @Excel(name = "退款金额" ,sort = 240)
     private BigDecimal refundMoney;
 
+    /** 申请原因 */
+    @Excel(name = "申请原因",sort = 250)
+    private String reasons;
+
+    /** 说明 */
+    @Excel(name = "说明",sort = 260)
+    private String explains;
+
 }

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

@@ -168,5 +168,5 @@ public interface LiveDataMapper {
      * @param liveId 直播间ID
      * @return 用户详情列表
      */
-    List<LiveUserDetailVo> selectLiveUserDetailListBySql(@Param("liveId") Long liveId);
+    List<LiveUserDetailVo> selectLiveUserDetailListBySql(@Param("liveId") Long liveId,@Param("companyId") Long companyId,@Param("companyUserId") Long companyUserId);
 }

+ 46 - 7
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -138,15 +138,50 @@ public interface LiveMapper
             "select * from live where 1=1 " +
             " <if test='param.companyId!=null' > and company_id = #{param.companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1 " +
             " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if> " +
-            " order by create_time desc" +
+            " <if test='param.startTime!=null and param.endTime!=null' > and start_time between #{param.startTime} and  #{param.endTime}  </if> " +
+            " UNION " +
+            "select l.* from live l " +
+            "LEFT JOIN ( " +
+            "    SELECT live_id, SUM(COALESCE(duration, 0)) AS total_duration " +
+            "    FROM live_video " +
+            "    WHERE video_type IN (1, 2) " +
+            "    GROUP BY live_id " +
+            ") video_duration ON l.live_id = video_duration.live_id " +
+            "where 1=1 " +
+            " <if test='param.companyId!=null' > and l.company_id = #{param.companyId} </if> " +
+            "and l.live_type IN (1,2, 3) AND l.status = 2 AND l.is_del = 0 and l.is_audit=1 " +
+            "and l.start_time IS NOT NULL " +
+            "and TIMESTAMPDIFF(SECOND, l.start_time, NOW()) > COALESCE(video_duration.total_duration, 0) " +
+            "and COALESCE(video_duration.total_duration, 0) > 0 " +
+            " <if test='param.liveName!=null' > and l.live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and l.start_time between #{param.startTime} and  #{param.endTime}  </if> " +
+            "order by create_time desc" +
             " </script>"})
     List<Live> listLiveData(@Param("param") LiveDataParam param);
 
     @Select({"<script>" +
-            "select count(1) from live where 1=1 " +
-            " <if test='param.companyId!=null' > and company_id = #{param.companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
-            " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if>" +
-            "  order by create_time desc " +
+            "select count(1) from ( " +
+            "select * from live where 1=1 " +
+            " <if test='param.companyId!=null' > and company_id = #{param.companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1 " +
+            " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and start_time between #{param.startTime} and  #{param.endTime}  </if> " +
+            " UNION " +
+            "select l.* from live l " +
+            "LEFT JOIN ( " +
+            "    SELECT live_id, SUM(COALESCE(duration, 0)) AS total_duration " +
+            "    FROM live_video " +
+            "    WHERE video_type IN (1, 2) " +
+            "    GROUP BY live_id " +
+            ") video_duration ON l.live_id = video_duration.live_id " +
+            "where 1=1 " +
+            " <if test='param.companyId!=null' > and l.company_id = #{param.companyId} </if> " +
+            "and l.live_type IN (1,2, 3) AND l.status = 2 AND l.is_del = 0 and l.is_audit=1 " +
+            "and l.start_time IS NOT NULL " +
+            "and TIMESTAMPDIFF(SECOND, l.start_time, NOW()) > COALESCE(video_duration.total_duration, 0) " +
+            "and COALESCE(video_duration.total_duration, 0) > 0 " +
+            " <if test='param.liveName!=null' > and l.live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and l.start_time between #{param.startTime} and  #{param.endTime}  </if> " +
+            ") as temp " +
             " </script>"})
     int listLiveDataCount(@Param("param") LiveDataParam param);
 
@@ -176,6 +211,10 @@ public interface LiveMapper
             "GROUP BY l.live_id, l.start_time")
     Integer selectLiveFlagByLiveId(@Param("liveId") Long liveId);
 
-    @Select("SELECT * FROM live WHERE is_audit = 1 and is_del = 0 and status in (1,2,4) and live_type in (2,3) order by create_time desc")
-    List<Live> listToLiveNoEnd(Live live);
+    @Select({"<script>" +
+            " SELECT * FROM live WHERE is_audit = 1 and is_del = 0 and status in (1,2,4) and live_type in (2,3) " +
+            "  <if test='live.liveName!=null' > and live_name like concat('%',#{live.liveName},'%') </if> " +
+            " order by create_time desc" +
+            " </script>"})
+    List<Live> listToLiveNoEnd(@Param("live") Live live);
 }

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

@@ -370,7 +370,8 @@ public interface LiveOrderMapper {
     int batchUpdateErpByOrderIds(@Param("maps")ArrayList<Map<String, String>> maps);
 
     @Select({"<script> " +
-            "select o.order_id,o.total_num,o.create_time, o.discount_money ,o.live_id,o.order_code,o.item_json,o.pay_price,o.status,o.delivery_sn as delivery_id,o.finish_time  from live_order o  " +
+            "select a.id as afterSalesId,o.order_id,o.total_num,o.create_time, o.discount_money ,o.live_id,o.order_code,o.item_json,o.pay_price,o.status,o.delivery_sn as delivery_id,o.finish_time  from live_order o  " +
+            " left join ( SELECT t.*,ROW_NUMBER() OVER (PARTITION BY t.order_id ORDER BY t.create_time DESC) AS rn FROM live_after_sales t ) a ON o.order_id = a.order_id AND a.rn = 1 " +
             "where o.is_del=0 " +
             "<if test = 'maps.status != null and maps.status != \"\"     '> " +
             "and o.status =#{maps.status} " +

+ 2 - 2
fs-service/src/main/java/com/fs/live/service/ILiveDataService.java

@@ -145,7 +145,7 @@ public interface ILiveDataService {
      * @param liveId 直播间ID
      * @return 用户详情列表
      */
-    R getLiveUserDetailListBySql(Long liveId);
+    R getLiveUserDetailListBySql(Long liveId, Long companyId, Long companyUserId);
 
     /**
      * 查询直播间详情数据(查询数据服务器处理方式)
@@ -166,5 +166,5 @@ public interface ILiveDataService {
      * @param liveId 直播间ID
      * @return 导出VO列表
      */
-    List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId);
+    List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId, Long companyId, Long companyUserId);
 }

+ 12 - 17
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -24,6 +24,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.service.ICompanyService;
 import com.fs.config.cloud.CloudHostProper;
+import com.fs.core.utils.OrderCodeUtils;
 import com.fs.erp.constant.AfterSalesOrderStatusEnum;
 import com.fs.erp.domain.FsJstAftersalePush;
 import com.fs.erp.dto.BaseResponse;
@@ -313,10 +314,11 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
             if (storeAfterSales.getOrderStatus().equals(OrderInfoEnum.STATUS_1.getValue()) ) {
                 if(StringUtils.isNotEmpty(order.getExtendOrderId())){
                     //更新订单code
-                    String orderSn = IdUtil.getSnowflake(0, 0).nextIdStr();
+                    String orderSn = OrderCodeUtils.getOrderSn();
                     LiveOrder orderMap=new LiveOrder();
                     orderMap.setOrderId(order.getOrderId());
                     orderMap.setOrderCode(orderSn);
+                    orderMap.setStatus(order.getStatus());
                     liveOrderService.updateLiveOrder(orderMap);
                     liveOrderItemMapper.updateFsStoreOrderCode(order.getOrderId(),orderSn);
                     try {
@@ -426,6 +428,7 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         storeAfterSales.setSalesStatus(0);
         storeAfterSales.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
         storeAfterSales.setIsDel(0);
+        storeAfterSales.setOrderStatus(orderStatus);
         storeAfterSales.setUserId(Long.valueOf(userId));
         storeAfterSales.setOrderStatus(orderStatus);
         storeAfterSales.setCompanyId(order.getCompanyId());
@@ -458,17 +461,11 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         request.setOid(order.getOrderCode());
         request.setRefund_state(1);
         request.setStoreAfterSalesId(storeAfterSales.getId());
+        request.setOrderStatus(orderStatus);
         if (StringUtils.isNotBlank(order.getExtendOrderId())){
-            ErpOrderQueryRequert queryRequest = new ErpOrderQueryRequert();
-            queryRequest.setCode(order.getExtendOrderId());
-            ErpOrderQueryResponse response = erpOrderService.getLiveOrder(queryRequest);
-            if (response.getOrders() != null && response.getOrders().size() > 0) {
-                if (response.getOrders().get(0).getCancle() != null && !response.getOrders().get(0).getCancle()) {
-                    BaseResponse res = erpOrderService.refundUpdateLive(request);
-                    if(res.getSuccess()){
-                        return R.ok();
-                    }
-                }
+            BaseResponse response=erpOrderService.refundUpdateLive(request);
+            if(response.getSuccess()){
+                return R.ok();
             }
             else{
                 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@@ -584,9 +581,6 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
     @Override
     public int updateLiveAfterSales(LiveAfterSales liveAfterSales)
     {
-        if (StringUtils.isNotEmpty(liveAfterSales.getDeliveryName()) && StringUtils.isNotEmpty(liveAfterSales.getDeliverySn()) && StringUtils.isNotEmpty(liveAfterSales.getDeliveryCode())) {
-            liveAfterSales.setStatus(2);
-        }
 
         return baseMapper.updateLiveAfterSales(liveAfterSales);
     }
@@ -913,16 +907,17 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         logs.setStoreAfterSalesId(storeAfterSales.getId());
         logs.setChangeMessage(OrderInfoEnum.REFUND_STATUS_1.getDesc());
         liveAfterSalesLogsMapper.insertLiveAfterSalesLogs(logs);
-        if (storeAfterSales.getOrderStatus().equals(2)) {
+        if (storeAfterSales.getOrderStatus().equals(1)) {
             if (StringUtils.isNotEmpty(order.getExtendOrderId())) {
                 //更新订单code
-                String orderSn = IdUtil.getSnowflake(0, 0).nextIdStr();
+                String orderSn = OrderCodeUtils.getOrderSn();
                 if (StringUtils.isEmpty(orderSn)) {
                     return R.error("订单生成失败,请重试");
                 }
                 LiveOrder orderMap = new LiveOrder();
                 orderMap.setOrderId(order.getOrderId());
                 orderMap.setOrderCode(orderSn);
+                orderMap.setStatus(order.getStatus());
                 liveOrderService.updateLiveOrder(orderMap);
                 //生成新的订单
                 List<LiveOrderPayment> payments = liveOrderPaymentMapper.selectLiveOrderPaymentByPay(5, order.getOrderId());
@@ -934,7 +929,7 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
                 }
 
                 try {
-                    if (liveOrderPaymentMapper.selectByBuissnessId(Long.valueOf(order.getOrderCode())) != null) {
+                    if (liveOrderPaymentMapper.selectByBuissnessId(order.getOrderId()) != null) {
                         liveOrderService.createOmsOrder(order.getOrderId());
                     }
                 } catch (Exception e) {

+ 4 - 4
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -695,8 +695,8 @@ public class LiveDataServiceImpl implements ILiveDataService {
     }
 
     @Override
-    public R getLiveUserDetailListBySql(Long liveId) {
-        List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId);
+    public R getLiveUserDetailListBySql(Long liveId, Long companyId, Long companyUserId ) {
+        List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId, companyId, companyUserId);
         return R.ok().put("data", userDetailList);
     }
 
@@ -1081,9 +1081,9 @@ public class LiveDataServiceImpl implements ILiveDataService {
      * @return 导出VO列表
      */
     @Override
-    public List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId) {
+    public List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId, Long companyId, Long companyUserId) {
         // 查询用户详情列表
-        List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId);
+        List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId, companyId, companyUserId);
         if (userDetailList == null || userDetailList.isEmpty()) {
             return new ArrayList<>();
         }

+ 26 - 13
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -1177,6 +1177,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
+            request.setOrderStatus(order.getStatus());
 
             if (ObjectUtils.equals(order.getStatus(), 2)) {
                 LiveAfterSalesParam param = new LiveAfterSalesParam();
@@ -1373,7 +1374,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
-
+            request.setOrderStatus(order.getStatus());
             if (ObjectUtils.equals(order.getStatus(), 2)) {
                 LiveAfterSalesParam param = new LiveAfterSalesParam();
                 param.setOrderCode(order.getOrderCode());
@@ -1675,7 +1676,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
         erpOrder.setPlatform_code(order.getOrderCode());
         erpOrder.setWarehouse_code(erpConfig.getErpWarehouseCode());
-        erpOrder.setShop_code(erpConfig.getErpShopCode());
+        erpOrder.setShop_code(erpConfig.getErpJstShopCode());
         erpOrder.setBuyer_account(order.getUserName());
 
 //      erpOrder.setPost_fee(order.getTotalPostage().doubleValue());
@@ -1709,13 +1710,13 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             order.setDeliverySn(express.getCode());
         }
         erpOrder.setPayments(payments);
-        if (order.getCompanyId() != null) {
+        if (order.getCompanyId() != null && order.getCompanyId() > 0L) {
             Company company = companyService.selectCompanyById(order.getCompanyId());
             if (company != null) {
                 erpOrder.setSeller_memo(company.getCompanyName());
             }
         }
-        if (order.getCompanyUserId() != null) {
+        if (order.getCompanyUserId() != null && order.getCompanyUserId() > 0L) {
             CompanyUser companyUser = companyUserService.selectCompanyUserById(order.getCompanyUserId());
             if (companyUser != null) {
                 CompanyDept dept = companyDeptService.selectCompanyDeptById(companyUser.getDeptId());
@@ -2467,6 +2468,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
     private BigDecimal handleDeliveryMoney(Long cityId, FsStoreProductScrm fsStoreProduct, String totalNumSize) {
         BigDecimal storePostage = BigDecimal.ZERO;
+        if (ObjectUtil.isNull(fsStoreProduct.getTempId())) {
+            return storePostage;
+        }
         List<Long> citys = new ArrayList<>();
         citys.add(cityId);
         citys.add(0l);
@@ -2631,6 +2635,12 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     {
         liveOrder.setUpdateTime(DateUtils.getNowDate());
         liveUserLotteryRecordMapper.updateOrderStatusByOrderId(liveOrder.getOrderId(), liveOrder.getStatus());
+        //推送修改的商城订单地址到聚水潭ERP
+        try {
+            pushOrderAddressToErp(liveOrder);
+        }catch (Exception e){
+            log.error("修改商城订单地址推送到聚水潭ERP失败,orderId: {}", liveOrder.getOrderId(), e);
+        }
         return baseMapper.updateLiveOrder(liveOrder);
     }
 
@@ -2973,7 +2983,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
         String orderId=redisCache.getCacheObject("isPaying:"+order.getOrderId());
         if(StringUtils.isNotEmpty(orderId)&&order.getOrderId().toString().equals(orderId)){
-            return R.error("正在支付中...");
+            return R.error(501,"正在支付中...");
         }
         FsUserScrm user=userMapper.selectFsUserById(Long.valueOf(order.getUserId()));
         if(user == null){
@@ -3028,7 +3038,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 }
                 baseMapper.updateLiveOrder(order);
             }
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode = OrderCodeUtils.getOrderSn();
 //            order.setOrderCode(orderCode);
 //            if(order.getPayType().equals("1")||order.getPayType().equals("2")){
             if((order.getPayType().equals("1")||order.getPayType().equals("2")||order.getPayType().equals("3")) && order.getPayMoney().compareTo(new BigDecimal(0))>0){
@@ -3433,6 +3443,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     }
 
     @Override
+    @Transactional
     public void payConfirmPayment(Long existPayedRecordId) {
         LiveOrderPayment payment = liveOrderPaymentMapper.selectLiveOrderPaymentByPaymentId(existPayedRecordId);
         if (payment == null) {
@@ -3440,7 +3451,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             return;
         }
         log.info("手动查询单号:" + existPayedRecordId + ":" + payment);
-//        this.payConfirm(1,"",payment.getPayCode(),payment.getTradeNo(),)
+        this.payConfirm(1, null, payment.getPayCode(), payment.getTradeNo(), payment.getBankSerialNo(), payment.getBankSerialNo());
     }
 
 
@@ -3480,6 +3491,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if (attrValue != null) {
             attrValue.setStock(attrValue.getStock() - Integer.parseInt(liveOrder.getTotalNum()));
             attrValue.setSales(attrValue.getSales() + Integer.parseInt(liveOrder.getTotalNum()));
+            fsStoreProductAttrValueMapper.updateFsStoreProductAttrValue(attrValue);
         } else {
             // 更改店铺库存
             fsStoreProduct.setStock(fsStoreProduct.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
@@ -3505,7 +3517,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setCompanyId(liveUserFirstEntry.getCompanyId());
         liveOrder.setCompanyUserId(liveUserFirstEntry.getCompanyUserId());
         liveOrder.setTuiUserId(liveUserFirstEntry.getCompanyUserId());
-        String orderSn = IdUtil.getSnowflake(0, 0).nextIdStr();
+        String orderSn = OrderCodeUtils.getOrderSn();
+//        String orderSn = "123"; // todo yhq
         log.info("订单生成:"+orderSn);
         liveOrder.setOrderCode(orderSn);
         BigDecimal payPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
@@ -3554,7 +3567,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setPayType("1");
         liveOrder.setTotalPrice(payPrice);
         liveOrder.setPayMoney(BigDecimal.ZERO);
-        liveOrder.setPayPrice(payPrice);
+        liveOrder.setPayPrice(payPrice.subtract(liveOrder.getDiscountMoney()));
         try {
             if (baseMapper.insertLiveOrder(liveOrder) > 0) {
                 LiveOrderItemDTO dto=new LiveOrderItemDTO();
@@ -3565,9 +3578,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                     dto.setBarCode(fsStoreProductAttrValue.getBarCode());
                     dto.setGroupBarCode(fsStoreProductAttrValue.getGroupBarCode());
                 }
-                if (fsStoreProductAttrValue != null) {
-                    dto.setBarCode(fsStoreProductAttrValue.getBarCode());
-                    dto.setGroupBarCode(fsStoreProductAttrValue.getGroupBarCode());
+                if (attrValue != null) {
+                    dto.setBarCode(attrValue.getBarCode());
+                    dto.setGroupBarCode(attrValue.getGroupBarCode());
                 }
                 dto.setPrice(fsStoreProduct.getPrice());
                 dto.setProductName(fsStoreProduct.getProductName());
@@ -3697,7 +3710,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
     @Override
     public R confirmOrder(LiveOrderConfirmParam param) {
-        String uuid = IdUtil.randomUUID();
+        String uuid = OrderCodeUtils.getOrderSn();
         redisCache.setCacheObject("orderKey:"+uuid,uuid,200, TimeUnit.MINUTES);
         return R.ok().put("orderKey",uuid);
     }

+ 10 - 2
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -290,10 +290,18 @@ public class LiveServiceImpl implements ILiveService
                 .eq(FsUserWx::getFsUserId, userId)
                 .eq(FsUserWx::getAppId, StringUtils.isEmpty(param.getAppId()) ? "wx44beed5640bcb1ba" : param.getAppId()); // 卓美小程序
         FsUserWx fsUserWx = fsUserWxMapper.selectOne(queryWrapper);
-        String maOpenId = fsUserWx.getOpenId();
-        if (StringUtils.isEmpty(maOpenId)) {
+        String maOpenId = "";
+        if (fsUserWx == null) {
             maOpenId = param.getMaOpenId();
+        }else {
+            maOpenId = fsUserWx.getOpenId();
+        }
+
+        if (StringUtils.isEmpty(maOpenId)) {
+            log.error("用户没有绑定微信,无法发送预约提醒:{}", userId);
+            return R.ok();
         }
+
         notifyTask.setTouser(maOpenId);
         notifyTask.setPage(String.valueOf(1));
 

+ 1 - 2
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -201,13 +201,13 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
 
     @Override
     public LiveWatchUser join(FsUserScrm fsUser,long liveId, long userId, String location) {
-        Date now = DateUtils.getNowDate();
 
         // 查询直播间信息
         Live live = liveMapper.selectLiveByLiveId(liveId);
         if (live == null) {
             throw new RuntimeException("直播间不存在");
         }
+        Date now = DateUtils.getNowDate();
 
         // 获取直播/回放状态(带缓存)
         Map<String, Integer> flagMap = getLiveFlagWithCache(liveId);
@@ -429,7 +429,6 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         liveWatchUser.setLiveId(liveId);
         List<LiveWatchUserVO> liveWatchUserVOS = selectOnlineUserList(liveWatchUser);
 
-        log.info("开始同步直播在线人数到缓存,共{}条数据", liveWatchUserVOS.size());
         if (CollUtil.isNotEmpty(liveWatchUserVOS)){
             ThreadUtil.execute(()->{
                 String hashKey  = String.format(LiveKeysConstant.LIVE_WATCH_USERS, liveId);

+ 1 - 0
fs-service/src/main/java/com/fs/live/vo/FsMyLiveOrderListQueryVO.java

@@ -26,6 +26,7 @@ public class FsMyLiveOrderListQueryVO implements Serializable
     private Long id;
     private Long orderId;
     private Long liveId;
+    private Long afterSalesId;
 
     /** 订单号 */
     private String orderCode;

+ 2 - 0
fs-service/src/main/java/com/fs/live/vo/LiveDataDetailVo.java

@@ -98,3 +98,5 @@ public class LiveDataDetailVo {
 
 
 
+
+

+ 2 - 0
fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java

@@ -40,3 +40,5 @@ public class LiveUserDetailVo {
 
 
 
+
+

+ 2 - 0
fs-service/src/main/java/com/fs/live/vo/ProductSalesVo.java

@@ -28,3 +28,5 @@ public class ProductSalesVo {
 
 
 
+
+

+ 2 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwIpadServerUserMapper.java

@@ -5,6 +5,7 @@ import com.fs.qw.domain.QwIpadServerUser;
 import com.fs.qw.param.IPadServerUserParam;
 import com.fs.qw.vo.QwIPadServerUserVO;
 import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
 
@@ -73,4 +74,5 @@ public interface QwIpadServerUserMapper extends BaseMapper<QwIpadServerUser>{
     @Delete("DELETE FROM qw_ipad_server_user WHERE qw_user_id = #{id}")
     void deleteQwIpadServerUserByQwUserId(Long id);
 
+    void deleteQwIpadServerUserByQwUserIds(@Param("ids") List<Long> ids);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java

@@ -78,6 +78,18 @@ public interface QwTagGroupMapper
             "</script>"})
     List<QwTagGroupListVO> selectQwTagGroupListVO(QwTagGroup qwTagGroup);
 
+    @Select({"<script> " +
+            "select * from qw_tag_group "+
+            "<where>\n" +
+            "            <if test=\"groupId != null  and groupId != ''\"> and group_id = #{groupId}</if>\n" +
+            "            <if test=\"name != null  and name != ''\"> and name like concat( '%',#{name}, '%')</if>\n" +
+            "            <if test=\"order != null  and order != ''\"> and order = #{order}</if>\n" +
+            "            <if test=\"corpId != null  and corpId != ''\"> and corp_id = #{corpId}</if>\n" +
+            "            <if test=\"companyId != null \"> and company_id = #{companyId}</if>\n" +
+            "        </where> order by `order` desc ,id desc"+
+            "</script>"})
+    List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup);
+
     @Select("select * from qw_tag_group where id=#{id}")
     QwTagGroupVO selectQwTagGroupByIdVO(Long id);
 

+ 15 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -488,4 +488,19 @@ public interface QwUserMapper extends BaseMapper<QwUser>
     List<QwUser> selectQwUserByIds(@Param("qwUserIdList") List<Long> qwUserIdList);
 
     List<QwUser> selectQwUserByTest();
+
+    @Select("<script>" +
+            "select id from qw_user where 1=1 " +
+            "            <if test=\"userIds != null and userIds.size() > 0\">\n" +
+            "                AND company_user_id IN\n" +
+            "                <foreach collection=\"userIds\" open=\"(\" close=\")\" separator=\",\" item=\"item\">\n" +
+            "                    ${item}\n" +
+            "                </foreach>\n" +
+            "            </if>" +
+            "</script>")
+    List<Long> selectQwUserListByCompanyUserIdS(@Param("userIds") List<String> userIds);
+
+    List<QwUser> selectQwUserByServerIds(@Param("serverIds")List<String> serverIds);
+
+    int batchUpdateUnbind(@Param("ids")List<Long> ids);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactAddTagParam.java

@@ -9,4 +9,10 @@ public class QwExternalContactAddTagParam {
     List<Long> userIds;
     List<String> tagIds;
     String corpId;
+    /**
+     * 指筛选条件  我的,部门,还是全部
+     */
+    private Integer addType;
+    private boolean filter;
+    private QwExternalContactParam param;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/qw/param/QwTagParam.java

@@ -1,5 +1,6 @@
 package com.fs.qw.param;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
@@ -17,4 +18,11 @@ public class QwTagParam  {
      * 公司id
      */
     private Long companyId;
+
+    @TableField(exist = false)
+    private Integer pageNum = 1;
+
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/IQwIpadServerUserService.java

@@ -71,4 +71,6 @@ public interface IQwIpadServerUserService extends IService<QwIpadServerUser>{
     int deleteQwIpadServerUserById(Long id);
 
     void deleteQwIpadServerUserByQwUserId(Long id);
+
+    void deleteQwIpadServerUserByQwUserIds(List<Long> ids);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/IQwTagGroupService.java

@@ -87,4 +87,6 @@ public interface IQwTagGroupService
     void addQwTagByAi(String trimTag, Long extId);
 
     void delQwTagByAi(String trimTag, Long extId);
+
+    List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -204,4 +204,8 @@ public interface IQwUserService
     List<Long> selectDeptByParentId(Long deptId,String cropId);
 
     List<QwUser> selectQwUserByIds(List<Long> qwUserIdList);
+    List<Long> selectQwUserListByCompanyUserIdS(List<String> userIds);
+
+    R unbindQwUserByServerIds(List<String> serverIds);
+
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwIpadServerUserServiceImpl.java

@@ -106,4 +106,9 @@ public class QwIpadServerUserServiceImpl extends ServiceImpl<QwIpadServerUserMap
     public void deleteQwIpadServerUserByQwUserId(Long id) {
         qwIpadServerUserMapper.deleteQwIpadServerUserByQwUserId(id);
     }
+
+    @Override
+    public void deleteQwIpadServerUserByQwUserIds(List<Long> ids) {
+        qwIpadServerUserMapper.deleteQwIpadServerUserByQwUserIds(ids);
+    }
 }

+ 14 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java

@@ -636,4 +636,18 @@ public class QwTagGroupServiceImpl implements IQwTagGroupService {
         }
 
     }
+
+    @Override
+    public List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup) {
+        List<QwTagGroupListVO> vo = qwTagGroupMapper.selectQwTagGroupListVOPage(qwTagGroup);
+
+        for (QwTagGroupListVO qwTagGroupListVO : vo) {
+            QwTag qwTag = new QwTag();
+            qwTag.setGroupId(qwTagGroupListVO.getGroupId());
+            qwTag.setCompanyId(qwTagGroupListVO.getCompanyId());
+            List<QwTagVO> qwTags = qwTagMapper.selectQwTagListVO(qwTag);
+            qwTagGroupListVO.setTag(qwTags);
+        }
+        return vo;
+    }
 }

+ 58 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -58,6 +58,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.io.*;
 import java.net.URL;
@@ -1563,6 +1564,63 @@ public class QwUserServiceImpl implements IQwUserService
     public List<QwUser> selectQwUserByIds(List<Long> qwUserIdList) {
         return qwUserMapper.selectQwUserByIds(qwUserIdList);
     }
+
+    @Override
+    public List<Long> selectQwUserListByCompanyUserIdS(List<String> userIds) {
+        return qwUserMapper.selectQwUserListByCompanyUserIdS(userIds);
+    }
+
+    @Override
+    @Transactional
+    public R unbindQwUserByServerIds(List<String> serverIds) {
+        if (serverIds!= null && !serverIds.isEmpty()) {
+            serverIds = serverIds.stream().distinct().collect(Collectors.toList()); //去重
+            //查询所有状态为 绑定了AI主机的
+            List<QwUser> list = qwUserMapper.selectQwUserByServerIds(serverIds);
+            if (list != null && !list.isEmpty()) {
+                for (QwUser qwUser : list) {
+
+                    try {
+                        QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
+                        qwIpadServerLog.setType(2);
+                        qwIpadServerLog.setTilie("退订解绑");
+                        qwIpadServerLog.setServerId(qwUser.getServerId());
+                        qwIpadServerLog.setQwUserId(qwUser.getId());
+                        qwIpadServerLog.setCompanyUserId(qwUser.getCompanyUserId());
+                        qwIpadServerLog.setCompanyId(qwUser.getCompanyId());
+                        qwIpadServerLog.setCreateTime(new Date());
+                        qwIpadServerLogService.insertQwIpadServerLog(qwIpadServerLog);
+//                        WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+//                        wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
+//                        wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
+                    } catch (Exception e) {
+                        log.error("企微用户:{},解绑ipad报错:{}", JSON.toJSONString(qwUser), e);
+                    }
+                }
+                List<Long> ids = list.stream().map(QwUser::getId).collect(Collectors.toList());
+                qwUserMapper.batchUpdateUnbind(ids); //修改qwUser的serverId和绑定状态
+//                ipadServerService.addServers(serverIds); // ipad数量退订归100
+                qwIpadServerUserService.deleteQwIpadServerUserByQwUserIds(ids); //删除 qwIpadServerUser
+                Long[] array = serverIds.stream()
+                        .map(str -> {
+                            if (str != null && !str.trim().isEmpty()) {
+                                try {
+                                    return Long.valueOf(str.trim());
+                                } catch (NumberFormatException e) {
+                                    return null; // 或抛出异常
+                                }
+                            }
+                            return null;
+                        })
+                        .toArray(Long[]::new);
+                ipadServerService.deleteQwIpadServerByIds(array); // 删除ipad
+            }
+
+
+        }
+        return R.ok();
+    }
+
     /**
      * 根据销售公司和企微ID查询企微用户
      */

+ 4 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java

@@ -95,12 +95,16 @@ public class QwExternalContactVO {
     private Long companyId;
 
     private Integer transferStatus;
+    /*状态名称 用于excel导出*/
+    @Excel(name = "状态")
+    private String statusName;
     private Integer status;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @Excel(name = "添加时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "流失时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     private Date lossTime;
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date delTime;

+ 32 - 1
fs-service/src/main/java/com/fs/sop/domain/QwSop.java

@@ -8,7 +8,9 @@ import com.fs.common.annotation.Excel;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 企微sop对象 qw_sop
@@ -135,7 +137,7 @@ public class QwSop implements Serializable
     private String userType;
 
     @TableField(exist = false)
-    private List<Long> qwUserIdList;
+    private List<Long> qwUserIdList = new ArrayList<>();  // 初始化
 
     @Excel(name = "开启评论或者弹幕,1-开启评论;2-开启弹幕;3-都关闭")
     @TableField(exist = false)
@@ -154,4 +156,33 @@ public class QwSop implements Serializable
     private Integer autoUserReg;
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String pullTime;
+
+
+    @TableField(exist = false)
+    private List<String> userIds = new ArrayList<>();
+
+
+    private Integer pageNum;
+    private Integer pageSize;
+
+    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;
+    }
 }

+ 27 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java

@@ -111,6 +111,33 @@ public class QwSopTemp implements Serializable
     @TableField(exist = false)
     private List<Long> userIds;
 
+    @TableField(exist = false)
+    private List<String> companyUserIds=new ArrayList<>();
+
+    private Integer pageNum;
+    private Integer pageSize;
+
+    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;
+    }
+
     /**
      * 部门类型 00 管理员 01 员工
      */

+ 43 - 0
fs-service/src/main/java/com/fs/store/dto/ClientCredGrantReqDTO.java

@@ -0,0 +1,43 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 客户端凭证授权请求DTO
+ * <p>
+ * 用于构建客户端凭证授权模式下的请求参数。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class ClientCredGrantReqDTO implements Serializable {
+
+    /**
+     * 授权类型
+     * <p>
+     * 固定值 "client_credential",表示客户端凭证授权模式。
+     * </p>
+     */
+    private String grant_type;
+
+    /**
+     * 应用ID
+     * <p>
+     * 应用程序的唯一标识符。
+     * </p>
+     */
+    private String appid;
+
+    /**
+     * 应用密钥
+     * <p>
+     * 应用程序的密钥,用于验证请求的合法性。  <b>注意:应妥善保管,避免泄露。</b>
+     * </p>
+     */
+    private String secret;
+}

+ 52 - 0
fs-service/src/main/java/com/fs/store/dto/MiniGramSubsMsgResultDTO.java

@@ -0,0 +1,52 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 消息发送结果DTO
+ * <p>
+ * 用于封装消息发送接口的响应结果。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class MiniGramSubsMsgResultDTO implements Serializable {
+
+    /**
+     * 错误码
+     * <p>
+     * 返回码,0表示成功,其他值表示失败。
+     * </p>
+     */
+    private Integer errcode;
+
+    /**
+     * 错误信息
+     * <p>
+     * 返回码的文本描述,成功时为 "ok",失败时包含具体的错误信息。
+     * </p>
+     */
+    private String errmsg;
+
+    /**
+     * 消息ID
+     * <p>
+     * 消息的唯一标识符,成功发送时返回。
+     * </p>
+     *  <p>
+     *     注意:这个字段可能为null,发送失败时,此字段可能为null
+     *  </p>
+     */
+    private Long msgid;
+
+    /**
+     * rid  请求的唯一标识
+     * 仅在发生错误时出现
+     */
+    private String rid;
+}

+ 68 - 0
fs-service/src/main/java/com/fs/store/dto/TemplateMessageSendRequestDTO.java

@@ -0,0 +1,68 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 模板消息发送请求DTO
+ * <p>
+ * 用于构建发送模板消息的请求体。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class TemplateMessageSendRequestDTO {
+
+    /**
+     * 接收者openid
+     * <p>
+     * 用户的唯一标识符。
+     * </p>
+     */
+    private String touser;
+
+    /**
+     * 模板ID
+     * <p>
+     * 所需下发的模板消息的id。
+     * </p>
+     */
+    private String template_id;
+
+    /**
+     * 跳转页面
+     * <p>
+     * 点击模板消息后跳转的页面,可以为空。
+     * </p>
+     */
+    private String page;
+
+    /**
+     * 模板数据
+     * <p>
+     * 模板内容,键值对形式,键名为模板中的变量名,值为要替换的内容。
+     * </p>
+     */
+    private Map<String, TemplateDataValue> data;
+
+    /**
+     * 模板数据值对象
+     * <p>
+     * 内部类,用于表示模板数据中的单个值。
+     * </p>
+     */
+    @Data
+    public static class TemplateDataValue {
+        /**
+         * 模板变量值
+         * <p>
+         * 要替换模板变量的具体内容。
+         * </p>
+         */
+        private String value;
+    }
+}

+ 36 - 0
fs-service/src/main/java/com/fs/store/dto/WeXinAccessTokenDTO.java

@@ -0,0 +1,36 @@
+package com.fs.store.dto;
+
+import lombok.Data;
+
+/**
+ * 访问令牌DTO
+ * <p>
+ * 用于存储从认证服务器获取的访问令牌及其相关信息。
+ * </p>
+ *
+ * @author xdd
+ * @version 1.0
+ * @since 2025-02-27
+ */
+@Data
+public class WeXinAccessTokenDTO {
+
+    /**
+     * 访问令牌
+     * <p>
+     * 用于访问受保护资源的令牌。
+     * </p>
+     */
+    private String accessToken;
+
+    /**
+     * 过期时间(秒)
+     * <p>
+     * 访问令牌的有效时间,单位为秒。
+     * </p>
+     */
+    private Integer expiresIn;
+
+    private Long errcode;
+    private String errmsg;
+}

Some files were not shown because too many files changed in this diff