Преглед изворни кода

Merge remote-tracking branch 'origin/master' into Payment-Configuration

yfh пре 2 дана
родитељ
комит
831ad429bd
100 измењених фајлова са 2523 додато и 138 уклоњено
  1. 103 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java
  2. 38 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java
  3. 34 7
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  4. 8 2
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  5. 3 3
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  6. 13 0
      fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java
  7. 35 3
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  8. 111 0
      fs-admin/src/main/java/com/fs/task/MiniProgramSubTask.java
  9. 116 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyRedPacketBalanceLogsController.java
  10. 68 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  11. 6 3
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  12. 46 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceLogs.java
  13. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  14. 64 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceLogsMapper.java
  15. 64 0
      fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceLogsService.java
  16. 99 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceLogsServiceImpl.java
  17. 13 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  18. 25 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java
  19. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  20. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseFinishTempMapper.java
  21. 8 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  22. 28 0
      fs-service/src/main/java/com/fs/course/param/FsCourseAnswerLogsParam.java
  23. 29 0
      fs-service/src/main/java/com/fs/course/param/FsCourseRedPacketLogParam.java
  24. 26 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  25. 155 30
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  26. 5 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseFinishTempListVO.java
  27. 28 22
      fs-service/src/main/java/com/fs/course/vo/FsCourseRedPacketLogListPVO.java
  28. 7 5
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  29. 3 0
      fs-service/src/main/java/com/fs/his/domain/FsUserInformationCollection.java
  30. 8 6
      fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java
  31. 3 2
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  32. 20 6
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  33. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  34. 2 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  35. 3 3
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  36. 2 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderItemScrmService.java
  37. 10 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderItemScrmServiceImpl.java
  38. 2 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderErpExportVO.java
  39. 1 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderExportVO.java
  40. 1 1
      fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java
  41. 36 5
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  42. 2 1
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  43. 2 2
      fs-service/src/main/java/com/fs/live/service/ILiveDataService.java
  44. 11 14
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  45. 4 4
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  46. 12 6
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  47. 10 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  48. 1 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  49. 1 0
      fs-service/src/main/java/com/fs/live/vo/FsMyLiveOrderListQueryVO.java
  50. 2 0
      fs-service/src/main/java/com/fs/live/vo/LiveDataDetailVo.java
  51. 2 0
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java
  52. 2 0
      fs-service/src/main/java/com/fs/live/vo/ProductSalesVo.java
  53. 11 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  54. 1 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  55. 6 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  56. 28 1
      fs-service/src/main/java/com/fs/sop/domain/QwSop.java
  57. 25 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java
  58. 43 0
      fs-service/src/main/java/com/fs/store/dto/ClientCredGrantReqDTO.java
  59. 52 0
      fs-service/src/main/java/com/fs/store/dto/MiniGramSubsMsgResultDTO.java
  60. 68 0
      fs-service/src/main/java/com/fs/store/dto/TemplateMessageSendRequestDTO.java
  61. 36 0
      fs-service/src/main/java/com/fs/store/dto/WeXinAccessTokenDTO.java
  62. 44 0
      fs-service/src/main/java/com/fs/store/enums/MiniAppNotifyTaskStatusEnum.java
  63. 28 0
      fs-service/src/main/java/com/fs/store/service/IWechatMiniProgrService.java
  64. 29 0
      fs-service/src/main/java/com/fs/store/service/impl/IWechatMiniProgrServiceImpl.java
  65. 95 0
      fs-service/src/main/java/com/fs/store/utils/MiniProgramHttp.java
  66. 43 0
      fs-service/src/main/java/com/fs/store/vo/ClientCredGrantReqDTO.java
  67. 36 0
      fs-service/src/main/java/com/fs/store/vo/WeXinAccessTokenDTO.java
  68. 9 0
      fs-service/src/main/java/com/fs/wxcid/dto/admin/GenAuthKey1Request.java
  69. 13 0
      fs-service/src/main/java/com/fs/wxcid/dto/admin/GenAuthKey3Request.java
  70. 21 0
      fs-service/src/main/java/com/fs/wxcid/dto/callback/CallbackConfigRequest.java
  71. 66 0
      fs-service/src/main/java/com/fs/wxcid/dto/callback/CallbackMessage.java
  72. 11 0
      fs-service/src/main/java/com/fs/wxcid/dto/callback/ImgBuf.java
  73. 19 0
      fs-service/src/main/java/com/fs/wxcid/dto/callback/MessageCallbackRequest.java
  74. 11 0
      fs-service/src/main/java/com/fs/wxcid/dto/callback/ReturnMessage.java
  75. 17 0
      fs-service/src/main/java/com/fs/wxcid/dto/callback/StringWrapper.java
  76. 37 0
      fs-service/src/main/java/com/fs/wxcid/dto/common/ApiResponse.java
  77. 35 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/AgreeAddRequest.java
  78. 15 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/DelContactRequest.java
  79. 21 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/GetContactDetailsListRequest.java
  80. 19 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/GetContactListRequest.java
  81. 15 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/GetFriendRelationRequest.java
  82. 27 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/SearchContactRequest.java
  83. 24 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/UploadMContactRequest.java
  84. 7 0
      fs-service/src/main/java/com/fs/wxcid/dto/friend/VerifyUserRequest.java
  85. 21 0
      fs-service/src/main/java/com/fs/wxcid/dto/login/DeviceInfo.java
  86. 32 0
      fs-service/src/main/java/com/fs/wxcid/dto/login/LoginRequest.java
  87. 19 0
      fs-service/src/main/java/com/fs/wxcid/dto/login/QrCodeRequest.java
  88. 21 0
      fs-service/src/main/java/com/fs/wxcid/dto/login/SlideVerifyRequest.java
  89. 20 0
      fs-service/src/main/java/com/fs/wxcid/dto/login/VerifyCodeRequest.java
  90. 16 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/AddMessageMgrRequest.java
  91. 16 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/AppMessageItem.java
  92. 17 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/CdnUploadVideoRequest.java
  93. 16 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/DownloadEmojiGifRequest.java
  94. 23 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/EmojiItem.java
  95. 15 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardEmojiRequest.java
  96. 25 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardImageItem.java
  97. 16 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardImageMessageRequest.java
  98. 27 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardVideoItem.java
  99. 5 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardVideoMessageRequest.java
  100. 34 0
      fs-service/src/main/java/com/fs/wxcid/dto/message/GetMsgBigImgRequest.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));
