فهرست منبع

Merge remote-tracking branch 'origin/master'

yjwang 1 هفته پیش
والد
کامیت
be4977da12
100فایلهای تغییر یافته به همراه4529 افزوده شده و 337 حذف شده
  1. 145 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserWatchCourseStatisticsController.java
  2. 118 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserWatchStatisticsController.java
  3. 103 0
      fs-admin/src/main/java/com/fs/his/controller/FsUserOnlineStateController.java
  4. 56 0
      fs-admin/src/main/java/com/fs/his/task/WatchCourseTask.java
  5. 2 1
      fs-admin/src/main/resources/application.yml
  6. 9 0
      fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java
  7. 5 1
      fs-company-app/src/main/java/com/fs/app/param/LoginParam.java
  8. 12 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  9. 35 21
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseAnswerLogsController.java
  10. 1 2
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  11. 1 1
      fs-company/src/main/java/com/fs/company/controller/fastGpt/FastGptChatSessionController.java
  12. 5 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  13. 1 2
      fs-company/src/main/java/com/fs/company/controller/store/FsUserController.java
  14. 118 0
      fs-company/src/main/java/com/fs/company/controller/store/FsUserOnlineStateController.java
  15. 15 3
      fs-company/src/main/java/com/fs/user/FsUserAdminController.java
  16. 3 2
      fs-company/src/main/resources/application.yml
  17. 49 0
      fs-qw-api-msg/src/main/java/com/fs/app/config/QWConfigProperties.java
  18. 182 13
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  19. 1 1
      fs-qw-api-msg/src/main/java/com/fs/app/controller/imgTest.java
  20. 55 0
      fs-qw-api-msg/src/main/java/com/fs/app/controller/test.java
  21. 20 3
      fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java
  22. 1 1
      fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java
  23. 2 0
      fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java
  24. 3 2
      fs-qw-api-msg/src/main/resources/application.yml
  25. 4 2
      fs-qw-api/src/main/resources/application.yml
  26. 1 1
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  27. 45 0
      fs-service/src/main/java/com/fs/company/domain/CompanyCompanyFsuser.java
  28. 20 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  29. 68 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyCompanyFsuserMapper.java
  30. 5 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  31. 5 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  32. 9 1
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  33. 11 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  34. 4 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java
  35. 15 0
      fs-service/src/main/java/com/fs/company/vo/OptionVO.java
  36. 1 1
      fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java
  37. 2 2
      fs-service/src/main/java/com/fs/course/domain/FsCourseWatchLog.java
  38. 6 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java
  39. 139 0
      fs-service/src/main/java/com/fs/course/domain/FsUserWatchCourseStatistics.java
  40. 77 0
      fs-service/src/main/java/com/fs/course/domain/FsUserWatchStatistics.java
  41. 4 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  42. 9 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  43. 7 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  44. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  45. 75 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserWatchCourseStatisticsMapper.java
  46. 74 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserWatchStatisticsMapper.java
  47. 17 0
      fs-service/src/main/java/com/fs/course/param/FsCourseAnswerLogsParam.java
  48. 1 1
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java
  49. 4 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseAnswerLogsService.java
  50. 3 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  51. 75 0
      fs-service/src/main/java/com/fs/course/service/IFsUserWatchCourseStatisticsService.java
  52. 67 0
      fs-service/src/main/java/com/fs/course/service/IFsUserWatchStatisticsService.java
  53. 92 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseAnswerLogsServiceImpl.java
  54. 2 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  55. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  56. 29 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  57. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  58. 242 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserWatchCourseStatisticsServiceImpl.java
  59. 216 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserWatchStatisticsServiceImpl.java
  60. 46 32
      fs-service/src/main/java/com/fs/course/vo/FsCourseAnswerLogsListVO.java
  61. 8 2
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseListPVO.java
  62. 63 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoAppletVO.java
  63. 101 0
      fs-service/src/main/java/com/fs/course/vo/FsUserWatchCourseStatisticsExportVO.java
  64. 23 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventLog.java
  65. 23 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventTokenLog.java
  66. 12 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeyword.java
  67. 19 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordArtificial.java
  68. 55 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordSend.java
  69. 2 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java
  70. 77 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatArtificialWords.java
  71. 56 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptEventLogTotal.java
  72. 7 4
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatMsgMapper.java
  73. 72 0
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptKeywordSendMapper.java
  74. 1 1
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptRoleMapper.java
  75. 4 0
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  76. 9 1
      fs-service/src/main/java/com/fs/fastGpt/service/IFastGptChatMsgService.java
  77. 72 0
      fs-service/src/main/java/com/fs/fastGpt/service/IFastGptKeywordSendService.java
  78. 497 196
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  79. 24 3
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptChatMsgServiceImpl.java
  80. 116 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptKeywordSendServiceImpl.java
  81. 78 0
      fs-service/src/main/java/com/fs/fastGpt/vo/FastGptChatSessionVo.java
  82. 31 0
      fs-service/src/main/java/com/fs/fastgptApi/param/DouBaoAiParam.java
  83. 118 20
      fs-service/src/main/java/com/fs/fastgptApi/util/AiImgUtil.java
  84. 28 0
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogQueue.java
  85. 154 0
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java
  86. 1 1
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  87. 96 0
      fs-service/src/main/java/com/fs/his/domain/FsUserOnlineState.java
  88. 33 0
      fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java
  89. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  90. 24 1
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  91. 76 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserOnlineStateMapper.java
  92. 11 0
      fs-service/src/main/java/com/fs/his/param/PushFgOrderConfig.java
  93. 1 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  94. 68 0
      fs-service/src/main/java/com/fs/his/service/IFsUserOnlineStateService.java
  95. 10 1
      fs-service/src/main/java/com/fs/his/service/IFsUserService.java
  96. 4 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  97. 136 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserOnlineStateServiceImpl.java
  98. 33 8
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  99. 51 0
      fs-service/src/main/java/com/fs/his/vo/FsComplaintOrderExportVO.java
  100. 13 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAndUserVo.java

+ 145 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserWatchCourseStatisticsController.java

@@ -0,0 +1,145 @@
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsUserWatchCourseStatistics;
+import com.fs.course.service.IFsUserWatchCourseStatisticsService;
+import com.fs.course.vo.FsUserWatchCourseStatisticsExportVO;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 会员看课统计-按课程统计Controller
+ *
+ * @author fs
+ * @date 2025-06-16
+ */
+@RestController
+@RequestMapping("/course/userWatchCourseStatistics")
+public class FsUserWatchCourseStatisticsController extends BaseController
+{
+    @Autowired
+    private IFsUserWatchCourseStatisticsService fsUserWatchCourseStatisticsService;
+
+    /**
+     * 查询会员看课统计-按课程统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchCourseStatistics:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        startPage();
+        List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出会员看课统计-按课程统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchCourseStatistics:export')")
+    @Log(title = "会员看课统计-按课程统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
+        ExcelUtil<FsUserWatchCourseStatistics> util = new ExcelUtil<FsUserWatchCourseStatistics>(FsUserWatchCourseStatistics.class);
+        return util.exportExcel(list, "会员观看数据明细");
+    }
+
+    /**
+     * 获取会员看课统计-按课程统计详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchCourseStatistics:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsById(id));
+    }
+
+    /**
+     * 新增会员看课统计-按课程统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchCourseStatistics:add')")
+    @Log(title = "会员看课统计-按课程统计", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        return toAjax(fsUserWatchCourseStatisticsService.insertFsUserWatchCourseStatistics(fsUserWatchCourseStatistics));
+    }
+
+    /**
+     * 修改会员看课统计-按课程统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchCourseStatistics:edit')")
+    @Log(title = "会员看课统计-按课程统计", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        return toAjax(fsUserWatchCourseStatisticsService.updateFsUserWatchCourseStatistics(fsUserWatchCourseStatistics));
+    }
+
+    /**
+     * 删除会员看课统计-按课程统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchCourseStatistics:remove')")
+    @Log(title = "会员看课统计-按课程统计", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserWatchCourseStatisticsService.deleteFsUserWatchCourseStatisticsByIds(ids));
+    }
+
+    /**
+     * 查询会员观看数据明细汇总
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchCourseStatistics:listTotal')")
+    @GetMapping("/listTotal")
+    public TableDataInfo listTotal(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        startPage();
+        List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsListTotal(fsUserWatchCourseStatistics);
+        if(!list.isEmpty()){
+            for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
+                userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
+                userWatchCourseStatistics.setOnlineRatePercent(userWatchCourseStatistics.getOnlineRate() + "%");
+            }
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出会员看课统计-按课程汇总统计列表
+     */
+    @Log(title = "会员看课统计-按课程汇总统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportTotal")
+    public AjaxResult exportTotal(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        List<FsUserWatchCourseStatistics> list = fsUserWatchCourseStatisticsService.selectFsUserWatchCourseStatisticsListTotal(fsUserWatchCourseStatistics);
+        List<FsUserWatchCourseStatisticsExportVO> listVO = list.stream().map(v -> {
+            FsUserWatchCourseStatisticsExportVO vo = new FsUserWatchCourseStatisticsExportVO();
+            BeanUtils.copyProperties(v, vo);
+            vo.setCompleteWatchRatePercent(v.getCompleteWatchRate() + "%");
+            vo.setOnlineRatePercent(v.getOnlineRate() + "%");
+            return vo;
+        }).collect(Collectors.toList());
+        ExcelUtil<FsUserWatchCourseStatisticsExportVO> util = new ExcelUtil<FsUserWatchCourseStatisticsExportVO>(FsUserWatchCourseStatisticsExportVO.class);
+        return util.exportExcel(listVO, "会员观看数据明细汇总");
+    }
+
+
+    @PostMapping("/test")
+    @ApiOperation("测试看课统计明细定时任务")
+    public void userCourseCountTask() {
+        fsUserWatchCourseStatisticsService.insertWatchCourseStatistics();
+    }
+}

+ 118 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserWatchStatisticsController.java

@@ -0,0 +1,118 @@
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsUserWatchStatistics;
+import com.fs.course.service.IFsUserWatchStatisticsService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 会员看课统计-按营期统计Controller
+ *
+ * @author fs
+ * @date 2025-06-16
+ */
+@RestController
+@RequestMapping("/course/userWatchStatistics")
+public class FsUserWatchStatisticsController extends BaseController
+{
+    @Autowired
+    private IFsUserWatchStatisticsService fsUserWatchStatisticsService;
+
+    /**
+     * 查询会员看课统计-按营期统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchStatistics:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserWatchStatistics fsUserWatchStatistics)
+    {
+        startPage();
+        List<FsUserWatchStatistics> list = fsUserWatchStatisticsService.selectFsUserWatchStatisticsList(fsUserWatchStatistics);
+        if(!list.isEmpty()){
+            for (FsUserWatchStatistics userWatchStatistics : list) {
+                userWatchStatistics.setCompleteWatchRatePercent(userWatchStatistics.getCompleteWatchRate() + "%");
+                userWatchStatistics.setOnlineRatePercent(userWatchStatistics.getOnlineRate() + "%");
+            }
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出会员看课统计-按营期统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchStatistics:export')")
+    @Log(title = "会员看课统计-按营期统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserWatchStatistics fsUserWatchStatistics)
+    {
+        List<FsUserWatchStatistics> list = fsUserWatchStatisticsService.selectFsUserWatchStatisticsList(fsUserWatchStatistics);
+        if(!list.isEmpty()){
+            for (FsUserWatchStatistics userWatchStatistics : list) {
+                userWatchStatistics.setCompleteWatchRatePercent(userWatchStatistics.getCompleteWatchRate() + "%");
+                userWatchStatistics.setOnlineRatePercent(userWatchStatistics.getOnlineRate() + "%");
+            }
+        }
+        ExcelUtil<FsUserWatchStatistics> util = new ExcelUtil<FsUserWatchStatistics>(FsUserWatchStatistics.class);
+        return util.exportExcel(list, "会员看课统计-按营期统计数据");
+    }
+
+    /**
+     * 获取会员看课统计-按营期统计详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchStatistics:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserWatchStatisticsService.selectFsUserWatchStatisticsById(id));
+    }
+
+    /**
+     * 新增会员看课统计-按营期统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchStatistics:add')")
+    @Log(title = "会员看课统计-按营期统计", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserWatchStatistics fsUserWatchStatistics)
+    {
+        return toAjax(fsUserWatchStatisticsService.insertFsUserWatchStatistics(fsUserWatchStatistics));
+    }
+
+    /**
+     * 修改会员看课统计-按营期统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchStatistics:edit')")
+    @Log(title = "会员看课统计-按营期统计", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserWatchStatistics fsUserWatchStatistics)
+    {
+        return toAjax(fsUserWatchStatisticsService.updateFsUserWatchStatistics(fsUserWatchStatistics));
+    }
+
+    /**
+     * 删除会员看课统计-按营期统计
+     */
+    @PreAuthorize("@ss.hasPermi('course:userWatchStatistics:remove')")
+    @Log(title = "会员看课统计-按营期统计", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserWatchStatisticsService.deleteFsUserWatchStatisticsByIds(ids));
+    }
+
+
+    @PostMapping("/test")
+    @ApiOperation("测试营期看课统计定时任务")
+    public void userCourseCountTask() {
+        fsUserWatchStatisticsService.insertStatistics();
+    }
+
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/his/controller/FsUserOnlineStateController.java

@@ -0,0 +1,103 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsUserOnlineState;
+import com.fs.his.service.IFsUserOnlineStateService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
+/**
+ * 用户上线情况Controller
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@RestController
+@RequestMapping("/store/userOnlineState")
+public class FsUserOnlineStateController extends BaseController
+{
+    @Autowired
+    private IFsUserOnlineStateService fsUserOnlineStateService;
+
+    /**
+     * 查询用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserOnlineState fsUserOnlineState)
+    {
+        startPage();
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:export')")
+    @Log(title = "用户上线情况", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserOnlineState fsUserOnlineState)
+    {
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        ExcelUtil<FsUserOnlineState> util = new ExcelUtil<FsUserOnlineState>(FsUserOnlineState.class);
+        return util.exportExcel(list, "未上线会员");
+    }
+
+    /**
+     * 获取用户上线情况详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return AjaxResult.success(fsUserOnlineStateService.selectFsUserOnlineStateById(userId));
+    }
+
+    /**
+     * 新增用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:add')")
+    @Log(title = "用户上线情况", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.insertFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 修改用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:edit')")
+    @Log(title = "用户上线情况", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.updateFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 删除用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:remove')")
+    @Log(title = "用户上线情况", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(fsUserOnlineStateService.deleteFsUserOnlineStateByIds(userIds));
+    }
+
+    @ApiOperation("新增/更新未上线人员-定时任务")
+    @PostMapping("/testTask")
+    public void test(){
+        fsUserOnlineStateService.insertUserNotOnline();
+    }
+}

+ 56 - 0
fs-admin/src/main/java/com/fs/his/task/WatchCourseTask.java

@@ -0,0 +1,56 @@
+package com.fs.his.task;
+
+import com.fs.course.service.IFsUserWatchCourseStatisticsService;
+import com.fs.course.service.IFsUserWatchStatisticsService;
+import com.fs.erp.service.IErpOrderService;
+import com.fs.his.service.IFsUserOnlineStateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 定时任务调度测试
+ *
+ * @author fs
+ */
+@Component("storeTask")
+public class WatchCourseTask
+{
+    @Autowired
+    IErpOrderService erpOrderService;
+
+    @Autowired
+    private IFsUserWatchCourseStatisticsService fsUserWatchCourseStatisticsService;
+
+    @Autowired
+    private IFsUserWatchStatisticsService fsUserWatchStatisticsService;
+
+    @Autowired
+    private IFsUserOnlineStateService fsUserOnlineStateService;
+
+
+    /**
+     * 添加看课汇总统计
+     */
+    public void insertWatchStatistics(){
+        /***************************************进入营期会员看课汇总统计定时任务****************************************/
+        fsUserWatchStatisticsService.insertStatistics();
+        /***************************************营期会员看课汇总统计定时任务结束***************************************/
+    }
+
+    /**
+     * 添加看课明细统计
+     */
+    public void insertWatchCourseStatistics(){
+        /***************************************进入营期会员看课明细统计定时任务*******************************/
+        fsUserWatchCourseStatisticsService.insertWatchCourseStatistics();
+        /***************************************进入营期会员看课明细统计定时任务结束**********************************************/
+    }
+
+    /**
+     * 定时查询未上线的用户
+     */
+    public void insertUserNotOnline(){
+        fsUserOnlineStateService.insertUserNotOnline();
+    }
+
+}

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

@@ -4,9 +4,10 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-myhk-test
+#    active: druid-myhk-test
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz
 #    active: druid-sft
+    active: druid-hzyy-test
 

+ 9 - 0
fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java

@@ -50,6 +50,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
+import static com.fs.his.utils.PhoneUtil.encryptPhone;
+
 @Slf4j
 @Api(tags = "用户会员相关接口")
 @RestController
@@ -377,4 +379,11 @@ public class FsUserController extends AppBaseController {
         userCourseCountService.insertFsUserCourseCountTask();
     }
 
+    @GetMapping("/test1")
+    @ApiOperation("1")
+    public void aa(String phone) {
+        String s = encryptPhone(phone);
+        System.out.println(s);
+    }
+
 }

+ 5 - 1
fs-company-app/src/main/java/com/fs/app/param/LoginParam.java

@@ -1,5 +1,7 @@
 package com.fs.app.param;
 
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
@@ -7,6 +9,7 @@ import javax.validation.constraints.NotBlank;
 
 
 @Data
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class LoginParam {
     @NotBlank(message = "请填写帐号")
     private String account;
@@ -14,6 +17,7 @@ public class LoginParam {
     private String password;
 
     private String jpushId;
-
+    @JsonAlias({"appid", "appId"})
     private String appid;
+
 }

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

@@ -416,4 +416,16 @@ public class CompanyUserController extends BaseController
             return AjaxResult.error("操作失败");
         }
     }
+
+    @Log(title = "是否允许所有方式注册会员", businessType = BusinessType.UPDATE)
+    @PutMapping("/allowedAllRegister")
+    public AjaxResult isAllowedAllRegister(@RequestParam Boolean status, @RequestBody List<Long> userIds) {
+        Boolean r = companyUserService.isAllowedAllRegister(status, userIds);
+        if (r) {
+            return AjaxResult.success();
+        } else {
+            return AjaxResult.error("操作失败");
+        }
+    }
+
 }

+ 35 - 21
fs-company/src/main/java/com/fs/company/controller/course/FsCourseAnswerLogsController.java

@@ -1,6 +1,7 @@
 package com.fs.company.controller.course;
 
 import com.fs.common.annotation.Log;
+import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
@@ -44,16 +45,21 @@ public class FsCourseAnswerLogsController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(FsCourseAnswerLogsParam param)
     {
-
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
-            param.setPhone(encryptPhone(param.getPhoneMk()));
+            param.setPhone(param.getPhoneMk());
         }
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
 
