62 Commitit 4fdd9d85b1 ... 971a2db8ee

Tekijä SHA1 Viesti Päivämäärä
  吴树波 971a2db8ee Merge branch 'master' into master-ai-cell 2 viikkoa sitten
  peicj 61ed6f2edb 解决bug 2 viikkoa sitten
  peicj e8c43329af Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  jzp 9ed43b6659 1.增加ai定时处理会话延时 2 viikkoa sitten
  xw 479f2679ea Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  xw 4a3be137bd 积分查询 2 viikkoa sitten
  yfh 5fb3c5e362 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  yfh b2dc00b37a 鹤颜堂红包记录增加营期数据 2 viikkoa sitten
  yuhongqi 7e07388a8e 小程序合并订单,直播数据,订单售后,退回退款,直播数据, 2 viikkoa sitten
  yuhongqi 3a03b69a22 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  wjj 00a1156d90 修改润元堂服务号配置 2 viikkoa sitten
  caoliqin 41ad289ba7 feat:套餐包制单 2 viikkoa sitten
  caoliqin 87e9e68d34 Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java 2 viikkoa sitten
  caoliqin f696d5eda9 feat:商城店铺管理和广告位管理的菜单权限点调整(商城店铺管理之前用的互医的权限点) 2 viikkoa sitten
  三七 c64e0fabbb 红包导出 2 viikkoa sitten
  xgb 7f35ab5386 bug 修复 2 viikkoa sitten
  yh 35dbefca51 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  yh 9ef6b8918a 扫码登录 2 viikkoa sitten
  ct 21045d7145 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  ct 730313d004 update:扫码配置 2 viikkoa sitten
  yfh d96b7d89c3 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  yfh 3afef42629 小节配置快进 2 viikkoa sitten
  yuhongqi aa551091dc Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  ct 5f608a7d91 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  ct ce7fa865b6 update:扫码配置 2 viikkoa sitten
  yuhongqi 4dae24f50c 卓美连接数 2 viikkoa sitten
  yfh 295c8320b5 多商户配置后台,表结构启动的时候自动创建,需要将菜单和表数据配置上 2 viikkoa sitten
  peicj 34587cfcd2 修复叮当国医bug 2 viikkoa sitten
  peicj 8e512adabe Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  ct c969436097 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  ct 5befd17c86 update:积分订单 2 viikkoa sitten
  yuhongqi 3568e2e25d Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  yuhongqi 8aa9e57754 直播名称搜索 2 viikkoa sitten
  lmx 6cb1674c69 北京卓美存在的问题修复 2 viikkoa sitten
  yuhongqi 45857ef651 商城售后订单 2 viikkoa sitten
  yuhongqi 98fc690733 直播售后订单 2 viikkoa sitten
  jzp d34d4bbd75 1.提交ai客服夜间不回复设置 2 viikkoa sitten
  lk 3ddea697d9 企微客户管理所有的标签弹窗搜索功能异常 2 viikkoa sitten
  lmx 7523fc13bd 北京卓美存在的问题修复 2 viikkoa sitten
  lk a217604f25 企微客户--增加批量添加标签(筛选条件),按钮及功能 2 viikkoa sitten
  yh c08fdd75c7 扫码登录 2 viikkoa sitten
  yh accbde8ffd Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  yh 0abc441b6b 扫码登录 2 viikkoa sitten
  xyx c156b28306 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  xyx 2b67615702 企微客户,列表导出加上状态和流失时间 2 viikkoa sitten
  三七 ebbcb19265 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  三七 f3a7a95ccb 总后台-按部门筛选 2 viikkoa sitten
  caoliqin fe1476bf0d Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java 2 viikkoa sitten
  caoliqin b02325c44a feat:配置文件调整 2 viikkoa sitten
  lmx 7ce46cc72c 直播重复登录问题 2 viikkoa sitten
  三七 e6ac13f2fc 总后台-按部门筛选 2 viikkoa sitten
  lmx 8aaa84f210 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  lmx e5a5033c72 直播重复登录问题 2 viikkoa sitten
  yuhongqi 3a465bc421 模板空指针,直播数据,售后条件 2 viikkoa sitten
  yuhongqi 4c07f49889 添加限定条件 2 viikkoa sitten
  yuhongqi 4ea0e8e9d4 直播销售数据添加 2 viikkoa sitten
  yuhongqi 8ae1e8f5d1 Merge remote-tracking branch 'origin/master' 2 viikkoa sitten
  yuhongqi 8ce19e49a4 直播回调 2 viikkoa sitten
  cgp 32bef1f5a7 优化CID接口参数命名 2 viikkoa sitten
  ct 1bb356aea0 update:设置erp推送号码,申请售后判断 2 viikkoa sitten
  peicj 0b073eda5e Merge remote-tracking branch 'origin/master' 3 viikkoa sitten
  peicj 5e8af07d9e 陕西今正需求调整界面 3 viikkoa sitten
100 muutettua tiedostoa jossa 2941 lisäystä ja 423 poistoa
  1. 18 6
      fs-admin/src/main/java/com/fs/course/controller/FsCourseAnswerLogsController.java
  2. 24 0
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  3. 47 17
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java
  4. 27 2
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  5. 6 6
      fs-admin/src/main/java/com/fs/his/controller/FsAdvController.java
  6. 96 17
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  7. 107 0
      fs-admin/src/main/java/com/fs/his/controller/MerchantAppConfigController.java
  8. 50 4
      fs-admin/src/main/java/com/fs/his/task/Task.java
  9. 3 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  10. 9 9
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreScrmController.java
  11. 9 0
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  12. 3 0
      fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java
  13. 4 4
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  14. 18 7
      fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java
  15. 17 6
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  16. 129 7
      fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java
  17. 10 0
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java
  18. 33 0
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  19. 51 4
      fs-company/src/main/java/com/fs/company/controller/company/CompanyLoginController.java
  20. 10 4
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  21. 4 2
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  22. 11 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  23. 10 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwTagGroupController.java
  24. 1 1
      fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java
  25. 284 0
      fs-company/src/main/java/com/fs/framework/service/CompanyLoginService.java
  26. 1 1
      fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java
  27. 207 0
      fs-framework/src/main/java/com/fs/framework/web/service/SysLoginService.java
  28. 3 2
      fs-live-app/src/main/java/com/fs/live/controller/LiveController.java
  29. 18 2
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  30. 49 0
      fs-service/src/main/java/com/fs/common/service/WechatLoginService.java
  31. 10 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  32. 5 4
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  33. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsCoursePlaySourceConfig.java
  34. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCoursePlaySourceConfigMapper.java
  35. 7 7
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  36. 2 2
      fs-service/src/main/java/com/fs/course/param/FsCourseAnswerLogsParam.java
  37. 3 0
      fs-service/src/main/java/com/fs/course/param/FsCoursePlaySourceConfigEditParam.java
  38. 30 23
      fs-service/src/main/java/com/fs/course/param/FsCourseRedPacketLogParam.java
  39. 20 18
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  40. 5 0
      fs-service/src/main/java/com/fs/course/param/PeriodStatisticCountParam.java
  41. 7 0
      fs-service/src/main/java/com/fs/course/service/IFsCoursePlaySourceConfigService.java
  42. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCoursePlaySourceConfigServiceImpl.java
  43. 19 5
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  44. 19 6
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  45. 12 4
      fs-service/src/main/java/com/fs/course/vo/FsCourseRedPacketLogListPVO.java
  46. 5 2
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  47. 5 0
      fs-service/src/main/java/com/fs/course/vo/newfs/FsUserCourseVideoDetailsVO.java
  48. 75 10
      fs-service/src/main/java/com/fs/erp/service/impl/FsJstAftersalePushScrmServiceImpl.java
  49. 1 1
      fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java
  50. 13 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java
  51. 39 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  52. 3 0
      fs-service/src/main/java/com/fs/his/domain/FsPayConfig.java
  53. 66 0
      fs-service/src/main/java/com/fs/his/domain/MerchantAppConfig.java
  54. 6 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralOrderMapper.java
  55. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserWxMapper.java
  56. 74 0
      fs-service/src/main/java/com/fs/his/mapper/MerchantAppConfigMapper.java
  57. 3 0
      fs-service/src/main/java/com/fs/his/param/FsIntegralOrderParam.java
  58. 61 0
      fs-service/src/main/java/com/fs/his/service/IMerchantAppConfigService.java
  59. 1 1
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java
  60. 200 0
      fs-service/src/main/java/com/fs/his/service/impl/MerchantAppConfigServiceImpl.java
  61. 1 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java
  62. 2 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsPayConfigScrm.java
  63. 17 17
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  64. 98 0
      fs-service/src/main/java/com/fs/hisStore/mapper/MergedOrderMapper.java
  65. 24 0
      fs-service/src/main/java/com/fs/hisStore/service/IMergedOrderService.java
  66. 1 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  67. 16 7
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  68. 90 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/MergedOrderServiceImpl.java
  69. 78 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsMergedOrderListQueryVO.java
  70. 8 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java
  71. 10 2
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  72. 1 3
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  73. 19 8
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  74. 1 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  75. 2 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchConfigServiceImpl.java
  76. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  77. 6 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactAddTagParam.java
  78. 8 0
      fs-service/src/main/java/com/fs/qw/param/QwTagParam.java
  79. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwTagGroupService.java
  80. 14 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java
  81. 4 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  82. 4 0
      fs-service/src/main/java/com/fs/sop/domain/QwSop.java
  83. 2 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java
  84. 2 0
      fs-service/src/main/java/com/fs/system/mapper/SysUserMapper.java
  85. 2 0
      fs-service/src/main/java/com/fs/system/service/ISysUserService.java
  86. 4 0
      fs-service/src/main/java/com/fs/system/service/impl/SysUserServiceImpl.java
  87. 131 3
      fs-service/src/main/java/com/fs/wxcid/service/FriendService.java
  88. 21 7
      fs-service/src/main/java/com/fs/wxcid/service/LoginService.java
  89. 5 6
      fs-service/src/main/java/com/fs/wxcid/service/MessageCallbackService.java
  90. 24 4
      fs-service/src/main/java/com/fs/wxcid/service/MessageService.java
  91. 82 22
      fs-service/src/main/java/com/fs/wxcid/service/impl/FriendServiceImpl.java
  92. 73 86
      fs-service/src/main/java/com/fs/wxcid/service/impl/LoginServiceImpl.java
  93. 40 45
      fs-service/src/main/java/com/fs/wxcid/service/impl/MessageCallbackServiceImpl.java
  94. 138 17
      fs-service/src/main/java/com/fs/wxcid/service/impl/MessageServiceImpl.java
  95. 1 1
      fs-service/src/main/resources/application-config-druid-bjzm-test.yml
  96. 8 2
      fs-service/src/main/resources/application-config-druid-cfryt.yml
  97. 1 1
      fs-service/src/main/resources/application-config-druid-gzzdy.yml
  98. 3 3
      fs-service/src/main/resources/application-druid-bjzm.yml
  99. 10 0
      fs-service/src/main/resources/application-druid-jnmy-test.yml
  100. 11 0
      fs-service/src/main/resources/application-druid-jnmy.yml

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

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

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

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

+ 47 - 17
fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java

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

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

@@ -2,8 +2,12 @@ package com.fs.course.controller;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
+import cn.hutool.core.collection.CollectionUtil;
 import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.param.FsCourseOverParam;
@@ -14,6 +18,8 @@ import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.service.IQwWatchLogService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -56,10 +62,29 @@ public class FsCourseWatchLogController extends BaseController
     public TableDataInfo list(FsCourseWatchLogListParam param)
     {
         startPage();
+        if(CollectionUtil.isNotEmpty(param.getUserIds())){
+            param.setUserIds(param.getUserIds().stream()
+                    .filter(userId -> userId != null && userId.startsWith("user_"))
+                    .map(userId -> userId.substring(5))
+                    .collect(Collectors.toList())
+            );
+        }
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         return getDataTable(list);
     }
 
+    /**
+     * 查询短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:pageList')")
+    @PostMapping("/pageList")
+    public R pageList(@RequestBody FsCourseWatchLogListParam param)
+    {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
     @GetMapping("/qwWatchLogAllStatisticsList")
     public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
     {
@@ -114,8 +139,8 @@ public class FsCourseWatchLogController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('course:courseWatchLog:export')")
     @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsCourseWatchLogListParam param)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody FsCourseWatchLogListParam param)
     {
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);

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

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

+ 96 - 17
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -7,6 +7,8 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.CloudHostUtils;
@@ -22,6 +24,7 @@ import com.fs.his.param.BatchSetErpOrderParam;
 import com.fs.his.vo.*;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.*;
+import com.fs.system.service.ISysRoleService;
 import com.fs.utils.OrderContextHolder;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,6 +32,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
+import lombok.extern.slf4j.Slf4j;
 
 import java.text.ParseException;
 import java.time.LocalDateTime;
@@ -45,6 +49,7 @@ import static com.fs.his.utils.PhoneUtil.decryptPhone;
  * @author fs
  * @date 2023-11-02
  */
+@Slf4j
 @RestController
 @RequestMapping("/his/integralOrder")
 public class FsIntegralOrderController extends BaseController
@@ -78,44 +83,118 @@ public class FsIntegralOrderController extends BaseController
     public TableDataInfo list(FsIntegralOrderParam fsIntegralOrder)
     {
         startPage();
+        List<FsIntegralOrderListVO> list = new ArrayList<>();
         if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
             if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
                 fsIntegralOrder.setStatus("1");
                 fsIntegralOrder.setIsPush(0);
             }