+    }
+}

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

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

+ 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 - 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, "直播间用户详情数据");
     }

+ 13 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java

@@ -27,6 +27,7 @@ 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,6 +54,7 @@ public class QwSopController extends BaseController
     @Autowired
     private FsUserCourseVideoMapper fsUserCourseVideoMapper;
 
+
     /**
      * 查询企微sop列表
      */
@@ -60,6 +62,17 @@ public class QwSopController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(QwSop qwSop)
     {
+        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 getDataTable(new ArrayList<>());
+            }
+        }
+
+
         startPage();
         List<QwSop> list = qwSopService.selectQwSopList(qwSop);
         return getDataTable(list);

+ 35 - 3
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,14 @@ 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 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,6 +45,8 @@ public class QwSopTempController extends BaseController
     private IQwSopTempService qwSopTempService;
     @Autowired
     private TokenService tokenService;
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
     /**
      * 查询sop模板列表
      */
@@ -53,6 +57,34 @@ public class QwSopTempController extends BaseController
         startPage();
 //        List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
         List<QwSopTemp> list = qwSopTempService.selectQwSopTempListNew(qwSopTemp);
+        // 收集所有需要查询的用户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());
+                    }
+                }
+
+            });
+        }
+
         return getDataTable(list);
     }
 

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

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

+ 68 - 0
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, "直播间用户详情数据");
+    }
+
     /**
      * 直播数据页面卡片数据
      */

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

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

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

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

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

+ 28 - 0
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
@@ -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;
+    }
+
 }

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

@@ -1,9 +1,12 @@
 package com.fs.course.param;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class FsCourseRedPacketLogParam {
@@ -34,4 +37,30 @@ public class FsCourseRedPacketLogParam {
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date sTime;
+
+    @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;
+    }
+
 }

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

@@ -1,9 +1,11 @@
 package com.fs.course.param;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
@@ -107,4 +109,28 @@ public class FsCourseWatchLogListParam implements Serializable {
     private Long deptId;
     private List<Long> deptIds;
     private String 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;
+    }
 }

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

@@ -1726,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("奖励发送失败,请联系客服");
+            }
         }
     }
 
@@ -1972,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;
 
 

+ 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;
 
@@ -72,21 +83,16 @@ 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;
 
 }

+ 7 - 5
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -1157,7 +1157,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 +1168,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 +1973,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 +2013,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 +2038,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 +2061,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 - 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("修改为待发货时,请填写物流单号");
         }
         //其他逻辑,目前暂定这个

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

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

+ 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:已退款) */

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

+ 36 - 5
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -138,15 +138,46 @@ 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" +
+            " 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> " +
+            "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> " +
+            " 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> " +
+            ") as temp " +
             " </script>"})
     int listLiveDataCount(@Param("param") LiveDataParam param);
 

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

+ 11 - 14
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());
@@ -459,16 +462,9 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         request.setRefund_state(1);
         request.setStoreAfterSalesId(storeAfterSales.getId());
         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();