-        startPage();
-        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
-        return getDataTable(list);
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVONew(param);
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVONewCount(param));
+
+        return rspData;
     }
 
     /**
@@ -64,17 +70,22 @@ public class FsCourseAnswerLogsController extends BaseController
     public TableDataInfo myList(FsCourseAnswerLogsParam param)
     {
 
-
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
-
+        param.setCompanyUserId(loginUser.getUser().getUserId());
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
-            param.setPhone(encryptPhone(param.getPhoneMk()));
+            param.setPhone(param.getPhoneMk());
         }
-        startPage();
 
-        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
-        return getDataTable(list);
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVONew(param);
+
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVONewCount(param));
+
+        return rspData;
     }
     /**
      * 导出答题日志列表
@@ -85,31 +96,34 @@ public class FsCourseAnswerLogsController extends BaseController
     public AjaxResult export(FsCourseAnswerLogsParam param)
     {
 
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
+
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
-            param.setPhone(encryptPhone(param.getPhoneMk()));
+            param.setPhone(param.getPhoneMk());
         }
-        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVONew(param);
         ExcelUtil<FsCourseAnswerLogsListVO> util = new ExcelUtil<FsCourseAnswerLogsListVO>(FsCourseAnswerLogsListVO.class);
-        return util.exportExcel(list, "答题日志数据");
+        return util.exportExcel(list, "答题记录数据");
     }
 
     /**
-     * 导出答题日志列表
+     * 导出我的答题日志列表
      */
     @PreAuthorize("@ss.hasPermi('course:courseAnswerLog:myExport')")
-    @Log(title = "答题日志", businessType = BusinessType.EXPORT)
+    @Log(title = "我的答题日志", businessType = BusinessType.EXPORT)
     @GetMapping("/myExport")
     public AjaxResult myExport(FsCourseAnswerLogsParam param)
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
-
+        param.setCompanyUserId(loginUser.getUser().getUserId());
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
-            param.setPhone(encryptPhone(param.getPhoneMk()));
+            param.setPhone(param.getPhoneMk());
         }
-        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVO(param);
+        List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVONew(param);
         ExcelUtil<FsCourseAnswerLogsListVO> util = new ExcelUtil<FsCourseAnswerLogsListVO>(FsCourseAnswerLogsListVO.class);
-        return util.exportExcel(list, "答题日志数据");
+        return util.exportExcel(list, "我的答题记录数据");
     }
 
 //    /**

+ 1 - 2
fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java

@@ -226,7 +226,6 @@ public class FsCourseWatchLogController extends BaseController
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
-        param.setCompanyUserId( loginUser.getUser().getUserId());
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
         return util.exportExcel(list, "短链课程看课记录数据");
@@ -241,7 +240,7 @@ public class FsCourseWatchLogController extends BaseController
     public AjaxResult myExport(FsCourseWatchLogListParam param)
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-//        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyUserId( loginUser.getUser().getUserId());
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/fastGpt/FastGptChatSessionController.java

@@ -79,7 +79,7 @@ public class FastGptChatSessionController extends BaseController
     {
         FastGptChatSessionCVO sessionCVO = fastGptChatSessionService.selectFastGptChatSessionCVOBySessionId(sessionId);
 
-        List<FastGptChatMsgCVO> list = fastGptChatMsgService.selectFastGptChatMsgCVOBySessionId(sessionId);
+        List<FastGptChatMsgCVO> list = fastGptChatMsgService.selectFastGptChatMsgCVOBySessionId(sessionId,sessionCVO.getUserId());
 
         return R.ok().put("data",sessionCVO).put("list",list);
     }

+ 5 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -191,6 +191,11 @@ public class QwUserController extends BaseController
     public R TwoCodeStatus(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.getTwoCodeStatus(loginParam);
     }
+
+    @PostMapping("/getQwIpadStatus")
+    public R getQwIpadStatus(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.getLoginQwIpadStatus(loginParam);
+    }
     /**
     * 直接授权key
     */

+ 1 - 2
fs-company/src/main/java/com/fs/company/controller/store/FsUserController.java

@@ -24,7 +24,6 @@ import com.fs.his.vo.UserVo;
 import com.fs.qw.dto.UserProjectDTO;
 import com.fs.store.param.h5.FsUserPageListParam;
 import com.fs.store.vo.h5.FsUserPageListVO;
-import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -247,7 +246,7 @@ public class FsUserController extends BaseController
         PageInfo<FsUserPageListVO> fsUserPageListVOPageInfo = fsUserService.selectFsUserPageList(param);
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("rows", fsUserPageListVOPageInfo.getList());
-        map.put("total", fsUserPageListVOPageInfo.getList().size());
+        map.put("total", fsUserPageListVOPageInfo.getTotal());
         return R.ok(map);
     }
 

+ 118 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsUserOnlineStateController.java

@@ -0,0 +1,118 @@
+package com.fs.company.controller.store;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsUserOnlineState;
+import com.fs.his.service.IFsUserOnlineStateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 用户上线情况Controller
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@RestController
+@RequestMapping("/store/userOnlineState")
+public class FsUserOnlineStateController extends BaseController
+{
+    @Autowired
+    private IFsUserOnlineStateService fsUserOnlineStateService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserOnlineState fsUserOnlineState)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        // 如果是管理员,可以查询当前公司所有数据
+        if(loginUser.getUser().isAdmin()){
+            fsUserOnlineState.setCompanyId( loginUser.getCompany().getCompanyId());
+        } else{
+            fsUserOnlineState.setCompanyUserId( loginUser.getUser().getUserId());
+        }
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户上线情况列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:export')")
+    @Log(title = "用户上线情况", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserOnlineState fsUserOnlineState)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        // 如果是管理员,可以查询当前公司所有数据
+        if(loginUser.getUser().isAdmin()){
+            fsUserOnlineState.setCompanyId( loginUser.getCompany().getCompanyId());
+        } else{
+            fsUserOnlineState.setCompanyUserId( loginUser.getUser().getUserId());
+        }
+        List<FsUserOnlineState> list = fsUserOnlineStateService.selectFsUserOnlineStateList(fsUserOnlineState);
+        ExcelUtil<FsUserOnlineState> util = new ExcelUtil<FsUserOnlineState>(FsUserOnlineState.class);
+        return util.exportExcel(list, "未上线会员");
+    }
+
+    /**
+     * 获取用户上线情况详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:query')")
+    @GetMapping(value = "/{userId}")
+    public AjaxResult getInfo(@PathVariable("userId") Long userId)
+    {
+        return AjaxResult.success(fsUserOnlineStateService.selectFsUserOnlineStateById(userId));
+    }
+
+    /**
+     * 新增用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:add')")
+    @Log(title = "用户上线情况", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.insertFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 修改用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:edit')")
+    @Log(title = "用户上线情况", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserOnlineState fsUserOnlineState)
+    {
+        return toAjax(fsUserOnlineStateService.updateFsUserOnlineState(fsUserOnlineState));
+    }
+
+    /**
+     * 删除用户上线情况
+     */
+    @PreAuthorize("@ss.hasPermi('store:userOnlineState:remove')")
+    @Log(title = "用户上线情况", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        return toAjax(fsUserOnlineStateService.deleteFsUserOnlineStateByIds(userIds));
+    }
+
+}

+ 15 - 3
fs-company/src/main/java/com/fs/user/FsUserAdminController.java

@@ -18,7 +18,6 @@ import com.fs.qw.domain.CustomerTransferApproval;
 import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.service.ICustomerTransferApprovalService;
 import com.fs.store.param.h5.FsUserPageListParam;
-import com.fs.store.vo.h5.FsUserPageListVO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
@@ -29,8 +28,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.Date;
 
-import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
-
 @Api(tags = "会员管理接口")
 @RestController
 @Slf4j
@@ -69,6 +66,21 @@ public class FsUserAdminController extends BaseController {
         return fsUserService.selectFsUserPageListNew(param);
     }
 