-            List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+
+            // 金牛明医项目:支持多个订单ID查询
+//            if (fsIntegralOrder.getOrderCodes() != null && !fsIntegralOrder.getOrderCodes().isEmpty()) {
+//                // 如果传了orderIds参数,使用新的查询逻辑
+//                List<FsIntegralOrder> orders = fsIntegralOrderService.selectFsIntegralOrderByOrderIdsv2(fsIntegralOrder.getOrderCodes());
+//                List<FsIntegralOrderListVO> list = orders.stream()
+//                    .map(this::convertOrderToListVO)
+//                    .collect(Collectors.toList());
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            } else {
+//                // 原有逻辑:单个orderId或其他条件查询
+//                List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            }
+            list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        if (sysRole != null && !(sysRole.getIsCheckPhone()==1)) {
             for (FsIntegralOrderListVO vo : list) {
                 vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
             }
-            return getDataTable(list);
-        }
-        List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
-        for (FsIntegralOrderListVO vo : list) {
-            vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
         }
         return getDataTable(list);
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private SysRole isCheckPermission() {
+        SysRole sysRole = new SysRole();
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            sysRole.setIsCheckPhone(1);
+            sysRole.setIsCheckAddress(1);
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (roles != null && !roles.isEmpty()) {
+                Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+                return sysRoleService.getIsCheckPermission(roleIds);
+            }
+        }
+        return sysRole;
+    }
+
     /**
      * 导出积分商品订单列表
      */
     @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(FsIntegralOrder fsIntegralOrder)
-    {
-        if (CloudHostUtils.hasCloudHostName("金牛明医")&&fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals(6)) {
+    public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
+        List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
-            FsIntegralOrderParam param = new FsIntegralOrderParam();
-            BeanUtil.copyProperties(fsIntegralOrder, param);
-            param.setStatus("1");
-            param.setIsPush(0);
-            List<FsIntegralOrderListVO> fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(param);
-            ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
-            return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
+            if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
+                fsIntegralOrder.setStatus("1");
+                fsIntegralOrder.setIsPush(0);
+            }
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        // 处理商品名称:解析item_json并设置goodsName
+        for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
+            if (StringUtils.isNotEmpty(vo.getItemJson())) {
+                try {
+                    // 尝试解析JSON格式的商品信息
+                    if (vo.getItemJson().startsWith("[")) {
+                        // 数组格式,取第一个商品
+                        com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(vo.getItemJson());
+                        if (jsonArray != null && !jsonArray.isEmpty()) {
+                            com.alibaba.fastjson.JSONObject goods = jsonArray.getJSONObject(0);
+                            if (goods != null && goods.getString("goodsName") != null) {
+                                vo.setGoodsName(goods.getString("goodsName"));
+                            }
+                        }
+                    } else if (vo.getItemJson().startsWith("{")) {
+                        // 对象格式
+                        com.alibaba.fastjson.JSONObject goods = com.alibaba.fastjson.JSONObject.parseObject(vo.getItemJson());
+                        if (goods != null && goods.getString("goodsName") != null) {
+                            vo.setGoodsName(goods.getString("goodsName"));
+                        }
+                    }
+                } catch (Exception e) {
+                    // 解析失败时保持goodsName为空,避免导出出错
+                    log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
+                }
+            }
+            if (!(sysRole.getIsCheckPhone()==1)){
+                // 加密手机号
+                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+            }
+
         }
-        return fsIntegralOrderService.export(fsIntegralOrder);
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
     }
     /**
      * 发货

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

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

+ 50 - 4
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -3,6 +3,7 @@ package com.fs.his.task;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -33,12 +34,10 @@ import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.dto.ErpOrderResponse;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
-import com.fs.fastGpt.domain.FastGptEventTokenLog;
-import com.fs.fastGpt.domain.FastGptPushTokenTotal;
-import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
-import com.fs.fastGpt.domain.FastgptEventLogTotal;
+import com.fs.fastGpt.domain.*;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
 import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.fastGpt.service.AiHookService;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.fastgptApi.util.AudioUtils;
 import com.fs.fastgptApi.vo.AudioVO;
@@ -67,7 +66,9 @@ import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.*;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSopTempVoice;
@@ -225,6 +226,51 @@ public class Task {
     @Autowired
     private FsIntegralOrderMapper fsIntegralOrderMapper;
 
+    private final String DELAY_MSG = "delayMsg";
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private AiHookService aiHookService;
+
+
+    /**
+     * 定时任务,处理ai禁止回复之后的消息
+     */
+    public void forbidTimeMsgTask() {
+        Map<String, Object> cacheMap = redisCache.hGetAll(DELAY_MSG);
+        for (String key : cacheMap.keySet()) {
+            String value = (String) cacheMap.get(key);
+            //获取sessionId
+            Long sessionId = Long.parseLong(key);
+            try {
+                Thread.sleep(800);
+            } catch (Exception e) {
+                log.error("定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
+            }
+            try {
+                if (value != null && !value.isEmpty()) {
+                    FastGptChatSession chatSession = fastGptChatSessionMapper.selectFastGptChatSessionBySessionId(sessionId);
+                    Long qwUserId = chatSession.getQwUserId();
+                    QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+                    String uid = qwUser.getUid();
+
+                    JSONObject jsonObject = JSONObject.parseObject(value);
+                    String content = jsonObject.getString("content");
+                    Long sender = jsonObject.getLong("sender");
+                    Integer type = jsonObject.getInteger("type");
+
+                    aiHookService.qwHookNotifyAiReply(qwUserId,sender,content,uid,type);
+                    //删除已经处理的缓存
+                    redisCache.hDel(DELAY_MSG,key);
+                }
+            } catch (Exception e) {
+                log.error("个人定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
+            }
+        }
+    }
+
     /**
      * sop任务token消耗统计
      */

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

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

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

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

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

@@ -12,6 +12,7 @@ import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
+import com.fs.erp.service.FsJstAftersalePushScrmService;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.dto.ExpressInfoDTO;
@@ -160,6 +161,14 @@ public class LiveTask {
     @Autowired
     private IFsStoreOrderScrmService orderService;
 
+    @Autowired
+    private FsJstAftersalePushScrmService fsJstAftersalePushScrmService;
+
+    // 聚水潭 推送售后信息
+    public void pushJst(){
+        fsJstAftersalePushScrmService.pushJst();
+    }
+
 
     // 订单银行回调数据丢失补偿
     public void recoveryBankOrder() {

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

@@ -143,6 +143,8 @@ public class LiveAfterSalesController extends BaseController
 //                            zmvo.setAfterSalesNumber
                             zmvo.setRefundMoney(vo.getRefundAmount());
                             zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
                         } catch (Exception e) {
                             // 处理异常
                             e.printStackTrace();
@@ -193,6 +195,7 @@ public class LiveAfterSalesController extends BaseController
         logs.setOperator(loginUser.getUser().getNickName());
         logs.setStoreAfterSalesId(liveAfterSales.getId());
         logs.setChangeMessage(FsStoreAfterSalesStatusEnum.STATUS_2.getDesc());
+        liveAfterSales.setStatus(FsStoreAfterSalesStatusEnum.STATUS_2.getValue());
         liveAfterSalesLogsService.insertLiveAfterSalesLogs(logs);
         return toAjax(liveAfterSalesService.updateLiveAfterSales(liveAfterSales));
     }

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

@@ -93,7 +93,7 @@ public class LiveController extends BaseController {
     @PutMapping
     public AjaxResult edit(@RequestBody Live live) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
         return toAjax(liveService.updateLive(live));
     }
 
@@ -105,7 +105,7 @@ public class LiveController extends BaseController {
     @DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
         return toAjax(liveService.deleteLiveByLiveIds(liveIds, new Live()));
     }
 
@@ -130,7 +130,7 @@ public class LiveController extends BaseController {
     @PostMapping("/handleShelfOrUn")
     public R handleShelfOrUn(@RequestBody LiveListVo listVo) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleShelfOrUnAdmin(listVo);
     }
 
@@ -141,7 +141,7 @@ public class LiveController extends BaseController {
     @PostMapping("/handleDeleteSelected")
     public R handleDeleteSelected(@RequestBody LiveListVo listVo) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleDeleteSelectedAdmin(listVo);
     }
     /**

+ 18 - 7
fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java

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

+ 17 - 6
fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java

@@ -22,6 +22,8 @@ import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.service.IQwSopTempService;
 import com.fs.sop.vo.UpdateRedVo;
 import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -51,10 +53,10 @@ public class QwSopTempController extends BaseController
      * 查询sop模板列表
      */
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(QwSopTemp qwSopTemp)
+    @PostMapping("/list")
+    public R list(@RequestBody QwSopTemp qwSopTemp)
     {
-        startPage();
+
 //        List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
         List<QwSopTemp> list = qwSopTempService.selectQwSopTempListNew(qwSopTemp);
         // 收集所有需要查询的用户ID
@@ -85,7 +87,16 @@ public class QwSopTempController extends BaseController
             });
         }
 
-        return getDataTable(list);
+        if(qwSopTemp.getPageNum() == null) {
+            qwSopTemp.setPageNum(1);
+        }
+        if(qwSopTemp.getPageSize() == null) {
+            qwSopTemp.setPageSize(10);
+        }
+
+        PageHelper.startPage(qwSopTemp.getPageNum(), qwSopTemp.getPageSize());
+
+        return R.ok().put("data",new PageInfo<>(list));
     }
 
     /**
@@ -93,8 +104,8 @@ public class QwSopTempController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:export')")
     @Log(title = "sop模板", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(QwSopTemp qwSopTemp)
+    @PostMapping("/export")
+    public AjaxResult export(@RequestBody QwSopTemp qwSopTemp)
     {
         List<QwSopTemp> list = qwSopTempService.selectQwSopTempList(qwSopTemp);
         ExcelUtil<QwSopTemp> util = new ExcelUtil<QwSopTemp>(QwSopTemp.class);

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

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

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

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

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

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

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

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

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

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

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

@@ -60,7 +60,9 @@ public class LiveDataController extends BaseController
     @GetMapping("/getLiveUserDetailListBySql")
     public R getLiveUserDetailListBySql(@RequestParam Long liveId, HttpServletRequest request) {
         CompanyUser user = tokenService.getLoginUser(request).getUser();
-
+        if ("00".equals(user.getUserType())) {
+            return liveDataService.getLiveUserDetailListBySql(liveId,user.getCompanyId(),null);
+        }
         return liveDataService.getLiveUserDetailListBySql(liveId,user.getCompanyId(),user.getUserId());
     }
 
@@ -135,7 +137,7 @@ public class LiveDataController extends BaseController
     @PostMapping("/listLiveData")
     public R listLiveData(@RequestBody LiveDataParam param, HttpServletRequest request)
     {
-        param.setCompanyId(tokenService.getLoginUser(request).getUser().getCompanyId());
+//        param.setCompanyId(tokenService.getLoginUser(request).getUser().getCompanyId());
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
         return liveDataService.listLiveData(param);
     }

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

@@ -7,6 +7,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.DictUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
@@ -370,6 +371,10 @@ public class QwExternalContactController extends BaseController
 
             if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
                 item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
+
+            }
+            if (item.getStatus()!=null){
+                item.setStatusName(DictUtils.getDictLabel("sys_qw_external_contact_status", String.valueOf(item.getStatus())));
             }
         });
         ExcelUtil<QwExternalContactVO> util = new ExcelUtil<QwExternalContactVO>(QwExternalContactVO.class);
@@ -457,7 +462,12 @@ public class QwExternalContactController extends BaseController
     @Log(title = "添加标签", businessType = BusinessType.UPDATE)
     @PostMapping("/addTag")
     public R addTag(@RequestBody QwExternalContactAddTagParam Param) throws JSONException {
-
+        if(Param.isFilter()){
+            Param.setUserIds(getList(Param.getAddType(), Param.getParam()));
+        }
+        if(Param.getUserIds() == null || Param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
         return qwExternalContactService.addUserTag(Param);
     }
 

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

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

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

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

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

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

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

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

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

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

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

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

+ 18 - 2
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -157,7 +157,7 @@ public class WebSocketServer {
                 sendMsgVo.setNickName(fsUser.getNickname());
                 sendMsgVo.setAvatar(fsUser.getAvatar());
                 // 广播连接消息
-                broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+                broadcastWebMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
             }
 
             LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, userId);
@@ -242,7 +242,7 @@ public class WebSocketServer {
                 sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
                 sendMsgVo.setNickName(fsUser.getNickname());
                 sendMsgVo.setAvatar(fsUser.getAvatar());
-                broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+                broadcastWebMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
             }
 
         } else {
@@ -588,6 +588,22 @@ public class WebSocketServer {
         session.getAsyncRemote().sendText(JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
     }
 
+    /**
+     * 广播消息
+     * @param liveId   直播间ID
+     * @param message  消息内容
+     */
+    public void broadcastWebMessage(Long liveId, String message) {
+        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
+
+        // 普通用户房间:并行发送
+        room.forEach((k, v) -> {
+            if (v.isOpen()) {
+                sendWithRetry(v,message,1);
+            }
+        });
+    }
+
     /**
      * 广播消息
      * @param liveId   直播间ID

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

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

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

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

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

@@ -202,10 +202,11 @@ public class CompanyServiceImpl implements ICompanyService
             List<CompanyMiniapp> miniApp = companyMiniappService.getMiniAppListByCompanyList(Collections.singletonList(company.getCompanyId()));
             company.setMiniAppMaster(GET_MINI_APP_STR.apply(0, miniApp));
             company.setMiniAppServer(GET_MINI_APP_STR.apply(1, miniApp));
-        }
-        String redPackageMoney = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY+company.getCompanyId());
-        if(redPackageMoney!=null){
-            company.setRedPackageMoney(new BigDecimal(redPackageMoney));
+
+            String redPackageMoney = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY+company.getCompanyId());
+            if(redPackageMoney!=null){
+                company.setRedPackageMoney(new BigDecimal(redPackageMoney));
+            }
         }
         return company;
     }

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

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

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

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

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

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

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

@@ -36,8 +36,8 @@ public class FsCourseAnswerLogsParam  extends BaseEntity  {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date sTime;
 
-    private Long pageNum;
-    private Long pageSize;
+    private Integer pageNum;
+    private Integer pageSize;
 
     /**
      * 用户(昵称_id组合)

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

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

+ 30 - 23
fs-service/src/main/java/com/fs/course/param/FsCourseRedPacketLogParam.java

@@ -1,15 +1,15 @@
 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.io.Serializable;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Data
-public class FsCourseRedPacketLogParam {
+public class FsCourseRedPacketLogParam implements Serializable {
 
     private Long userId;
 
@@ -23,6 +23,11 @@ public class FsCourseRedPacketLogParam {
 
     private Long videoId;
 
+    /**
+     * 营期id
+     */
+    private Long periodId;
+
     private Long status;
 
     private String phone;