@@ -913,16 +909,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 +931,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<>();
         }

+ 12 - 6
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -2631,6 +2631,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);
     }
 
@@ -3028,7 +3034,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){
@@ -3506,7 +3512,7 @@ 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();
         log.info("订单生成:"+orderSn);
         liveOrder.setOrderCode(orderSn);
         BigDecimal payPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
@@ -3566,9 +3572,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());
@@ -3698,7 +3704,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 {
 
 
 
+
+

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

@@ -488,4 +488,15 @@ 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);
 }

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

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

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

@@ -1563,6 +1563,12 @@ 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);
+    }
+
     /**
      * 根据销售公司和企微ID查询企微用户
      */

+ 28 - 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,29 @@ 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<>();
+
+    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;
+    }
 }

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

@@ -111,6 +111,31 @@ public class QwSopTemp implements Serializable
     @TableField(exist = false)
     private List<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;
+    }
+
     /**
      * 部门类型 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;
+}

+ 44 - 0
fs-service/src/main/java/com/fs/store/enums/MiniAppNotifyTaskStatusEnum.java

@@ -0,0 +1,44 @@
+package com.fs.store.enums;
+
+
+import lombok.Getter;
+
+@Getter
+public enum MiniAppNotifyTaskStatusEnum {
+    /**
+     * 待执行
+     */
+    WAITING(0),
+    /**
+     * 执行中
+     */
+    RUNNING(1),
+    /**
+     * 执行成功
+     */
+    SUCCESS(2),
+    /**
+     * 执行失败
+     */
+    FAILED(3),
+    /**
+     * 已取消
+     */
+    CANCELED(4);
+
+    private final int value;
+
+    MiniAppNotifyTaskStatusEnum(int value) {
+        this.value = value;
+    }
+
+
+    public static MiniAppNotifyTaskStatusEnum fromValue(int value) {
+        for (MiniAppNotifyTaskStatusEnum status : values()) {
+            if (status.getValue() == value) {
+                return status;
+            }
+        }
+        throw new IllegalArgumentException("Invalid value: " + value);
+    }
+}

+ 28 - 0
fs-service/src/main/java/com/fs/store/service/IWechatMiniProgrService.java

@@ -0,0 +1,28 @@
+package com.fs.store.service;
+
+
+import com.fs.store.dto.ClientCredGrantReqDTO;
+import com.fs.store.dto.MiniGramSubsMsgResultDTO;
+import com.fs.store.dto.TemplateMessageSendRequestDTO;
+import com.fs.store.dto.WeXinAccessTokenDTO;
+
+/**
+ * 小程序调用相关
+ */
+public interface IWechatMiniProgrService {
+    /**
+     * 获取稳定的token
+     *
+     * @param param 请求参数
+     * @return {@link com.fs.store.dto.WeXinAccessTokenDTO}
+     */
+    WeXinAccessTokenDTO getStableToken(ClientCredGrantReqDTO param);
+
+    /**
+     * 微信小程序发送订阅消息
+     *
+     * @param param 请求参数
+     * @return {@link MiniGramSubsMsgResultDTO}
+     */
+    MiniGramSubsMsgResultDTO sendSubscribeMsg(String accessToken, TemplateMessageSendRequestDTO param);
+}

+ 29 - 0
fs-service/src/main/java/com/fs/store/service/impl/IWechatMiniProgrServiceImpl.java

@@ -0,0 +1,29 @@
+package com.fs.store.service.impl;
+
+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.store.service.IWechatMiniProgrService;
+import com.fs.store.utils.MiniProgramHttp;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class IWechatMiniProgrServiceImpl implements IWechatMiniProgrService {
+
+    private final MiniProgramHttp miniProgramHttp;
+
+    @Override
+    public WeXinAccessTokenDTO getStableToken(ClientCredGrantReqDTO param) {
+        return miniProgramHttp.getStableAccessToken(param);
+    }
+
+    @Override
+    public MiniGramSubsMsgResultDTO sendSubscribeMsg(String accessToken,TemplateMessageSendRequestDTO param) {
+        return miniProgramHttp.sendSubscribeMessage(accessToken,param);
+    }
+}

+ 95 - 0
fs-service/src/main/java/com/fs/store/utils/MiniProgramHttp.java