+    @PreAuthorize("@ss.hasPermi('user:fsUser:myList')")
+    @PostMapping("/myList")
+    @ApiOperation("我的会员列表(与移动端使用的相同查询)")
+    public TableDataInfo pageMyList(@RequestBody FsUserPageListParam param) {
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        param.setCompanyUserId(String.valueOf(loginUser.getUser().getUserId()));
+
+        if(param.getCompanyUserId() == null) {
+            throw new IllegalArgumentException("当前销售不存在!");
+        }
+        return fsUserService.selectFsUserPageListNew(param);
+    }
+
     @PostMapping("/auditUser")
     @ApiOperation("审核用户(移除小黑屋)")
     public R auditUser(@RequestBody String[] userIds) {

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

@@ -4,10 +4,11 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-    active: dev
-#    active: druid-jzzx
+#    active: dev
+#    active: druid-jzzx-test
 #    active: druid-hdt
 #    active: druid-sxjz
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft
+    active: druid-hzyy

+ 49 - 0
fs-qw-api-msg/src/main/java/com/fs/app/config/QWConfigProperties.java

@@ -0,0 +1,49 @@
+package com.fs.app.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class QWConfigProperties {
+
+    @Value("${custom.token}")
+    private String token;
+
+    @Value("${custom.encoding-aes-key}")
+    private String encodingAesKey;
+
+    @Value("${custom.corp-id}")
+    private String corpId;
+    @Value("${custom.secret}")
+    private String secret;
+
+    @Value("${custom.private-key-path}")
+    private String privateKeyPath;
+
+    @Value("${custom.webhook-url}")
+    private String webhookUrl;
+
+    public String getToken() {
+        return token;
+    }
+
+    public String getEncodingAesKey() {
+        return encodingAesKey;
+    }
+
+    public String getCorpId() {
+        return corpId;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public String getPrivateKeyPath() {
+        return privateKeyPath;
+    }
+
+    public String getWebhookUrl() {
+        return webhookUrl;
+    }
+}

+ 182 - 13
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -1,12 +1,22 @@
 package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.uuid.IdUtils;
 import com.fs.fastGpt.service.AiHookService;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.service.IFsStoreOrderService;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwUserVoiceLogService;
+import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.params.GetQwSopLogsByJsApiParam;
+import com.fs.voice.utils.StringUtil;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
 import io.swagger.annotations.Api;
@@ -15,8 +25,7 @@ import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -37,6 +46,70 @@ public class QwMsgController {
     WxWorkService wxWorkService;
     @Autowired
     IQwUserVoiceLogService qwUserVoiceLogService;
+    @Autowired
+    IFsStoreOrderService fsStoreOrderService;
+    @Autowired
+    IQwExternalContactService externalContactService;
+    @Autowired
+    QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    SopUserLogsInfoMapper sopUserLogsInfoMapper;
+    @Autowired
+    QwSopLogsMapper qwSopLogsMapper;
+
+    @GetMapping("/sendExpressInfo/{orderId}")
+    public R sendExpressInfo(@PathVariable Long orderId){
+        FsStoreOrder order = fsStoreOrderService.selectFsStoreOrderByOrderId(orderId);
+        if(order != null && order.getUserId() != null){
+            List<QwExternalContact> qwExternalContact = externalContactService.selectQwExternalContactByFsUserId(order.getUserId());
+            if(qwExternalContact != null && !qwExternalContact.isEmpty()){
+                for (QwExternalContact externalContact : qwExternalContact) {
+                    Long qwUserId = externalContact.getQwUserId();
+                    if(qwUserId != null ){
+                        QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+                        if(qwUser != null && qwUser.getUid() != null && qwUser.getServerId() != null && qwUser.getServerStatus() == 1 && qwUser.getIpadStatus() == 1){
+                            WxWorkUserId2VidDTO wxWorkUserId2VidDTO = new WxWorkUserId2VidDTO();
+                            wxWorkUserId2VidDTO.setOpenid(Collections.singletonList(externalContact.getExternalUserId()));
+                            wxWorkUserId2VidDTO.setUuid(qwUser.getUid());
+                            WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.UserId2Vid(wxWorkUserId2VidDTO,qwUser.getServerId());
+                            List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+                            StringBuilder sBuilder = new StringBuilder();
+                            if(data != null && !data.isEmpty()){
+                                Long sendId = data.get(0).getUser_id();
+                                switch (order.getStatus())
+                                {
+                                    case -1:
+                                    case -2:
+                                    case 1:
+                                        break;
+                                    case 2:
+                                        sBuilder.append("您好,您购买的").append(order.getPackageName()).append("正在准备发货,请耐心等待;\n").append("\uD83C\uDF39\uD83C\uDF39\uD83C\uDF39");
+                                        break;
+                                    case 3:
+                                    case 4:
+                                    case 5:
+                                        break;
+                                }
+                                if(!"".contentEquals(sBuilder)){
+                                    //2.发送模板中的文字内容
+                                    String content = sBuilder.toString();
+                                    WxWorkSendTextMsgDTO wxWorkSendTextMsgDTO = new WxWorkSendTextMsgDTO();
+                                    wxWorkSendTextMsgDTO.setSend_userid(sendId);
+                                    wxWorkSendTextMsgDTO.setUuid(qwUser.getUid());
+                                    wxWorkSendTextMsgDTO.setContent(content);
+                                    wxWorkSendTextMsgDTO.setIsRoom(false);
+                                    wxWorkService.SendTextMsg(wxWorkSendTextMsgDTO,qwUser.getServerId());
+                                }
+                                return R.ok();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
 
     @PostMapping("/callback/{serverId}")
     @ResponseBody
@@ -80,7 +153,7 @@ public class QwMsgController {
                     WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
                     wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
                     wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    System.out.println("调用退出登录");
+                    log.info("调用退出登录");
                     break;
                 }
                 if (!qu.getQwUserId().equals(jsonObject.get("acctid"))){
@@ -89,7 +162,7 @@ public class QwMsgController {
                     WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
                     wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
                     wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    System.out.println("调用退出登录");
+                    log.info("调用退出登录");
                     break;
                 }
                 QwUser qwUser = new QwUser();
@@ -101,39 +174,45 @@ public class QwMsgController {
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),104001,10, TimeUnit.MINUTES);
                 break;
             case 100006:
-                System.out.println("企业切换");
+
                 break;
             case 100004:
                 System.out.println("需要验证二维码消息");
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100004,10, TimeUnit.MINUTES);
                 break;
             case 100012:
-                System.out.println("需要二次验证");
+                log.info("需要二次验证:"+wxWorkMsgResp.getJson());
+
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100012,10, TimeUnit.MINUTES);
 
                 break;
             case 100005:
-                System.out.println("手机端结束登录");
+
                 log.info("手机端结束登录:"+wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
             case 100008:
-                System.out.println("当前账号在其他设备登录");
-                log.info("当前账号在其他设备登录:"+wxWorkMsgResp.getJson());
-                qwUserStatus(wxWorkMsgResp.getUuid(),0);
+                QwUser vidUser = qwUserMapper.selectQwUserById(id);
+                if (vidUser.getUid().equals(wxWorkMsgResp.getUuid())){
+                    log.info("当前账号在其他设备登录:"+wxWorkMsgResp.getJson());
+                    qwUserStatus(wxWorkMsgResp.getUuid(),0);
+                }
+                log.info("当前账号重新登录:"+wxWorkMsgResp.getJson());
                 break;
             case 100007:
-                System.out.println("异常断开");
                 log.info("异常断开:"+wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
             case 100009:
-                System.out.println("二次验证");
                 log.info("二次验证:"+wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
+            case 102001:
+            case 102002:
+                WxWorkMessageDTO wxWorkMessageDTO1 = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
+                handleSopBlockOrDel(id,wxWorkMessageDTO1,wxWorkMsgResp.getUuid(),5);
+                break;
             case 102000:
-
                 WxWorkMessageDTO wxWorkMessageDTO = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
                 if (wxWorkMessageDTO.getIs_room()!=0){
                     break;
@@ -155,6 +234,15 @@ public class QwMsgController {
                         ste.setUuid(wxWorkMsgResp.getUuid());
                         WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
                         System.out.println(dto);
+                        if(dto.getErrcode() != 0){
+                            try {
+                                TimeUnit.SECONDS.sleep(1); // 阻塞1秒
+                            } catch (InterruptedException e) {
+                                Thread.currentThread().interrupt(); // 处理中断异常
+                                System.out.println("第一次语音转换失败");
+                            }
+                            dto = wxWorkService.SpeechToTextEntity(ste, serverId);
+                        }
                         WxwSpeechToTextEntityRespDTO data = dto.getData();
                         content = data.getText();
                         System.out.println("语音消息"+content);
@@ -239,6 +327,87 @@ public class QwMsgController {
         return map;
     }
 
+    /**
+     * 处理被拉黑的用户
+     * @param qwUserId
+     * @param wxWorkMessageDTO
+     * @param uuid
+     * @param status
+     */
+    private void handleSopBlockOrDel(Long qwUserId,WxWorkMessageDTO wxWorkMessageDTO,String uuid, Integer status){
+        QwUser user = qwUserMapper.selectQwUserById(qwUserId);
+        //查询接收人
+        if(user==null){
+            System.out.println("查询接收人为空");
+        }
+        if(user.getFastGptRoleId()==null){
+            System.out.println("未绑定角色");
+        }
+        Long serverId = user.getServerId();
+        System.out.println("服务器id"+serverId);
+        if (serverId == null) {
+            System.out.println("服务id为空");
+        }
+
+        WxWorkVid2UserIdDTO wxWorkVid2UserIdDTO = new WxWorkVid2UserIdDTO();
+        wxWorkVid2UserIdDTO.setUser_id(Arrays.asList(wxWorkMessageDTO.getReceiver()));
+        wxWorkVid2UserIdDTO.setUuid(uuid);
+        //下面的方法是返回当前对象
+        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.Vid2UserId(wxWorkVid2UserIdDTO,serverId);
+        List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+        if (data==null|| data.isEmpty()){
+
+            System.out.println("未获取到extId"+wxWorkVid2UserIdDTO);
+        }
+        com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO dto = data.get(0);
+
+        QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(dto.getOpenid(), user.getCorpId(),user.getQwUserId());
+        if (qwExternalContacts==null){
+            System.out.println("没有外部联系人");
+        }
+
+        //处理拉黑的
+        String appKey = user.getAppKey();
+        //客户编号
+        String receiverOpenid = qwExternalContacts.getExternalUserId();
+
+        if (StringUtil.strIsNullOrEmpty(appKey) || StringUtil.strIsNullOrEmpty(receiverOpenid)){
+            log.error("删除或拉黑-处理营期失败数据不对:"+appKey+"|"+receiverOpenid);
+            return;
+        }
+
+        try {
+            QwUser qwUser = qwUserMapper.selectQwUserByAppKey(appKey);
+            if (qwUser!=null){
+
+                //修改客户状态
+                QwExternalContact contact=new QwExternalContact();
+                contact.setStatus(status);
+                contact.setUserId(qwUser.getQwUserId());
+                contact.setCorpId(qwUser.getCorpId());
+                contact.setExternalUserId(receiverOpenid);
+                qwExternalContactMapper.updateQwExternalContactByUseridBlock(contact);
+
+
+                log.info("删除或拉黑-处理营期-"+qwUser.getQwUserId()+"|"+qwUser.getCorpId()+"|"+receiverOpenid);
+
+                //删除营期
+                sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactId(qwUser.getQwUserId(),qwUser.getCorpId(),receiverOpenid);
+
+                //删除待发送记录
+                GetQwSopLogsByJsApiParam apiParam=new GetQwSopLogsByJsApiParam();
+                apiParam.setQwUserId(qwUser.getQwUserId());
+                apiParam.setCorpId(qwUser.getCorpId());
+                apiParam.setExternalUserId(receiverOpenid);
+                qwSopLogsMapper.deleteQwSopLogsByJsApi(apiParam);
+            }
+
+        }catch (Exception e){
+            log.error("删除或拉黑-处理营期失败:"+appKey+"|"+receiverOpenid+"|"+e.getMessage());
+        }
+
+    }
+
     /**
      * 处理图片消息
      * @param serverId          服务器ID

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/app/controller/imgTest.java

@@ -18,7 +18,7 @@ public class imgTest {
             String apiKey = "208d3549-8dc9-4ef6-b3fa-5aa358f1ab20";
             String requestBody = String.format(
                     "{" +
-                            "\"model\": \"doubao-vision-lite-32k-241015\"," +
+                            "\"model\": \"doubao-1-5-thinking-vision-pro-250428\"," +
                             "\"messages\": [{" +
                             "\"role\": \"user\"," +
                             "\"content\": [" +

+ 55 - 0
fs-qw-api-msg/src/main/java/com/fs/app/controller/test.java

@@ -0,0 +1,55 @@
+package com.fs.app.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Excel;
+import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastgptApi.util.AiImgUtil;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.wxwork.dto.WxwSilkVoceDTO;
+import com.fs.wxwork.utils.WxWorkHttpUtil;
+
+import javax.validation.constraints.Null;
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class test {
+    public static void main(String[] args) {
+
+        AiImgUtil aiImgUtil = new AiImgUtil();
+        String imageParse = aiImgUtil.getImageParse("https://cos.his.cdwjyyh.com/fs/20250606/098d6e12acf747548372ad61bdade4e3.jpg");
+        System.out.println(imageParse);
+    }
+
+    private static String imgEmoticon(String imageParse) {
+        Pattern img = Pattern.compile("【表情包:(.*?)】", Pattern.DOTALL);
+        Matcher imgMatcher = img.matcher(imageParse);
+        while  (imgMatcher.find()) {
+            String imgTag = imgMatcher.group(1).trim();
+            if(imgTag!=null&&!imgTag.equals("")){
+                return imgTag;
+            }
+
+        }
+        return null;
+    }
+
+
+    void voice(){
+        String  url="http://162.14.193.126:8009/app/common/voice?voice="+"你好"+"&id="+2020;
+        String json = WxWorkHttpUtil.get(url);
+        System.out.println(json);
+        WxwSilkVoceDTO wxwSilkVoceDTO = JSON.parseObject(json, WxwSilkVoceDTO.class);
+    }
+
+
+    void img(){
+        AiImgUtil aiImgUtil = new AiImgUtil();
+        String imageParse = aiImgUtil.getImageParse("https://cos.his.cdwjyyh.com/fs/20250604/e8458ed36e534135b50e7459e67b19af.jpg");
+        System.out.println(imageParse);
+    }
+
+}

+ 20 - 3
fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -21,12 +21,16 @@ import java.util.Map;
 
 @Configuration
 public class DataSourceConfig {
-
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource.sop.druid.master")
     public DataSource sopDataSource() {
         return new DruidDataSource();
     }
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.clickhouse")
+    public DataSource clickhouseDataSource() {
+        return new DruidDataSource();
+    }
 
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.master")
@@ -34,13 +38,26 @@ public class DataSourceConfig {
         return new DruidDataSource();
     }
 
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.slave")
+    public DataSource slaveDataSource() {
+        return new DruidDataSource();
+    }
+
 
 
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,
+                                        @Qualifier("masterDataSource") DataSource masterDataSource,
+                                        @Qualifier("sopDataSource") DataSource sopDataSource,
+                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+
+        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource); // Ensure matching key
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 
@@ -49,7 +66,7 @@ public class DataSourceConfig {
      */
     @SuppressWarnings({ "rawtypes", "unchecked" })
     @Bean
-    @ConditionalOnProperty(name = "spring.datasource.mysql.druid.statViewServlet.enabled", havingValue = "true")
+    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
     public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
     {
         // 获取web监控页面的参数

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java

@@ -26,7 +26,7 @@ import java.util.List;
 
 /**
  * Mybatis支持*匹配扫描包
- *
+ * 
 
  */
 @Configuration

+ 2 - 0
fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -110,6 +110,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 ).permitAll()
 
                 .antMatchers("/**").anonymous()
+                .antMatchers("**/errorLogUpload").anonymous()
                 .antMatchers("/msg/**").anonymous()
                 .antMatchers("/msg/**/**").anonymous()
                 .antMatchers("/msg").anonymous()
@@ -125,6 +126,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/*/api-docs").anonymous()
                 .antMatchers("/druid/**").anonymous()
                 .antMatchers("/qw/data/**").anonymous()
+                .antMatchers("/qw/test/**").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()

+ 3 - 2
fs-qw-api-msg/src/main/resources/application.yml

@@ -1,9 +1,10 @@
 server:
-  port: 8006
+  port: 8667
 # Spring配置
 spring:
   profiles:
-    active: dev
+#    active: dev
 #    active: druid-jzzx
 #    active: druid-hdt
 #    active: druid-sxjz
+    active: druid-hzyy-test

+ 4 - 2
fs-qw-api/src/main/resources/application.yml

@@ -1,11 +1,13 @@
 server:
   # 服务器的HTTP端口,默认为8080
-  port: 8006
+  port: 8007
 
 # Spring配置
 spring:
   profiles:
-    active: dev
+#    active: dev
 #    active: druid-hdt
 #    active: druid-sft
 #    active: druid-myhk
+    active: config-druid-hzyy
+

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

@@ -911,7 +911,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                             createParam.setCompanyUserId(Long.parseLong(companyUserId));
                             createParam.setCompanyId(Long.parseLong(companyId));
                             createParam.setChatId(logVo.getChatId());
-                            createParam.setQwUserId(Long.parseLong(qwUserId));
+                            createParam.setQwUserId(Long.valueOf(qwUserId));
                             createParam.setDays(setting.getExpiresDays());
                             R createLink = courseLinkService.createRoomLinkUrl(createParam);
                             if (createLink.get("code").equals(500)) {

+ 45 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyCompanyFsuser.java

@@ -0,0 +1,45 @@
+package com.fs.company.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 销售绑定用户(一个用户绑定唯一一个销售)对象 company_company_fsuser
+ *
+ * @author fs
+ * @date 2025-04-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyCompanyFsuser extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 销售id */
+    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 会员id */
+    @Excel(name = "会员id")
+    private Long userId;
+
+    @Excel(name = "企微外部联系人id")
+    private Long qwContactId;
+
+
+    /** 状态 0:禁用 1:正常 */
+    @Excel(name = "状态 0:禁用 1:正常")
+    private Integer status;
+
+    /** 绑定类型 0:链接二维码 1:看课 */
+    private Integer bindType;
+
+
+}

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

@@ -142,6 +142,19 @@ public class CompanyUser extends BaseEntity
 
     private String addressId;
 
+    //绑定二维码
+    private String bindCode;
+
+    private String imNickName;
+
+    public String getImNickName() {
+        return imNickName;
+    }
+
+    public void setImNickName(String imNickName) {
+        this.imNickName = imNickName;
+    }
+
     /** 看课域名 */
     private String domain;
 
@@ -526,4 +539,11 @@ public class CompanyUser extends BaseEntity
     public void setPosts(List<CompanyPost> posts) {
         this.posts = posts;
     }
+    public String getBindCode() {
+        return bindCode;
+    }
+
+    public void setBindCode(String bindCode) {
+        this.bindCode = bindCode;
+    }
 }

+ 68 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyCompanyFsuserMapper.java

@@ -0,0 +1,68 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyCompanyFsuser;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 销售绑定用户Mapper接口
+ *
+ * @author fs
+ * @date 2025-04-09
+ */
+public interface CompanyCompanyFsuserMapper extends BaseMapper<CompanyCompanyFsuser>{
+    /**
+     * 查询销售绑定用户
+     *
+     * @param id 销售绑定用户主键
+     * @return 销售绑定用户
+     */
+    CompanyCompanyFsuser selectCompanyCompanyUserById(Long id);
+
+    /**
+     * 查询销售绑定用户列表
+     *
+     * @param companyCompanyUser 销售绑定用户
+     * @return 销售绑定用户集合
+     */
+    List<CompanyCompanyFsuser> selectCompanyCompanyUserList(CompanyCompanyFsuser companyCompanyUser);
+
+    /**
+     * 新增销售绑定用户
+     *
+     * @param companyCompanyUser 销售绑定用户
+     * @return 结果
+     */
+    int insertCompanyCompanyUser(CompanyCompanyFsuser companyCompanyUser);
+
+    /**
+     * 修改销售绑定用户
+     *
+     * @param companyCompanyUser 销售绑定用户
+     * @return 结果
+     */
+    int updateCompanyCompanyUser(CompanyCompanyFsuser companyCompanyUser);
+
+    /**
+     * 删除销售绑定用户
+     *
+     * @param id 销售绑定用户主键
+     * @return 结果
+     */
+    int deleteCompanyCompanyUserById(Long id);
+
+    /**
+     * 批量删除销售绑定用户
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyCompanyUserByIds(Long[] ids);
+
+    CompanyCompanyFsuser getInfoByUserId(@Param("userId") String userId);
+
+    List<CompanyCompanyFsuser> selectNoHistoryApp(@Param("limit") Integer limit);
+
+}

+ 5 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -2,6 +2,8 @@ package com.fs.company.mapper;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Set;
+
 import com.fs.company.domain.Company;
 import com.fs.company.param.CompanyParam;
 import com.fs.company.vo.CompanyCrmVO;
@@ -184,4 +186,7 @@ public interface CompanyMapper
      * 通过企业id批量查询
      * **/
     List<Company> selectCompanyByIds(@Param("ids") List<Long> ids);
+
+    List<Company> selectCompanyByIds2(@Param("companyIds") Set<Long> companyIds);
+
 }

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

@@ -302,5 +302,10 @@ public interface CompanyUserMapper
      * **/
     void batchUpdateUserDept(@Param("companyUserList") List<CompanyUser> companyUserList);
 
+    CompanyUser selectCompanyUserByCompanyUserId(Long companyUserId);
+
     String selectCompanyUserNameByIds(@Param("companyUserIds")String companyUserIds);
+
+    int updateAllowedAllRegister(@Param("status") boolean status, @Param("userIds")List<Long> userIds);
+
 }

+ 9 - 1
fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java

@@ -14,7 +14,6 @@ import com.fs.qw.dto.UserProjectDTO;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
-import com.fs.store.vo.h5.UserListPageVO;
 import com.fs.wxUser.domain.CompanyWxUser;
 
 import java.util.List;
@@ -212,4 +211,13 @@ public interface ICompanyUserService {
     List<QwOptionsVO> selectQwUserListLikeName(Map<String, Object> params);
 
     String selectCompanyUserByStrIds(String companyUserIds);
+
+    /**
+     * 批量设置是否允许所有方式注册会员
+     * @param status 开/关
+     * @param userIds 员工id
+     * @return true/false
+     */
+    Boolean isAllowedAllRegister(boolean status,  List<Long> userIds);
+
 }

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

@@ -613,4 +613,15 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public String selectCompanyUserByStrIds(String companyUserIds) {
         return companyUserMapper.selectCompanyUserNameByIds(companyUserIds);
     }
+
+    @Override
+    public Boolean isAllowedAllRegister(boolean status, List<Long> userIds) {
+        try {
+            companyUserMapper.updateAllowedAllRegister(status, userIds);
+        } catch (RuntimeException e) {
+            throw new ServiceException("操作异常");
+        }
+        return true;
+    }
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java

@@ -124,4 +124,8 @@ public class CompanyUserQwListVO extends BaseEntity {
 
     /** 是否需要单独注册会员,1-是,0-否*/
     private Integer isNeedRegisterMember;
+
+    /** 是否允许所有方式注册会员,1-是,0-否,默认1(用于个微注册会员) */
+    private Integer isAllowedAllRegister;
+
 }

+ 15 - 0
fs-service/src/main/java/com/fs/company/vo/OptionVO.java

@@ -0,0 +1,15 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+@Data
+public class OptionVO {
+    /**
+     * 选项名称
+     */
+    private String label;
+    /**
+     * 选项值
+     */
+    private Long value;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java

@@ -65,7 +65,7 @@ public class WxMaConfiguration {
                 if (appid.equals(courseMaConfig.getAppid())) {
                     continue;
                 }
-                if (courseMaConfig.getType().equals("1")){
+                if (courseMaConfig.getType() != null && courseMaConfig.getType().equals("1")){
                     WxMaConfig.Config wxMaConfig = new WxMaConfig.Config();
                     BeanUtils.copyProperties(courseMaConfig, wxMaConfig);
                     c.add(wxMaConfig);

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

@@ -47,8 +47,8 @@ public class FsCourseWatchLog extends BaseEntity
     @Excel(name = "播放时长")
     private Long duration;
 
-    /** 分享人企微userId */
-    @Excel(name = "分享人企微userId")
+    /** 分享人企微userId 主键 */
+    @Excel(name = "分享人企微userId 主键")
     private Long qwUserId;
 
     /** 销售id */

+ 6 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java

@@ -9,6 +9,7 @@ import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -112,4 +113,9 @@ public class FsUserCoursePeriod
      */
     private Integer delFlag;
 
+    /**
+     * 营期线,即营期首次播放课程的日期
+     */
+    private Date periodLine;
+
 }

+ 139 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserWatchCourseStatistics.java

@@ -0,0 +1,139 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 会员看课统计-按课程统计对象 fs_user_watch_course_statistics
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserWatchCourseStatistics extends BaseEntity{
+
+    /** 主键id */
+    private Long id;
+
+    /** 营期id */
+//    @Excel(name = "营期id")
+    private Long periodId;
+
+    /** 营期名称 */
+    @Excel(name = "营期名称")
+    private String periodName;
+
+    /** 营期开始日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "营期线", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date periodStartingTime;
+
+    /** 课程开始日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "播出时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date courseStartDateTime;
+
+    /** 课程id */
+//    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 课程名称 */
+    @Excel(name = "课程名称")
+    private String courseName;
+
+    /** 视频id */
+//    @Excel(name = "视频id")
+    private Long videoId;
+
+    /** 视频标题 */
+    @Excel(name = "视频小节")
+    private String videoTitle;
+
+    /** 销售公司id */
+//    @Excel(name = "销售公司id")
+    private Long companyId;
+
+    /** 销售公司名称 */
+    @Excel(name = "销售公司")
+    private String companyName;
+
+    /** 销售id */
+//    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 销售名称 */
+    @Excel(name = "所属销售")
+    private String companyUserName;
+
+    /** 新增会员数量 */
+    @Excel(name = "新增会员")
+    private Integer newUserNum;
+
+    /** 会员数量 */
+    @Excel(name = "会员数量")
+    private Integer userNum;
+
+    /** 观看人数 */
+    @Excel(name = "观看人数")
+    private Integer watchNum;
+
+    /** 上线率+% */
+    @Excel(name = "上线率")
+    private String onlineRatePercent;
+
+    /** 完播人数 */
+    @Excel(name = "完播人数")
+    private Integer completeWatchNum;
+
+    /** 上线率 */
+    private BigDecimal onlineRate;
+
+    /** 完播率 */
+//    @Excel(name = "完播率")
+    private BigDecimal completeWatchRate;
+
+    /** 完播率+% */
+    @Excel(name = "完播率")
+    private String completeWatchRatePercent;
+
+    /** 答题人数 */
+    @Excel(name = "答题人数")
+    private Integer answerNum;
+
+    /** 答题正确人数 */
+    @Excel(name = "正确人数")
+    private Integer answerRightNum;
+
+    /** 答题正确率 */
+//    @Excel(name = "正确率")
+    private BigDecimal answerRightRate;
+
+    /** 答题正确率+% */
+    @Excel(name = "正确率")
+    private String answerRightRatePercent;
+
+    /** 红包领取数量 */
+    @Excel(name = "红包领取个数")
+    private Integer redPacketNum;
+
+    /** 红包领取总额 */
+    @Excel(name = "红包领取总额")
+    private BigDecimal redPacketAmount;
+
+    /** 用户的创建日期 */
+    private Date userCreateDate;
+
+    /** 营期日期 */
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date periodLine;
+
+}

+ 77 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserWatchStatistics.java

@@ -0,0 +1,77 @@
+package com.fs.course.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 会员看课统计-按营期统计对象 fs_user_watch_statistics
+ *
+ * @author fs
+ * @date 2025-06-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserWatchStatistics extends BaseEntity{
+
+    /** 主键id */
+    private Long id;
+
+    /** 营期id */
+//    @Excel(name = "营期id")
+    private Long periodId;
+
+    /** 营期名称 */
+    @Excel(name = "营期名称")
+    private String periodName;
+
+    /** 营期开始日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "营期线", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date periodStartingTime;
+
+    /** 销售公司id */
+//    @Excel(name = "销售公司id")
+    private String companyId;
+
+    /** 销售公司名称 */
+    @Excel(name = "销售公司")
+    private String companyName;
+
+    /** 新增会员数量 */
+    @Excel(name = "新增会员数量")
+    private Integer newUserNum;
+
+    /** 会员数量 */
+    @Excel(name = "会员数量")
+    private Integer userNum;
+
+    /** 观看人数 */
+    @Excel(name = "观看人数")
+    private Integer watchNum;
+
+    /** 上线率+% */
+    @Excel(name = "上线率")
+    private String onlineRatePercent;
+
+    /** 完播人数 */
+    @Excel(name = "完播人数")
+    private Integer completeWatchNum;
+
+    /** 上线率 */
+    private BigDecimal onlineRate;
+
+    /** 完播率 */
+//    @Excel(name = "完播率")
+    private BigDecimal completeWatchRate;
+
+    /** 完播率+% */
+    @Excel(name = "完播率")
+    private String completeWatchRatePercent;
+
+}

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

@@ -123,4 +123,8 @@ public interface FsCourseAnswerLogsMapper
     @Select("select count(log_id) from fs_course_red_packet_log where user_id = #{userId} and video_id = #{videoId}")
     Long selectRedStatus(@Param("userId") Long userId, @Param("videoId") Long videoId);
 
+    List<FsCourseAnswerLogsListVO> selectFsCourseAnswerLogsListVONew(FsCourseAnswerLogsParam param);
+
+    Long selectFsCourseAnswerLogsListVONewCount(FsCourseAnswerLogsParam param);
+
 }

+ 9 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -276,4 +276,13 @@ public interface FsUserCourseMapper
      * 查询当天用户-项目看课记录条数
      */
     Integer selectTodayCourseWatchLogCountByUserIdAndProjectId(@Param("userId") Long userId, @Param("projectId") Long projectId);
+
+    @Select("select course_id,course_name,description,img_url,second_img secondImg,views from fs_user_course where is_private = 0 order by sort,course_id")
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
+
+    @Select("select video_id,title,course_id,video_url,SEC_TO_TIME(duration) as total_duration," +
+            "thumbnail videoImgUrl,description videoDescription,video_url videoUrl,question_bank_id questionBankId " +
+            " from fs_user_course_video where course_id = #{courseId}  order by course_sort,video_id")
+    List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(@Param("courseId") Long courseId);
+
 }

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

@@ -2,6 +2,7 @@ package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsUserCoursePeriodDays;
+import com.fs.course.domain.FsUserWatchCourseStatistics;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Update;
@@ -97,6 +98,12 @@ public interface FsUserCoursePeriodDaysMapper extends BaseMapper<FsUserCoursePer
      */
     void endPeriodCourse(@Param("now") LocalDateTime now);
 
+    /**
+     * 查询视频相关信息
+     * @return
+     */
+    List<FsUserWatchCourseStatistics> selectDaysCountList();
+
     /**
      * 批量删除
      * @param ids

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

@@ -71,8 +71,8 @@ public interface FsUserCourseVideoRedPackageMapper
             "ON DUPLICATE KEY UPDATE red_packet_money = VALUES(red_packet_money);")
     void insertOrUpdateFsUserCourseVideoRedPackage(FsUserCourseVideoParam fsUserCourseVideo);
 
-    @Select("select * from fs_user_course_video_red_package where video_id =#{videoId} and company_id = #{companyId} and period_id = #{periodId}")
     FsUserCourseVideoRedPackage selectRedPacketByCompanyId(@Param("videoId") Long videoId,@Param("companyId") Long companyId, @Param("periodId") Long periodId);
+
     /**
      * 批量查询匹配的红包数据
      *

+ 75 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserWatchCourseStatisticsMapper.java

@@ -0,0 +1,75 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsUserWatchCourseStatistics;
+
+import java.util.List;
+
+/**
+ * 会员看课统计-按课程统计Mapper接口
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+public interface FsUserWatchCourseStatisticsMapper extends BaseMapper<FsUserWatchCourseStatistics>{
+    /**
+     * 查询会员看课统计-按课程统计
+     *
+     * @param id 会员看课统计-按课程统计主键
+     * @return 会员看课统计-按课程统计
+     */
+    FsUserWatchCourseStatistics selectFsUserWatchCourseStatisticsById(Long id);
+
+    /**
+     * 查询会员看课统计-按课程统计列表
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 会员看课统计-按课程统计集合
+     */
+    List<FsUserWatchCourseStatistics> selectFsUserWatchCourseStatisticsList(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 查询会员看课明细汇总
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 会员看课统计-按课程统计集合
+     */
+    List<FsUserWatchCourseStatistics> selectFsUserWatchCourseStatisticsListTotal(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 新增会员看课统计-按课程统计
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 结果
+     */
+    int insertFsUserWatchCourseStatistics(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 修改会员看课统计-按课程统计
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 结果
+     */
+    int updateFsUserWatchCourseStatistics(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 删除会员看课统计-按课程统计
+     *
+     * @param id 会员看课统计-按课程统计主键
+     * @return 结果
+     */
+    int deleteFsUserWatchCourseStatisticsById(Long id);
+
+    /**
+     * 批量删除会员看课统计-按课程统计
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserWatchCourseStatisticsByIds(Long[] ids);
+
+    /**
+     * 插入看课统计数据
+     */
+    void insertFsUserWatchCourseStatisticsTask(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+}

+ 74 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserWatchStatisticsMapper.java

@@ -0,0 +1,74 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsUserWatchStatistics;
+
+import java.util.List;
+
+/**
+ * 会员看课统计-按营期统计Mapper接口
+ *
+ * @author fs
+ * @date 2025-06-16
+ */
+public interface FsUserWatchStatisticsMapper extends BaseMapper<FsUserWatchStatistics>{
+    /**
+     * 查询会员看课统计-按营期统计
+     *
+     * @param id 会员看课统计-按营期统计主键
+     * @return 会员看课统计-按营期统计
+     */
+    FsUserWatchStatistics selectFsUserWatchStatisticsById(Long id);
+
+    /**
+     * 查询会员看课统计-按营期统计列表
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 会员看课统计-按营期统计集合
+     */
+    List<FsUserWatchStatistics> selectFsUserWatchStatisticsList(FsUserWatchStatistics fsUserWatchStatistics);
+
+    /**
+     * 新增会员看课统计-按营期统计
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 结果
+     */
+    int insertFsUserWatchStatistics(FsUserWatchStatistics fsUserWatchStatistics);
+
+    /**
+     * 修改会员看课统计-按营期统计
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 结果
+     */
+    int updateFsUserWatchStatistics(FsUserWatchStatistics fsUserWatchStatistics);
+
+    /**
+     * 删除会员看课统计-按营期统计
+     *
+     * @param id 会员看课统计-按营期统计主键
+     * @return 结果
+     */
+    int deleteFsUserWatchStatisticsById(Long id);
+
+    /**
+     * 批量删除会员看课统计-按营期统计
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserWatchStatisticsByIds(Long[] ids);
+
+    /**
+     * 获取看课统计-按营期统计
+     * @return
+     */
+    List<FsUserWatchStatistics> getCourseWatchStatistics();
+
+    /**
+     * 插入看课统计数据
+     */
+    void insertFsUserWatchStatisticsTask(FsUserWatchStatistics fsUserWatchStatistics);
+
+}

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

@@ -5,6 +5,7 @@ import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
 import java.util.Date;
+import java.util.Set;
 
 @Data
 public class FsCourseAnswerLogsParam  extends BaseEntity  {
@@ -13,6 +14,10 @@ public class FsCourseAnswerLogsParam  extends BaseEntity  {
     private String courseId;
     private String videoId;
     private String companyUserName;
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
     private Long companyId;
     private Long isRight;
     private Long watchLogId;
@@ -26,4 +31,16 @@ public class FsCourseAnswerLogsParam  extends BaseEntity  {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date sTime;
 
+    private Long pageNum;
+    private Long pageSize;
+
+    /**
+     * 用户(昵称_id组合)
+     */
+    private String userName;
+    /**
+     * 用户id
+     */
+    private Set<Long> userIds;
+
 }

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

@@ -12,7 +12,7 @@ public class FsCourseLinkCreateParam {
 
     private Integer days;
 
-//    private Long qwUserIdLong;
+    private Long qwUserIdLong;
     private Long qwUserId;
 
     private String corpId;

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

@@ -62,4 +62,8 @@ public interface IFsCourseAnswerLogsService
      * @return 结果
      */
     public int deleteFsCourseAnswerLogsByLogId(Long logId);
+
+    public List<FsCourseAnswerLogsListVO> selectFsCourseAnswerLogsListVONew(FsCourseAnswerLogsParam param);
+
+    public Long selectFsCourseAnswerLogsListVONewCount(FsCourseAnswerLogsParam param);
 }

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

@@ -125,4 +125,7 @@ public interface IFsUserCourseService
 
     int copyFsUserCourse(Long courseId);
 
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
+
+    List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId);
 }

+ 75 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserWatchCourseStatisticsService.java

@@ -0,0 +1,75 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsUserWatchCourseStatistics;
+
+import java.util.List;
+
+/**
+ * 会员看课统计-按课程统计Service接口
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+public interface IFsUserWatchCourseStatisticsService extends IService<FsUserWatchCourseStatistics>{
+    /**
+     * 查询会员看课统计-按课程统计
+     *
+     * @param id 会员看课统计-按课程统计主键
+     * @return 会员看课统计-按课程统计
+     */
+    FsUserWatchCourseStatistics selectFsUserWatchCourseStatisticsById(Long id);
+
+    /**
+     * 查询会员看课统计-按课程统计列表
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 会员看课统计-按课程统计集合
+     */
+    List<FsUserWatchCourseStatistics> selectFsUserWatchCourseStatisticsList(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 查询会员看课明细汇总
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 会员看课统计-按课程统计集合
+     */
+    List<FsUserWatchCourseStatistics> selectFsUserWatchCourseStatisticsListTotal(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 新增会员看课统计-按课程统计
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 结果
+     */
+    int insertFsUserWatchCourseStatistics(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 修改会员看课统计-按课程统计
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 结果
+     */
+    int updateFsUserWatchCourseStatistics(FsUserWatchCourseStatistics fsUserWatchCourseStatistics);
+
+    /**
+     * 批量删除会员看课统计-按课程统计
+     *
+     * @param ids 需要删除的会员看课统计-按课程统计主键集合
+     * @return 结果
+     */
+    int deleteFsUserWatchCourseStatisticsByIds(Long[] ids);
+
+    /**
+     * 删除会员看课统计-按课程统计信息
+     *
+     * @param id 会员看课统计-按课程统计主键
+     * @return 结果
+     */
+    int deleteFsUserWatchCourseStatisticsById(Long id);
+
+    /**
+     * 新增按营期课程统计会员看课表数据
+     */
+    void insertWatchCourseStatistics();
+}

+ 67 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserWatchStatisticsService.java

@@ -0,0 +1,67 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsUserWatchStatistics;
+
+import java.util.List;
+
+/**
+ * 会员看课统计-按营期统计Service接口
+ *
+ * @author fs
+ * @date 2025-06-16
+ */
+public interface IFsUserWatchStatisticsService extends IService<FsUserWatchStatistics>{
+    /**
+     * 查询会员看课统计-按营期统计
+     *
+     * @param id 会员看课统计-按营期统计主键
+     * @return 会员看课统计-按营期统计
+     */
+    FsUserWatchStatistics selectFsUserWatchStatisticsById(Long id);
+
+    /**
+     * 查询会员看课统计-按营期统计列表
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 会员看课统计-按营期统计集合
+     */
+    List<FsUserWatchStatistics> selectFsUserWatchStatisticsList(FsUserWatchStatistics fsUserWatchStatistics);
+
+    /**
+     * 新增会员看课统计-按营期统计
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 结果
+     */
+    int insertFsUserWatchStatistics(FsUserWatchStatistics fsUserWatchStatistics);
+
+    /**
+     * 修改会员看课统计-按营期统计
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 结果
+     */
+    int updateFsUserWatchStatistics(FsUserWatchStatistics fsUserWatchStatistics);
+
+    /**
+     * 批量删除会员看课统计-按营期统计
+     *
+     * @param ids 需要删除的会员看课统计-按营期统计主键集合
+     * @return 结果
+     */
+    int deleteFsUserWatchStatisticsByIds(Long[] ids);
+
+    /**
+     * 删除会员看课统计-按营期统计信息
+     *
+     * @param id 会员看课统计-按营期统计主键
+     * @return 结果
+     */
+    int deleteFsUserWatchStatisticsById(Long id);
+
+    /**
+     * 新增按营期统计会员看课表数据
+     */
+    void insertStatistics();
+}

+ 92 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseAnswerLogsServiceImpl.java

@@ -1,16 +1,31 @@
 package com.fs.course.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.DictUtils;
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
 import com.fs.course.domain.FsCourseAnswerLogs;
+import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsCourseAnswerLogsMapper;
 import com.fs.course.param.FsCourseAnswerLogsParam;
 import com.fs.course.service.IFsCourseAnswerLogsService;
+import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.FsCourseAnswerLogsListVO;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.store.service.cache.IFsUserCacheService;
+import com.hc.openapi.tool.util.StringUtils;
+import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 答题日志Service业务层处理
@@ -19,11 +34,22 @@ import java.util.List;
  * @date 2024-10-26
  */
 @Service
+@RequiredArgsConstructor
 public class FsCourseAnswerLogsServiceImpl implements IFsCourseAnswerLogsService
 {
+    private final IFsUserCourseVideoCacheService fsUserCourseVideoCacheService;
+
     @Autowired
     private FsCourseAnswerLogsMapper fsCourseAnswerLogsMapper;
 
+    private final IFsUserCacheService fsUserCacheService;
+
+    private final ICompanyCacheService companyCacheService;
+
+    private final ICompanyUserCacheService companyUserCacheService;
+
+    private final IFsUserService fsUserService;
+
     /**
      * 查询答题日志
      *
@@ -101,4 +127,70 @@ public class FsCourseAnswerLogsServiceImpl implements IFsCourseAnswerLogsService
     {
         return fsCourseAnswerLogsMapper.deleteFsCourseAnswerLogsByLogId(logId);
     }
+
+    @Override
+    public List<FsCourseAnswerLogsListVO> selectFsCourseAnswerLogsListVONew(FsCourseAnswerLogsParam param) {
+
+        if(StringUtils.isNotEmpty(param.getUserName())){
+            List<FsUser> fsUsers = fsUserService.selectFsUserListByJointUserNameKey(param.getUserName());
+            if(fsUsers.isEmpty()) {
+                return Collections.emptyList();
+            }
+            Set<Long> userIds = fsUsers.stream().map(FsUser::getUserId).collect(Collectors.toSet());
+            param.setUserIds(userIds);
+        }
+
+        List<FsCourseAnswerLogsListVO> data = fsCourseAnswerLogsMapper.selectFsCourseAnswerLogsListVONew(param);
+        for (FsCourseAnswerLogsListVO datum : data) {
+            FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(datum.getVideoId());
+            if (fsUserCourseVideo != null) {
+                datum.setVideoName(fsUserCourseVideo.getTitle());
+                datum.setCourseId(fsUserCourseVideo.getCourseId());
+            }
+            // 用户
+            FsUser fsUser = fsUserCacheService.selectFsUserById(datum.getUserId());
+            if(fsUser != null) {
+                datum.setUserName(String.format("%s_%d",fsUser.getNickName(),fsUser.getUserId()));
+                datum.setFsAvatar(fsUser.getAvatar());
+            }
+
+            // 公司名称
+            if(datum.getCompanyId() != null) {
+                Company company = companyCacheService.selectCompanyById(datum.getCompanyId());
+                if(company != null) {
+                    datum.setCompanyName(String.format("%s_%d", company.getCompanyName(), company.getCompanyId()));
+                }
+            }
+            // 销售
+            if(datum.getCompanyUserId() != null) {
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(datum.getCompanyUserId());
+                if(companyUser != null) {
+                    datum.setCompanyUserName(String.format("%s_%d",companyUser.getUserName(),companyUser.getUserId()));
+                }
+            }
+            // 项目名
+            if(datum.getProject() != null) {
+                String sysCourseProject = DictUtils.getDictLabel("sys_course_project", datum.getProject());
+                if(StringUtils.isNotBlank(sysCourseProject)){
+                    datum.setProjectName(sysCourseProject);
+                }
+            }
+
+            // 是否全部正确
+            if(ObjectUtil.isNotNull(datum.getIsRight())){
+                String sysCompanyOr = DictUtils.getDictLabel("sys_company_or", String.valueOf(datum.getIsRight()));
+                if(StringUtils.isNotBlank(sysCompanyOr)){
+                    datum.setIsRightText(sysCompanyOr);
+                }
+            }
+        }
+
+        return data;
+    }
+
+    @Override
+    public Long selectFsCourseAnswerLogsListVONewCount(FsCourseAnswerLogsParam param) {
+        return fsCourseAnswerLogsMapper.selectFsCourseAnswerLogsListVONewCount(param);
+    }
+
 }

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

@@ -310,7 +310,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
 
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(param.getCompanyId());
-        link.setQwUserId(param.getQwUserId());
+        link.setQwUserId(Long.valueOf(param.getQwUserId()));
         link.setCompanyUserId(param.getCompanyUserId());
         link.setVideoId(param.getVideoId());
         link.setCorpId(param.getCorpId());
@@ -531,7 +531,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
 
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(param.getCompanyId());
-        link.setQwUserId(param.getQwUserId());
+        link.setQwUserId(Long.valueOf(param.getQwUserId()));
         link.setCompanyUserId(param.getCompanyUserId());
         link.setVideoId(param.getVideoId());
         link.setCorpId(param.getCorpId());

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

@@ -514,7 +514,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Override
     public void testFinishMsg() {
         FsCourseWatchLog finishLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(341170L);
-        QwUser qwUser = qwUserMapper.selectQwUserById(finishLog.getQwUserId());
+        QwUser qwUser = qwUserMapper.selectQwUserById(Long.valueOf(finishLog.getQwUserId()));
         QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(finishLog.getQwExternalContactId());
         FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyUserId(finishLog.getCompanyUserId(),finishLog.getVideoId());
         QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();

+ 29 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -9,6 +9,7 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.util.*;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
@@ -43,7 +44,9 @@ import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.impl.AsyncUploadQwCourseImageService;
 import com.fs.qwApi.Result.QwUploadResult;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
+import com.fs.system.vo.DictVO;
 import com.fs.voice.utils.StringUtil;
 import com.google.zxing.BarcodeFormat;
 import com.google.zxing.EncodeHintType;
@@ -83,6 +86,8 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private FsCourseWatchLogMapper fsCourseWatchLogMapper;
     @Autowired
+    private SysDictDataMapper sysDictDataMapper;
+    @Autowired
     private FsUserCourseCommentMapper fsUserCourseCommentMapper;
     @Autowired
     private FsUserCourseNoteMapper fsUserCourseNoteMapper;
@@ -279,7 +284,20 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
 
     @Override
     public List<FsUserCourseListPVO> selectFsUserCourseListPVO(FsUserCourse param) {
-        return fsUserCourseMapper.selectFsUserCourseListPVO(param);
+        List<DictVO> dictVOS = sysDictDataMapper.selectDictDataListByType("sys_course_project");
+        // Create a map for faster lookup (dictValue -> dictVO)
+        Map<String, String> projectMap = dictVOS.stream()
+                .collect(Collectors.toMap(DictVO::getDictValue, DictVO::getDictLabel));
+        List<FsUserCourseListPVO> list =  fsUserCourseMapper.selectFsUserCourseListPVO(param);
+        for (FsUserCourseListPVO vo : list) {
+            if (vo.getProject() != null) {
+                String projectName = projectMap.get(vo.getProject().toString());
+                if (projectName != null) {
+                    vo.setProjectName(projectName);
+                }
+            }
+        }
+        return list;
     }
 
     @Override
@@ -670,6 +688,16 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return 0;
     }
 
+    @Override
+    public List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet() {
+        return fsUserCourseMapper.selectFsUserCourseVideoApplet();
+    }
+
+    @Override
+    public List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId) {
+        return fsUserCourseMapper.selectFsUserCourseVideoAppletByCourseId(courseId);
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();

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

@@ -553,7 +553,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         log.setUserId(param.getUserId());
         log.setVideoId(param.getVideoId());
         log.setDuration(0L);
-        log.setQwUserId(Long.parseLong(param.getQwUserId()));
+        log.setQwUserId(Long.valueOf(param.getQwUserId()));
         log.setCreateTime(new Date());
         log.setLogType(3);
         logger.info("zyp \n【群聊生成看课记录】:{}",param);

+ 242 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserWatchCourseStatisticsServiceImpl.java

@@ -0,0 +1,242 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.course.domain.FsUserWatchCourseStatistics;
+import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
+import com.fs.course.mapper.FsUserWatchCourseStatisticsMapper;
+import com.fs.course.service.IFsUserWatchCourseStatisticsService;
+import com.fs.his.mapper.FsUserMapper;
+import com.google.common.collect.Lists;
+import lombok.AllArgsConstructor;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+/**
+ * 会员看课统计-按课程统计Service业务层处理
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+@Service
+@AllArgsConstructor
+public class FsUserWatchCourseStatisticsServiceImpl extends ServiceImpl<FsUserWatchCourseStatisticsMapper, FsUserWatchCourseStatistics> implements IFsUserWatchCourseStatisticsService {
+
+    private FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;
+
+    private FsUserMapper fsUserMapper;
+
+    @Autowired
+    private SqlSessionFactory sqlSessionFactory;
+
+    /**
+     * 查询会员看课统计-按课程统计
+     *
+     * @param id 会员看课统计-按课程统计主键
+     * @return 会员看课统计-按课程统计
+     */
+    @Override
+    public FsUserWatchCourseStatistics selectFsUserWatchCourseStatisticsById(Long id)
+    {
+        return baseMapper.selectFsUserWatchCourseStatisticsById(id);
+    }
+
+    /**
+     * 查询会员看课统计-按课程统计列表
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 会员看课统计-按课程统计
+     */
+    @Override
+    public List<FsUserWatchCourseStatistics> selectFsUserWatchCourseStatisticsList(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        List<FsUserWatchCourseStatistics> list = baseMapper.selectFsUserWatchCourseStatisticsList(fsUserWatchCourseStatistics);
+        if(!list.isEmpty()){
+            for (FsUserWatchCourseStatistics userWatchCourseStatistics : list) {
+                userWatchCourseStatistics.setCompleteWatchRatePercent(userWatchCourseStatistics.getCompleteWatchRate() + "%");
+                userWatchCourseStatistics.setAnswerRightRatePercent(userWatchCourseStatistics.getAnswerRightRate() + "%");
+                userWatchCourseStatistics.setOnlineRatePercent(userWatchCourseStatistics.getOnlineRate() + "%");
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public List<FsUserWatchCourseStatistics> selectFsUserWatchCourseStatisticsListTotal(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        return baseMapper.selectFsUserWatchCourseStatisticsListTotal(fsUserWatchCourseStatistics);
+    }
+
+    /**
+     * 新增会员看课统计-按课程统计
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserWatchCourseStatistics(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        return baseMapper.insertFsUserWatchCourseStatistics(fsUserWatchCourseStatistics);
+    }
+
+    /**
+     * 修改会员看课统计-按课程统计
+     *
+     * @param fsUserWatchCourseStatistics 会员看课统计-按课程统计
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserWatchCourseStatistics(FsUserWatchCourseStatistics fsUserWatchCourseStatistics)
+    {
+        return baseMapper.updateFsUserWatchCourseStatistics(fsUserWatchCourseStatistics);
+    }
+
+    /**
+     * 批量删除会员看课统计-按课程统计
+     *
+     * @param ids 需要删除的会员看课统计-按课程统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserWatchCourseStatisticsByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsUserWatchCourseStatisticsByIds(ids);
+    }
+
+    /**
+     * 删除会员看课统计-按课程统计信息
+     *
+     * @param id 会员看课统计-按课程统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserWatchCourseStatisticsById(Long id)
+    {
+        return baseMapper.deleteFsUserWatchCourseStatisticsById(id);
+    }
+
+    @Override
+    public void insertWatchCourseStatistics() {
+        // 1、获取统计数据
+        // 查询课程相关数据
+        List<FsUserWatchCourseStatistics> fsUserWatchCourseStatistics = fsUserCoursePeriodDaysMapper.selectDaysCountList();
+
+        // 查询统计数据
+        List<FsUserWatchCourseStatistics> watchLog = fsUserMapper.selectWatchLogCount();
+        List<FsUserWatchCourseStatistics> redPacketLog = fsUserMapper.selectRedPacketLogCount();
+        List<FsUserWatchCourseStatistics> answerLog = fsUserMapper.selectAnswerLogCount();
+        List<FsUserWatchCourseStatistics> userTotal = fsUserMapper.selectFsUserDetail();
+
+        // 转化为自定义键的map
+        Map<String, FsUserWatchCourseStatistics> watchLogMap = watchLog.stream().collect(Collectors.toMap(k -> String.format("%s-%s-%s", k.getPeriodId(), k.getVideoId(), k.getCompanyUserId()), v -> v));
+        Map<String, FsUserWatchCourseStatistics> redPacketLogMap = redPacketLog.stream().collect(Collectors.toMap(k -> String.format("%s-%s-%s", k.getPeriodId(), k.getVideoId(), k.getCompanyUserId()), v -> v));
+        Map<String, FsUserWatchCourseStatistics> answerLogMap = answerLog.stream().collect(Collectors.toMap(k -> String.format("%s-%s-%s", k.getPeriodId(), k.getVideoId(), k.getCompanyUserId()), v -> v));
+        Map<Long, List<FsUserWatchCourseStatistics>> userTotalMap = userTotal.stream()
+                .collect(Collectors.groupingBy(FsUserWatchCourseStatistics::getCompanyUserId));
+
+        // 处理数据
+        List<FsUserWatchCourseStatistics> list = new ArrayList<>();
+        for (FsUserWatchCourseStatistics data : fsUserWatchCourseStatistics) {
+            FsUserWatchCourseStatistics vo = new FsUserWatchCourseStatistics();
+            String key = String.format("%s-%s-%s", data.getPeriodId(), data.getVideoId(), data.getCompanyUserId());
+            FsUserWatchCourseStatistics watchLogData = watchLogMap.get(key);
+            FsUserWatchCourseStatistics redPacketLogData = redPacketLogMap.get(key);
+            FsUserWatchCourseStatistics answerLogData = answerLogMap.get(key);
+            List<FsUserWatchCourseStatistics> userTotalDataList = userTotalMap.get(data.getCompanyUserId());
+            BeanUtils.copyProperties(data, vo);
+            // 改成使用营期线来表示营期开始时间
+            vo.setPeriodStartingTime(data.getPeriodLine() != null ? data.getPeriodLine() : data.getPeriodStartingTime());
+
+            // 单独一个一个set,不用copy,避免copy出来的结果被前面的覆盖
+            if(userTotalDataList != null && !userTotalDataList.isEmpty()){
+                // 获取过滤时间后的销售会员数量
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+
+                int userNum = userTotalDataList.stream()
+                        .filter(v -> v.getUserCreateDate().before(data.getCourseStartDateTime())).mapToInt(FsUserWatchCourseStatistics::getUserNum).sum();
+                int newUserNum = userTotalDataList.stream()
+                        .filter(v -> sdf.format(v.getUserCreateDate()).equals(sdf.format(data.getCourseStartDateTime()))).mapToInt(FsUserWatchCourseStatistics::getUserNum).sum();
+                vo.setUserNum(userNum);
+                vo.setNewUserNum(newUserNum);
+            } else {
+                vo.setUserNum(0);
+                vo.setNewUserNum(0);
+            }
+
+            if(watchLogData != null) {
+                vo.setWatchNum(watchLogData.getWatchNum());
+                vo.setCompleteWatchNum(watchLogData.getCompleteWatchNum());
+                vo.setCompleteWatchRate(watchLogData.getCompleteWatchRate());
+            } else {
+                vo.setWatchNum(0);
+                vo.setCompleteWatchNum(0);
+                vo.setCompleteWatchRate(BigDecimal.ZERO);
+            }
+
+            if(redPacketLogData != null) {
+                vo.setRedPacketNum(redPacketLogData.getRedPacketNum());
+                vo.setRedPacketAmount(redPacketLogData.getRedPacketAmount());
+            } else {
+                vo.setRedPacketNum(0);
+                vo.setRedPacketAmount(BigDecimal.ZERO);
+            }
+
+            if(answerLogData != null) {
+                vo.setAnswerNum(answerLogData.getAnswerNum());
+                vo.setAnswerRightNum(answerLogData.getAnswerRightNum());
+                vo.setAnswerRightRate(answerLogData.getAnswerRightRate());
+            } else {
+                vo.setAnswerNum(0);
+                vo.setAnswerRightNum(0);
+                vo.setAnswerRightRate(BigDecimal.ZERO);
+            }
+
+            // 设置上线率
+            BigDecimal watchNum = new BigDecimal(vo.getWatchNum());
+            BigDecimal userNum = new BigDecimal(vo.getUserNum());
+            if(!userNum.equals(BigDecimal.ZERO)){
+                BigDecimal onlineRate = watchNum.divide(userNum, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
+                vo.setOnlineRate(onlineRate);
+            } else{
+                vo.setOnlineRate(BigDecimal.ZERO);
+            }
+
+            vo.setCreateTime(new Date());
+            vo.setUpdateTime(new Date());
+            list.add(vo);
+        }
+
+        //2、分批次插入数据
+        this.batchInsert(list);
+
+    }
+
+    private void batchInsert(List<FsUserWatchCourseStatistics> list) {
+        // 分批次处理,一次提交500条
+        List<List<FsUserWatchCourseStatistics>> batches = Lists.partition(list, 500);
+        batches.forEach(batch -> {
+            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
+            try {
+                FsUserWatchCourseStatisticsMapper mapper = sqlSession.getMapper(FsUserWatchCourseStatisticsMapper.class);
+                batch.forEach(mapper::insertFsUserWatchCourseStatisticsTask);
+                sqlSession.commit();
+            } finally {
+                sqlSession.close();
+            }
+        });
+    }
+
+}

+ 216 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserWatchStatisticsServiceImpl.java

@@ -0,0 +1,216 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.company.domain.Company;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.course.domain.FsUserCoursePeriod;
+import com.fs.course.domain.FsUserWatchStatistics;
+import com.fs.course.mapper.FsUserCoursePeriodMapper;
+import com.fs.course.mapper.FsUserWatchStatisticsMapper;
+import com.fs.course.service.IFsUserWatchStatisticsService;
+import com.fs.his.mapper.FsUserMapper;
+import com.google.common.collect.Lists;
+import lombok.AllArgsConstructor;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 会员看课统计-按营期统计Service业务层处理
+ *
+ * @author fs
+ * @date 2025-06-16
+ */
+@Service
+@AllArgsConstructor
+public class FsUserWatchStatisticsServiceImpl extends ServiceImpl<FsUserWatchStatisticsMapper, FsUserWatchStatistics> implements IFsUserWatchStatisticsService {
+
+    private final FsUserCoursePeriodMapper fsUserCoursePeriodMapper;
+
+    private final FsUserMapper fsUserMapper;
+
+    @Autowired
+    private SqlSessionFactory sqlSessionFactory;
+
+    private final CompanyMapper companyMapper;
+
+    /**
+     * 查询会员看课统计-按营期统计
+     *
+     * @param id 会员看课统计-按营期统计主键
+     * @return 会员看课统计-按营期统计
+     */
+    @Override
+    public FsUserWatchStatistics selectFsUserWatchStatisticsById(Long id)
+    {
+        return baseMapper.selectFsUserWatchStatisticsById(id);
+    }
+
+    /**
+     * 查询会员看课统计-按营期统计列表
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 会员看课统计-按营期统计
+     */
+    @Override
+    public List<FsUserWatchStatistics> selectFsUserWatchStatisticsList(FsUserWatchStatistics fsUserWatchStatistics)
+    {
+        return baseMapper.selectFsUserWatchStatisticsList(fsUserWatchStatistics);
+    }
+
+    /**
+     * 新增会员看课统计-按营期统计
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserWatchStatistics(FsUserWatchStatistics fsUserWatchStatistics)
+    {
+        return baseMapper.insertFsUserWatchStatistics(fsUserWatchStatistics);
+    }
+
+    /**
+     * 修改会员看课统计-按营期统计
+     *
+     * @param fsUserWatchStatistics 会员看课统计-按营期统计
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserWatchStatistics(FsUserWatchStatistics fsUserWatchStatistics)
+    {
+        return baseMapper.updateFsUserWatchStatistics(fsUserWatchStatistics);
+    }
+
+    /**
+     * 批量删除会员看课统计-按营期统计
+     *
+     * @param ids 需要删除的会员看课统计-按营期统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserWatchStatisticsByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsUserWatchStatisticsByIds(ids);
+    }
+
+    /**
+     * 删除会员看课统计-按营期统计信息
+     *
+     * @param id 会员看课统计-按营期统计主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserWatchStatisticsById(Long id)
+    {
+        return baseMapper.deleteFsUserWatchStatisticsById(id);
+    }
+
+    @Override
+    @Transactional
+    public void insertStatistics() {
+        // 1、获取统计结果
+        // 获取所有的营期,并拆分公司id
+        FsUserCoursePeriod periodParam = new FsUserCoursePeriod();
+        List<FsUserCoursePeriod> fsUserCoursePeriods = fsUserCoursePeriodMapper.selectFsUserCoursePeriodList(periodParam);
+        Set<Long> companyIds = fsUserCoursePeriods.stream()
+                .flatMap(item -> Arrays.stream(item.getCompanyId().split(",")))
+                .map(Long::valueOf)
+                .collect(Collectors.toSet());
+
+        // 获取公司信息
+        List<Company> companies = companyMapper.selectCompanyByIds2(companyIds);
+        Map<Long, Company> companyMap = companies.stream().collect(Collectors.toMap(Company::getCompanyId, Function.identity()));
+
+        // 获取看课统计(按营期按公司)
+        List<FsUserWatchStatistics> courseWatchStatistics = baseMapper.getCourseWatchStatistics();
+        Map<String, FsUserWatchStatistics> courseWatchStatisticsMap = courseWatchStatistics.stream().collect(Collectors.toMap(k -> String.format("%s-%s", k.getPeriodId(), k.getCompanyId()), v -> v));
+
+        //获取公司的会员数量和今日新增会员数量
+        List<FsUserWatchStatistics> userTotal = fsUserMapper.selectFsUserTotal();
+        Map<String, FsUserWatchStatistics> userTotalMap = userTotal.stream().collect(Collectors.toMap(FsUserWatchStatistics::getCompanyId, Function.identity()));
+
+        List<FsUserWatchStatistics> list = fsUserCoursePeriods.stream()
+                .flatMap(item -> Arrays.stream(item.getCompanyId().split(","))
+                        .map(companyIdStr -> {
+                            Long companyId = Long.valueOf(companyIdStr.trim());
+                            Company company = companyMap.get(companyId);
+
+                            // 赋值
+                            FsUserWatchStatistics fsUserWatchStatistics = new FsUserWatchStatistics();
+                            BeanUtils.copyProperties(item, fsUserWatchStatistics);
+                            ZonedDateTime zonedDateTime = item.getPeriodStartingTime().atStartOfDay(ZoneId.systemDefault());
+                            // 改成使用营期线来表示营期开始时间
+                            fsUserWatchStatistics.setPeriodStartingTime(item.getPeriodLine() != null ? item.getPeriodLine() : Date.from(zonedDateTime.toInstant()));
+                            fsUserWatchStatistics.setCompanyId(companyIdStr.trim());
+                            fsUserWatchStatistics.setCompanyName(company != null ? company.getCompanyName() : null);
+
+                            FsUserWatchStatistics userTotalData = userTotalMap.get(fsUserWatchStatistics.getCompanyId());
+
+                            String key = String.format("%s-%s", fsUserWatchStatistics.getPeriodId(), fsUserWatchStatistics.getCompanyId());
+                            FsUserWatchStatistics watchData = courseWatchStatisticsMap.get(key);
+
+                            if(userTotalData != null){
+                                fsUserWatchStatistics.setUserNum(userTotalData.getUserNum());
+                                fsUserWatchStatistics.setNewUserNum(userTotalData.getNewUserNum());
+                            } else {
+                                fsUserWatchStatistics.setUserNum(0);
+                                fsUserWatchStatistics.setNewUserNum(0);
+                            }
+
+                            if(watchData != null){
+                                fsUserWatchStatistics.setWatchNum(watchData.getWatchNum());
+                                fsUserWatchStatistics.setCompleteWatchNum(watchData.getCompleteWatchNum());
+                                fsUserWatchStatistics.setCompleteWatchRate(watchData.getCompleteWatchRate());
+                            } else {
+                                fsUserWatchStatistics.setWatchNum(0);
+                                fsUserWatchStatistics.setCompleteWatchNum(0);
+                                fsUserWatchStatistics.setCompleteWatchRate(BigDecimal.ZERO);
+                            }
+
+                            // 计算上线率
+                            BigDecimal watchNum = new BigDecimal(fsUserWatchStatistics.getWatchNum());
+                            BigDecimal userNum = new BigDecimal(fsUserWatchStatistics.getUserNum());
+                            if(!userNum.equals(BigDecimal.ZERO)){
+                                BigDecimal onlineRate = watchNum.divide(userNum, 2, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
+                                fsUserWatchStatistics.setOnlineRate(onlineRate);
+                            } else {
+                                fsUserWatchStatistics.setOnlineRate(BigDecimal.ZERO);
+                            }
+
+                            fsUserWatchStatistics.setCreateTime(new Date());
+                            fsUserWatchStatistics.setUpdateTime(new Date());
+                            return fsUserWatchStatistics;
+                        })).collect(Collectors.toList());
+
+        //2、分批次插入数据
+        this.batchInsert(list);
+    }
+
+    private void batchInsert(List<FsUserWatchStatistics> list) {
+        // 分批次处理,一次提交500条
+        List<List<FsUserWatchStatistics>> batches = Lists.partition(list, 500);
+        batches.forEach(batch -> {
+            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
+            try {
+                FsUserWatchStatisticsMapper mapper = sqlSession.getMapper(FsUserWatchStatisticsMapper.class);
+                batch.forEach(mapper::insertFsUserWatchStatisticsTask);
+                sqlSession.commit();
+            } finally {
+                sqlSession.close();
+            }
+        });
+    }
+}

+ 46 - 32
fs-service/src/main/java/com/fs/course/vo/FsCourseAnswerLogsListVO.java

@@ -3,6 +3,8 @@ package com.fs.course.vo;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
+import java.util.Date;
+
 @Data
 public class FsCourseAnswerLogsListVO {
 
@@ -10,71 +12,83 @@ public class FsCourseAnswerLogsListVO {
     private Long logId;
 
     /**
-    * 小程序id
-    */
+     * 小程序id
+     */
     private Long userId;
     /**
-    * 小程序用户名
-    */
-    @Excel(name = "小程序用户")
+     * 小程序用户名
+     */
+    @Excel(name = "用户", sort = 1)
     private String userName;
     private String fsAvatar;
     /**
-    * 课程id
-    */
+     * 课程id
+     */
     private Long courseId;
     /**
-    * 课程名称
-    */
+     * 课程名称
+     */
     @Excel(name = "课程名称")
     private String courseName;
     /**
-    * 视频id
-    */
+     * 视频id
+     */
     private Long videoId;
     /**
-    * 视频名称
-    */
-    @Excel(name = "视频名称")
+     * 视频名称
+     */
+    @Excel(name = "小节名称",width = 70)
     private String videoName;
     /**
-    * 是否全部正确 0否 1是
-    */
-    @Excel(name = "是否全部正确 0否 1是")
+     * 是否全部正确 0否 1是
+     */
     private Integer isRight;
+    @Excel(name = "是否全部正确")
+    private String isRightText;
     /**
-    * 创建时间
-    */
+     * 创建时间
+     */
     @Excel(name = "创建时间")
     private String createTime;
     /**
-    * 销售id
-    */
+     * 销售id
+     */
     private Long companyUserId;
     /**
-    * 销售名称
-    */
+     * 销售名称
+     */
     @Excel(name = "销售名称")
     private String companyUserName;
     /**
-    * 公司id
-    */
+     * 公司id
+     */
     private Long companyId;
     /**
-    * 公司名称
-    */
+     * 公司名称
+     */
     @Excel(name = "公司名称")
     private String companyName;
     /**
-    * 企业微信员工id
-    */
+     * 企业微信员工id
+     */
     private Long qwUserId;
     /**
-    * 企业微信员工名称
-    */
-    @Excel(name = "企业微信员工名称")
+     * 企业微信员工名称
+     */
     private String qwUserName;
     private String answer;
     private String questionTitle;
     private String questionJson;
+    /**
+     * 项目
+     */
+    private String project;
+    /**
+     * 项目名称
+     */
+    @Excel(name = "项目", sort = 2)
+    private String projectName;
+
+    private Date sTime;
+    private Date eTime;
 }

+ 8 - 2
fs-service/src/main/java/com/fs/course/vo/FsUserCourseListPVO.java

@@ -44,6 +44,12 @@ public class FsUserCourseListPVO extends BaseEntity
     private Integer isShow;
 
     private Integer isPrivate;
-
-
+    /**
+     * 项目ID
+     */
+    private Long project;
+    /**
+     * 项目名称
+     */
+    private String projectName;
 }

+ 63 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoAppletVO.java

@@ -0,0 +1,63 @@
+package com.fs.course.vo;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 课堂视频对象 fs_user_video
+ *
+ * @author fs
+ * @date 2024-05-15
+ */
+@Data
+public class FsUserCourseVideoAppletVO extends BaseEntity
+{
+    private Long courseId;
+
+    /** 课程名称 */
+    private String courseName;
+
+    /** 课程描述 */
+    private String description;
+
+    /** 课程封面 */
+    private String imgUrl;
+
+    /** 小封面 */
+    private String secondImg;
+
+    /** 总播放量 */
+    private Long views;
+
+    /** 视频总数 */
+    private Long videoTotal;
+
+    private List<FsUserCourseVideo> fsUserCourseVideoList;
+
+    @Data
+    public static class FsUserCourseVideo{
+        /** ID */
+        private Long videoId;
+
+        /** 视频标题 */
+        private String title;
+
+        /** 视频封面 */
+        private String videoImgUrl;
+
+        /** 视频地址 */
+        private String videoUrl;
+
+        /** 视频描述 */
+        private String videoDescription;
+
+        /** 总播放时长 */
+        private String totalDuration;
+
+        private String questionBankId;
+    }
+
+
+}

+ 101 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserWatchCourseStatisticsExportVO.java

@@ -0,0 +1,101 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserWatchCourseStatisticsExportVO extends BaseEntity{
+
+    /** 主键id */
+    private Long id;
+
+    /** 营期id */
+//    @Excel(name = "营期id")
+    private Long periodId;
+
+    /** 营期名称 */
+    @Excel(name = "营期名称")
+    private String periodName;
+
+    /** 营期开始日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "营期线", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date periodStartingTime;
+
+    /** 课程开始日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "播出时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date courseStartDateTime;
+
+    /** 课程id */
+//    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 课程名称 */
+    @Excel(name = "课程名称")
+    private String courseName;
+
+    /** 视频id */
+//    @Excel(name = "视频id")
+    private Long videoId;
+
+    /** 视频标题 */
+    @Excel(name = "视频小节")
+    private String videoTitle;
+
+    /** 销售公司id */
+//    @Excel(name = "销售公司id")
+    private Long companyId;
+
+    /** 销售公司名称 */
+    @Excel(name = "销售公司")
+    private String companyName;
+
+    /** 销售id */
+//    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 销售名称 */
+//    @Excel(name = "所属销售")
+    private String companyUserName;
+
+    /** 新增会员数量 */
+    @Excel(name = "新增会员")
+    private Integer newUserNum;
+
+    /** 会员数量 */
+    @Excel(name = "会员数量")
+    private Integer userNum;
+
+    /** 观看人数 */
+    @Excel(name = "观看人数")
+    private Integer watchNum;
+
+    /** 上线率+% */
+    @Excel(name = "上线率")
+    private String onlineRatePercent;
+
+    /** 完播人数 */
+    @Excel(name = "完播人数")
+    private Integer completeWatchNum;
+
+    /** 完播率 */
+//    @Excel(name = "完播率")
+    private BigDecimal completeWatchRate;
+
+    /** 完播率+% */
+    @Excel(name = "完播率")
+    private String completeWatchRatePercent;
+
+    /** 红包领取数量 */
+    @Excel(name = "红包领取个数")
+    private Integer redPacketNum;
+
+}

+ 23 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventLog.java

@@ -0,0 +1,23 @@
+package com.fs.fastGpt.domain;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FastGptEventLog {
+    private Long id;
+    private Long senderId;
+    private Long roleId;
+    private String eventName;
+    private Long count;
+    /**
+     * 事件类型(1互动 1总对话 3转人工 4AI无法回复转人工 5AI回复不合适转人工 6完课回复 7物流事件 8图片回复 9自定义事件回复 10用户未回复AI再次提醒)
+     */
+    private Integer type;
+    private Long companyId;
+    private Long companyUserId;
+    private Long qwUserId;
+    private Date createTime;
+
+}

+ 23 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventTokenLog.java

@@ -0,0 +1,23 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FastGptEventTokenLog extends BaseEntity {
+
+    private Long id;
+    private Long senderId;
+    private Long roleId;
+    private String eventName;
+    private Integer eventType;
+    private Long tokenCount;
+    private Integer tokenType;
+    private Long companyId;
+    private Long companyUserId;
+    private Long qwUserId;
+    private String statTime;
+    private Date createTime;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeyword.java

@@ -0,0 +1,12 @@
+package com.fs.fastGpt.domain;
+
+import lombok.Data;
+
+@Data
+public class FastGptKeyword {
+    private Long id;
+    //关键字
+    private String keyword;
+    //关键字类型
+    private Integer keywordType;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordArtificial.java

@@ -0,0 +1,19 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FastGptKeywordArtificial extends BaseEntity {
+
+    private Long id;
+    private String userId;
+    private Long companyId;
+    private Long companyUserId;
+    private Long keywordSendId;
+    private String keywordSendContent;
+    private Date createTime;
+
+}

+ 55 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordSend.java

@@ -0,0 +1,55 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)对象 fastgpt_keyword_send
+ *
+ * @author fs
+ * @date 2025-05-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastGptKeywordSend extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 营销关键字 */
+    @Excel(name = "营销关键字")
+    private String keyword;
+
+    @Excel(name = "营销关键字类型 0客服端  1总后台")
+    private Long keywordType;
+
+    /** 发送文字内容 */
+    @Excel(name = "发送文字内容")
+    private String content;
+
+    /** 内容类型 */
+    @Excel(name = "内容类型")
+    private Integer contentType;
+
+    /** 图片访问地址 */
+    @Excel(name = "图片访问地址")
+    private String imgUrl;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Integer status;
+
+    /** Ai角色id*/
+    @Excel(name = "Ai角色id")
+    private String roleIds;
+
+    private Long cropId;
+
+    private Long companyId;
+
+    private Long companyUserId;
+
+
+}

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

@@ -61,4 +61,6 @@ public class FastGptRole extends BaseEntity
     private String bindCorpId;
 
     private String contactInfo;
+
+    private String channelType;
 }

+ 77 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatArtificialWords.java

@@ -0,0 +1,77 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 转人工提示词对象 fastgpt_chat_artificial_words
+ *
+ * @author fs
+ * @date 2025-05-07
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastgptChatArtificialWords extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 类型 1报错 2关键词 */
+    @Excel(name = "类型 1报错 2关键词")
+    private Long type;
+
+    /** 文本 */
+    @Excel(name = "文本")
+    private String content;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Long status;
+
+    /** 排序 */
+    @Excel(name = "排序")
+    private Long sort;
+
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getType() {
+        return type;
+    }
+
+    public void setType(Long type) {
+        this.type = type;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Long getStatus() {
+        return status;
+    }
+
+    public void setStatus(Long status) {
+        this.status = status;
+    }
+
+    public Long getSort() {
+        return sort;
+    }
+
+    public void setSort(Long sort) {
+        this.sort = sort;
+    }
+}

+ 56 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastgptEventLogTotal.java

@@ -0,0 +1,56 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * ai事件埋点统计对象 fastgpt_event_log_total
+ *
+ * @author fs
+ * @date 2025-06-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastgptEventLogTotal extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 角色id */
+    @Excel(name = "角色id")
+    private Long roleId;
+
+    /** 数量 */
+    @Excel(name = "数量")
+    private Long count;
+
+    /** 日志类型 */
+    @Excel(name = "日志类型")
+    private Integer type;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 公司用户id */
+    @Excel(name = "公司用户id")
+    private Long companyUserId;
+
+    /** 企微用户id */
+    @Excel(name = "企微用户id")
+    private Long qwUserId;
+
+    @Excel(name = "日志生成时间")
+    private String statTime;
+
+    private Long senderCount;
+
+    private String qwUserIds;
+
+    private List<String> userIds;
+
+}

+ 7 - 4
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatMsgMapper.java

@@ -6,6 +6,8 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.fastGpt.domain.FastGptChatMsg;
 import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
 import com.fs.fastGpt.param.FastGptChatMsgListCParam;
 import com.fs.fastGpt.vo.FastGptChatMsgCVO;
 import com.fs.fastGpt.vo.FastGptChatMsgListCVO;
@@ -20,7 +22,6 @@ import org.springframework.stereotype.Repository;
  * @date 2024-10-10
  */
 @Repository
-@DataSource(DataSourceType.CLICKHOUSE)
 public interface FastGptChatMsgMapper
 {
     /**
@@ -47,7 +48,6 @@ public interface FastGptChatMsgMapper
      * @param fastGptChatMsg 聊天记录
      * @return 结果
      */
-    @DataSource(DataSourceType.CLICKHOUSE)
     public int insertFastGptChatMsg(FastGptChatMsg fastGptChatMsg);
 
     /**
@@ -74,8 +74,7 @@ public interface FastGptChatMsgMapper
      */
     public int deleteFastGptChatMsgByMsgIds(Long[] msgIds);
 
-    @Select("select m.*  from fastgpt_chat_msg m  where m.session_id=#{sessionId} and msg_type=2 order by m.msg_id desc")
-    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(@Param("sessionId")Long sessionId);
+    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(@Param("sessionId")Long sessionId,@Param("userId") String userId);
 
 
     @Select({"<script> " +
@@ -114,5 +113,9 @@ public interface FastGptChatMsgMapper
     @Select("select * from fastgpt_chat_msg where  session_id =#{sessionId} and msg_type=1 ORDER BY msg_id DESC  limit 20 ")
     List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionId(Long sessionId);
 
+    void insertFastGptEventLog(FastGptEventLog fastGptEventLog);
 
+    void insertFastGptEventTokenLog(FastGptEventTokenLog fastGptEventTokenLog);
+
+    List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionIdAndExtId(@Param("sessionId") Long sessionId,@Param("extId") String extId);
 }

+ 72 - 0
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptKeywordSendMapper.java

@@ -0,0 +1,72 @@
+package com.fs.fastGpt.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.fastGpt.domain.FastGptKeyword;
+import com.fs.fastGpt.domain.FastGptKeywordArtificial;
+import com.fs.fastGpt.domain.FastGptKeywordSend;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)Mapper接口
+ * 
+ * @author fs
+ * @date 2025-05-12
+ */
+public interface FastGptKeywordSendMapper extends BaseMapper<FastGptKeywordSend>{
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    FastGptKeywordSend selectFastGptKeywordSendById(@Param("id") Long id,@Param("keywordType") Long keywordType);
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)列表
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return Ai关键字表(根据关键字发送文本和图片)集合
+     */
+    List<FastGptKeywordSend> selectFastGptKeywordSendList(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 新增Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int insertFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 修改Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int updateFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendById(Long id);
+
+    /**
+     * 批量删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendByIds(Long[] ids);
+
+    List<FastGptKeywordSend> selectFastGptKeywordSendByRoleId(@Param("roleIds") Long roleId);
+
+    List<FastGptKeywordArtificial> selectFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    int insertFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    List<FastGptKeyword> selectFastGptKeywordList(int type);
+}

+ 1 - 1
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptRoleMapper.java

@@ -86,6 +86,6 @@ public interface FastGptRoleMapper
 
     @Select("select id dictValue,name dictLabel from fastgpt_role_type ")
     List<OptionsVO> selectFastGptRoleType();
-    @Select("select r.role_id, r.role_name,t.contact_info,r.company_id, r.create_time, r.update_time, r.role_type, r.mode_config_json, r.mode, r.kf_id, r.kf_url, r.avatar, r.kf_media_id,r.reminder_words, r.bind_corp_id from fastgpt_role r LEFT JOIN fastgpt_role_type t on t.id =r.role_type where role_id = #{roleId}")
+    @Select("select r.role_id, r.role_name,t.contact_info,r.company_id, r.create_time, r.update_time, r.role_type, r.mode_config_json, r.mode, r.kf_id, r.kf_url, r.avatar, r.kf_media_id,r.reminder_words, r.bind_corp_id,r.channel_type from fastgpt_role r LEFT JOIN fastgpt_role_type t on t.id =r.role_type where role_id = #{roleId}")
     FastGptRole selectFastGptRoleTypeByRoleId(Long roleId);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -1,6 +1,7 @@
 package com.fs.fastGpt.service;
 
 import com.fs.common.core.domain.R;
+import com.fs.im.vo.OpenImMsgCallBackVO;
 import com.fs.qwHookApi.vo.QwHookVO;
 import com.fs.wxwork.dto.WxWorkResponseDTO;
 
@@ -14,6 +15,9 @@ public interface AiHookService {
     /** 转人工 **/
     void artificial(QwHookVO vo);
 
+    /** ai自动回复 **/
+    R AiReply(OpenImMsgCallBackVO openImMsgDTO, Long companyId);
+
     R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid);
 
     void expireAiMsg();

+ 9 - 1
fs-service/src/main/java/com/fs/fastGpt/service/IFastGptChatMsgService.java

@@ -2,6 +2,8 @@ package com.fs.fastGpt.service;
 
 import com.fs.fastGpt.domain.FastGptChatMsg;
 import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
 import com.fs.fastGpt.param.FastGptChatMsgListCParam;
 import com.fs.fastGpt.vo.FastGptChatMsgCVO;
 import com.fs.fastGpt.vo.FastGptChatMsgListCVO;
@@ -64,7 +66,7 @@ public interface IFastGptChatMsgService
      */
     public int deleteFastGptChatMsgByMsgId(Long msgId);
 
-    List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId);
+    List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId,String userId);
 
     List<FastGptChatMsgListCVO> selectFastGptChatMsgListCVO(FastGptChatMsgListCParam param);
 
@@ -74,4 +76,10 @@ public interface IFastGptChatMsgService
 
 
     List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionId(Long sessionId);
+
+    void insertFastGptEventLog(FastGptEventLog fastGptEventLog);
+
+    void insertFastGptEventTokenLog(FastGptEventTokenLog fastGptEventTokenLog);
+
+    List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionIdAndExtId(Long sessionId, Long extId);
 }

+ 72 - 0
fs-service/src/main/java/com/fs/fastGpt/service/IFastGptKeywordSendService.java

@@ -0,0 +1,72 @@
+package com.fs.fastGpt.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.fastGpt.domain.FastGptKeyword;
+import com.fs.fastGpt.domain.FastGptKeywordArtificial;
+import com.fs.fastGpt.domain.FastGptKeywordSend;
+
+import java.util.List;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)Service接口
+ * 
+ * @author fs
+ * @date 2025-05-12
+ */
+public interface IFastGptKeywordSendService extends IService<FastGptKeywordSend>{
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    FastGptKeywordSend selectFastGptKeywordSendById(Long id,Long keywordType);
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)列表
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return Ai关键字表(根据关键字发送文本和图片)集合
+     */
+    List<FastGptKeywordSend> selectFastGptKeywordSendList(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 新增Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int insertFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 修改Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int updateFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 批量删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param ids 需要删除的Ai关键字表(根据关键字发送文本和图片)主键集合
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendByIds(Long[] ids);
+
+    /**
+     * 删除Ai关键字表(根据关键字发送文本和图片)信息
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendById(Long id);
+
+    List<FastGptKeywordSend> selectFastGptKeywordSendByRoleId(Long roleId);
+
+    List<FastGptKeywordArtificial> selectFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    int insertFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    List<FastGptKeyword> selectFastGptKeywordList(int type);
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 497 - 196
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java


+ 24 - 3
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptChatMsgServiceImpl.java

@@ -4,8 +4,12 @@ import java.util.Collections;
 import java.util.List;
 
 import cn.hutool.core.lang.Snowflake;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.DateUtils;
 import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
 import com.fs.fastGpt.param.FastGptChatMsgListCParam;
 import com.fs.fastGpt.vo.FastGptChatMsgCVO;
 import com.fs.fastGpt.vo.FastGptChatMsgListCVO;
@@ -61,7 +65,7 @@ public class FastGptChatMsgServiceImpl implements IFastGptChatMsgService
     public int insertFastGptChatMsg(FastGptChatMsg fastGptChatMsg)
     {
         fastGptChatMsg.setCreateTime(DateUtils.getNowDate());
-        fastGptChatMsg.setMsgId((new Snowflake(1, 1)).nextId());
+        //fastGptChatMsg.setMsgId((new Snowflake(1, 1)).nextId());
         return fastGptChatMsgMapper.insertFastGptChatMsg(fastGptChatMsg);
     }
 
@@ -102,8 +106,8 @@ public class FastGptChatMsgServiceImpl implements IFastGptChatMsgService
     }
 
     @Override
-    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId) {
-        return fastGptChatMsgMapper.selectFastGptChatMsgCVOBySessionId(sessionId);
+    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId,String userId) {
+        return fastGptChatMsgMapper.selectFastGptChatMsgCVOBySessionId(sessionId,userId);
     }
 
     @Override
@@ -128,4 +132,21 @@ public class FastGptChatMsgServiceImpl implements IFastGptChatMsgService
     public List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionId(Long sessionId) {
         return fastGptChatMsgMapper.selectFastGptChatMsgByMsgSessionId(sessionId);
     }
+
+    @Override
+    @DataSource(DataSourceType.CLICKHOUSE)
+    public void insertFastGptEventLog(FastGptEventLog fastGptEventLog) {
+        fastGptChatMsgMapper.insertFastGptEventLog(fastGptEventLog);
+    }
+
+    @Override
+    @DataSource(DataSourceType.CLICKHOUSE)
+    public void insertFastGptEventTokenLog(FastGptEventTokenLog fastGptEventTokenLog) {
+        fastGptChatMsgMapper.insertFastGptEventTokenLog(fastGptEventTokenLog);
+    }
+
+    @Override
+    public List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionIdAndExtId(Long sessionId, Long extId) {
+        return fastGptChatMsgMapper.selectFastGptChatMsgByMsgSessionIdAndExtId(sessionId,String.valueOf(extId));
+    }
 }

+ 116 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptKeywordSendServiceImpl.java

@@ -0,0 +1,116 @@
+package com.fs.fastGpt.service.impl;
+
+import java.util.List;
+
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.fastGpt.domain.FastGptKeyword;
+import com.fs.fastGpt.domain.FastGptKeywordArtificial;
+import org.springframework.stereotype.Service;
+import com.fs.fastGpt.mapper.FastGptKeywordSendMapper;
+import com.fs.fastGpt.domain.FastGptKeywordSend;
+import com.fs.fastGpt.service.IFastGptKeywordSendService;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-05-12
+ */
+@Service
+public class FastGptKeywordSendServiceImpl extends ServiceImpl<FastGptKeywordSendMapper, FastGptKeywordSend> implements IFastGptKeywordSendService {
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    @Override
+    public FastGptKeywordSend selectFastGptKeywordSendById(Long id,Long keywordType)
+    {
+        return baseMapper.selectFastGptKeywordSendById(id,keywordType);
+    }
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)列表
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    @Override
+    public List<FastGptKeywordSend> selectFastGptKeywordSendList(FastGptKeywordSend fastGptKeywordSend)
+    {
+        return baseMapper.selectFastGptKeywordSendList(fastGptKeywordSend);
+    }
+
+    /**
+     * 新增Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    @Override
+    public int insertFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend)
+    {
+        fastGptKeywordSend.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFastGptKeywordSend(fastGptKeywordSend);
+    }
+
+    /**
+     * 修改Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    @Override
+    public int updateFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend)
+    {
+        return baseMapper.updateFastGptKeywordSend(fastGptKeywordSend);
+    }
+
+    /**
+     * 批量删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param ids 需要删除的Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastGptKeywordSendByIds(Long[] ids)
+    {
+        return baseMapper.deleteFastGptKeywordSendByIds(ids);
+    }
+
+    /**
+     * 删除Ai关键字表(根据关键字发送文本和图片)信息
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastGptKeywordSendById(Long id)
+    {
+        return baseMapper.deleteFastGptKeywordSendById(id);
+    }
+
+    @Override
+    public List<FastGptKeywordSend> selectFastGptKeywordSendByRoleId(Long roleId) {
+        return baseMapper.selectFastGptKeywordSendByRoleId(roleId);
+    }
+
+    @Override
+    public List<FastGptKeywordArtificial> selectFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial) {
+        return baseMapper.selectFastGptKeywordArtificial(keywordArtificial);
+    }
+
+    @Override
+    public int insertFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial) {
+        return baseMapper.insertFastGptKeywordArtificial(keywordArtificial);
+    }
+
+    @Override
+    public List<FastGptKeyword> selectFastGptKeywordList(int type) {
+        return baseMapper.selectFastGptKeywordList(type);
+    }
+}

+ 78 - 0
fs-service/src/main/java/com/fs/fastGpt/vo/FastGptChatSessionVo.java

@@ -0,0 +1,78 @@
+package com.fs.fastGpt.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 对话关系对象 fastgpt_chat_session
+ *
+ * @author fs
+ * @date 2024-10-10
+ */
+@Data
+public class FastGptChatSessionVo extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 会话ID */
+    private Long sessionId;
+
+    /** 聊天id */
+    @Excel(name = "聊天id")
+    private String chatId;
+
+    /** 客户ID uid */
+    @Excel(name = "客户ID uid")
+    private String userId;
+
+    /** 客服ID 应用id? */
+    @Excel(name = "客服ID 应用id?")
+    private String kfId;
+
+    /** 状态 1会话中 2已结束 */
+    @Excel(name = "状态 1会话中 2已结束")
+    private Integer status;
+
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+
+    /** 是否查看 */
+    @Excel(name = "是否查看")
+    private Long isLook;
+
+    /** 用户类型 1微信用户 2小程序用户 3销售用户 */
+    @Excel(name = "用户类型 1微信用户 2小程序用户 3销售用户")
+    private Integer userType;
+
+    /** 客户昵称 */
+    @Excel(name = "客户昵称")
+    private String nickName;
+
+    /** 头像 */
+    @Excel(name = "头像")
+    private String avatar;
+
+    private Integer isArtificial;
+
+    private Date remindTime;
+
+    private Integer remindStatus;
+
+    private Integer remindCount;
+
+    private Long qwExtId;
+    private Long qwUserId;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastTime;
+
+    private Integer isReply;
+
+    private Integer overTime;
+
+}

+ 31 - 0
fs-service/src/main/java/com/fs/fastgptApi/param/DouBaoAiParam.java

@@ -0,0 +1,31 @@
+package com.fs.fastgptApi.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class DouBaoAiParam {
+    private String model;
+    private List<Message> messages;
+    private Integer temperature=1;
+    private double top_p=0.7;
+    private Integer max_tokens=4096;
+    @Data
+    public static class Message {
+        private String role;
+        private List<Content> content;
+    }
+
+    @Data
+    public static class Content {
+        private String type;
+        private imageUrl image_url;
+        private String text;
+    }
+
+    @Data
+    public static class imageUrl {
+        private String url;
+    }
+}

+ 118 - 20
fs-service/src/main/java/com/fs/fastgptApi/util/AiImgUtil.java

@@ -1,7 +1,9 @@
 package com.fs.fastgptApi.util;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.fastgptApi.param.DouBaoAiParam;
 import com.fs.fastgptApi.result.AiImgResult;
+import com.fs.qw.domain.QwUser;
 import org.springframework.stereotype.Service;
 
 import java.io.BufferedReader;
@@ -11,6 +13,8 @@ import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
 
 @Service
 public class AiImgUtil {
@@ -18,31 +22,125 @@ public class AiImgUtil {
 
     public String getImageParse(String imageUrl) {
         try {
-            String requestBody = String.format(
-                    "{" +
-                            "\"model\": \"doubao-vision-lite-32k-241015\"," +
-                            "\"messages\": [{" +
-                            "\"role\": \"user\"," +
-                            "\"content\": [" +
-                            "{" +
-                            "\"type\": \"image_url\"," +
-                            "\"image_url\": {\"url\": \"" + imageUrl + "\"}" +
-                            "}," +
-                            "{" +
-                            "\"type\": \"text\"," +
-                            "\"text\": \"解析一下这张图片\"" +
-                            "}" +
-                            "]" +
-                            "}]" +
-                            "}"
-            );
 
+
+            DouBaoAiParam.Content imageContent = new DouBaoAiParam.Content();
+            imageContent.setType("image_url");
+            imageContent.setImage_url(new DouBaoAiParam.imageUrl());
+            imageContent.getImage_url().setUrl(imageUrl);
+
+
+            DouBaoAiParam.Content textContent = new DouBaoAiParam.Content();
+            textContent.setType("text");
+            textContent.setText(
+                    "识别图片内容 \n" +
+                            "情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 \n" +
+                            "情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 \n" +
+                            "情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出,如果是卡通图片,需要在结尾输出【这是卡通图片】这几个字");
+
+
+            List<DouBaoAiParam.Content> contents = new ArrayList<>();
+            contents.add(imageContent);
+            contents.add(textContent);
+
+
+            DouBaoAiParam.Message message = new DouBaoAiParam.Message();
+            message.setRole("user");
+            message.setContent(contents);
+
+            // Add message to list
+            List<DouBaoAiParam.Message> messages = new ArrayList<>();
+
+            DouBaoAiParam.Content textContent2 = new DouBaoAiParam.Content();
+            textContent2.setType("text");
+            textContent2.setText(
+                    "识别图片内容 情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出");
+
+
+            List<DouBaoAiParam.Content> contents2 = new ArrayList<>();
+            contents2.add(textContent2);
+            DouBaoAiParam.Message message2 = new DouBaoAiParam.Message();
+            message2.setRole("system");
+            message2.setContent(contents2);
+            // messages.add(message2);
+            messages.add(message);
+            // Create request
+            DouBaoAiParam request = new DouBaoAiParam();
+            request.setModel("doubao-1-5-thinking-vision-pro-250428");
+            request.setMessages(messages);
+
+            String jsonString = JSON.toJSONString(request);
+            System.out.println(jsonString);
+            // 发送请求
+            String response = sendAiImgHttpRequest(jsonString);
+            System.out.println("API响应: " + response);
+            AiImgResult aiImgResult = JSON.parseObject(response, AiImgResult.class);
+            String content = aiImgResult.getChoices().get(0).getMessage().getContent();
+
+            return content;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public String getImageParse(String imageUrl, QwUser qwUser,Long sender) {
+        try {
+
+
+            DouBaoAiParam.Content imageContent = new DouBaoAiParam.Content();
+            imageContent.setType("image_url");
+            imageContent.setImage_url(new DouBaoAiParam.imageUrl());
+            imageContent.getImage_url().setUrl(imageUrl);
+
+
+            DouBaoAiParam.Content textContent = new DouBaoAiParam.Content();
+            textContent.setType("text");
+            textContent.setText(
+                    "识别图片内容 \n" +
+                            "情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 \n" +
+                            "情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 \n" +
+                            "情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出,如果是卡通图片,需要在结尾输出【这是卡通图片】这几个字");
+
+
+            List<DouBaoAiParam.Content> contents = new ArrayList<>();
+            contents.add(imageContent);
+            contents.add(textContent);
+
+
+            DouBaoAiParam.Message message = new DouBaoAiParam.Message();
+            message.setRole("user");
+            message.setContent(contents);
+
+            // Add message to list
+            List<DouBaoAiParam.Message> messages = new ArrayList<>();
+
+            DouBaoAiParam.Content textContent2 = new DouBaoAiParam.Content();
+            textContent2.setType("text");
+            textContent2.setText(
+                    "识别图片内容 情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出");
+
+
+            List<DouBaoAiParam.Content> contents2 = new ArrayList<>();
+            contents2.add(textContent2);
+            DouBaoAiParam.Message message2 = new DouBaoAiParam.Message();
+            message2.setRole("system");
+            message2.setContent(contents2);
+            // messages.add(message2);
+            messages.add(message);
+            // Create request
+            DouBaoAiParam request = new DouBaoAiParam();
+            request.setModel("doubao-1-5-thinking-vision-pro-250428");
+            request.setMessages(messages);
+
+            String jsonString = JSON.toJSONString(request);
+            System.out.println(jsonString);
             // 发送请求
-            String response = sendAiImgHttpRequest(requestBody);
+            String response = sendAiImgHttpRequest(jsonString);
             System.out.println("API响应: " + response);
             AiImgResult aiImgResult = JSON.parseObject(response, AiImgResult.class);
+            EventLogUtils.createEventTokenLog("读取图片",qwUser,sender,aiImgResult);
+            EventLogUtils.recordEventLog(sender,1L,8, qwUser);
             String content = aiImgResult.getChoices().get(0).getMessage().getContent();
-            System.out.println(content);
             return content;
         } catch (Exception e) {
             return null;

+ 28 - 0
fs-service/src/main/java/com/fs/fastgptApi/util/EventLogQueue.java

@@ -0,0 +1,28 @@
+package com.fs.fastgptApi.util;
+
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class EventLogQueue {
+    private static final BlockingQueue<FastGptEventLog> eventLogQueue = new LinkedBlockingQueue<>(10000);
+    private static final BlockingQueue<FastGptEventTokenLog> eventTokenLogQueue = new LinkedBlockingQueue<>(10000);
+
+    public static void addEventLog(FastGptEventLog log) {
+        eventLogQueue.offer(log);
+    }
+
+    public static void addEventTokenLog(FastGptEventTokenLog log) {
+        eventTokenLogQueue.offer(log);
+    }
+
+    public static FastGptEventLog pollEventLog() {
+        return eventLogQueue.poll();
+    }
+
+    public static FastGptEventTokenLog pollEventTokenLog() {
+        return eventTokenLogQueue.poll();
+    }
+}

+ 154 - 0
fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java

@@ -0,0 +1,154 @@
+package com.fs.fastgptApi.util;
+
+import com.fs.common.core.domain.entity.SysDictData;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
+import com.fs.fastGpt.service.IFastGptChatMsgService;
+import com.fs.fastgptApi.result.AiImgResult;
+import com.fs.fastgptApi.result.ChatDetailFStreamFResult;
+import com.fs.fastgptApi.result.ChatDetailTStreamFResult;
+import com.fs.qw.domain.QwUser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+import static com.fs.common.utils.DictUtils.getDictCache;
+
+@Component
+@Slf4j
+public class EventLogUtils {
+
+    private static IFastGptChatMsgService fastGptChatMsgService;
+
+    @Autowired
+    public void setFastGptChatMsgService(IFastGptChatMsgService service) {
+        EventLogUtils.fastGptChatMsgService = service;
+    }
+
+    @Scheduled(fixedDelay = 200)
+    public void consumeLogs() {
+        FastGptEventLog eventLog;
+        while ((eventLog = EventLogQueue.pollEventLog()) != null) {
+            try {
+                fastGptChatMsgService.insertFastGptEventLog(eventLog);
+            } catch (Exception e) {
+                // 记录失败日志,可加入重试机制
+                log.error("Ai事件日志写入失败:" + eventLog, e);
+            }
+        }
+
+        FastGptEventTokenLog tokenLog;
+        while ((tokenLog = EventLogQueue.pollEventTokenLog()) != null) {
+            try {
+                fastGptChatMsgService.insertFastGptEventTokenLog(tokenLog);
+            } catch (Exception e) {
+                log.error("Ai的token事件日志写入失败:" + eventLog, e);
+            }
+        }
+    }
+
+    /**
+     *  记录ai事件日志
+     * @param count     触发数量
+     * @param type      事件类型(1互动 1总对话 3转人工 4AI无法回复转人工 5AI回复不合适转人工 6完课回复 7物流事件 8图片回复 9自定义事件回复 10用户未回复AI再次提醒)
+     * @param user      企微用户
+     */
+    public static void recordEventLog(Long senderId, Long count, Integer type, QwUser user) {
+        List<SysDictData> dictCache = getDictCache("sys_fastgpt_event_log_type");
+        String content = "未知事件";
+        if(dictCache != null){
+            for (SysDictData sysDictData : dictCache) {
+                if (type.toString().equals(sysDictData.getDictValue())){
+                    content = sysDictData.getDictLabel();
+                }
+            }
+        }
+        FastGptEventLog fastGptEventLog = new FastGptEventLog();
+        fastGptEventLog.setEventName(content);
+        fastGptEventLog.setSenderId(senderId);
+        fastGptEventLog.setRoleId(user.getFastGptRoleId());
+        fastGptEventLog.setCount(count);
+        fastGptEventLog.setType(type);
+        fastGptEventLog.setCompanyId(user.getCompanyId());
+        fastGptEventLog.setCompanyUserId(user.getCompanyUserId());
+        fastGptEventLog.setQwUserId(user.getId());
+        fastGptEventLog.setCreateTime(new Date());
+
+
+        //EventLogQueue.addEventLog(fastGptEventLog); // 入队
+        //fastGptChatMsgService.insertFastGptEventLog(fastGptEventLog);
+    }
+
+    /**
+     * 记录ai事件token消耗日志
+     * @param content       事件名称
+     * @param eventType     事件类型(1文字 2图片 3...)
+     * @param tokenCount    token数量
+     * @param tokenType     token类型(1输入 2输出)
+     * @param user
+     */
+    public static void recordEventTokenLog(String content,Long senderId,Integer eventType, Long tokenCount, Integer tokenType, QwUser user) {
+        FastGptEventTokenLog fastGptEventTokenLog = new FastGptEventTokenLog();
+        fastGptEventTokenLog.setEventName(content);
+        fastGptEventTokenLog.setSenderId(senderId);
+        fastGptEventTokenLog.setRoleId(user.getFastGptRoleId());
+        fastGptEventTokenLog.setEventType(eventType);
+        fastGptEventTokenLog.setTokenCount(tokenCount);
+        fastGptEventTokenLog.setTokenType(tokenType);
+        fastGptEventTokenLog.setCompanyId(user.getCompanyId());
+        fastGptEventTokenLog.setCompanyUserId(user.getCompanyUserId());
+        fastGptEventTokenLog.setQwUserId(user.getId());
+        fastGptEventTokenLog.setCreateTime(new Date());
+
+        //EventLogQueue.addEventTokenLog(fastGptEventTokenLog); // 入队
+        //fastGptChatMsgService.insertFastGptEventTokenLog(fastGptEventTokenLog);
+    }
+
+    public static void createEventTokenLog(String content,QwUser user,Long senderId,ChatDetailTStreamFResult result) {
+
+        //计算token
+        List<ChatDetailTStreamFResult.ResponseNode> responseData = result.getResponseData();
+        Long totalTokens = 0L;
+        for (ChatDetailTStreamFResult.ResponseNode responseDatum : responseData) {
+            int tokens = responseDatum.getTokens();
+            totalTokens+=tokens;
+        }
+        recordEventTokenLog(content + "消耗token",senderId,2,totalTokens,0, user);
+
+        Long promptTokens = (long) result.getUsage().getPrompt_tokens();
+        recordEventTokenLog(content + "输入token",senderId,2,promptTokens,1, user);
+
+        Long completionTokens = (long) result.getUsage().getCompletion_tokens();
+        recordEventTokenLog(content + "输出token",senderId,2,completionTokens,2, user);
+    }
+
+
+    public static void createEventTokenLog(String content,QwUser user,Long senderId,ChatDetailFStreamFResult result) {
+
+        Long totalTokens = (long) result.getUsage().getTotalTokens();
+        recordEventTokenLog(content + "消耗token",senderId,2,totalTokens,0, user);
+
+        Long promptTokens = (long) result.getUsage().getPromptTokens();
+        recordEventTokenLog(content + "输入token",senderId,2,promptTokens,1, user);
+
+        Long completionTokens = (long) result.getUsage().getCompletionTokens();
+        recordEventTokenLog(content + "输出token",senderId,2,completionTokens,2, user);
+    }
+
+    public static void createEventTokenLog(String content,QwUser user,Long senderId,AiImgResult result) {
+
+        Long totalTokens = (long) result.getUsage().getTotal_tokens();
+        recordEventTokenLog(content + "消耗token",senderId,2,totalTokens,0, user);
+
+        Long promptTokens = (long) result.getUsage().getPrompt_tokens();
+        recordEventTokenLog(content + "输入token",senderId,2,promptTokens,1, user);
+
+        Long completionTokens = (long) result.getUsage().getCompletion_tokens();
+        recordEventTokenLog(content + "输出token",senderId,2,completionTokens,2, user);
+    }
+
+}

+ 1 - 1
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -31,7 +31,7 @@ public class FsUser extends BaseEntity
     private String idCard;
 
     /** 用户昵称 */
-    @Excel(name = "用户昵称")
+    @Excel(name = "会员昵称", sort = 1)
     private String nickName;
 
     /** 用户头像 */

+ 96 - 0
fs-service/src/main/java/com/fs/his/domain/FsUserOnlineState.java

@@ -0,0 +1,96 @@
+package com.fs.his.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 用户上线情况对象 fs_user_online_state
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserOnlineState extends BaseEntity{
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    /** 用户id */
+    private Long userId;
+
+    /** 用户昵称 */
+    @Excel(name = "用户昵称")
+    private String nickname;
+
+    /** 用户头像 */
+    @Excel(name = "用户头像")
+    private String avatar;
+
+    /** 手机号码 */
+    @Excel(name = "手机号码")
+    private String phone;
+
+    /** 微信小程序OPENID */
+    @Excel(name = "微信小程序OPENID")
+    private String maOpenId;
+
+    /** 微信公众号OPENID */
+    @Excel(name = "微信公众号OPENID")
+    private String mpOpenId;
+
+    /** 关联ID */
+    @Excel(name = "关联ID")
+    private String unionId;
+
+    /** 用户状态,1-正常,0-禁止 */
+    @Excel(name = "用户状态,1-正常,0-禁止")
+    private Integer status;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 公司名称 */
+    @Excel(name = "所属公司")
+    private String companyName;
+
+    /** 销售id */
+    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 销售名称 */
+    @Excel(name = "所属销售")
+    private String companyUserName;
+
+    /** 上线状态,1-已上线;2-未上线 */
+    @Excel(name = "上线状态,1-已上线;2-未上线")
+    private Integer onlineStatus;
+
+    /** 上线时间(第一次看课的时间) */
+    @Excel(name = "上线时间(第一次看课的时间)")
+    private Date onlineTime;
+
+    /** 看课数量 */
+    @Excel(name = "看课数量")
+    private Integer watchCourseCount;
+
+    /** 参与营期数量 */
+    @Excel(name = "参与营期数量")
+    private Integer partCourseCount;
+
+    /** 最后一次看课时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "最后一次看课时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date lastWatchDate;
+
+    /** 更新时间 */
+    @Excel(name = "更新时间", readConverterExp = "更新时间")
+    private Date updateTime;
+
+}

+ 33 - 0
fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java

@@ -0,0 +1,33 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class PayloadDTO {
+    private String data;
+    private Extension extension;
+    private String description;
+
+    @Data
+    public static class Extension{
+        private String title;
+        private String patientName;
+        private String sex;
+        private String mobile;
+        private String duration;
+        private String isVisit;
+        private String diagnose;
+        private String prescribeId;
+        private String description;
+        private String followId;
+        private Integer status;
+        private Date sendTime;
+        private String courseUrl;
+        private String appRealLink;
+        private String writeStatus;
+
+    }
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java

@@ -1099,4 +1099,6 @@ public interface FsStoreOrderMapper
     List<FsStoreOrder> selectShippedOrder();
 
     List<FsStoreOrderListVO> selectFsStoreOrderListVOByErpAccount(@Param("maps") FsStoreOrderParam fsStoreOrder);
+
+    List<FsStoreOrder> selectFsStoreOrderByFsUserId(@Param("fsUserId") Long fsUserId);
 }

+ 24 - 1
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -3,12 +3,14 @@ package com.fs.his.mapper;
 import java.util.List;
 import java.util.Map;
 
+import com.fs.course.domain.FsUserWatchCourseStatistics;
+import com.fs.course.domain.FsUserWatchStatistics;
 import com.fs.course.param.CourseAnalysisParam;
 import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.param.FsUserParam;
-import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserVO;
+import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
@@ -43,6 +45,8 @@ public interface FsUserMapper
      */
     public List<FsUser> selectFsUserList(FsUser fsUser);
 
+    public List<FsUserVO> selectFsUserVOList(@Param("maps") FsUser fsUser);
+
     /**
      * 新增用户
      *
@@ -284,6 +288,8 @@ public interface FsUserMapper
 
     int batchUpdateUserCompanyUser(@Param("userIds") List<Long> userIds, @Param("companyUserId") Long companyUserId, @Param("companyId") Long companyId);
 
+    int batchUpdateCompanyUserRelation(@Param("userIds") List<Long> userIds, @Param("companyUserId") Long companyUserId, @Param("companyId") Long companyId);
+
     /**
      * 查询会员选项列表
      * @param params    参数
@@ -357,4 +363,21 @@ public interface FsUserMapper
     FsUserAndCompanyAndDoctorVo selectCompanyAndDoctor(@Param("userId")Long userId);
 
     List<FsUser> selectUserNameByIds(@Param("userIds") String userIds);
+
+    /**
+     * 获取会员总数和新增总数-可以按营期/课程
+     * @return
+     */
+    List<FsUserWatchStatistics> selectFsUserTotal();
+
+    List<FsUserWatchCourseStatistics> selectWatchLogCount();
+
+    List<FsUserWatchCourseStatistics> selectRedPacketLogCount();
+
+    List<FsUserWatchCourseStatistics> selectAnswerLogCount();
+
+    List<FsUserWatchCourseStatistics> selectFsUserDetail();
+
+    List<FsUser> selectFsUserListByJointUserNameKey(String userNameKey);
+
 }

+ 76 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserOnlineStateMapper.java

@@ -0,0 +1,76 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserOnlineState;
+
+import java.util.List;
+
+/**
+ * 用户上线情况Mapper接口
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+public interface FsUserOnlineStateMapper extends BaseMapper<FsUserOnlineState>{
+    /**
+     * 查询用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 用户上线情况
+     */
+    FsUserOnlineState selectFsUserOnlineStateById(Long userId);
+
+    /**
+     * 查询用户上线情况列表
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 用户上线情况集合
+     */
+    List<FsUserOnlineState> selectFsUserOnlineStateList(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 新增用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int insertFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 修改用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int updateFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 删除用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateById(Long userId);
+
+    /**
+     * 批量删除用户上线情况
+     *
+     * @param userIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateByIds(Long[] userIds);
+
+    /**
+     * 查询没有看课记录的用户
+     * @return
+     */
+    List<FsUserOnlineState> selectUserNotOnline();
+
+    /**
+     * 查询短时间已经有且仅有一条看课记录的用户(查询的时间需要大于等于定时任务的时间)
+     * @return
+     */
+    List<FsUser> selectExistWatchLogUser();
+
+}

+ 11 - 0
fs-service/src/main/java/com/fs/his/param/PushFgOrderConfig.java

@@ -0,0 +1,11 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class PushFgOrderConfig {
+
+    private List<String> companyIds;
+}

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

@@ -247,4 +247,5 @@ public interface IFsStoreOrderService
     R receiveWaybillPush(String body);
 
     List<FsStoreOrderListVO> selectFsStoreOrderListVOByErpAccount(FsStoreOrderParam fsStoreOrder);
+    List<FsStoreOrder> selectFsStoreOrderByFsUserId(Long fsUserId);
 }

+ 68 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserOnlineStateService.java

@@ -0,0 +1,68 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsUserOnlineState;
+
+import java.util.List;
+
+/**
+ * 用户上线情况Service接口
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+public interface IFsUserOnlineStateService extends IService<FsUserOnlineState>{
+    /**
+     * 查询用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 用户上线情况
+     */
+    FsUserOnlineState selectFsUserOnlineStateById(Long userId);
+
+    /**
+     * 查询用户上线情况列表
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 用户上线情况集合
+     */
+    List<FsUserOnlineState> selectFsUserOnlineStateList(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 新增用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int insertFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 修改用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    int updateFsUserOnlineState(FsUserOnlineState fsUserOnlineState);
+
+    /**
+     * 批量删除用户上线情况
+     *
+     * @param userIds 需要删除的用户上线情况主键集合
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateByIds(Long[] userIds);
+
+    /**
+     * 删除用户上线情况信息
+     *
+     * @param userId 用户上线情况主键
+     * @return 结果
+     */
+    int deleteFsUserOnlineStateById(Long userId);
+
+    /**
+     * 统计未上线的用户(定时更新上线状态)
+     */
+    void insertUserNotOnline();
+
+}

+ 10 - 1
fs-service/src/main/java/com/fs/his/service/IFsUserService.java

@@ -13,9 +13,9 @@ import com.fs.course.vo.newfs.FsCourseAnalysisVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserAddress;
 import com.fs.his.param.FsUserParam;
+import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
-import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.UserVo;
 import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
@@ -185,4 +185,13 @@ public interface IFsUserService
     FsUserAndCompanyAndDoctorVo selectCompanyAndDoctor(Long userId);
 
     List<FsUser> selectUserByIds(String userIds);
+
+
+    /**
+     * 根据组合key(用户昵称+id)查询用户信息
+     * @param userNameKey (用户昵称+id)
+     * @return
+     */
+    List<FsUser> selectFsUserListByJointUserNameKey(String userNameKey);
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -3402,5 +3402,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
         return null;
     }
 
+    @Override
+    public List<FsStoreOrder> selectFsStoreOrderByFsUserId(Long fsUserId) {
+        return fsStoreOrderMapper.selectFsStoreOrderByFsUserId(fsUserId);
+    }
 
 }

+ 136 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserOnlineStateServiceImpl.java

@@ -0,0 +1,136 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserOnlineState;
+import com.fs.his.mapper.FsUserOnlineStateMapper;
+import com.fs.his.service.IFsUserOnlineStateService;
+import com.google.common.collect.Lists;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户上线情况Service业务层处理
+ *
+ * @author fs
+ * @date 2025-06-30
+ */
+@Service
+public class FsUserOnlineStateServiceImpl extends ServiceImpl<FsUserOnlineStateMapper, FsUserOnlineState> implements IFsUserOnlineStateService {
+
+    @Autowired
+    private SqlSessionFactory sqlSessionFactory;
+
+    /**
+     * 查询用户上线情况
+     *
+     * @param userId 用户上线情况主键
+     * @return 用户上线情况
+     */
+    @Override
+    public FsUserOnlineState selectFsUserOnlineStateById(Long userId)
+    {
+        return baseMapper.selectFsUserOnlineStateById(userId);
+    }
+
+    /**
+     * 查询用户上线情况列表
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 用户上线情况
+     */
+    @Override
+    public List<FsUserOnlineState> selectFsUserOnlineStateList(FsUserOnlineState fsUserOnlineState)
+    {
+        return baseMapper.selectFsUserOnlineStateList(fsUserOnlineState);
+    }
+
+    /**
+     * 新增用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserOnlineState(FsUserOnlineState fsUserOnlineState)
+    {
+        fsUserOnlineState.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserOnlineState(fsUserOnlineState);
+    }
+
+    /**
+     * 修改用户上线情况
+     *
+     * @param fsUserOnlineState 用户上线情况
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserOnlineState(FsUserOnlineState fsUserOnlineState)
+    {
+        return baseMapper.updateFsUserOnlineState(fsUserOnlineState);
+    }
+
+    /**
+     * 批量删除用户上线情况
+     *
+     * @param userIds 需要删除的用户上线情况主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOnlineStateByIds(Long[] userIds)
+    {
+        return baseMapper.deleteFsUserOnlineStateByIds(userIds);
+    }
+
+    /**
+     * 删除用户上线情况信息
+     *
+     * @param userId 用户上线情况主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOnlineStateById(Long userId)
+    {
+        return baseMapper.deleteFsUserOnlineStateById(userId);
+    }
+
+    @Override
+    public void insertUserNotOnline() {
+        // 1、获取需要新增/更新的数据
+        // 获取当前所有没有看课记录的用户
+        List<FsUserOnlineState> fsUserOnlineStates = baseMapper.selectUserNotOnline();
+
+        // 2、分批次新增
+        this.batchInsertOnline(fsUserOnlineStates);
+
+        // 3、移除有看课记录的用户
+        List<FsUser> fsUsers = baseMapper.selectExistWatchLogUser();
+        List<Long> moveUserIds = fsUsers.stream().map(FsUser::getUserId).collect(Collectors.toList());
+        if(!moveUserIds.isEmpty()){
+            baseMapper.deleteFsUserOnlineStateByIds(moveUserIds.toArray(new Long[0]));
+        }
+
+    }
+
+    private void batchInsertOnline(List<FsUserOnlineState> list) {
+        // 分批次处理,一次提交500条
+        List<List<FsUserOnlineState>> batches = Lists.partition(list, 500);
+        batches.forEach(batch -> {
+            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
+            try {
+                FsUserOnlineStateMapper mapper = sqlSession.getMapper(FsUserOnlineStateMapper.class);
+                batch.forEach(mapper::insertFsUserOnlineState);
+                sqlSession.commit();
+            } finally {
+                sqlSession.close();
+            }
+        });
+    }
+}

+ 33 - 8
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -45,18 +45,20 @@ import com.fs.his.param.FsUserAddIntegralTemplateParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserProjectTagService;
+import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.FsUserExportListVO;
 import com.fs.his.vo.FsUserFollowDoctorVO;
-import com.fs.his.vo.FsUserVO;
 import com.fs.his.vo.UserVo;
 import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.store.domain.FsUserCourseCount;
+import com.fs.store.mapper.FsUserCourseCountMapper;
 import com.fs.store.param.h5.FsUserPageListParam;
 import com.fs.store.param.h5.UserStatisticsCommonParam;
 import com.fs.store.service.cache.IFsUserCourseCountCacheService;
+import com.fs.store.vo.FsUserLastCount;
 import com.fs.store.vo.h5.*;
 import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
@@ -134,6 +136,9 @@ public class FsUserServiceImpl implements IFsUserService
     @Autowired
     private IFsUserProjectTagService userProjectTagService;
 
+    @Autowired
+    private FsUserCourseCountMapper fsUserCourseCountMapper;
+
 
     /**
      * 查询用户
@@ -568,11 +573,25 @@ public class FsUserServiceImpl implements IFsUserService
         if(companyUserId != null) {
             Long companyUser = Long.parseLong(companyUserId);
             Set<Long> userIds = companyUserCacheService.selectUserAllCompanyUserId(companyUser);
+            if (userIds != null || userIds.size() <= 1) {
+                if (param.getIsAdmin()) {
+                    List<CompanyUser> companyUsers = companyUserMapper.selectCompanyUserByCompanyId(param.getCompanyId());
+                    userIds = companyUsers.stream().map(CompanyUser::getUserId).collect(Collectors.toSet());
+                }
+            }
             param.setCompanyUserIds(userIds);
         }
 
         List<FsUserPageListVO> fsUserPageListVOS = fsUserMapper.selectFsUserPageListNew(param);
         Map<Long, CompanyTag> tagMap = companyTagCacheService.queryAllTagMap();
+        //获取会员的最新的看课状态和最后看课时间
+        Set<Long> userIds = fsUserPageListVOS.stream().map(FsUserPageListVO::getUserId).collect(Collectors.toSet());
+        List<FsUserLastCount> fsUserCourseCounts = Collections.emptyList();
+        if(!userIds.isEmpty()){
+            fsUserCourseCounts = fsUserCourseCountMapper.selectUserLastCount(userIds);
+        }
+        Map<Long, FsUserLastCount> countMap = fsUserCourseCounts.stream().collect(Collectors.toMap(FsUserLastCount::getUserId, Function.identity()));
+
         for (FsUserPageListVO item : fsUserPageListVOS) {
             if(item.getCompanyUserId() != null) {
                 String companyUserName = companyUserCacheService.selectCompanyUserNameUserById(item.getCompanyUserId());
@@ -596,18 +615,19 @@ public class FsUserServiceImpl implements IFsUserService
                     item.setMissCourseCount(byUserId.getMissCourseCount());
                     item.setMissCourseStatus(byUserId.getMissCourseStatus());
                     if(StringUtils.isNotEmpty(byUserId.getPartCourseCount())){
-                        item.setPartCourseCount(Long.valueOf(byUserId.getPartCourseCount()));
+                        item.setPartCourseCount(new BigDecimal(byUserId.getPartCourseCount()).longValue());
                     }
-                    item.setCourseCountStatus(byUserId.getStatus());
+//                    item.setCourseCountStatus(byUserId.getStatus());
                     item.setStopWatchDays(byUserId.getStopWatchDays());
                     item.setCompleteWatchDate(byUserId.getCompleteWatchDate());
+                    item.setLastWatchDate(byUserId.getLastWatchDate());
                 }
-                String userTagByUserId = null;
-                if (item.getUserId()!=null && item.getCompanyUserId() != null){
-                    userTagByUserId = companyTagCacheService
-                            .findUserTagByUserId(item.getUserId(),item.getCompanyUserId());
+                FsUserLastCount fsUserCourseCount = countMap.get(item.getUserId());
+                if(fsUserCourseCount != null){
+                    item.setCourseCountStatus(fsUserCourseCount.getStatus());
                 }
-
+                String userTagByUserId = companyTagCacheService
+                        .findUserTagByUserId(item.getUserId(),item.getCompanyUserId());
                 if(StringUtils.isNotEmpty(userTagByUserId)) {
                     String[] split = userTagByUserId.split(",");
                     Set<String> tagNames = new HashSet<>();
@@ -1026,6 +1046,11 @@ public class FsUserServiceImpl implements IFsUserService
         return fsUserMapper.selectUserNameByIds(userIds);
     }
 
+    @Override
+    public List<FsUser> selectFsUserListByJointUserNameKey(String userNameKey) {
+        return fsUserMapper.selectFsUserListByJointUserNameKey(userNameKey);
+    }
+
 
     private FsUserStatisticsVO getUserStatistics(UserStatisticsCommonParam param) {
         FsUserStatisticsVO fsUserStatisticsVO = new FsUserStatisticsVO();

+ 51 - 0
fs-service/src/main/java/com/fs/his/vo/FsComplaintOrderExportVO.java

@@ -0,0 +1,51 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author MixLiu
+ * @date 2025/7/2 下午5:13)
+ */
+
+@Data
+public class FsComplaintOrderExportVO implements Serializable  {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long OrderId;
+
+    @Excel(name = "订单号")
+    private String orderCode;
+
+    @Excel(name = "公司")
+    private String companyName;
+
+    @Excel(name = "业务员")
+    private String companyUserNickName;
+
+    /** 用户姓名 */
+    @Excel(name = "收货人")
+    private String userName;
+
+    /** 支付时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "支付时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date payTime;
+
+    /** 投诉时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "投诉时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date complaintTime;
+
+    @Excel(name = "异议内容")
+    private String objectionContent;
+
+    @Excel(name = "处理结果")
+    private String handleResult;
+
+}

+ 13 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAndUserVo.java

@@ -0,0 +1,13 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+@Data
+public class FsStoreOrderAndUserVo {
+    /** 订单ID */
+    private Float type; //订单类型
+    private Long orderId;
+    private Long userId;
+    private String jpushId; //推送id
+
+}

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