@@ -38,29 +43,31 @@ public class FsCourseRedPacketLogParam {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date sTime;
 
-    @TableField(exist = false)
-    private List<String> companyUserIds=new ArrayList<>();
+    private Integer pageNum;
+    private Integer pageSize;
 
+    private List<String> userIds;
 
-    public List<String> getCompanyUserIds() {
-        if (companyUserIds == null || companyUserIds.isEmpty()) {
-            return companyUserIds;
+    public List<String> getUserIds() {
+        if (userIds == null || userIds.isEmpty()) {
+            return userIds;
         }
 
-        // 直接在原始列表上修改
-        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;
+        return userIds.stream()
+                .map(id -> {
+                    if (id == null) {
+                        return null;
+                    }
+                    // 去除前缀
+                    if (id.startsWith("dept_")) {
+                        return "0";
+                    } else if (id.startsWith("company_")) {
+                        return "0";
+                    } else if (id.startsWith("user_")) {
+                        return id.substring(5);
+                    }
+                    return "0";
+                })
+                .collect(Collectors.toList());
     }
-
 }

+ 20 - 18
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java

@@ -1,13 +1,12 @@
 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;
+import java.util.stream.Collectors;
 
 @Data
 public class FsCourseWatchLogListParam implements Serializable {
@@ -110,27 +109,30 @@ public class FsCourseWatchLogListParam implements Serializable {
     private List<Long> deptIds;
     private String ids;
 
-    @TableField(exist = false)
-    private List<String> userIds = new ArrayList<>();
+    private Integer pageNum;
+    private Integer pageSize;
+    private List<String> userIds;
 
     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;
+        return userIds.stream()
+                .map(id -> {
+                    if (id == null) {
+                        return null;
+                    }
+                    // 去除前缀
+                    if (id.startsWith("dept_")) {
+                        return "0";
+                    } else if (id.startsWith("company_")) {
+                        return "0";
+                    } else if (id.startsWith("user_")) {
+                        return id.substring(5);
+                    }
+                    return "0";
+                })
+                .collect(Collectors.toList());
     }
 }

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

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

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

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

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

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

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

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

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

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

+ 12 - 4
fs-service/src/main/java/com/fs/course/vo/FsCourseRedPacketLogListPVO.java

@@ -23,7 +23,7 @@ public class FsCourseRedPacketLogListPVO extends BaseEntity
 
     @Excel(name = "会员id")
     private Long userId;
-
+    @Excel(name = "会员名称")
     private String fsNickName;
 
     private String fsAvatar;
@@ -49,8 +49,8 @@ public class FsCourseRedPacketLogListPVO extends BaseEntity
     @Excel(name = "转帐金额")
     private BigDecimal amount;
 
-    @Excel(name = "状态",dictType = "sys_course_red_packet_status")
-    private Integer status;//状态 0 发送中  1  已发送
+    @Excel(name = "状态", readConverterExp = "0=发送中,1=已发送,2余额不足待发送")
+    private Integer status; // 状态 0 发送中 1 已发送
 
     @Excel(name = "分享人企微Id")
     private String qwUserId;
@@ -72,7 +72,13 @@ public class FsCourseRedPacketLogListPVO extends BaseEntity
 
 
     @Excel(name = "发送方式")
-    private Integer sendType; //归属发送方式:1 个微  2 企微
+    private Integer sendType;
+
+//    @Excel(name = "营期id")
+    private Long periodId;
+
+    @Excel(name = "营期名称")
+    private String periodName;
 
 
     /** 创建时间 */
@@ -95,4 +101,6 @@ public class FsCourseRedPacketLogListPVO extends BaseEntity
     @Excel(name = "播放时长")
     private String duration;
 
+    private Long deptId;
+    private Long companyId;
 }

+ 5 - 2
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java

@@ -60,7 +60,7 @@ public class FsCourseWatchLogListVO extends BaseEntity
     @Excel(name = "小节名称")
     private String videoName;
 
-    @Excel(name = "记录类型" ,dictType = "sys_course_watch_log_type")
+    @Excel(name = "记录类型" ,dictType = "sys_course_watch_log_type_new")
     private Integer logType;
 
     @Excel(name = "奖励类型 1红包 2积分")
@@ -85,6 +85,9 @@ public class FsCourseWatchLogListVO extends BaseEntity
     @Excel(name = "所属销售")
     private String companyUserName;
 
+    @Excel(name = "销售部门")
+    private String deptName;
+
     @Excel(name = "所属sop任务")
     private String sopId;
 
@@ -137,5 +140,5 @@ public class FsCourseWatchLogListVO extends BaseEntity
      */
     private Long imMsgSendDetailId;
 
-
+    private Long deptId;
 }

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

@@ -31,6 +31,11 @@ public class FsUserCourseVideoDetailsVO {
     @ApiModelProperty(value = "课程ID")
     private Long courseId;
 
+
+    @ApiModelProperty(value = "是否启用倍速(0:否;1:是)")
+    private Integer isSpeed;
+
+
     @ApiModelProperty(value = "题库内容")
     private List<FsUserVideoQuestionVO> questionBankList;
 

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

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

+ 1 - 1
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java

@@ -878,7 +878,7 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
         log.info("订单号: {},发货状态: {},是否发货后: {}",liveOrder.getOrderCode(),liveOrder.getStatus(),ObjectUtils.equals(liveOrder.getStatus(),2));
 
         // 发货后退款
-        if(ObjectUtils.equals(param.getOrderStatus(),2)){
+        if(ObjectUtils.equals(param.getOrderStatus(),2) || ObjectUtils.equals(param.getOrderStatus(),3)){
 
             FsJstAftersalePush fsJstAftersalePush = new FsJstAftersalePush();
             fsJstAftersalePush.setOrderId(liveOrder.getOrderCode());

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

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

+ 39 - 1
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

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

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

@@ -15,6 +15,9 @@ public class FsPayConfig {
     private String appId;
     private String wxMchId;
     private String wxMchKey;
+    private String keyPath;
+    private String wxApiV3Key;
+    private String notifyUrlScrm;
 
     private String ybNotifyUrl;
     private String tzPayDecrypt;

+ 66 - 0
fs-service/src/main/java/com/fs/his/domain/MerchantAppConfig.java

@@ -0,0 +1,66 @@
+package com.fs.his.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+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;
+
+/**
+ * 商户应用配置对象 merchant_app_config
+ *
+ * @author fs
+ * @date 2025-12-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MerchantAppConfig extends BaseEntity{
+
+    /** 主键ID */
+    private Long id;
+
+    /** 商户类型 */
+    @Excel(name = "商户类型")
+    private String merchantType;
+
+    // 应用ID
+    @Excel(name = "应用ID")
+    private String appId;
+
+    /** 回调地址,用于接收支付结果等通知 */
+    @Excel(name = "回调地址,用于接收支付结果等通知")
+    private String callbackUrl;
+
+    /** 配置详情 */
+    @Excel(name = "配置详情")
+    private String dataJson;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createdTime;
+
+    /** 修改时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "修改时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date updatedTime;
+
+    /** 删除状态:0-正常,1-已删除 */
+    @Excel(name = "删除状态:0-正常,1-已删除")
+    private Long isDeleted;
+
+    /** 创建人ID或用户名 */
+    private String createdBy;
+
+    /** 修改人ID或用户名 */
+    private String updatedBy;
+
+    /**
+     * 商户号
+     */
+    private String merchantId;
+
+
+}

+ 6 - 0
fs-service/src/main/java/com/fs/his/mapper/FsIntegralOrderMapper.java

@@ -71,6 +71,12 @@ public interface FsIntegralOrderMapper extends BaseMapper<FsIntegralOrder>
     @Select({"<script> select order_id, order_code, user_id, user_name, user_phone, user_address, item_json, integral,pay_money,is_pay,pay_time,pay_type, status, delivery_code, delivery_name, delivery_sn, delivery_time, create_time,qw_user_id,company_user_id,company_id, remark from fs_integral_order " +
             "<where>  \n" +
             "            <if test=\"orderCode != null  and orderCode != ''\"> and order_code = #{orderCode}</if>\n" +
+            "            <if test=\"orderCodes != null and orderCodes.size > 0\"> " +
+            "                and order_code in " +
+            "                <foreach collection='orderCodes' item='orderCode' open='(' close=')' separator=','> " +
+            "                    #{orderCode} " +
+            "                </foreach> " +
+            "            </if>\n" +
             "            <if test=\"userName != null  and userName != ''\"> and user_name like concat('%', #{userName}, '%')</if>\n" +
             "            <if test=\"userPhone != null  and userPhone != ''\"> and user_phone = #{userPhone}</if>\n" +
             "            <if test=\"integral != null  and integral != ''\"> and integral = #{integral}</if>\n" +

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

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

+ 74 - 0
fs-service/src/main/java/com/fs/his/mapper/MerchantAppConfigMapper.java

@@ -0,0 +1,74 @@
+package com.fs.his.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.MerchantAppConfig;
+
+/**
+ * 商户应用配置Mapper接口
+ *
+ * @author fs
+ * @date 2025-12-05
+ */
+public interface MerchantAppConfigMapper extends BaseMapper<MerchantAppConfig>{
+
+
+    /**
+     * 检查表是否存在
+     */
+    Integer checkTableExists();
+
+    /**
+     * 创建商户配置表
+     */
+    void createMerchantAppConfigTable();
+
+
+    /**
+     * 查询商户应用配置
+     *
+     * @param id 商户应用配置主键
+     * @return 商户应用配置
+     */
+    MerchantAppConfig selectMerchantAppConfigById(Long id);
+
+    /**
+     * 查询商户应用配置列表
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 商户应用配置集合
+     */
+    List<MerchantAppConfig> selectMerchantAppConfigList(MerchantAppConfig merchantAppConfig);
+
+    /**
+     * 新增商户应用配置
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 结果
+     */
+    int insertMerchantAppConfig(MerchantAppConfig merchantAppConfig);
+
+    /**
+     * 修改商户应用配置
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 结果
+     */
+    int updateMerchantAppConfig(MerchantAppConfig merchantAppConfig);
+
+    /**
+     * 删除商户应用配置
+     *
+     * @param id 商户应用配置主键
+     * @return 结果
+     */
+    int deleteMerchantAppConfigById(Long id);
+
+    /**
+     * 批量删除商户应用配置
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteMerchantAppConfigByIds(Long[] ids);
+}

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

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

+ 61 - 0
fs-service/src/main/java/com/fs/his/service/IMerchantAppConfigService.java

@@ -0,0 +1,61 @@
+package com.fs.his.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.MerchantAppConfig;
+
+/**
+ * 商户应用配置Service接口
+ *
+ * @author fs
+ * @date 2025-12-05
+ */
+public interface IMerchantAppConfigService extends IService<MerchantAppConfig>{
+    /**
+     * 查询商户应用配置
+     *
+     * @param id 商户应用配置主键
+     * @return 商户应用配置
+     */
+    MerchantAppConfig selectMerchantAppConfigById(Long id);
+
+    /**
+     * 查询商户应用配置列表
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 商户应用配置集合
+     */
+    List<MerchantAppConfig> selectMerchantAppConfigList(MerchantAppConfig merchantAppConfig);
+
+    /**
+     * 新增商户应用配置
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 结果
+     */
+    int insertMerchantAppConfig(MerchantAppConfig merchantAppConfig);
+
+    /**
+     * 修改商户应用配置
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 结果
+     */
+    int updateMerchantAppConfig(MerchantAppConfig merchantAppConfig);
+
+    /**
+     * 批量删除商户应用配置
+     *
+     * @param ids 需要删除的商户应用配置主键集合
+     * @return 结果
+     */
+    int deleteMerchantAppConfigByIds(Long[] ids);
+
+    /**
+     * 删除商户应用配置信息
+     *
+     * @param id 商户应用配置主键
+     * @return 结果
+     */
+    int deleteMerchantAppConfigById(Long id);
+}

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

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

+ 200 - 0
fs-service/src/main/java/com/fs/his/service/impl/MerchantAppConfigServiceImpl.java

@@ -0,0 +1,200 @@
+package com.fs.his.service.impl;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.entity.SysDictType;
+import com.fs.his.domain.FsPayConfig;
+import com.fs.his.domain.MerchantAppConfig;
+import com.fs.his.mapper.MerchantAppConfigMapper;
+import com.fs.his.service.IMerchantAppConfigService;
+import com.fs.hisStore.domain.FsPayConfigScrm;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
+import com.fs.system.mapper.SysDictTypeMapper;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 商户应用配置Service业务层处理
+ *
+ * @author fs
+ * @date 2025-12-05
+ */
+@Service
+@Slf4j
+public class MerchantAppConfigServiceImpl extends ServiceImpl<MerchantAppConfigMapper, MerchantAppConfig> implements IMerchantAppConfigService {
+
+    @Autowired
+    private SysConfigMapper sysConfigMapper;
+
+    /**
+     * 异步初始化方法
+     */
+    @PostConstruct
+    @Async("merchantInitExecutor")
+    public void init() {
+        log.info("开始异步初始化商户配置表...");
+
+        // 使用CompletableFuture进行异步初始化
+        CompletableFuture.runAsync(() -> {
+            try {
+                // 延迟5秒,等待数据库连接就绪
+                Thread.sleep(5000);
+                Integer count = baseMapper.checkTableExists();
+                if (ObjectUtil.isNotNull(count)&&count>0) {
+                    return;
+                }
+                // 1. 检查并创建表
+                initMerchantTable();
+
+            } catch (Exception e) {
+                log.error("初始化商户配置表失败", e);
+            }
+        });
+    }
+
+    /**
+     * 初始化商户配置表
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void initMerchantTable() {
+        try {
+            // 检查表是否存在
+            log.info("商户配置表不存在,开始创建...");
+            baseMapper.createMerchantAppConfigTable();
+            log.info("商户配置表创建成功");
+        } catch (Exception e) {
+            log.error("初始化商户配置表失败", e);
+            throw e;
+        }
+    }
+    /**
+     * 查询商户应用配置
+     *
+     * @param id 商户应用配置主键
+     * @return 商户应用配置
+     */
+    @Override
+    public MerchantAppConfig selectMerchantAppConfigById(Long id)
+    {
+        return baseMapper.selectMerchantAppConfigById(id);
+    }
+
+    /**
+     * 查询商户应用配置列表
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 商户应用配置
+     */
+    @Override
+    public List<MerchantAppConfig> selectMerchantAppConfigList(MerchantAppConfig merchantAppConfig)
+    {
+        return baseMapper.selectMerchantAppConfigList(merchantAppConfig);
+    }
+
+    /**
+     * 新增商户应用配置
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 结果
+     */
+    @Override
+    public int insertMerchantAppConfig(MerchantAppConfig merchantAppConfig)
+    {
+        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+        switch (merchantAppConfig.getMerchantType()){
+            case "yb": // 易宝
+                merchantAppConfig.setMerchantId(fsPayConfig.getYbAccount());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getYbNotifyUrl());
+
+                break;
+            case "tz": // 台州
+                merchantAppConfig.setMerchantId(fsPayConfig.getTzPlatMerCstNo());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getTzPayDecrypt());
+                break;
+            case "wx": // 微信
+                merchantAppConfig.setMerchantId(fsPayConfig.getWxMchId());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getNotifyUrlScrm());
+                break;
+            case "hf": // 汇付
+                merchantAppConfig.setMerchantId(fsPayConfig.getHuifuId());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getHfPayNotifyUrl());
+                break;
+            default:
+                throw new RuntimeException("商户类型错误");
+        }
+
+        return baseMapper.insertMerchantAppConfig(merchantAppConfig);
+    }
+
+    /**
+     * 修改商户应用配置
+     *
+     * @param merchantAppConfig 商户应用配置
+     * @return 结果
+     */
+    @Override
+    public int updateMerchantAppConfig(MerchantAppConfig merchantAppConfig)
+    {
+        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+        switch (merchantAppConfig.getMerchantType()){
+            case "yb": // 易宝
+                merchantAppConfig.setMerchantId(fsPayConfig.getYbAccount());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getYbNotifyUrl());
+
+                break;
+            case "tz": // 台州
+                merchantAppConfig.setMerchantId(fsPayConfig.getTzPlatMerCstNo());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getTzPayDecrypt());
+                break;
+            case "wx": // 微信
+                merchantAppConfig.setMerchantId(fsPayConfig.getWxMchId());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getNotifyUrlScrm());
+                break;
+            case "hf": // 汇付
+                merchantAppConfig.setMerchantId(fsPayConfig.getHuifuId());
+                merchantAppConfig.setCallbackUrl(fsPayConfig.getHfPayNotifyUrl());
+                break;
+            default:
+                throw new RuntimeException("商户类型错误");
+        }
+        return baseMapper.updateMerchantAppConfig(merchantAppConfig);
+    }
+
+    /**
+     * 批量删除商户应用配置
+     *
+     * @param ids 需要删除的商户应用配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteMerchantAppConfigByIds(Long[] ids)
+    {
+        return baseMapper.deleteMerchantAppConfigByIds(ids);
+    }
+
+    /**
+     * 删除商户应用配置信息
+     *
+     * @param id 商户应用配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteMerchantAppConfigById(Long id)
+    {
+        MerchantAppConfig merchantAppConfig = new MerchantAppConfig();
+        merchantAppConfig.setId( id);
+        merchantAppConfig.setIsDeleted(1L);
+        return baseMapper.updateMerchantAppConfig(merchantAppConfig);
+    }
+}

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

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

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

@@ -35,4 +35,6 @@ public class FsPayConfigScrm {
     private String notifyUrlScrm;
     private String publicKeyPath;
     private String publicKeyId;
+
+
 }

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

@@ -75,8 +75,8 @@ public interface FsStoreProductScrmMapper
      */
     public int deleteFsStoreProductByIds(Long[] productIds);
     @Select({"<script> " +
-            "select p.*,pc.cate_name, fs_store.store_name from fs_store_product_scrm p left join fs_store_product_category_scrm pc on p.cate_id=pc.cate_id  " +
-            "left join fs_store on fs_store.store_id = p.store_id " +
+            "select p.*,pc.cate_name, fs.store_name from fs_store_product_scrm p left join fs_store_product_category_scrm pc on p.cate_id=pc.cate_id  " +
+            "left join fs_store fs on fs.store_id = p.store_id " +
             "where 1=1 " +
             "<if test = 'maps.productName != null and  maps.productName !=\"\"    '> " +
             "and p.product_name like CONCAT('%',#{maps.productName},'%') " +
@@ -97,53 +97,53 @@ public interface FsStoreProductScrmMapper
             "and p.store_id = #{maps.storeId} " +
             "</if>"+
             "<if test = 'maps.storeIds != null '>" +
-            "and store_id in " +
+            "and p.store_id in " +
             "<foreach collection='maps.storeIds'  item='item' index='index'  open='(' separator=',' close=')'> #{item} </foreach>" +
             "</if>" +
             "<if test = 'maps.isAudit != null '> " +
             "and p.is_audit = #{maps.isAudit} " +
             "</if>" +
             "<if test='maps.drugRegCertNo != null and maps.drugRegCertNo != \"\"'>" +
-            "            AND drug_reg_cert_no LIKE CONCAT('%', #{maps.drugRegCertNo}, '%')" +
+            "    AND p.drug_reg_cert_no LIKE CONCAT('%', #{maps.drugRegCertNo}, '%')" +
             "</if>" +
             "<if test='maps.commonName != null and maps.commonName != \"\"'>" +
-            "    AND common_name LIKE CONCAT('%', #{maps.commonName}, '%')" +
+            "    AND p.common_name LIKE CONCAT('%', #{maps.commonName}, '%')" +
             "</if>" +
             "<if test='maps.dosageForm != null and maps.dosageForm != \"\"'>" +
-            "    AND dosage_form LIKE CONCAT('%', #{maps.dosageForm}, '%')" +
+            "    AND p.dosage_form LIKE CONCAT('%', #{maps.dosageForm}, '%')" +
             "</if>" +
             "<if test='maps.unitPrice != null and maps.unitPrice != \"\"'>" +
-            "    AND unit_price = #{maps.unitPrice}" +
+            "    AND p.unit_price = #{maps.unitPrice}" +
             "</if>" +
             "<if test='maps.batchNumber != null and maps.batchNumber != \"\"'>" +
-            "    AND batch_number LIKE CONCAT('%', #{maps.batchNumber}, '%')" +
+            "    AND p.batch_number LIKE CONCAT('%', #{maps.batchNumber}, '%')" +
             "</if>" +
             "<if test='maps.mah != null and maps.mah != \"\"'>" +
-            "    AND mah LIKE CONCAT('%', #{maps.mah}, '%')" +
+            "    AND p.mah LIKE CONCAT('%', #{maps.mah}, '%')" +
             "</if>" +
             "<if test='maps.mahAddress != null and maps.mahAddress != \"\"'>" +
-            "    AND mah_address LIKE CONCAT('%', #{maps.mahAddress}, '%')" +
+            "    AND p.mah_address LIKE CONCAT('%', #{maps.mahAddress}, '%')" +
             "</if>" +
             "<if test='maps.manufacturer != null and maps.manufacturer != \"\"'>" +
-            "    AND manufacturer LIKE CONCAT('%', #{maps.manufacturer}, '%')" +
+            "    AND p.manufacturer LIKE CONCAT('%', #{maps.manufacturer}, '%')" +
             "</if>" +
             "<if test='maps.manufacturerAddress != null and maps.manufacturerAddress != \"\"'>" +
-            "    AND manufacturer_address LIKE CONCAT('%', #{maps.manufacturerAddress}, '%')" +
+            "    AND p.manufacturer_address LIKE CONCAT('%', #{maps.manufacturerAddress}, '%')" +
             " </if>" +
             " <if test='maps.indications != null and maps.indications != \"\"'>" +
-            "     AND indications LIKE CONCAT('%', #{maps.indications}, '%')" +
+            "     AND p.indications LIKE CONCAT('%', #{maps.indications}, '%')" +
             " </if>" +
             " <if test='maps.dosage != null and maps.dosage != \"\"'>" +
-            "     AND dosage LIKE CONCAT('%', #{maps.dosage}, '%')" +
+            "     AND p.dosage LIKE CONCAT('%', #{maps.dosage}, '%')" +
             " </if>" +
             " <if test='maps.adverseReactions != null and maps.adverseReactions != \"\"'>" +
-            "     AND adverse_reactions LIKE CONCAT('%', #{maps.adverseReactions}, '%')" +
+            "     AND p.adverse_reactions LIKE CONCAT('%', #{maps.adverseReactions}, '%')" +
             " </if>" +
             " <if test='maps.contraindications != null and maps.contraindications != \"\"'>" +
-            "     AND contraindications LIKE CONCAT('%', #{maps.contraindications}, '%')" +
+            "     AND p.contraindications LIKE CONCAT('%', #{maps.contraindications}, '%')" +
             " </if>" +
             " <if test='maps.precautions != null and maps.precautions != \"\"'>" +
-            "     AND precautions LIKE CONCAT('%', #{maps.precautions}, '%')" +
+            "     AND p.precautions LIKE CONCAT('%', #{maps.precautions}, '%')" +
             " </if>"+
             " order by p.product_id desc "+
             "</script>"})