@@ -0,0 +1,95 @@
+package com.fs.store.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSONObject;
+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.hc.openapi.tool.fastjson.JSON;
+import com.hc.openapi.tool.util.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class MiniProgramHttp {
+
+    /**
+     * 微信小程序-发送订阅消息地址
+     */
+    private static final String BASE_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";
+
+    /**
+     * 微信小程序-获取accessToken地址
+     */
+    private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token";
+
+
+    /**
+     * 发送微信订阅消息 (使用 Hutool)
+     * @param accessToken token
+     * @param param 请求数据
+     * @return String
+     */
+    public MiniGramSubsMsgResultDTO sendSubscribeMessage(String accessToken, TemplateMessageSendRequestDTO param) {
+        String url = BASE_URL + "?access_token=" + accessToken;
+
+        log.info("发送小程序订阅消息, 请求 URL: {}", url);
+
+        String requestBody = JSON.toJSONString(param);
+        log.info("发送小程序订阅消息, 请求参数: {}", requestBody);
+
+        try {
+            String response = HttpUtil.post(url, requestBody);
+            log.info("发送小程序订阅消息, HTTP 请求 URL: {}", url);
+            log.info("发送小程序订阅消息, HTTP 请求体: {}", requestBody);
+            log.info("发送小程序订阅消息, HTTP 响应: {}", response);
+
+            MiniGramSubsMsgResultDTO result = JSONObject.parseObject(response, MiniGramSubsMsgResultDTO.class);
+            if(ObjectUtil.notEqual(result.getErrcode(),0)){
+                throw new RuntimeException("发送小程序订阅消息失败, " + result.getErrmsg());
+            }
+            log.info("发送小程序订阅消息, 解析结果: {}", JSON.toJSONString(result));
+            return result;
+
+        } catch (Exception e) {
+            log.error("发送小程序订阅消息失败: {}", e.getMessage());
+            throw e;
+        }
+    }
+
+
+    /**
+     * 获取微信 Stable Access Token
+     * @return WeXinAccessTokenDTO
+     */
+    public WeXinAccessTokenDTO getStableAccessToken(ClientCredGrantReqDTO param) {
+        String requestBody = JSONObject.toJSONString(param);
+        log.info("获取微信 Stable Access Token, 请求参数: {}", requestBody); // 打印请求参数
+
+        try {
+            String responseJson =
+            HttpRequest.post(TOKEN_URL).contentType("application/json").body(requestBody).execute().body();
+            log.info("获取微信 Stable Access Token, HTTP 请求 URL: {}", TOKEN_URL);
+            log.info("获取微信 Stable Access Token, HTTP 请求体: {}", requestBody);
+            log.info("获取微信 Stable Access Token, HTTP 响应: {}", responseJson);
+
+            if(StringUtils.isBlank(responseJson)){
+                throw new RuntimeException("获取微信 Stable Access Token 失败,response为空");
+            }
+            WeXinAccessTokenDTO result = JSONObject.parseObject(responseJson, WeXinAccessTokenDTO.class);
+            log.info("获取微信 Stable Access Token, 解析结果: {}", JSONObject.toJSONString(result)); //记录解析结果
+            if(result == null || StringUtils.isBlank(result.getAccessToken())){
+                throw new RuntimeException("获取微信 Stable Access Token 失败, accessToken为空");
+            }
+            return result;
+
+        } catch (Exception e) {
+            log.error("获取微信 Stable Access Token 失败", e);
+            throw e;
+        }
+    }
+}

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

@@ -0,0 +1,43 @@
+package com.fs.store.vo;
+
+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;
+}

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

@@ -0,0 +1,36 @@
+package com.fs.store.vo;
+
+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;
+}

+ 9 - 0
fs-service/src/main/java/com/fs/wxcid/dto/admin/GenAuthKey1Request.java

@@ -0,0 +1,9 @@
+package com.fs.wxcid.dto.admin;
+
+import lombok.Data;
+
+@Data
+public class GenAuthKey1Request {
+    private Integer Count = 1;
+    private Integer Days = 30;
+}

+ 13 - 0
fs-service/src/main/java/com/fs/wxcid/dto/admin/GenAuthKey3Request.java