+ 98 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/MergedOrderMapper.java

@@ -0,0 +1,98 @@
+package com.fs.hisStore.mapper;
+
+import com.fs.hisStore.param.FsMyStoreOrderQueryParam;
+import com.fs.hisStore.vo.FsMergedOrderListQueryVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 合并订单Mapper接口
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+public interface MergedOrderMapper
+{
+    /**
+     * 查询合并的订单列表(商城订单+直播订单)
+     *
+     * @param param 查询参数
+     * @return 合并后的订单列表
+     */
+    @Select({"<script> " +
+            "SELECT * FROM ( " +
+            "  SELECT " +
+            "    o.id, " +
+            "    NULL AS order_id, " +
+            "    NULL AS live_id, " +
+            "    NULL AS after_sales_id, " +
+            "    o.order_code, " +
+            "    o.pay_price, " +
+            "    o.status, " +
+            "    o.is_package, " +
+            "    o.package_json, " +
+            "    o.item_json, " +
+            "    o.delivery_id, " +
+            "    o.finish_time, " +
+            "    o.create_time, " +
+            "    NULL AS total_num, " +
+            "    NULL AS discount_money, " +
+            "    1 AS order_type " +
+            "  FROM fs_store_order_scrm o " +
+            "  WHERE o.is_del = 0 AND o.is_sys_del = 0 " +
+            "  <if test = 'maps.status != null and maps.status != \"\"'> " +
+            "    AND o.status = #{maps.status} " +
+            "  </if> " +
+            "  <if test = 'maps.keyword != null and maps.keyword != \"\"'> " +
+            "    AND o.order_code LIKE CONCAT('%', #{maps.keyword}, '%') " +
+            "  </if> " +
+            "  <if test = 'maps.deliveryStatus != null'> " +
+            "    AND o.delivery_status = #{maps.deliveryStatus} " +
+            "  </if> " +
+            "  <if test = 'maps.userId != null'> " +
+            "    AND o.user_id = #{maps.userId} " +
+            "  </if> " +
+            "  UNION ALL " +
+            "  SELECT " +
+            "    NULL AS id, " +
+            "    o.order_id, " +
+            "    o.live_id, " +
+            "    a.id AS after_sales_id, " +
+            "    o.order_code, " +
+            "    o.pay_price, " +
+            "    o.status, " +
+            "    NULL AS is_package, " +
+            "    NULL AS package_json, " +
+            "    o.item_json, " +
+            "    o.delivery_sn AS delivery_id, " +
+            "    o.finish_time, " +
+            "    o.create_time, " +
+            "    o.total_num, " +
+            "    o.discount_money, " +
+            "    2 AS order_type " +
+            "  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} " +
+            "  </if> " +
+            "  <if test = 'maps.keyword != null and maps.keyword != \"\"'> " +
+            "    AND o.order_code LIKE CONCAT('%', #{maps.keyword}, '%') " +
+            "  </if> " +
+            "  <if test = 'maps.deliveryStatus != null'> " +
+            "    AND o.delivery_status = #{maps.deliveryStatus} " +
+            "  </if> " +
+            "  <if test = 'maps.userId != null'> " +
+            "    AND o.user_id = #{maps.userId} " +
+            "  </if> " +
+            ") AS merged_orders " +
+            "ORDER BY create_time DESC " +
+            "</script>"})
+    List<FsMergedOrderListQueryVO> selectMergedOrderListVO(@Param("maps") FsMyStoreOrderQueryParam param);
+}
+

+ 24 - 0
fs-service/src/main/java/com/fs/hisStore/service/IMergedOrderService.java

@@ -0,0 +1,24 @@
+package com.fs.hisStore.service;
+
+import com.fs.hisStore.param.FsMyStoreOrderQueryParam;
+import com.fs.hisStore.vo.FsMergedOrderListQueryVO;
+
+import java.util.List;
+
+/**
+ * 合并订单Service接口
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+public interface IMergedOrderService
+{
+    /**
+     * 查询合并的订单列表(商城订单+直播订单)
+     *
+     * @param param 查询参数
+     * @return 合并后的订单列表
+     */
+    List<FsMergedOrderListQueryVO> selectMergedOrderListVO(FsMyStoreOrderQueryParam param);
+}
+

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

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

+ 16 - 7
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -2239,11 +2239,19 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
         erpOrder.setDetails(details);
         erpOrder.setReceiver_name(order.getRealName());