@@ -0,0 +1,13 @@
+package com.fs.wxcid.dto.admin;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class GenAuthKey3Request {
+    @JsonProperty("Count")
+    private Integer Count;
+
+    @JsonProperty("Type")
+    private Integer Type;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wxcid/dto/callback/CallbackConfigRequest.java

@@ -0,0 +1,21 @@
+package com.fs.wxcid.dto.callback;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 设置消息回调地址的请求参数
+ * <p>对应接口:POST /message/SetCallback</p>
+ */
+@Data
+public class CallbackConfigRequest {
+    /** 回调服务器的 URL 地址,例如:https://yourdomain.com/wechat/callback
+     *  微信客户端收到消息后,会将消息以 JSON 形式 POST 到该地址 */
+    @JsonProperty("CallbackURL")
+    private String CallbackURL;
+
+    /** 是否启用消息回调功能:
+     *  true:启用,消息将实时推送到 CallbackURL
+     *  false:禁用,不推送 */
+    @JsonProperty("Enabled")
+    private Boolean Enabled;
+}

+ 66 - 0
fs-service/src/main/java/com/fs/wxcid/dto/callback/CallbackMessage.java

@@ -0,0 +1,66 @@
+package com.fs.wxcid.dto.callback;
+
+import lombok.Data;
+
+/**
+ * 回调中的具体消息内容
+ */
+@Data
+public class CallbackMessage {
+
+    /** 消息 ID(整型) */
+    private Long msg_id;
+
+    /** 发送者 wxid(嵌套结构) */
+    private StringWrapper from_user_name;
+
+    /** 接收者 wxid(通常是当前账号) */
+    private StringWrapper to_user_name;
+
+    /** 消息类型:
+     *  1 = 文本,
+     *  3 = 图片,
+     *  34 = 语音,
+     *  43 = 视频,
+     *  47 = 表情/动画,
+     *  49 = App消息(链接、文件等),
+     *  51 = 系统通知(如 lastMessage)等 */
+    private Integer msg_type;
+
+    /** 消息内容(文本或 XML) */
+    private StringWrapper content;
+
+    /** 消息状态(通常为 3) */
+    private Integer status;
+
+    /** 图片状态(通常为 1) */
+    private Integer img_status;
+
+    /** 图片缓冲区(长度为 0 表示无图) */
+    private ImgBuf img_buf;
+
+    /** 消息创建时间戳(秒) */
+    private Long create_time;
+
+    /** 消息来源 XML(包含签名、扩展信息等) */
+    private String msg_source;
+
+    /** 推送通知内容(如 "昵称 : 消息文本") */
+    private String push_content;
+
+    /** 新版消息 ID(64位整数字符串) */
+    private String new_msg_id;
+
+
+    public String getFromWxId() {
+        return from_user_name != null ? from_user_name.getStr() : null;
+    }
+
+    public String getToWxId() {
+        return to_user_name != null ? to_user_name.getStr() : null;
+    }
+
+    public String getContentText() {
+        return content != null ? content.getStr() : null;
+    }
+}

+ 11 - 0
fs-service/src/main/java/com/fs/wxcid/dto/callback/ImgBuf.java

@@ -0,0 +1,11 @@
+package com.fs.wxcid.dto.callback;
+
+import lombok.Data;
+
+/**
+ * 图片缓冲区信息
+ */
+@Data
+public class ImgBuf {
+    private Integer len; // 长度,通常为 0
+}

+ 19 - 0
fs-service/src/main/java/com/fs/wxcid/dto/callback/MessageCallbackRequest.java

@@ -0,0 +1,19 @@
+package com.fs.wxcid.dto.callback;
+
+import lombok.Data;
+
+/**
+ * 微信消息回调顶层请求对象
+ */
+@Data
+public class MessageCallbackRequest {
+
+    /** 账号唯一标识(与发送时的 key 一致) */
+    private String key;
+
+    /** 回调类型,目前为 "message" */
+    private String type;
+
+    /** 具体消息内容 */
+    private CallbackMessage message;
+}

+ 11 - 0
fs-service/src/main/java/com/fs/wxcid/dto/callback/ReturnMessage.java

@@ -0,0 +1,11 @@
+package com.fs.wxcid.dto.callback;
+
+import lombok.Data;
+
+@Data
+public class ReturnMessage {
+    //消息来源Id
+    private String fromWxId;
+    //消息内容
+    private String text;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/wxcid/dto/callback/StringWrapper.java

@@ -0,0 +1,17 @@
+package com.fs.wxcid.dto.callback;
+
+import lombok.Data;
+
+/**
+ * 包装字符串的结构,用于 from_user_name / content 等字段
+ * 对应 JSON: { "str": "实际值" }
+ */
+@Data
+public class StringWrapper {
+    private String str;
+
+    @Override
+    public String toString() {
+        return str != null ? str : "";
+    }
+}

+ 37 - 0
fs-service/src/main/java/com/fs/wxcid/dto/common/ApiResponse.java

@@ -0,0 +1,37 @@
+package com.fs.wxcid.dto.common;
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ *
+ * 通用响应结构
+ */
+@Data
+public class ApiResponse {
+    @JSONField(name = "Code")
+    private int code;
+
+    @JSONField(name = "Data")
+    private Object data;
+
+    @JSONField(name = "Text")
+    private String text;
+
+    @JSONField(name = "Success")
+    private boolean success;
+
+    @JSONField(name = "Data62")
+    private String data62;
+
+    @JSONField(name = "Ticket")
+    private String ticket;
+
+    @Override
+    public String toString() {
+        return "ApiResponse{" +
+                "code=" + code +
+                ", text='" + text + '\'' +
+                ", success=" + success +
+                '}';
+    }
+}

+ 35 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/AgreeAddRequest.java

@@ -0,0 +1,35 @@
+package com.fs.wxcid.dto.friend;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 同意好友请求 或 验证添加好友 的请求参数
+ * 对应接口:/friend/AgreeAdd 和 /friend/VerifyUser
+ */
+@Data
+public class AgreeAddRequest {
+    /** 群聊用户名(当通过群聊添加时使用) */
+    @JsonProperty("ChatRoomUserName")
+    private String ChatRoomUserName;
+
+    /** 操作码,通常为 2 表示同意 */
+    @JsonProperty("OpCode")
+    private Integer OpCode;
+
+    /** 添加场景,例如 3 表示通过二维码 */
+    @JsonProperty("Scene")
+    private Integer Scene;
+
+    /** 微信内部加密参数 V3(来自好友请求) */
+    @JsonProperty("V3")
+    private String V3;
+
+    /** 微信内部加密参数 V4(来自好友请求) */
+    @JsonProperty("V4")
+    private String V4;
+
+    /** 验证附言(可选) */
+    @JsonProperty("VerifyContent")
+    private String VerifyContent;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/DelContactRequest.java

@@ -0,0 +1,15 @@
+package com.fs.wxcid.dto.friend;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 删除好友请求参数
+ * 对应接口:/friend/DelContact
+ */
+@Data
+public class DelContactRequest {
+    /** 要删除的好友用户名(wxid 或其他唯一标识) */
+    @JsonProperty("DelUserName")
+    private String DelUserName;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/GetContactDetailsListRequest.java

@@ -0,0 +1,21 @@
+package com.fs.wxcid.dto.friend;
+
+import lombok.Data;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 获取联系人详情列表的请求参数
+ * 支持同时查询多个用户和群聊
+ * 对应接口:/friend/GetContactDetailsList
+ */
+@Data
+public class GetContactDetailsListRequest {
+    /** 要查询的群聊 wxid 列表 */
+    @JsonProperty("RoomWxIDList")
+    private List<String> RoomWxIDList;
+
+    /** 要查询的用户 wxid 列表 */
+    @JsonProperty("UserNames")
+    private List<String> UserNames;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/GetContactListRequest.java

@@ -0,0 +1,19 @@
+package com.fs.wxcid.dto.friend;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 分页获取全部联系人(含群聊)的请求参数
+ * 对应接口:/friend/GetContactList
+ */
+@Data
+public class GetContactListRequest {
+    /** 当前群聊联系人的序列号(用于分页) */
+    @JsonProperty("CurrentChatRoomContactSeq")
+    private Long CurrentChatRoomContactSeq;
+
+    /** 当前普通联系人的序列号(用于分页) */
+    @JsonProperty("CurrentWxcontactSeq")
+    private Long CurrentWxcontactSeq;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/GetFriendRelationRequest.java

@@ -0,0 +1,15 @@
+package com.fs.wxcid.dto.friend;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 查询与指定用户的好友关系状态
+ * 对应接口:/friend/GetFriendRelation
+ */
+@Data
+public class GetFriendRelationRequest {
+    /** 目标用户的用户名(wxid) */
+    @JsonProperty("UserName")
+    private String UserName;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/SearchContactRequest.java

@@ -0,0 +1,27 @@
+package com.fs.wxcid.dto.friend;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 搜索联系人请求参数
+ * 对应接口:/friend/SearchContact
+ */
+@Data
+public class SearchContactRequest {
+    /** 来源场景(如 0 表示通用搜索) */
+    @JsonProperty("FromScene")
+    private Integer FromScene;
+
+    /** 操作码(通常为 0) */
+    @JsonProperty("OpCode")
+    private Integer OpCode;
+
+    /** 搜索场景(如 0 表示按昵称/微信号搜索) */
+    @JsonProperty("SearchScene")
+    private Integer SearchScene;
+
+    /** 要搜索的用户名、昵称或微信号 */
+    @JsonProperty("UserName")
+    private String UserName;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/UploadMContactRequest.java

@@ -0,0 +1,24 @@
+package com.fs.wxcid.dto.friend;
+
+import lombok.Data;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 上传手机通讯录以匹配微信好友
+ * 对应接口:/friend/UploadMContact
+ */
+@Data
+public class UploadMContactRequest {
+    /** 当前手机号(可选) */
+    @JsonProperty("Mobile")
+    private String Mobile;
+
+    /** 手机通讯录中的号码列表 */
+    @JsonProperty("MobileList")
+    private List<String> MobileList;
+
+    /** 操作码(通常为 0) */
+    @JsonProperty("Opcode") // 注意:此处是 Opcode(大写 O),非 OpCode
+    private Integer Opcode;
+}

+ 7 - 0
fs-service/src/main/java/com/fs/wxcid/dto/friend/VerifyUserRequest.java

@@ -0,0 +1,7 @@
+package com.fs.wxcid.dto.friend;
+
+/**
+ * 验证/添加好友请求,结构与 AgreeAddRequest 完全一致
+ * 可复用或单独定义以增强语义
+ */
+public class VerifyUserRequest extends AgreeAddRequest {}

+ 21 - 0
fs-service/src/main/java/com/fs/wxcid/dto/login/DeviceInfo.java

@@ -0,0 +1,21 @@
+package com.fs.wxcid.dto.login;
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 设备信息,用于模拟 Android 设备登录
+ */
+@Data
+public class DeviceInfo {
+    @JsonProperty("AndroidId")
+    private String androidId;         // Android 设备 ID
+
+    @JsonProperty("ImeI")
+    private String imei;              // IMEI 号(可伪造)
+
+    @JsonProperty("Manufacturer")
+    private String manufacturer;      // 厂商,如 "HUAWEI"
+
+    @JsonProperty("Model")
+    private String model;             // 型号,如 "P40"
+}

+ 32 - 0
fs-service/src/main/java/com/fs/wxcid/dto/login/LoginRequest.java

@@ -0,0 +1,32 @@
+package com.fs.wxcid.dto.login;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * A16 数据登录 或 62账号密码登录 的通用请求参数
+ * <p>两者结构完全一致,可复用</p>
+ */
+@Data
+public class LoginRequest {
+
+    @JsonProperty("DeviceInfo")
+    private DeviceInfo deviceInfo;    // 模拟设备信息
+
+    @JsonProperty("LoginData")
+    private String loginData;         // 62数据 或 A16数据(Base64 编码)
+
+    @JsonProperty("Password")
+    private String password;          // 微信密码(部分登录方式需要)
+
+    @JsonProperty("Proxy")
+    private String proxy;             // 代理地址,格式:socks5://user:pass@ip:port
+
+    @JsonProperty("Ticket")
+    private String ticket;            // 登录票据(如扫码后的 ticket)
+
+    @JsonProperty("Type")
+    private Integer type;             // 登录类型,0 表示普通登录
+
+    @JsonProperty("UserName")
+    private String userName;          // 微信号 或 手机号
+}

+ 19 - 0
fs-service/src/main/java/com/fs/wxcid/dto/login/QrCodeRequest.java

@@ -0,0 +1,19 @@
+package com.fs.wxcid.dto.login;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 获取二维码请求(Car / Mac / iPad / Direct)
+ * */
+@Data
+public class QrCodeRequest {
+
+    @JsonProperty("Check")
+    private Boolean check;            // 是否检查环境(通常 false)
+
+    @JsonProperty("IpadOrmac")
+    private String ipadOrmac;         // 设备标识,如 "iPad" 或 "Mac"
+
+    @JsonProperty("Proxy")//"socks5://username:password@ipv4:port";
+    private String proxy="socks5://t16517392102382:1dhye1x5@d247.kdltpspro.com:15818";             // 代理地址(异地 IP 必填)
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wxcid/dto/login/SlideVerifyRequest.java

@@ -0,0 +1,21 @@
+package com.fs.wxcid.dto.login;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 滑块验证
+ * */
+@Data
+public class SlideVerifyRequest {
+    @JsonProperty("data62")
+    private String data62;            // 62 数据
+
+    @JsonProperty("randstr")
+    private String randstr;           // 随机字符串(来自滑块验证)
+
+    @JsonProperty("slideticket")
+    private String slideTicket;       // 滑块验证票据
+
+    @JsonProperty("ticket")
+    private String ticket;            // 主登录票据
+}

+ 20 - 0
fs-service/src/main/java/com/fs/wxcid/dto/login/VerifyCodeRequest.java

@@ -0,0 +1,20 @@
+package com.fs.wxcid.dto.login;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * iPad 登录时输入验证码的请求
+ */
+@Data
+public class VerifyCodeRequest {
+
+    @JsonProperty("code")
+    private String code;              // 用户输入的 6 位数字验证码
+
+    @JsonProperty("data62")
+    private String data62;            // 62 数据(用于恢复会话)
+
+    @JsonProperty("ticket")
+    private String ticket;            // 登录票据
+}

+ 16 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/AddMessageMgrRequest.java

@@ -0,0 +1,16 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 添加消息到发送管理器(用于批量或延迟发送)
+ */
+@Data
+public class AddMessageMgrRequest {
+
+    /** 消息项列表 */
+    @JsonProperty("MsgItem")
+    private List<MsgItem> msgItemList;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/AppMessageItem.java

@@ -0,0 +1,16 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@Data
+public class AppMessageItem {
+    @JsonProperty("ContentType")
+    private Integer ContentType;
+
+    @JsonProperty("ContentXML")
+    private String ContentXML;
+
+    @JsonProperty("ToUserName")
+    private String ToUserName;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/CdnUploadVideoRequest.java

@@ -0,0 +1,17 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@Data
+public class CdnUploadVideoRequest {
+    @JsonProperty("ThumbData")
+    private List<Integer> ThumbData; // 实际应为 byte[],但 Swagger 写 [0],先用 List<Integer>
+
+    @JsonProperty("ToUserName")
+    private String ToUserName;
+
+    @JsonProperty("VideoData")
+    private List<Integer> VideoData;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/DownloadEmojiGifRequest.java

@@ -0,0 +1,16 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 下载表情 GIF 动图
+ * <p>需提供表情的 XML 描述内容</p>
+ */
+@Data
+public class DownloadEmojiGifRequest {
+
+    /** 表情的 XML 内容(来自消息体) */
+    @JsonProperty("xml_content")
+    private String xmlContent;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/EmojiItem.java

@@ -0,0 +1,23 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 表情信息项(用于发送或转发)
+ */
+@Data
+public class EmojiItem {
+
+    /** 表情 MD5 值(唯一标识) */
+    @JsonProperty("EmojiMd5")
+    private String emojiMd5;
+
+    /** 表情文件大小(字节) */
+    @JsonProperty("EmojiSize")
+    private Long emojiSize;
+
+    /** 接收者 wxid */
+    @JsonProperty("ToUserName")
+    private String toUserName;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardEmojiRequest.java

@@ -0,0 +1,15 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 转发表情(支持动图)
+ */
+@Data
+public class ForwardEmojiRequest {
+
+    /** 表情列表 */
+    @JsonProperty("EmojiList")
+    private java.util.List<EmojiItem> emojiList;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardImageItem.java

@@ -0,0 +1,25 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 转发图片项
+ */
+@Data
+public class ForwardImageItem {
+    @JsonProperty("AesKey")
+    private String aesKey;
+
+    @JsonProperty("CdnMidImgSize")
+    private Long cdnMidImgSize;
+
+    @JsonProperty("CdnMidImgUrl")
+    private String cdnMidImgUrl;
+
+    @JsonProperty("CdnThumbImgSize")
+    private Long cdnThumbImgSize;
+
+    @JsonProperty("ToUserName")
+    private String toUserName;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardImageMessageRequest.java

@@ -0,0 +1,16 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * 转发图片消息请求(结构与视频转发相同)
+ */
+@Data
+public class ForwardImageMessageRequest {
+    @JsonProperty("ForwardImageList")
+    private List<ForwardImageItem> ForwardImageList;
+
+    @JsonProperty("ForwardVideoList")
+    private List<ForwardVideoItem> ForwardVideoList;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardVideoItem.java

@@ -0,0 +1,27 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * 转发视频项
+ */
+@Data
+public class ForwardVideoItem {
+    @JsonProperty("AesKey")
+    private String AesKey;
+
+    @JsonProperty("CdnThumbLength")
+    private Integer CdnThumbLength;
+
+    @JsonProperty("CdnVideoUrl")
+    private String CdnVideoUrl;
+
+    @JsonProperty("Length")
+    private Integer Length;
+
+    @JsonProperty("PlayLength")
+    private Integer PlayLength;
+
+    @JsonProperty("ToUserName")
+    private String ToUserName;
+}

+ 5 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/ForwardVideoMessageRequest.java

@@ -0,0 +1,5 @@
+package com.fs.wxcid.dto.message;
+/**
+ * 转发视频消息请求(结构与视频转发相同)
+ */
+public class ForwardVideoMessageRequest extends ForwardImageMessageRequest {}

+ 34 - 0
fs-service/src/main/java/com/fs/wxcid/dto/message/GetMsgBigImgRequest.java

@@ -0,0 +1,34 @@
+package com.fs.wxcid.dto.message;
+
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 获取高清图片(分片下载)
+ */
+@Data
+public class GetMsgBigImgRequest {
+    // 压缩类型,通常
+    @JsonProperty("CompressType")
+    private Integer compressType;
+
+    // 消息发送者
+    @JsonProperty("FromUserName")
+    private String fromUserName;
+
+    // 消息 ID
+    @JsonProperty("MsgId")
+    private Long msgId;
+
+    // 分片信息
+    @JsonProperty("Section")
+    private Section section;
+
+    // 当前账号 wxid
+    @JsonProperty("ToUserName")
+    private String toUserName;
+
+    // 文件总长度
+    @JsonProperty("TotalLen")
+    private Long totalLen;
+}

Неке датотеке нису приказане због велике количине промена