-        if (order.getUserPhone().length() > 11) {
-            erpOrder.setReceiver_phone(order.getUserPhone());
+        //2025.6.27 金牛要求erp推送电话可以设置默认 不影响其他推送
+        String phone = null;
+        if (CloudHostUtils.hasCloudHostName("康年堂") && StringUtils.isNotBlank(order.getErpPhone())) {
+            phone = order.getErpPhone();
         } else {
-            erpOrder.setReceiver_mobile(order.getUserPhone());
+            phone = order.getUserPhone();
         }
+        if (phone.length() > 11) {
+            erpOrder.setReceiver_phone(phone);
+        } else {
+            erpOrder.setReceiver_mobile(phone);
+        }
+
         String[] address = order.getUserAddress().split(" ");
         erpOrder.setReceiver_province(address[0]);
         erpOrder.setReceiver_city(address[1]);
@@ -2386,6 +2394,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
+            request.setOrderStatus(order.getStatus());
             //BaseResponse response=erpOrderService.refundUpdate(request);
 //            if(response.getSuccess()){
 //            }
@@ -5239,10 +5248,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         String uuid = IdUtil.randomUUID();
         redisCache.setCacheObject("createOrderKey:" + uuid, companyUser.getCompanyId() + "-" + companyUser.getUserId(), 24, TimeUnit.HOURS);
 
-        // 这里的carts是购物车信息,价格取的套餐包价格
-        for (FsStoreCartQueryVO vo : carts) {
-            vo.setPrice(storeProductPackage.getPayMoney());
-        }
+//        // 这里的carts是购物车信息,价格取的套餐包价格
+//        for (FsStoreCartQueryVO vo : carts) {
+//            vo.setPrice(storeProductPackage.getPayMoney());
+//        }
         redisCache.setCacheObject("orderCarts:" + uuid, carts, 24, TimeUnit.HOURS);
         if (orderType != null || orderMedium != null) {
             FsStoreOrderScrm fsStoreOrder = new FsStoreOrderScrm();

+ 90 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/MergedOrderServiceImpl.java

@@ -0,0 +1,90 @@
+package com.fs.hisStore.service.impl;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONUtil;
+import com.fs.common.utils.StringUtils;
+import com.fs.hisStore.enums.OrderInfoEnum;
+import com.fs.hisStore.mapper.MergedOrderMapper;
+import com.fs.hisStore.param.FsMyStoreOrderQueryParam;
+import com.fs.hisStore.service.IMergedOrderService;
+import com.fs.hisStore.vo.FsMergedOrderListQueryVO;
+import com.fs.hisStore.vo.FsStoreOrderItemVO;
+import com.fs.store.config.StoreConfig;
+import com.fs.system.service.ISysConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * 合并订单Service实现类
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+@Service
+public class MergedOrderServiceImpl implements IMergedOrderService
+{
+    @Autowired
+    private MergedOrderMapper mergedOrderMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Override
+    public List<FsMergedOrderListQueryVO> selectMergedOrderListVO(FsMyStoreOrderQueryParam param)
+    {
+        List<FsMergedOrderListQueryVO> list = mergedOrderMapper.selectMergedOrderListVO(param);
+        
+        for (FsMergedOrderListQueryVO vo : list)
+        {
+            // 处理商品JSON
+            if (StringUtils.isNotEmpty(vo.getItemJson()))
+            {
+                JSONArray jsonArray = JSONUtil.parseArray(vo.getItemJson());
+                List<FsStoreOrderItemVO> items = JSONUtil.toList(jsonArray, FsStoreOrderItemVO.class);
+                if (items != null && items.size() > 0)
+                {
+                    vo.setItems(items);
+                }
+            }
+            
+            // 处理是否可以申请售后
+            vo.setIsAfterSales(0);
+            if (vo.getStatus() != null && vo.getStatus().equals(OrderInfoEnum.STATUS_3.getValue()))
+            {
+                // 已完成订单
+                vo.setIsAfterSales(1);
+                if (vo.getFinishTime() != null)
+                {
+                    String json = configService.selectConfigByKey("his.store");
+                    if (StringUtils.isNotEmpty(json))
+                    {
+                        StoreConfig storeConfig = JSONUtil.toBean(json, StoreConfig.class);
+                        if (storeConfig != null && storeConfig.getStoreAfterSalesDay() != null && storeConfig.getStoreAfterSalesDay() > 0)
+                        {
+                            // 判断完成时间是否超过指定时间
+                            Calendar calendar = new GregorianCalendar();
+                            calendar.setTime(vo.getFinishTime());
+                            calendar.add(Calendar.DATE, storeConfig.getStoreAfterSalesDay());
+                            if (calendar.getTime().getTime() < new Date().getTime())
+                            {
+                                vo.setIsAfterSales(0);
+                            }
+                        }
+                    }
+                }
+            }
+            else if (vo.getStatus() != null && (vo.getStatus() == 1 || vo.getStatus() == 2))
+            {
+                vo.setIsAfterSales(1);
+            }
+        }
+        
+        return list;
+    }
+}
+

+ 78 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsMergedOrderListQueryVO.java

@@ -0,0 +1,78 @@
+package com.fs.hisStore.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 合并订单列表查询VO(商城订单+直播订单)
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+@Data
+public class FsMergedOrderListQueryVO implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 订单ID */
+    private Long id;
+
+    /** 订单ID(直播订单使用) */
+    private Long orderId;
+
+    /** 直播ID(直播订单使用) */
+    private Long liveId;
+
+    /** 售后ID(直播订单使用) */
+    private Long afterSalesId;
+
+    /** 订单号 */
+    private String orderCode;
+
+    /** 实际支付金额 */
+    private BigDecimal payPrice;
+
+    /** 订单状态 */
+    private Integer status;
+
+    /** 是否套餐 */
+    private Integer isPackage;
+
+    /** 套餐JSON */
+    private String packageJson;
+
+    /** 商品JSON */
+    private String itemJson;
+
+    /** 物流单号 */
+    private String deliveryId;
+
+    /** 是否可以申请售后 */
+    private Integer isAfterSales;
+
+    /** 完成时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date finishTime;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 总数量(直播订单使用) */
+    private Integer totalNum;
+
+    /** 优惠金额(直播订单使用) */
+    private Integer discountMoney;
+
+    /** 订单类型:1-商城订单,2-直播订单 */
+    private Integer orderType;
+
+    /** 订单商品列表 */
+    private List<FsStoreOrderItemVO> items;
+}
+

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

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

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

@@ -138,6 +138,7 @@ 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> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             " UNION " +
             "select l.* from live l " +
             "LEFT JOIN ( " +
@@ -153,6 +154,7 @@ public interface LiveMapper
             "and TIMESTAMPDIFF(SECOND, l.start_time, NOW()) > COALESCE(video_duration.total_duration, 0) " +
             "and COALESCE(video_duration.total_duration, 0) > 0 " +
             " <if test='param.liveName!=null' > and l.live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and l.start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             "order by create_time desc" +
             " </script>"})
     List<Live> listLiveData(@Param("param") LiveDataParam param);
@@ -162,6 +164,7 @@ 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> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             " UNION " +
             "select l.* from live l " +
             "LEFT JOIN ( " +
@@ -177,6 +180,7 @@ public interface LiveMapper
             "and TIMESTAMPDIFF(SECOND, l.start_time, NOW()) > COALESCE(video_duration.total_duration, 0) " +
             "and COALESCE(video_duration.total_duration, 0) > 0 " +
             " <if test='param.liveName!=null' > and l.live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and l.start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             ") as temp " +
             " </script>"})
     int listLiveDataCount(@Param("param") LiveDataParam param);
@@ -207,6 +211,10 @@ public interface LiveMapper
             "GROUP BY l.live_id, l.start_time")
     Integer selectLiveFlagByLiveId(@Param("liveId") Long liveId);
 
-    @Select("SELECT * FROM live WHERE is_audit = 1 and is_del = 0 and status in (1,2,4) and live_type in (2,3) order by create_time desc")
-    List<Live> listToLiveNoEnd(Live live);
+    @Select({"<script>" +
+            " SELECT * FROM live WHERE is_audit = 1 and is_del = 0 and status in (1,2,4) and live_type in (2,3) " +
+            "  <if test='live.liveName!=null' > and live_name like concat('%',#{live.liveName},'%') </if> " +
+            " order by create_time desc" +
+            " </script>"})
+    List<Live> listToLiveNoEnd(@Param("live") Live live);
 }

+ 1 - 3
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -461,6 +461,7 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         request.setOid(order.getOrderCode());
         request.setRefund_state(1);
         request.setStoreAfterSalesId(storeAfterSales.getId());
+        request.setOrderStatus(orderStatus);
         if (StringUtils.isNotBlank(order.getExtendOrderId())){
             BaseResponse response=erpOrderService.refundUpdateLive(request);
             if(response.getSuccess()){
@@ -580,9 +581,6 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
     @Override
     public int updateLiveAfterSales(LiveAfterSales liveAfterSales)
     {
-        if (StringUtils.isNotEmpty(liveAfterSales.getDeliveryName()) && StringUtils.isNotEmpty(liveAfterSales.getDeliverySn()) && StringUtils.isNotEmpty(liveAfterSales.getDeliveryCode())) {
-            liveAfterSales.setStatus(2);
-        }
 
         return baseMapper.updateLiveAfterSales(liveAfterSales);
     }

+ 19 - 8
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -748,7 +748,11 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             order.setPayTime(LocalDateTime.now());
             order.setIsPay("1");
             baseMapper.updateLiveOrder(order);
-            this.createOmsOrderCall(order);
+            try {
+                this.createOmsOrderCall(order);
+            } catch (Exception e) {
+                log.error("推送erp失败:{}",e.getMessage());
+            }
             return "SUCCESS";
         }catch (Exception e){
             log.info("支付错误:"+e.getMessage());
@@ -1177,6 +1181,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
+            request.setOrderStatus(order.getStatus());
 
             if (ObjectUtils.equals(order.getStatus(), 2)) {
                 LiveAfterSalesParam param = new LiveAfterSalesParam();
@@ -1373,7 +1378,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
-
+            request.setOrderStatus(order.getStatus());
             if (ObjectUtils.equals(order.getStatus(), 2)) {
                 LiveAfterSalesParam param = new LiveAfterSalesParam();
                 param.setOrderCode(order.getOrderCode());
@@ -1675,7 +1680,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
         erpOrder.setPlatform_code(order.getOrderCode());
         erpOrder.setWarehouse_code(erpConfig.getErpWarehouseCode());
-        erpOrder.setShop_code(erpConfig.getErpShopCode());
+        erpOrder.setShop_code(erpConfig.getErpJstShopCode());
         erpOrder.setBuyer_account(order.getUserName());
 
 //      erpOrder.setPost_fee(order.getTotalPostage().doubleValue());
@@ -1709,13 +1714,13 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             order.setDeliverySn(express.getCode());
         }
         erpOrder.setPayments(payments);
-        if (order.getCompanyId() != null) {
+        if (order.getCompanyId() != null && order.getCompanyId() > 0L) {
             Company company = companyService.selectCompanyById(order.getCompanyId());
             if (company != null) {
                 erpOrder.setSeller_memo(company.getCompanyName());
             }
         }
-        if (order.getCompanyUserId() != null) {
+        if (order.getCompanyUserId() != null && order.getCompanyUserId() > 0L) {
             CompanyUser companyUser = companyUserService.selectCompanyUserById(order.getCompanyUserId());
             if (companyUser != null) {
                 CompanyDept dept = companyDeptService.selectCompanyDeptById(companyUser.getDeptId());
@@ -2467,6 +2472,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
     private BigDecimal handleDeliveryMoney(Long cityId, FsStoreProductScrm fsStoreProduct, String totalNumSize) {
         BigDecimal storePostage = BigDecimal.ZERO;
+        if (ObjectUtil.isNull(fsStoreProduct.getTempId())) {
+            return storePostage;
+        }
         List<Long> citys = new ArrayList<>();
         citys.add(cityId);
         citys.add(0l);
@@ -2979,7 +2987,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
         String orderId=redisCache.getCacheObject("isPaying:"+order.getOrderId());
         if(StringUtils.isNotEmpty(orderId)&&order.getOrderId().toString().equals(orderId)){
-            return R.error("正在支付中...");
+            return R.error(501,"正在支付中...");
         }
         FsUserScrm user=userMapper.selectFsUserById(Long.valueOf(order.getUserId()));
         if(user == null){
@@ -3474,8 +3482,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
         LiveGoods goods = liveGoodsMapper.selectLiveGoodsByProductId(liveOrder.getLiveId(), liveOrder.getProductId());
         if(goods == null) return R.error("当前商品不存在");
-        if(fsStoreProduct == null) return R.error("店铺已下架商品,购买失败");
+        if(fsStoreProduct == null) return R.error("商品不存在,购买失败");
         if(fsStoreProduct.getIsShow() == 0 || goods.getStatus() == 0) return R.error("商品已下架,购买失败");
+        if(!"1".equals(fsStoreProduct.getIsAudit()) ) return R.error("商品已下架,购买失败");
         if(liveOrder.getTotalNum() == null || StringUtils.isEmpty(liveOrder.getTotalNum())) liveOrder.setTotalNum("1");
         if(goods.getStock() == null) return R.error("直播间商品库存不足");
         if(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum())) return R.error("抱歉,这款商品已被抢光,暂时无库存~");
@@ -3487,6 +3496,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if (attrValue != null) {
             attrValue.setStock(attrValue.getStock() - Integer.parseInt(liveOrder.getTotalNum()));
             attrValue.setSales(attrValue.getSales() + Integer.parseInt(liveOrder.getTotalNum()));
+            fsStoreProductAttrValueMapper.updateFsStoreProductAttrValue(attrValue);
         } else {
             // 更改店铺库存
             fsStoreProduct.setStock(fsStoreProduct.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
@@ -3513,6 +3523,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setCompanyUserId(liveUserFirstEntry.getCompanyUserId());
         liveOrder.setTuiUserId(liveUserFirstEntry.getCompanyUserId());
         String orderSn = OrderCodeUtils.getOrderSn();
+//        String orderSn = "123"; // todo yhq
         log.info("订单生成:"+orderSn);
         liveOrder.setOrderCode(orderSn);
         BigDecimal payPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
@@ -3561,7 +3572,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setPayType("1");
         liveOrder.setTotalPrice(payPrice);
         liveOrder.setPayMoney(BigDecimal.ZERO);
-        liveOrder.setPayPrice(payPrice);
+        liveOrder.setPayPrice(payPrice.subtract(liveOrder.getDiscountMoney()));
         try {
             if (baseMapper.insertLiveOrder(liveOrder) > 0) {
                 LiveOrderItemDTO dto=new LiveOrderItemDTO();

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

@@ -323,7 +323,7 @@ public class LiveServiceImpl implements ILiveService
         });
         notifyTask.setData(JSON.toJSONString(data));
 //        return R.ok("success");
-//        liveMiniprogramSubNotifyTaskMapper.insert(notifyTask);
+        liveMiniprogramSubNotifyTaskMapper.insert(notifyTask);
 
         return R.ok("success");
     }

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

@@ -75,7 +75,7 @@ public class LiveWatchConfigServiceImpl implements ILiveWatchConfigService {
     @Override
     public int insertLiveWatchConfig(String userId, String jsonConfig ,Long liveId)
     {
-        Live live = liveService.selectLiveByLiveId(liveId);
+        Live live = liveService.selectLiveDbByLiveId(liveId);
         if(live == null){
             return 0;
         }
@@ -95,7 +95,7 @@ public class LiveWatchConfigServiceImpl implements ILiveWatchConfigService {
     @Override
     public int updateLiveWatchConfig(String jsonConfig, Long liveId)
     {
-        Live live = liveService.selectLiveByLiveId(liveId);
+        Live live = liveService.selectLiveDbByLiveId(liveId);
         if(live == null){
             return 0;
         }

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

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

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

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

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

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

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

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

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

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

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

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

+ 4 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSop.java

@@ -161,6 +161,10 @@ public class QwSop implements Serializable
     @TableField(exist = false)
     private List<String> userIds = new ArrayList<>();
 
+
+    private Integer pageNum;
+    private Integer pageSize;
+
     public List<String> getUserIds() {
         if (userIds == null || userIds.isEmpty()) {
             return userIds;

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

@@ -114,6 +114,8 @@ public class QwSopTemp implements Serializable
     @TableField(exist = false)
     private List<String> companyUserIds=new ArrayList<>();
 
+    private Integer pageNum;
+    private Integer pageSize;
 
     public List<String> getCompanyUserIds() {
         if (companyUserIds == null || companyUserIds.isEmpty()) {

+ 2 - 0
fs-service/src/main/java/com/fs/system/mapper/SysUserMapper.java

@@ -130,4 +130,6 @@ public interface SysUserMapper
      * @return 结果
      */
     public SysUser checkEmailUnique(String email);
+
+    List<SysUser> selectUserByPhone(String phone);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/system/service/ISysUserService.java

@@ -214,4 +214,6 @@ public interface ISysUserService
     public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
 
     int updateUserInfo(SysUser sysuser);
+
+    List<SysUser> selectUserByPhone(String phone);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/system/service/impl/SysUserServiceImpl.java

@@ -586,4 +586,8 @@ public class SysUserServiceImpl implements ISysUserService
     public int updateUserInfo(SysUser sysuser) {
         return userMapper.updateUser(sysuser);
     }
+
+    public List<SysUser> selectUserByPhone(String phone) {
+        return userMapper.selectUserByPhone(phone);
+    }
 }

+ 131 - 3
fs-service/src/main/java/com/fs/wxcid/service/FriendService.java

@@ -1,6 +1,8 @@
+// 文件路径:src/main/java/com/fs/wxcid/service/FriendService.java
+
 package com.fs.wxcid.service;
 
-import com.fs.wxcid.dto.common.ApiResponseCommon;
+import com.fs.wxcid.dto.common.ApiResponse;
 import com.fs.wxcid.dto.friend.*;
 
 /**
@@ -13,6 +15,45 @@ import com.fs.wxcid.dto.friend.*;
  */
 public interface FriendService {
 
+    /**
+     * 同意好友请求
+     * <p>
+     * 对应接口:POST /friend/AgreeAdd
+     * 通常用于处理来自他人的加好友申请,需提供 V3/V4 等加密参数。
+     * </p>
+     *
+     * @param authKey    账号唯一标识(query 参数)
+     * @param request 同意请求参数(包含 V3、V4、Scene 等)
+     * @return 统一响应结果
+     */
+    ApiResponse agreeAdd(String authKey, AgreeAddRequest request);
+
+    /**
+     * 删除好友
+     * <p>
+     * 对应接口:POST /friend/DelContact
+     * 从联系人列表中移除指定用户。
+     * </p>
+     *
+     * @param authKey    账号唯一标识
+     * @param request 删除请求参数(含 DelUserName)
+     * @return 统一响应结果
+     */
+    ApiResponse delContact(String authKey, DelContactRequest request);
+
+    /**
+     * 批量获取联系人详细信息
+     * <p>
+     * 对应接口:POST /friend/GetContactDetailsList
+     * 可同时查询多个用户或群聊的详细资料。
+     * </p>
+     *
+     * @param authKey    账号唯一标识
+     * @param request 查询参数(支持 UserNames 和 RoomWxIDList)
+     * @return 统一响应结果
+     */
+    ApiResponse getContactDetailsList(String authKey, GetContactDetailsListRequest request);
+
     /**
      * 分页获取全部联系人(包括好友和群聊)
      * <p>
@@ -24,7 +65,94 @@ public interface FriendService {
      * @param request 分页参数(CurrentWxcontactSeq / CurrentChatRoomContactSeq)
      * @return 统一响应结果
      */
-    // 反序列化为泛型响应对象
-    ApiResponseCommon<ContactListResponse> getContactList(String authKey, GetContactListRequest request);
+    ApiResponse getContactList(String authKey, GetContactListRequest request);
+
+    /**
+     * 查询与指定用户的好友关系状态
+     * <p>
+     * 对应接口:POST /friend/GetFriendRelation
+     * 可判断是否为单向/双向好友、是否被拉黑等。
+     * </p>
+     *
+     * @param authKey    账号唯一标识
+     * @param request 目标用户名(UserName)
+     * @return 统一响应结果(含关系状态码)
+     */
+    ApiResponse getFriendRelation(String authKey, GetFriendRelationRequest request);
+
+    /**
+     * 获取已关注的公众号列表
+     * <p>
+     * 对应接口:GET /friend/GetGHList
+     * 返回当前账号关注的所有公众号信息。
+     * </p>
+     *
+     * @param authKey 账号唯一标识
+     * @return 统一响应结果
+     */
+    ApiResponse getGHList(String authKey);
 
+    /**
+     * 获取手机通讯录中匹配的微信好友
+     * <p>
+     * 对应接口:GET /friend/GetMFriend
+     * 需提前上传通讯录(UploadMContact),否则可能返回空。
+     * </p>
+     *
+     * @param authKey 账号唯一标识
+     * @return 统一响应结果
+     */
+    ApiResponse getMFriend(String authKey);
+
+    /**
+     * 获取已保存的群聊列表
+     * <p>
+     * 对应接口:GET /friend/GroupList
+     * 返回当前账号参与并保存的所有群聊。
+     * </p>
+     *
+     * @param authKey 账号唯一标识
+     * @return 统一响应结果
+     */
+    ApiResponse getGroupList(String authKey);
+
+    /**
+     * 搜索联系人
+     * <p>
+     * 对应接口:POST /friend/SearchContact
+     * 支持按昵称、微信号、手机号等模糊搜索。
+     * </p>
+     *
+     * @param authKey    账号唯一标识
+     * @param request 搜索参数(UserName + 场景配置)
+     * @return 统一响应结果
+     */
+    ApiResponse searchContact(String authKey, SearchContactRequest request);
+
+    /**
+     * 上传手机通讯录用于匹配微信好友
+     * <p>
+     * 对应接口:POST /friend/UploadMContact
+     * 上传后可调用 GetMFriend 获取匹配结果。
+     * </p>
+     *
+     * @param authKey    账号唯一标识
+     * @param request 通讯录号码列表(MobileList)
+     * @return 统一响应结果
+     */
+    ApiResponse uploadMContact(String authKey, UploadMContactRequest request);
+
+    /**
+     * 发起好友验证或添加请求
+     * <p>
+     * 对应接口:POST /friend/VerifyUser
+     * 用于主动添加他人,需提供对方的 V3/V4(通常来自扫码或推荐)。
+     * 若无 V3/V4,部分场景可能无法添加。
+     * </p>
+     *
+     * @param authKey    账号唯一标识
+     * @param request 添加请求参数(含 V3、V4、Scene、验证语等)
+     * @return 统一响应结果
+     */
+    ApiResponse verifyUser(String authKey, VerifyUserRequest request);
 }

+ 21 - 7
fs-service/src/main/java/com/fs/wxcid/service/LoginService.java

@@ -1,25 +1,39 @@
 package com.fs.wxcid.service;
 
 import com.fs.wxcid.dto.common.ApiResponse;
-import com.fs.wxcid.dto.common.ApiResponseCommon;
-import com.fs.wxcid.dto.login.*;
-
+import com.fs.wxcid.dto.login.LoginRequest;
+import com.fs.wxcid.dto.login.QrCodeRequest;
+import com.fs.wxcid.dto.login.SlideVerifyRequest;
+import com.fs.wxcid.dto.login.VerifyCodeRequest;
 /**
  * 微信登录服务接口
  * <p>封装所有 /login/* 接口,支持多种登录方式</p>
  */
 public interface LoginService {
 
+    // —————— 账号密码 / 62 / A16 登录 ——————
+    ApiResponse a16Login(String authKey, LoginRequest request);
+    ApiResponse deviceLogin(String authKey, LoginRequest request);
+
     // —————— 二维码登录(iPad / Mac / 车载) ——————
-    ApiResponseCommon<LoginQrCodeResponseData> getLoginQrCodeNewDirect(String authKey, QrCodeRequest request); // 直登
+    ApiResponse getLoginQrCodeNew(String authKey, QrCodeRequest request);// iPad
+    ApiResponse getLoginQrCodeNewDirect(String authKey, QrCodeRequest request); // 直登
+    ApiResponse macLogin(String authKey, QrCodeRequest request);// Mac
+    ApiResponse carLogin(String authKey, QrCodeRequest request);// iPad
 
     // ------------------ 状态检测 ------------------
+    ApiResponse checkCanSetAlias(String authKey);// 检测是否可设昵称(判断登录环境)
+    ApiResponse checkLoginStatus(String authKey);// 检测扫码是否完成
+    ApiResponse getLoginStatus(String authKey);// 获取当前在线状态
 
-    ApiResponseCommon<LoginStatusResponseData> checkLoginStatus(String authKey);// 检测扫码是否完成
-    ApiResponseCommon<LoginStatusData> getLoginStatus(String authKey);// 获取当前在线状态
+    // —————— 数据管理 ——————
+    ApiResponse get62Data(String authKey);            // 提取 62 数据(用于免密登录)
 
+    // —————— 验证码与滑块 ——————
+    ApiResponse verifyCode(String authKey, VerifyCodeRequest request);
+    ApiResponse verifyCodeSlide(String authKey, SlideVerifyRequest request);
 
     // —————— 控制 ——————
     ApiResponse wakeUpLogin(String authKey, QrCodeRequest request); // 唤醒扫码登录
-    ApiResponseCommon<Void> logOut(String authKey);                // 退出登录
+    ApiResponse logOut(String authKey);                // 退出登录
 }

+ 5 - 6
fs-service/src/main/java/com/fs/wxcid/service/MessageCallbackService.java

@@ -1,9 +1,8 @@
 package com.fs.wxcid.service;
 
-import com.fs.wxcid.dto.callback.CallbackConfigRequest;
-import com.fs.wxcid.dto.callback.CallbackConfigResponse;
 import com.fs.wxcid.dto.callback.ReturnMessage;
-import com.fs.wxcid.dto.common.ApiResponseCommon;
+import com.fs.wxcid.dto.common.ApiResponse;
+import com.fs.wxcid.dto.callback.CallbackConfigRequest;
 
 import java.util.Map;
 
@@ -25,7 +24,7 @@ public interface MessageCallbackService {
      * @param config 回调配置(URL + 启用状态)
      * @return 统一响应结果
      */
-    ApiResponseCommon<Void> setCallback(String authKey, CallbackConfigRequest config);
+    ApiResponse setCallback(String authKey, CallbackConfigRequest config);
 
     /**
      * 获取当前账号的消息回调配置
@@ -34,7 +33,7 @@ public interface MessageCallbackService {
      * @param authKey 账号唯一标识
      * @return 包含 CallbackURL 和 Enabled 状态的响应
      */
-    ApiResponseCommon<CallbackConfigResponse> getCallback(String authKey);
+    ApiResponse getCallback(String authKey);
 
     /**
      * 删除(清空)消息回调配置
@@ -44,5 +43,5 @@ public interface MessageCallbackService {
      * @param authKey 账号唯一标识
      * @return 操作结果
      */
-    ApiResponseCommon<Void> deleteCallback(String authKey);
+    ApiResponse deleteCallback(String authKey);
 }

+ 24 - 4
fs-service/src/main/java/com/fs/wxcid/service/MessageService.java

@@ -1,12 +1,32 @@
 package com.fs.wxcid.service;
 
 
-import com.fs.wxcid.dto.common.ApiResponseCommon;
+import com.fs.wxcid.dto.common.ApiResponse;
+import com.fs.wxcid.dto.message.GetMsgBigImgRequest;
 import com.fs.wxcid.dto.message.*;
 
-import java.util.List;
-
 public interface MessageService {
 
-    ApiResponseCommon<List<SendMessageResult>> sendTextMessage(String authKey, SendTextMessageRequest request);
+    ApiResponse addMessageMgr(String authKey, AddMessageMgrRequest request);
+    ApiResponse cdnUploadVideo(String authKey, CdnUploadVideoRequest request);
+    ApiResponse downloadEmojiGif(String authKey, DownloadEmojiGifRequest request);
+    ApiResponse forwardEmoji(String authKey, ForwardEmojiRequest request);
+    ApiResponse forwardImageMessage(String authKey, ForwardImageMessageRequest request);
+    ApiResponse forwardVideoMessage(String authKey, ForwardVideoMessageRequest request);
+    ApiResponse getMsgBigImg(String authKey, GetMsgBigImgRequest request);
+    ApiResponse getMsgVideo(String authKey, GetMsgVideoRequest request);
+    ApiResponse getMsgVoice(String authKey, GetMsgVoiceRequest request);
+    ApiResponse groupMassMsgImage(String authKey, GroupMassMsgImageRequest request);
+    ApiResponse groupMassMsgText(String authKey, GroupMassMsgTextRequest request);
+    ApiResponse httpSyncMsg(String authKey, HttpSyncMsgRequest request);
+    ApiResponse newSyncHistoryMessage(String authKey); // 无 body
+    ApiResponse revokeMsg(String authKey, RevokeMsgRequest request);
+    ApiResponse revokeMsgNew(String authKey, RevokeMsgNewRequest request);
+    ApiResponse sendAppMessage(String authKey, SendAppMessageRequest request);
+    ApiResponse sendEmojiMessage(String authKey, SendEmojiMessageRequest request);
+    ApiResponse sendImageMessage(String authKey, SendImageMessageRequest request);
+    ApiResponse sendImageNewMessage(String authKey, SendImageNewMessageRequest request);
+    ApiResponse sendTextMessage(String authKey, SendTextMessageRequest request);
+    ApiResponse sendVoice(String authKey, SendVoiceRequest request);
+    ApiResponse shareCardMessage(String authKey, ShareCardMessageRequest request);
 }

+ 82 - 22
fs-service/src/main/java/com/fs/wxcid/service/impl/FriendServiceImpl.java

@@ -1,20 +1,19 @@
 package com.fs.wxcid.service.impl;
 
-import com.alibaba.fastjson.TypeReference;
-import com.fs.common.exception.CustomException;
-import com.fs.wxcid.dto.common.ApiResponseCommon;
+import com.fs.wxcid.dto.common.ApiResponse;
 import com.fs.wxcid.dto.friend.*;
 import com.fs.wxcid.service.FriendService;
 import com.fs.wxwork.utils.WxWorkHttpUtil;
-import lombok.extern.slf4j.Slf4j;
+import com.alibaba.fastjson.TypeReference;
 import org.springframework.stereotype.Service;
 
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * 好友管理服务实现类
  * 调用微信私有 API 的 /friend 模块
  */
-@Slf4j
 @Service
 public class FriendServiceImpl implements FriendService {
 
@@ -22,26 +21,87 @@ public class FriendServiceImpl implements FriendService {
     private static final String BASE_URL = "http://114.117.215.244:7006";
 
 
+    @Override
+    public ApiResponse agreeAdd(String authKey, AgreeAddRequest request) {
+        return post("/friend/AgreeAdd", authKey, request);
+    }
 
+    @Override
+    public ApiResponse delContact(String authKey, DelContactRequest request) {
+        return post("/friend/DelContact", authKey, request);
+    }
+
+    @Override
+    public ApiResponse getContactDetailsList(String authKey, GetContactDetailsListRequest request) {
+        return post("/friend/GetContactDetailsList", authKey, request);
+    }
 
     @Override
-    public ApiResponseCommon<ContactListResponse> getContactList(String authKey, GetContactListRequest request) {
-        log.info("开始获取联系人列表");
-        String url = BASE_URL + "/friend/GetContactList?key=" + authKey;
-        ApiResponseCommon<ContactListResponse> response = WxWorkHttpUtil.postWithType(
-                url,
-                request,
-                new TypeReference<ApiResponseCommon<ContactListResponse>>() {}
-        );
-        if (response.getCode() != 200 || response.getData() == null) {
-            throw new CustomException("获取联系人失败: " + response.getText());
-        }
+    public ApiResponse getContactList(String authKey, GetContactListRequest request) {
+        return post("/friend/GetContactList", authKey, request);
+    }
+
+    @Override
+    public ApiResponse getFriendRelation(String authKey, GetFriendRelationRequest request) {
+        return post("/friend/GetFriendRelation", authKey, request);
+    }
+
+    @Override
+    public ApiResponse getGHList(String authKey) {
+        return get("/friend/GetGHList", authKey);
+    }
+
+    @Override
+    public ApiResponse getMFriend(String authKey) {
+        return get("/friend/GetMFriend", authKey);
+    }
+
+    @Override
+    public ApiResponse getGroupList(String authKey) {
+        return get("/friend/GroupList", authKey);
+    }
+
+    @Override
+    public ApiResponse searchContact(String authKey, SearchContactRequest request) {
+        return post("/friend/SearchContact", authKey, request);
+    }
+
+    @Override
+    public ApiResponse uploadMContact(String authKey, UploadMContactRequest request) {
+        return post("/friend/UploadMContact", authKey, request);
+    }
+
+    @Override
+    public ApiResponse verifyUser(String authKey, VerifyUserRequest request) {
+        return post("/friend/VerifyUser", authKey, request);
+    }
+
+    // ------------------ 工具方法 ------------------
+    /**
+     * 通用 POST 请求方法
+     *
+     * @param path   接口路径,如 "/friend/AgreeAdd"
+     * @param authKey    账号唯一标识(query 参数)
+     * @param request 请求体对象
+     * @return 统一响应结果
+     */
+    private ApiResponse post(String path, String authKey, Object request) {
+        String url = BASE_URL + path + "?key=" + authKey;
+        return WxWorkHttpUtil.postWithType(url, request, new TypeReference<ApiResponse>() {});
+    }
 
-        // 安全打印日志(避免 NPE)
-        ContactListResponse data = response.getData();
-        if (data.getContactList() != null) {
-            log.info("联系人id合集: {}", data.getContactList().getContactUsernameList());
-        }
-        return response;
+    /**
+     * 通用 GET 请求方法(无请求体)
+     *
+     * @param path 接口路径
+     * @param authKey  账号唯一标识
+     * @return 统一响应结果
+     */
+    private ApiResponse get(String path, String authKey) {
+        String url = BASE_URL + path;
+        Map<String, Object> params = new HashMap<>();
+        params.put("authKey", authKey);
+        String resp = WxWorkHttpUtil.get(url, params);
+        return com.alibaba.fastjson.JSON.parseObject(resp, ApiResponse.class);
     }
 }

+ 73 - 86
fs-service/src/main/java/com/fs/wxcid/service/impl/LoginServiceImpl.java

@@ -1,114 +1,86 @@
 package com.fs.wxcid.service.impl;
 
-import com.alibaba.fastjson.TypeReference;
-import com.fs.common.exception.CustomException;
 import com.fs.wxcid.dto.common.ApiResponse;
-import com.fs.wxcid.dto.common.ApiResponseCommon;
-import com.fs.wxcid.dto.login.*;
+import com.fs.wxcid.dto.login.LoginRequest;
+import com.fs.wxcid.dto.login.QrCodeRequest;
+import com.fs.wxcid.dto.login.SlideVerifyRequest;
+import com.fs.wxcid.dto.login.VerifyCodeRequest;
 import com.fs.wxcid.service.LoginService;
 import com.fs.wxwork.utils.WxWorkHttpUtil;
-import lombok.extern.slf4j.Slf4j;
+import com.alibaba.fastjson.TypeReference;
 import org.springframework.stereotype.Service;
 
-@Slf4j
+import java.util.HashMap;
+import java.util.Map;
+
 @Service
 public class LoginServiceImpl implements LoginService {
 
     private static final String BASE_URL = "http://114.117.215.244:7006";
 
+    // ------------------ 账号密码登录 ------------------
+
+    public ApiResponse a16Login(String authKey, LoginRequest request) {
+        return post("/login/A16Login", authKey, request);
+    }
+
+    public ApiResponse deviceLogin(String authKey, LoginRequest request) {
+        return post("/login/DeviceLogin", authKey, request);
+    }
 
-    @Override
-    public ApiResponseCommon<LoginQrCodeResponseData> getLoginQrCodeNewDirect(String authKey, QrCodeRequest request) {
-        String url = BASE_URL + "/login/GetLoginQrCodeNewDirect?key=" + authKey;
-        ApiResponseCommon<LoginQrCodeResponseData> response = WxWorkHttpUtil.postWithType(
-                url,
-                request,
-                new TypeReference<ApiResponseCommon<LoginQrCodeResponseData>>() {}
-        );
+    // ------------------ 二维码登录 ------------------
 
-        //先判断是否成功
-        if (response.getCode() != 200 || response.getData() == null) {
-            String errorMsg = response.getText() != null ? response.getText() : "获取二维码失败";
-            throw new CustomException("获取登录二维码失败: " + errorMsg);
-        }
+    public ApiResponse getLoginQrCodeNew(String authKey, QrCodeRequest request) {
+        return post("/login/GetLoginQrCodeNew", authKey, request);
+    }
 
-        //安全访问 data
-        log.info("二维码照片url: {}", response.getData().getQrCodeUrl());
-        return response;
+    public ApiResponse getLoginQrCodeNewDirect(String authKey, QrCodeRequest request) {
+        return post("/login/GetLoginQrCodeNewDirect", authKey, request);
+    }
+
+    public ApiResponse macLogin(String authKey, QrCodeRequest request) {
+        return post("/login/MacLogin", authKey, request);
+    }
+
+    public ApiResponse carLogin(String authKey, QrCodeRequest request) {
+        return post("/login/CarLogin", authKey, request);
     }
 
-    @Override
     public ApiResponse wakeUpLogin(String authKey, QrCodeRequest request) {
         return post("/login/WakeUpLogin", authKey, request);
     }
 
     // ------------------ 状态检测 ------------------
 
-    @Override
-    public ApiResponseCommon<LoginStatusResponseData> checkLoginStatus(String authKey) {
-        String url = BASE_URL + "/login/CheckLoginStatus?key=" + authKey;
-        ApiResponseCommon<LoginStatusResponseData> response = WxWorkHttpUtil.getWithType(
-                url,
-                new TypeReference<ApiResponseCommon<LoginStatusResponseData>>() {}
-        );
-
-        // 只认 text == "账号已登录"
-        if ("账号已登录".equals(response.getText())) {
-            if (response.getData() == null) {
-                throw new CustomException("登录状态异常:text 显示已登录,但 data 为空");
-            }
-            return response;
-        } else {
-            // 抛出具体的错误原因
-            throw new CustomException("检查登录状态失败: " + (response.getText() != null ? response.getText() : "未知错误"));
-        }
-    }
-    @Override
-    public ApiResponseCommon<LoginStatusData> getLoginStatus(String authKey) {
-        String url = BASE_URL + "/login/GetLoginStatus?key=" + authKey;
-        ApiResponseCommon<LoginStatusData> response = WxWorkHttpUtil.getWithType(
-                url,
-                new TypeReference<ApiResponseCommon<LoginStatusData>>() {}
-        );
-
-        //核心判断:只有 code=200 且 data 不为 null 才算成功
-        if (response.getCode() != 200 || response.getData() == null) {
-            String errorMsg = response.getText() != null
-                    ? response.getText()
-                    : "获取登录状态失败,未知错误";
-            throw new CustomException("账号未登录或状态异常: " + errorMsg);
-        }
-
-        return response;
-    }
-
-
-    @Override
-    public ApiResponseCommon<Void> logOut(String authKey) {
-        String url = BASE_URL + "/login/LogOut?key=" + authKey;
-        ApiResponseCommon<Void> response = WxWorkHttpUtil.getWithType(
-                url,
-                new TypeReference<ApiResponseCommon<Void>>() {}
-        );
-
-        String text = response.getText();
-        int code = response.getCode();
-
-        //真正的成功退出
-        if (code == 200 && "退出成功!".equals(text)) {
-            return response;
-        }
-        //已经退出过了(幂等,视为成功)
-        else if (code == 300 && "你已退出登录".equals(text)) {
-            return response; // 也可记录日志:log.debug("重复登出,忽略");
-        }
-        //其他情况一律视为失败
-        else {
-            String errorMsg = text != null ? text : "退出登录失败,未知错误";
-            throw new CustomException("退出登录失败: " + errorMsg);
-        }
+    public ApiResponse checkCanSetAlias(String authKey) {
+        return get("/login/CheckCanSetAlias", authKey);
+    }
+
+    public ApiResponse checkLoginStatus(String authKey) {
+        return get("/login/CheckLoginStatus", authKey);
+    }
+
+    public ApiResponse getLoginStatus(String authKey) {
+        return get("/login/GetLoginStatus", authKey);
+    }
+
+    public ApiResponse get62Data(String authKey) {
+        return get("/login/Get62Data", authKey);
+    }
+
+    public ApiResponse logOut(String authKey) {
+        return get("/login/LogOut", authKey);
     }
 
+    // ------------------ 验证 ------------------
+
+    public ApiResponse verifyCode(String authKey, VerifyCodeRequest request) {
+        return post("/login/VerifyCode", authKey, request);
+    }
+
+    public ApiResponse verifyCodeSlide(String authKey, SlideVerifyRequest request) {
+        return post("/login/VerifyCodeSlide", authKey, request);
+    }
 
     // ------------------ 工具方法 ------------------
     /**
@@ -123,4 +95,19 @@ public class LoginServiceImpl implements LoginService {
         String url = BASE_URL + path + "?key=" + authKey;
         return WxWorkHttpUtil.postWithType(url, request, new TypeReference<ApiResponse>() {});
     }
+
+    /**
+     * 通用 GET 请求方法(无请求体)
+     *
+     * @param path 接口路径
+     * @param authKey  账号唯一标识
+     * @return 统一响应结果
+     */
+    private ApiResponse get(String path, String authKey) {
+        String url = BASE_URL + path;
+        Map<String, Object> params = new HashMap<>();
+        params.put("authKey", authKey);
+        String resp = WxWorkHttpUtil.get(url, params);
+        return com.alibaba.fastjson.JSON.parseObject(resp, ApiResponse.class);
+    }
 }

+ 40 - 45
fs-service/src/main/java/com/fs/wxcid/service/impl/MessageCallbackServiceImpl.java

@@ -1,17 +1,16 @@
 package com.fs.wxcid.service.impl;
 
 
-import com.alibaba.fastjson.TypeReference;
-import com.fs.common.exception.CustomException;
-import com.fs.wxcid.dto.callback.CallbackConfigRequest;
-import com.fs.wxcid.dto.callback.CallbackConfigResponse;
 import com.fs.wxcid.dto.callback.ReturnMessage;
-import com.fs.wxcid.dto.common.ApiResponseCommon;
+import com.fs.wxcid.dto.common.ApiResponse;
+import com.fs.wxcid.dto.callback.CallbackConfigRequest;
 import com.fs.wxcid.service.MessageCallbackService;
 import com.fs.wxwork.utils.WxWorkHttpUtil;
+import com.alibaba.fastjson.TypeReference;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.util.HashMap;
 import java.util.Map;
 @Slf4j
 @Service
@@ -21,8 +20,8 @@ public class MessageCallbackServiceImpl implements MessageCallbackService {
 
     @Override
     public ReturnMessage returnMessage(Map<String, Object> callback) {
-        // 安全地获取 key
-        String key = (String) callback.get("key");
+        // 安全地获取 authKey
+        String authKey = (String) callback.get("authKey");
 
         // 获取 message 子对象(也是一个 Map)
         Map<String, Object> message = (Map<String, Object>) callback.get("message");
@@ -46,55 +45,51 @@ public class MessageCallbackServiceImpl implements MessageCallbackService {
     /**
      * 设置消息回调
      */
-    @Override
-    public ApiResponseCommon<Void> setCallback(String key, CallbackConfigRequest config) {
-        String url = BASE_URL + "/message/SetCallback?key=" + key;
-        ApiResponseCommon<Void> response = WxWorkHttpUtil.postWithType(
-                url,
-                config,
-                new TypeReference<ApiResponseCommon<Void>>() {}
-        );
-
-        if (response.getCode() != 200) {
-            throw new CustomException("设置回调失败: " + (response.getText() != null ? response.getText() : "未知错误"));
-        }
-
-        return response;
+    public ApiResponse setCallback(String authKey, CallbackConfigRequest config) {
+        return post("/message/SetCallback", authKey, config);
     }
 
     /**
      * 获取消息回调配置
      */
-    @Override
-    public ApiResponseCommon<CallbackConfigResponse> getCallback(String key) {
-        String url = BASE_URL + "/message/GetCallback?key=" + key;
-        ApiResponseCommon<CallbackConfigResponse> response = WxWorkHttpUtil.getWithType(
-                url,
-                new TypeReference<ApiResponseCommon<CallbackConfigResponse>>() {}
-        );
-
-        if (response.getCode() != 200 || response.getData() == null) {
-            throw new CustomException("获取回调配置失败: " + (response.getText() != null ? response.getText() : "未知错误"));
-        }
-
-        return response;
+    public ApiResponse getCallback(String authKey) {
+        return get("/message/GetCallback", authKey);
     }
 
     /**
      * 删除消息回调配置
      */
-    @Override
-    public ApiResponseCommon<Void> deleteCallback(String key) {
-        String url = BASE_URL + "/message/DeleteCallback?key=" + key;
-        ApiResponseCommon<Void> response = WxWorkHttpUtil.getWithType(
-                url,
-                new TypeReference<ApiResponseCommon<Void>>() {}
-        );
+    public ApiResponse deleteCallback(String authKey) {
+        return get("/message/DeleteCallback", authKey);
+    }
 
-        if (response.getCode() != 200) {
-            throw new CustomException("删除回调配置失败: " + (response.getText() != null ? response.getText() : "未知错误"));
-        }
 
-        return response;
+    // ------------------ 工具方法 ------------------
+    /**
+     * 通用 POST 请求方法
+     *
+     * @param path   接口路径,如 "/friend/AgreeAdd"
+     * @param authKey    账号唯一标识(query 参数)
+     * @param request 请求体对象
+     * @return 统一响应结果
+     */
+    private ApiResponse post(String path, String authKey, Object request) {
+        String url = BASE_URL + path + "?key=" + authKey;
+        return WxWorkHttpUtil.postWithType(url, request, new TypeReference<ApiResponse>() {});
+    }
+
+    /**
+     * 通用 GET 请求方法(无请求体)
+     *
+     * @param path 接口路径
+     * @param authKey  账号唯一标识
+     * @return 统一响应结果
+     */
+    private ApiResponse get(String path, String authKey) {
+        String url = BASE_URL + path;
+        Map<String, Object> params = new HashMap<>();
+        params.put("authKey", authKey);
+        String resp = WxWorkHttpUtil.get(url, params);
+        return com.alibaba.fastjson.JSON.parseObject(resp, ApiResponse.class);
     }
 }

+ 138 - 17
fs-service/src/main/java/com/fs/wxcid/service/impl/MessageServiceImpl.java

@@ -1,14 +1,15 @@
 package com.fs.wxcid.service.impl;
 
-import com.alibaba.fastjson.TypeReference;
-import com.fs.common.exception.CustomException;
-import com.fs.wxcid.dto.common.ApiResponseCommon;
+import com.fs.wxcid.dto.common.ApiResponse;
+import com.fs.wxcid.dto.message.GetMsgBigImgRequest;
 import com.fs.wxcid.dto.message.*;
 import com.fs.wxcid.service.MessageService;
 import com.fs.wxwork.utils.WxWorkHttpUtil;
+import com.alibaba.fastjson.TypeReference;
 import org.springframework.stereotype.Service;
 
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 
 @Service
 public class MessageServiceImpl implements MessageService {
@@ -16,22 +17,142 @@ public class MessageServiceImpl implements MessageService {
     private static final String BASE_URL = "http://114.117.215.244:7006";
 
 
+    @Override
+    public ApiResponse addMessageMgr(String authKey, AddMessageMgrRequest request) {
+        return post("/message/AddMessageMgr", authKey, request);
+    }
+
+    @Override
+    public ApiResponse cdnUploadVideo(String authKey, CdnUploadVideoRequest request) {
+        return post("/message/CdnUploadVideo", authKey, request);
+    }
+
+    @Override
+    public ApiResponse downloadEmojiGif(String authKey, DownloadEmojiGifRequest request) {
+        return post("/message/DownloadEmojiGif", authKey, request);
+    }
+
+    @Override
+    public ApiResponse forwardEmoji(String authKey, ForwardEmojiRequest request) {
+        return post("/message/ForwardEmoji", authKey, request);
+    }
+
+    @Override
+    public ApiResponse forwardImageMessage(String authKey, ForwardImageMessageRequest request) {
+        return post("/message/ForwardImageMessage", authKey, request);
+    }
+
+    @Override
+    public ApiResponse forwardVideoMessage(String authKey, ForwardVideoMessageRequest request) {
+        return post("/message/ForwardVideoMessage", authKey, request);
+    }
+
+    @Override
+    public ApiResponse getMsgBigImg(String authKey, GetMsgBigImgRequest request) {
+        return post("/message/GetMsgBigImg", authKey, request);
+    }
+
+    @Override
+    public ApiResponse getMsgVideo(String authKey, GetMsgVideoRequest request) {
+        return post("/message/GetMsgVideo", authKey, request);
+    }
+
+    @Override
+    public ApiResponse getMsgVoice(String authKey, GetMsgVoiceRequest request) {
+        return post("/message/GetMsgVoice", authKey, request);
+    }
+
+    @Override
+    public ApiResponse groupMassMsgImage(String authKey, GroupMassMsgImageRequest request) {
+        return post("/message/GroupMassMsgImage", authKey, request);
+    }
+
+    @Override
+    public ApiResponse groupMassMsgText(String authKey, GroupMassMsgTextRequest request) {
+        return post("/message/GroupMassMsgText", authKey, request);
+    }
+
+    @Override
+    public ApiResponse httpSyncMsg(String authKey, HttpSyncMsgRequest request) {
+        return post("/message/HttpSyncMsg", authKey, request);
+    }
 
     @Override
-    public ApiResponseCommon<List<SendMessageResult>> sendTextMessage(String authKey, SendTextMessageRequest request) {
-        String url = BASE_URL + "/message/SendTextMessage?key=" + authKey;
-        ApiResponseCommon<List<SendMessageResult>> response = WxWorkHttpUtil.postWithType(
-                url,
-                request,
-                new TypeReference<ApiResponseCommon<List<SendMessageResult>>>() {}
-        );
+    public ApiResponse newSyncHistoryMessage(String authKey) {
+        return post("/message/NewSyncHistoryMessage", authKey, new Object()); // 无 body,传空对象或自定义
+    }
+
+    @Override
+    public ApiResponse revokeMsg(String authKey, RevokeMsgRequest request) {
+        return post("/message/RevokeMsg", authKey, request);
+    }
+
+    @Override
+    public ApiResponse revokeMsgNew(String authKey, RevokeMsgNewRequest request) {
+        return post("/message/RevokeMsgNew", authKey, request);
+    }
+
+    @Override
+    public ApiResponse sendAppMessage(String authKey, SendAppMessageRequest request) {
+        return post("/message/SendAppMessage", authKey, request);
+    }
+
+    @Override
+    public ApiResponse sendEmojiMessage(String authKey, SendEmojiMessageRequest request) {
+        return post("/message/SendEmojiMessage", authKey, request);
+    }
 
-        //统一异常判断:只要 code 不是 200,就抛异常
-        if (response.getCode() != 200 || response.getData() == null) {
-            String errorMsg = response.getText() != null ? response.getText() : "发送消息失败,未知错误";
-            throw new CustomException("发送文本消息失败: " + errorMsg);
-        }
+    @Override
+    public ApiResponse sendImageMessage(String authKey, SendImageMessageRequest request) {
+        return post("/message/SendImageMessage", authKey, request);
+    }
+
+    @Override
+    public ApiResponse sendImageNewMessage(String authKey, SendImageNewMessageRequest request) {
+        return post("/message/SendImageNewMessage", authKey, request);
+    }
+
+    @Override
+    public ApiResponse sendTextMessage(String authKey, SendTextMessageRequest request) {
+        return post("/message/SendTextMessage", authKey, request);
+    }
+
+    @Override
+    public ApiResponse sendVoice(String authKey, SendVoiceRequest request) {
+        return post("/message/SendVoice", authKey, request);
+    }
+
+    @Override
+    public ApiResponse shareCardMessage(String authKey, ShareCardMessageRequest request) {
+        return post("/message/ShareCardMessage", authKey, request);
+    }
+
+    // ------------------ 工具方法 ------------------
+    /**
+     * 通用 POST 请求方法
+     *
+     * @param path   接口路径,如 "/friend/AgreeAdd"
+     * @param authKey    账号唯一标识(query 参数)
+     * @param request 请求体对象
+     * @return 统一响应结果
+     */
+    private ApiResponse post(String path, String authKey, Object request) {
+        String url = BASE_URL + path + "?key=" + authKey;
+        return WxWorkHttpUtil.postWithType(url, request, new TypeReference<ApiResponse>() {});
+    }
 
-        return response;
+    /**
+     * 通用 GET 请求方法(无请求体)
+     *
+     * @param path 接口路径
+     * @param authKey  账号唯一标识
+     * @return 统一响应结果
+     */
+    private ApiResponse get(String path, String authKey) {
+        String url = BASE_URL + path;
+        Map<String, Object> params = new HashMap<>();
+        params.put("authKey", authKey);
+        String resp = WxWorkHttpUtil.get(url, params);
+        return com.alibaba.fastjson.JSON.parseObject(resp, ApiResponse.class);
     }
 }

+ 1 - 1
fs-service/src/main/resources/application-config-druid-bjzm-test.yml

@@ -71,7 +71,7 @@ nuonuo:
 tencent_cloud_config:
   secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
   secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
-  bucket: bjzm-1323137866
+  bucket: bjzmky-1323137866
   app_id: 1323137866
   region: ap-chongqing
   proxy: bjzm

+ 8 - 2
fs-service/src/main/resources/application-config-druid-cfryt.yml

@@ -42,8 +42,8 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-      - appId: wx17f36a56c701bdea # 第一个公众号的appid   //公众号名称
-        secret: 185030bbe7f8d7a0c16b94dd9d4ea542 # 公众号的appsecret
+      - appId: wxbf0cfcfbc92ccd72 # 第一个公众号的appid   //赤峰润元堂
+        secret: b08b0c6e763b5fa3c863b3dd114cd1c9 # 公众号的appsecret
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
 aifabu:  #爱链接
@@ -62,6 +62,12 @@ watch:
 fs :
   commonApi: http://172.26.180.67:7771
   h5CommonApi: http://172.26.180.67:7771
+  jwt:
+    # 加密秘钥
+    secret: f4e2e52034348f86b6d81e581c19jeb4
+    # token有效时长,7天,单位秒
+    expire: 31536000
+    header: AppToken
 nuonuo:
   key: 10924508
   secret: A2EB20764D304D16

+ 1 - 1
fs-service/src/main/resources/application-config-druid-gzzdy.yml

@@ -87,7 +87,7 @@ cloud_host:
 headerImg:
   imgUrl:
 ipad:
-  ipadUrl: http://ipad.dingdangtcm.cn
+  ipadUrl: http://175.178.218.55:8667
   aiApi: http://
   voiceApi: http://123.207.48.104:8009
   commonApi: http://123.207.48.104:7771

+ 3 - 3
fs-service/src/main/resources/application-druid-bjzm.yml

@@ -50,11 +50,11 @@ spring:
                     username: root
                     password: Ylrz_1q2w3e4r5t6y
                 # 初始连接数
-                initialSize: 5
+                initialSize: 50
                 # 最小连接池数量
-                minIdle: 10
+                minIdle: 50
                 # 最大连接池数量
-                maxActive: 20
+                maxActive: 500
                 # 配置获取连接等待超时的时间
                 maxWait: 60000
                 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒

+ 10 - 0
fs-service/src/main/resources/application-druid-jnmy-test.yml

@@ -232,5 +232,15 @@ isNewWxMerchant: true
 ipad:
     url: http://localhost:8999/dev-api
     companyId: 13
+#wechat:
+#    company:
+#        appid: wxd7c1e221622a0ccf
+#        secret: 70d3ed4f8eb68cca0cf525b8ce07405d
+#        redirectUri: http://ta6d97ec.natappfree.cc/callback
+#    admin:
+#        appid: wxd7c1e221622a0ccf
+#        secret: 70d3ed4f8eb68cca0cf525b8ce07405d
+#        redirectUri: http://ta6d97ec.natappfree.cc/callback
+#    isNeedScan: true
 
 

+ 11 - 0
fs-service/src/main/resources/application-druid-jnmy.yml

@@ -161,4 +161,15 @@ im:
 #是否为新商户,新商户不走mpOpenId
 isNewWxMerchant: true
 
+wechat:
+    company:
+        appid: wxd7c1e221622a0ccf
+        secret: 70d3ed4f8eb68cca0cf525b8ce07405d
+        redirectUri: https://company.jnmyunl.com/prod-api/callback
+    admin:
+        appid: wxd7c1e221622a0ccf
+        secret: 70d3ed4f8eb68cca0cf525b8ce07405d
+        redirectUri: https://admin.jnmyunl.com/prod-api/callback
+    isNeedScan: false
+
 

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä