Selaa lähdekoodia

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
yjwang 2 päivää sitten
vanhempi
commit
14e99d207d
66 muutettua tiedostoa jossa 2059 lisäystä ja 82 poistoa
  1. 13 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  2. 8 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  3. 1 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  4. 1 1
      fs-admin/src/main/resources/logback.xml
  5. 15 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyDeptController.java
  6. 21 3
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  7. 57 0
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseTrainingCampController.java
  8. 32 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  9. 20 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  10. 212 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserVoiceLogController.java
  11. 241 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwWorkTaskNewController.java
  12. 40 0
      fs-framework/src/main/java/com/fs/framework/config/LogInterceptor.java
  13. 8 3
      fs-framework/src/main/java/com/fs/framework/config/ResourcesConfig.java
  14. 35 0
      fs-framework/src/main/java/com/fs/framework/config/ThreadPoolTaskWrapExecutor.java
  15. 60 0
      fs-framework/src/main/java/com/fs/framework/util/ThreadMdcUtil.java
  16. 2 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  17. 2 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  18. 5 3
      fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  19. 2 2
      fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  20. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  21. 4 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  22. 11 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  23. 6 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  24. 17 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCompanyUser.java
  25. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java
  26. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  27. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyUserMapper.java
  28. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  29. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  30. 0 1
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  31. 47 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCompanyUserService.java
  32. 3 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  33. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  34. 91 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyUserServiceImpl.java
  35. 15 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  36. 4 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  37. 51 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  38. 4 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java
  39. 57 22
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  40. 40 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVO.java
  41. 37 0
      fs-service/src/main/java/com/fs/his/domain/FsUserOperationLog.java
  42. 35 0
      fs-service/src/main/java/com/fs/his/enums/FsUserOperationEnum.java
  43. 61 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserOperationLogMapper.java
  44. 61 0
      fs-service/src/main/java/com/fs/his/service/IFsUserOperationLogService.java
  45. 93 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserOperationLogServiceImpl.java
  46. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  47. 13 5
      fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java
  48. 11 0
      fs-service/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java
  49. 75 0
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncUploadQwCourseImageService.java
  50. 11 2
      fs-service/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java
  51. 36 5
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java
  52. 2 1
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  53. 9 0
      fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogTotalVo.java
  54. 2 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  55. 2 2
      fs-service/src/main/resources/application-config-druid-jzzx.yml
  56. 18 4
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  57. 1 1
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  58. 40 8
      fs-service/src/main/resources/mapper/course/FsUserCompanyUserMapper.xml
  59. 3 0
      fs-service/src/main/resources/mapper/course/FsUserCourseTrainingCampMapper.xml
  60. 19 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  61. 76 0
      fs-service/src/main/resources/mapper/his/FsUserOperationLogMapper.xml
  62. 18 0
      fs-user-app/src/main/java/com/fs/app/annotation/UserOperationLog.java
  63. 5 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseWxH5Controller.java
  64. 3 0
      fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserController.java
  65. 3 0
      fs-user-app/src/main/java/com/fs/app/controller/WxMpController.java
  66. 283 0
      fs-user-app/src/main/java/com/fs/framework/aspectj/UserOperationLogAspect.java

+ 13 - 2
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -164,14 +164,25 @@ public class FsUserCourseController extends BaseController
         return toAjax(1);
     }
 
+    /**
+     * 复制课程
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:copy')")
+    @Log(title = "课程", businessType = BusinessType.DELETE)
+    @GetMapping("/copy/{courseId}")
+    public AjaxResult copy(@PathVariable Long courseId)
+    {
+        int i = fsUserCourseService.copyFsUserCourse(courseId);
+        return toAjax(i);
+    }
+
     /**
      * 删除课程
      */
     @PreAuthorize("@ss.hasPermi('course:userCourse:remove')")
     @Log(title = "课程", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{courseIds}")
-    public AjaxResult remove(@PathVariable Long[] courseIds)
-    {
+    public AjaxResult remove(@PathVariable Long[] courseIds){
         fsUserCourseService.deleteFsUserCourseByCourseIds(courseIds);
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);

+ 8 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java

@@ -151,6 +151,14 @@ public class FsUserCoursePeriodController extends BaseController {
         return fsUserCoursePeriodDaysService.addCourse(entity);
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:dayRemove')")
+    @Log(title = "会员营期课程删除", businessType = BusinessType.DELETE)
+    @DeleteMapping("/day/{ids}")
+    public AjaxResult dayRemove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserCoursePeriodDaysService.deleteFsUserCoursePeriodDaysByIds(ids));
+    }
+
     @PreAuthorize("@ss.hasPermi('course:period:updateCourseTime')")
     @PostMapping("/updateCourseTime")
     public R updateCourseTime(@RequestBody UpdateCourseTimeVo vo){

+ 1 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java

@@ -207,6 +207,7 @@ public class FsUserVideoController extends BaseController
         try {
             extractFirstFrame(videoFile.getAbsolutePath(), frameFile.getAbsolutePath());
         } catch (Exception e) {
+            e.printStackTrace();
             // 记录错误日志
             return R.error("提取第一帧失败");
         }

+ 1 - 1
fs-admin/src/main/resources/logback.xml

@@ -3,7 +3,7 @@
     <!-- 日志存放路径 -->
 	<property name="log.path" value="/home/fs-admin/logs" />
     <!-- 日志输出格式 -->
-	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+    <property name="log.pattern" value="%replace([%X{traceId}] ){'\\[\\s*\\] ', ''}%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
 
 	<!-- 控制台输出 -->
 	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">

+ 15 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyDeptController.java

@@ -47,6 +47,21 @@ public class CompanyDeptController extends BaseController
         return AjaxResult.success(depts);
     }
 
+
+    /**
+     * 获取部门下拉树列表
+     */
+    @GetMapping("/myDeptTreeselect")
+    public AjaxResult myDeptTreeselect(CompanyDept dept)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        dept.setCompanyId(loginUser.getCompany().getCompanyId());
+        dept.setStatus("0");
+        dept.setParentId(loginUser.getUser().getDeptId());
+        List<CompanyDept> depts = deptService.selectCompanyDeptListByDeptAndParent(dept);
+        return AjaxResult.success(deptService.buildDeptTreeSelect(depts));
+    }
+
     /**
      * 查询部门列表(排除节点)
      */

+ 21 - 3
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -25,19 +25,20 @@ import com.fs.course.config.CourseConfig;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
+import com.fs.his.vo.OptionsVO;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.system.service.ISysConfigService;
 import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.stream.Collectors;
 
 
@@ -364,6 +365,23 @@ public class CompanyUserController extends BaseController
         return companyUserService.updateCompanyUserAreaList(param);
     }
 
+    /**
+     * 根据销售名称模糊查询
+     * @param name  名称
+     * @return  list
+     */
+    @GetMapping("/getCompanyUserListLikeName")
+    public R getCompanyUserListLikeName(@RequestParam(required = false) String name,
+                                        @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                        @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("nickName", name);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<OptionsVO> companyUserList = companyUserService.selectCompanyUserListByMap(params);
+        return R.ok().put("data", new PageInfo<>(companyUserList));
+    }
+
 
     /**
      * 获取用户列表

+ 57 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseTrainingCampController.java

@@ -0,0 +1,57 @@
+package com.fs.company.controller.course;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.SortUtils;
+import com.fs.course.domain.FsUserCourseTrainingCamp;
+import com.fs.course.dto.FsUserCourseTrainingCampDTO;
+import com.fs.course.dto.FsUserCourseTrainingCampUpdateDTO;
+import com.fs.course.service.IFsUserCourseTrainingCampService;
+import com.fs.course.vo.FsUserCourseTrainingCampVO;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.vo.OptionsVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/course/trainingCamp")
+@AllArgsConstructor
+public class FsUserCourseTrainingCampController {
+
+    private final IFsUserCourseTrainingCampService fsUserCourseTrainingCampService;
+    private final TokenService tokenService;
+    /**
+     * 查询训练营列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:trainingCamp:list')")
+    @GetMapping("/list")
+    public AjaxResult list(@RequestParam(required = false) String trainingCampName,
+                           @RequestParam String scs,
+                           @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                           @RequestParam(required = false, defaultValue = "10") Integer pageSize)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Map<String, Object> params = new HashMap<>();
+        params.put("trainingCampName", trainingCampName);
+        params.put("scs", SortUtils.parseSort(scs));
+        params.put("companyId", loginUser.getCompany().getCompanyId());
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsUserCourseTrainingCampVO> list = fsUserCourseTrainingCampService.selectFsUserCourseTrainingCampVOListByMap(params);
+        return AjaxResult.success(new PageInfo<>(list));
+    }
+
+}

+ 32 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -226,11 +226,43 @@ public class QwExternalContactController extends BaseController
     @Log(title = "企业微信客户", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(QwExternalContactParam qwExternalContact)
+    {
+        if (qwExternalContact.getCorpId()==null){
+            return AjaxResult.success();
+        }
+        List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+        ExcelUtil<QwExternalContactVO> util = new ExcelUtil<QwExternalContactVO>(QwExternalContactVO.class);
+        return util.exportExcel(list, "企业微信客户数据");
+    }
+
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:myExport')")
+    @Log(title = "企业微信客户", businessType = BusinessType.EXPORT)
+    @GetMapping("/myExport")
+    public AjaxResult myExport(QwExternalContactParam qwExternalContact)
     {
         if (qwExternalContact.getQwUserId()==null){
             return AjaxResult.success();
         }
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+
+        list.forEach(item->{
+
+            if (!Objects.equals(item.getTagIds(), "[]") && item.getTagIds()!=null) {
+                QwTagSearchParam param = new QwTagSearchParam();
+                Gson gson = new Gson();
+                List<String> tagIds = gson.fromJson(
+                        item.getTagIds(),
+                        new TypeToken<List<String>>() {
+                        }.getType()
+                );
+
+                param.setTagIds(tagIds);
+
+                item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
+            }
+        });
+
         ExcelUtil<QwExternalContactVO> util = new ExcelUtil<QwExternalContactVO>(QwExternalContactVO.class);
         return util.exportExcel(list, "企业微信客户数据");
     }

+ 20 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -42,6 +42,8 @@ import com.fs.qwApi.domain.inner.FollowInfo;
 import com.fs.qwApi.param.QwExternalListParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -357,6 +359,23 @@ public class QwUserController extends BaseController
         return qwUserService.qwUnbindCloudHost(appKey);
     }
 
+    /**
+     * 根据销售名称模糊查询
+     * @param qwUserName  名称
+     * @return  list
+     */
+    @GetMapping("/getQwUserListLikeName")
+    public R getQwUserListLikeName(@RequestParam(required = false) String qwUserName,
+                                        @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                        @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("qwUserName", qwUserName);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<QwOptionsVO> qwUserList = companyUserService.selectQwUserListLikeName(params);
+        return R.ok().put("data", new PageInfo<>(qwUserList));
+    }
+
     /**
      * 查询企微用户列表
      */
@@ -500,7 +519,7 @@ public class QwUserController extends BaseController
             if (string.equals(corpId)){
                 qwUserService.syncQwUser(string);
                 qwDeptService.insertOrUpdateQwDept(string);
-                System.out.println("同步完成");
+                logger.info("同步完成");
             }
         }
         return R.ok();

+ 212 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserVoiceLogController.java

@@ -0,0 +1,212 @@
+package com.fs.company.controller.qw;
+
+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.qw.domain.QwUserVoiceLog;
+import com.fs.qw.service.IQwUserVoiceLogService;
+import com.fs.qw.vo.QwUserVoiceLogTotalVo;
+import com.fs.qw.vo.QwUserVoiceLogVo;
+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-05-08
+ */
+@RestController
+@RequestMapping("/qw/qwUserVoiceLog")
+public class QwUserVoiceLogController extends BaseController
+{
+    @Autowired
+    private IQwUserVoiceLogService qwUserVoiceLogService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询企微用户通话记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwUserVoiceLogVo qwUserVoiceLog)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwUserVoiceLogVo> list = qwUserVoiceLogService.selectQwUserVoiceLogList(qwUserVoiceLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询我的通话记录列表
+     * @param qwUserVoiceLog
+     * @return
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:myList')")
+    @GetMapping("/myList")
+    public TableDataInfo myList(QwUserVoiceLogVo qwUserVoiceLog)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUserVoiceLog.setCompanyUserId(loginUser.getUser().getUserId());
+        qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwUserVoiceLogVo> list = qwUserVoiceLogService.selectQwUserVoiceLogList(qwUserVoiceLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出我的通话记录
+     */
+
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:myExport')")
+    @Log(title = "我的通话记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/myExport")
+    public AjaxResult myExport(QwUserVoiceLogVo qwUserVoiceLog)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUserVoiceLog.setCompanyUserId(loginUser.getUser().getUserId());
+        qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwUserVoiceLogVo> list = qwUserVoiceLogService.selectQwUserVoiceLogList(qwUserVoiceLog);
+        list.forEach(m-> {
+            m.setCompanyUserName(m.getCompanyUser().getUserName());
+            m.setCompanyName(m.getCompany().getCompanyName());
+            m.setExtName(m.getQwExternalContact().getName());
+            m.setQwUserName(m.getQwUser().getQwUserName());
+        });
+        ExcelUtil<QwUserVoiceLogVo> util = new ExcelUtil<QwUserVoiceLogVo>(QwUserVoiceLogVo.class);
+        return util.exportExcel(list, "企微用户通话记录数据");
+    }
+
+
+    /**
+     * 统计查询企微用户通话记录
+     * @param qwUserVoiceLog
+     * @return
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:totalList')")
+    @GetMapping("/totalList")
+    public TableDataInfo totalList(QwUserVoiceLogTotalVo qwUserVoiceLog)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        qwUserVoiceLog.setQwUserId(1L);
+        List<QwUserVoiceLogTotalVo> list = qwUserVoiceLogService.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:sellTotalList')")
+    @GetMapping("/sellTotalList")
+    public TableDataInfo sellTotalList(QwUserVoiceLogTotalVo qwUserVoiceLog)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwUserVoiceLogTotalVo> list = qwUserVoiceLogService.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:sellTotalExport')")
+    @Log(title = "企微用户通话记录统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/sellTotalExport")
+    public AjaxResult sellTotalExport(QwUserVoiceLogTotalVo qwUserVoiceLog)
+    {
+        List<QwUserVoiceLogTotalVo> list = qwUserVoiceLogService.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
+        list.forEach(m-> {
+            m.setQwUserName(m.getQwUser().getQwUserName());
+        });
+        ExcelUtil<QwUserVoiceLogTotalVo> util = new ExcelUtil<QwUserVoiceLogTotalVo>(QwUserVoiceLogTotalVo.class);
+        return util.exportExcel(list, "企微用户通话记录数据");
+    }
+
+    /**
+     * 导出统计企微用户通话记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:totalExport')")
+    @Log(title = "企微用户通话记录统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/totalExport")
+    public AjaxResult totalExport(QwUserVoiceLogTotalVo qwUserVoiceLog)
+    {
+        List<QwUserVoiceLogTotalVo> list = qwUserVoiceLogService.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
+        list.forEach(m-> {
+            m.setQwUserName(m.getQwUser().getQwUserName());
+        });
+        ExcelUtil<QwUserVoiceLogTotalVo> util = new ExcelUtil<QwUserVoiceLogTotalVo>(QwUserVoiceLogTotalVo.class);
+        return util.exportExcel(list, "企微用户通话记录数据");
+    }
+
+
+    /**
+     * 导出企微用户通话记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:export')")
+    @Log(title = "企微用户通话记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwUserVoiceLogVo qwUserVoiceLog)
+    {
+        List<QwUserVoiceLogVo> list = qwUserVoiceLogService.selectQwUserVoiceLogList(qwUserVoiceLog);
+        list.forEach(m-> {
+            m.setCompanyUserName(m.getCompanyUser().getUserName());
+            m.setCompanyName(m.getCompany().getCompanyName());
+            m.setExtName(m.getQwExternalContact().getName());
+            m.setQwUserName(m.getQwUser().getQwUserName());
+        });
+        ExcelUtil<QwUserVoiceLogVo> util = new ExcelUtil<QwUserVoiceLogVo>(QwUserVoiceLogVo.class);
+        return util.exportExcel(list, "企微用户通话记录数据");
+    }
+
+    /**
+     * 获取企微用户通话记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwUserVoiceLogService.selectQwUserVoiceLogById(id));
+    }
+
+    /**
+     * 新增企微用户通话记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:add')")
+    @Log(title = "企微用户通话记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwUserVoiceLog qwUserVoiceLog)
+    {
+        return toAjax(qwUserVoiceLogService.insertQwUserVoiceLog(qwUserVoiceLog));
+    }
+
+    /**
+     * 修改企微用户通话记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:edit')")
+    @Log(title = "企微用户通话记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwUserVoiceLog qwUserVoiceLog)
+    {
+        return toAjax(qwUserVoiceLogService.updateQwUserVoiceLog(qwUserVoiceLog));
+    }
+
+    /**
+     * 删除企微用户通话记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:remove')")
+    @Log(title = "企微用户通话记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwUserVoiceLogService.deleteQwUserVoiceLogByIds(ids));
+    }
+}

+ 241 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwWorkTaskNewController.java

@@ -0,0 +1,241 @@
+package com.fs.company.controller.qw;
+
+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.company.service.impl.CompanyDeptServiceImpl;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
+import com.fs.qw.vo.QwWorkTaskListVO;
+import com.fs.qwApi.domain.QwExternalContactRemarkResult;
+import com.fs.qwApi.param.QwExternalContactRemarkParam;
+import com.fs.qwApi.service.QwApiService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 企微任务看板Controller
+ *
+ * @author fs
+ * @date 2025-03-25
+ */
+@RestController
+@RequestMapping("/qw/QwWorkTaskNew")
+public class QwWorkTaskNewController extends BaseController
+{
+    @Autowired
+    private IQwWorkTaskService qwWorkTaskService;
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private CompanyDeptServiceImpl companyDeptService;
+
+    /**
+     * 查询企微任务看板列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        qwWorkTask.setCompanyUserId(loginUser.getUser().getUserId());
+        List<QwWorkTaskListVO> list = qwWorkTaskService.selectQwWorkTaskListVO(qwWorkTask);
+
+        for (QwWorkTaskListVO qwWorkTaskListVO : list) {
+            qwWorkTaskListVO.setLogs(fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTaskListVO.getExtId()));
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询企微任务部门催课看板列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:deptList')")
+    @GetMapping("/deptList")
+    public TableDataInfo deptList(QwWorkTaskListParam qwWorkTask)
+    {
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        List<Long> combinedList = new ArrayList<>();
+        //本部门
+        Long deptId = loginUser.getUser().getDeptId();
+        if (deptId!=null){
+            combinedList.add(deptId);
+        }
+        //本部门的下级部门
+        List<Long> deptList = companyDeptService.selectCompanyDeptByParentId(deptId);
+        if (!deptList.isEmpty()){
+            combinedList.addAll(deptList);
+        }
+
+        qwWorkTask.setCuDeptIdList(combinedList);
+        qwWorkTask.setUserType(loginUser.getUser().getUserType());
+
+
+        startPage();
+        List<QwWorkTaskListVO> list = qwWorkTaskService.selectQwWorkTaskListVO(qwWorkTask);
+        for (QwWorkTaskListVO qwWorkTaskListVO : list) {
+            qwWorkTaskListVO.setLogs(fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTaskListVO.getExtId()));
+        }
+        return getDataTable(list);
+    }
+
+    @GetMapping("/glList")
+    public TableDataInfo glList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwWorkTaskListVO> list = qwWorkTaskService.selectQwWorkTaskListVO(qwWorkTask);
+        for (QwWorkTaskListVO qwWorkTaskListVO : list) {
+            qwWorkTaskListVO.setLogs(fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTaskListVO.getExtId()));
+        }
+        return getDataTable(list);
+    }
+
+    @GetMapping("/allList")
+    public TableDataInfo allList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (qwWorkTask.getSTime()==null||qwWorkTask.getETime()==null){
+            return new TableDataInfo();
+        }
+        List<QwWorkTaskAllListVO> list = qwWorkTaskService.selectQwWorkTaskAllListVO(qwWorkTask);
+
+        return getDataTable(list);
+    }
+    /**
+     * 导出企微任务看板列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:export')")
+    @Log(title = "企微任务看板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwWorkTask qwWorkTask)
+    {
+        List<QwWorkTask> list = qwWorkTaskService.selectQwWorkTaskList(qwWorkTask);
+        ExcelUtil<QwWorkTask> util = new ExcelUtil<QwWorkTask>(QwWorkTask.class);
+        return util.exportExcel(list, "企微任务看板数据");
+    }
+
+    /**
+     * 获取企微任务看板详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwWorkTaskService.selectQwWorkTaskById(id));
+    }
+
+    /**
+     * 新增企微任务看板
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:add')")
+    @Log(title = "企微任务看板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwWorkTask qwWorkTask)
+    {
+        return toAjax(qwWorkTaskService.insertQwWorkTask(qwWorkTask));
+    }
+    @Autowired
+    QwApiService qwApiService;
+    @Autowired
+    IQwExternalContactService qwExternalContactService;
+    @Autowired
+    QwExternalContactMapper qwExternalContactMapper;
+    /**
+     * 修改企微任务看板
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:edit')")
+    @Log(title = "企微任务看板处理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwWorkTask qwWorkTask)
+    {
+        QwWorkTask task = new QwWorkTask();
+        task.setId(qwWorkTask.getId());
+        task.setStatus(1);
+        task.setTrackType(qwWorkTask.getTrackType());
+        task.setDescription(qwWorkTask.getDescription());
+        task.setUpdateTime(new Date());
+        if (task.getDescription()!=null&& !task.getDescription().isEmpty()){
+
+            QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(qwWorkTask.getExtId());
+            if (qwExternalContact!=null){
+                QwExternalContactRemarkParam param = new QwExternalContactRemarkParam();
+                param.setUserid(qwExternalContact.getUserId());
+                param.setExternal_userid(qwExternalContact.getExternalUserId());
+                param.setDescription(task.getDescription());
+
+                QwExternalContactRemarkResult qwExternalContactRemarkResult = qwApiService.externalcontactRemark(param, qwExternalContact.getCorpId());
+                logger.info("QwExternalContactRemarkResult206:" + qwExternalContactRemarkResult);
+                if (qwExternalContactRemarkResult.getErrcode() == 0) {
+                    QwExternalContact ext = new QwExternalContact();
+                    ext.setId(qwExternalContact.getId());
+                    ext.setDescription(task.getDescription());
+                    qwExternalContactMapper.updateQwExternalContact(ext);
+                }
+            }
+        }
+        return toAjax(qwWorkTaskService.updateQwWorkTask(task));
+    }
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:edit')")
+    @Log(title = "企微任务看板处理", businessType = BusinessType.UPDATE)
+    @PutMapping("/edit2")
+    public AjaxResult edit2(@RequestBody QwWorkTask qwWorkTask)
+    {
+        QwWorkTask task = new QwWorkTask();
+        task.setId(qwWorkTask.getId());
+        task.setStatus(1);
+        task.setUpdateTime(new Date());
+        task.setTrackType(2);
+        return toAjax(qwWorkTaskService.updateQwWorkTask(task));
+    }
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:edit')")
+    @Log(title = "企微任务看板处理", businessType = BusinessType.UPDATE)
+    @PutMapping("/edit3")
+    public AjaxResult edit3(@RequestBody QwWorkTask qwWorkTask)
+    {
+        QwWorkTask task = new QwWorkTask();
+        task.setId(qwWorkTask.getId());
+        task.setStatus(1);
+        task.setUpdateTime(new Date());
+        task.setTrackType(3);
+        return toAjax(qwWorkTaskService.updateQwWorkTask(task));
+    }
+    /**
+     * 删除企微任务看板
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwWorkTaskNew:remove')")
+    @Log(title = "企微任务看板", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwWorkTaskService.deleteQwWorkTaskByIds(ids));
+    }
+}

+ 40 - 0
fs-framework/src/main/java/com/fs/framework/config/LogInterceptor.java

@@ -0,0 +1,40 @@
+package com.fs.framework.config;
+
+
+
+import org.slf4j.MDC;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.UUID;
+
+
+/**
+ * @description: 日志拦截器
+ * @author: xdd
+ * @date: 2025/3/13
+ */
+@Component
+public class LogInterceptor implements HandlerInterceptor {
+
+    private static final String traceId = "traceId";
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        String tid = UUID.randomUUID().toString().replace("-", "");
+        if (!StringUtils.isEmpty(request.getHeader("traceId"))) {
+            tid = request.getHeader("traceId");
+        }
+        MDC.put(traceId, tid);
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
+                                Exception ex) {
+        MDC.remove(traceId);
+    }
+}

+ 8 - 3
fs-framework/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -19,8 +19,7 @@ import java.time.format.DateTimeFormatter;
 
 /**
  * 通用配置
- * 
-
+ *
  */
 @Configuration
 public class ResourcesConfig implements WebMvcConfigurer
@@ -28,6 +27,9 @@ public class ResourcesConfig implements WebMvcConfigurer
     @Autowired
     private RepeatSubmitInterceptor repeatSubmitInterceptor;
 
+    @Autowired
+    private LogInterceptor logInterceptor;
+
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry)
     {
@@ -45,6 +47,9 @@ public class ResourcesConfig implements WebMvcConfigurer
     public void addInterceptors(InterceptorRegistry registry)
     {
         registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+
+        registry.addInterceptor(logInterceptor)
+                .addPathPatterns("/**");
     }
 
     /**
@@ -73,4 +78,4 @@ public class ResourcesConfig implements WebMvcConfigurer
         registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 统一日期格式
         registrar.registerFormatters(registry);
     }
-}
+}

+ 35 - 0
fs-framework/src/main/java/com/fs/framework/config/ThreadPoolTaskWrapExecutor.java

@@ -0,0 +1,35 @@
+package com.fs.framework.config;
+
+import com.fs.framework.util.ThreadMdcUtil;
+import org.slf4j.MDC;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+
+/**
+ * @description:
+ * @author: xdd
+ * @date: 2025/3/13
+ */
+public final class ThreadPoolTaskWrapExecutor extends ThreadPoolTaskExecutor {
+    public ThreadPoolTaskWrapExecutor() {
+        super();
+    }
+
+    @Override
+    public void execute(Runnable task) {
+        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
+    }
+
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
+    }
+}

+ 60 - 0
fs-framework/src/main/java/com/fs/framework/util/ThreadMdcUtil.java

@@ -0,0 +1,60 @@
+package com.fs.framework.util;
+
+
+import org.slf4j.MDC;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+/**
+ * @description:
+ * @author: xdd
+ * @date: 2025/3/13
+ * @Description:
+ */
+public final class ThreadMdcUtil {
+    private static final String traceId = "traceId";
+
+    public static String generateTraceId() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    public static void setTraceIdIfAbsent() {
+        if (MDC.get(traceId) == null) {
+            MDC.put(traceId, generateTraceId());
+        }
+    }
+
+    public static<T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
+        return () -> {
+            if (context == null) {
+                MDC.clear();
+            } else {
+                MDC.setContextMap(context);
+            }
+            setTraceIdIfAbsent();
+            try {
+                return callable.call();
+            } finally {
+                MDC.clear();
+            }
+        };
+    }
+
+
+    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
+        return () -> {
+            if (context == null) {
+                MDC.clear();
+            } else {
+                MDC.setContextMap(context);
+            }
+            setTraceIdIfAbsent();
+            try {
+                runnable.run();
+            } finally {
+                MDC.clear();
+            }
+        };
+    }
+}

+ 2 - 2
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java

@@ -153,12 +153,12 @@ public class ApisFsUserCourseVideoController extends BaseController {
 
     @GetMapping("/createRoomLink")
     @ApiOperation("创建发群链接")
-    public R createRoomLink(FsCourseLinkRoomParam param) {
+    public R createRoomLink(FsCourseLinkMiniParam param) {
         QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
-        return courseLinkService.createRoomLink(param, qwUser);
+        return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 
 }

+ 2 - 2
fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -96,12 +96,12 @@ public class FsUserCourseVideoController {
 
     @GetMapping("/createRoomLink")
     @ApiOperation("创建发群链接")
-    public R createRoomLink(FsCourseLinkRoomParam param) {
+    public R createRoomLink(FsCourseLinkMiniParam param) {
         QwUser qwUser = qwUserService.getByQwUserIdAndCorId(param.getQwUserId(), param.getCorpId());
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
-        return courseLinkService.createRoomLink(param, qwUser);
+        return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 
     /**

+ 5 - 3
fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java

@@ -145,20 +145,22 @@ public class ApisFsUserCourseVideoController extends BaseController {
 
         if (param.getType()==null || param.getType()==1){
             return fsUserCourseVideoService.createCartLink(param);
-        }else {
+        }else if (param.getType()==2){
             return fsUserCourseVideoService.createMiniLink(param);
+        } else {
+            return fsUserCourseVideoService.createRoomMiniLink(param);
         }
 
     }
 
     @GetMapping("/createRoomLink")
     @ApiOperation("创建发群链接")
-    public R createRoomLink(FsCourseLinkRoomParam param) {
+    public R createRoomLink(FsCourseLinkMiniParam param) {
         QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
-        return courseLinkService.createRoomLink(param, qwUser);
+        return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 
 }

+ 2 - 2
fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -160,12 +160,12 @@ public class FsUserCourseVideoController {
 
     @GetMapping("/createRoomLink")
     @ApiOperation("创建发群链接")
-    public R createRoomLink(FsCourseLinkRoomParam param) {
+    public R createRoomLink(FsCourseLinkMiniParam param) {
         QwUser qwUser = qwUserService.getByQwUserIdAndCorId(param.getQwUserId(), param.getCorpId());
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
-        return courseLinkService.createRoomLink(param, qwUser);
+        return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 
 }

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

@@ -11,6 +11,7 @@ import com.fs.company.vo.CompanyUserVO;
 import com.fs.company.vo.DocCompanyUserVO;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.vo.CompanyUserQwVO;
+import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.wxUser.domain.CompanyWxUser;
 import org.apache.ibatis.annotations.Param;
@@ -292,4 +293,6 @@ public interface CompanyUserMapper
      * @param domain 域名
      * **/
     void batchUpdateUserDomain(@Param("ids") List<Long> ids,@Param("domain") String domain);
+
+    List<QwOptionsVO> selectQwUserListLikeName(@Param("params") Map<String, Object> params);
 }

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

@@ -11,6 +11,7 @@ import com.fs.company.vo.DocCompanyUserVO;
 import com.fs.his.vo.CitysAreaVO;
 import com.fs.his.vo.OptionsVO;
 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;
@@ -201,4 +202,7 @@ public interface ICompanyUserService {
      * 获取用户数据
      * **/
     List<CompanyUser> getCompanyUserList(CompanyUser companyUser);
+
+    List<QwOptionsVO> selectQwUserListLikeName(Map<String, Object> params);
+
 }

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

@@ -9,6 +9,7 @@ import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyParam;
@@ -166,11 +167,16 @@ public class CompanyServiceImpl implements ICompanyService
             salesRole.setStatus("0");
 
             //增加销售角色菜单权限
-            String json = configService.selectConfigByKey("companymenu.config");
-            CompanyMenuConfig config = JSONUtil.toBean(json, CompanyMenuConfig.class);
-            // 注意:此处需要额外处理销售角色的权限,设置为仅"我的"相关权限
-            salesRole.setMenuIds(config.getMenuIds());
-            roleService.insertRole(salesRole);
+            try {
+                String json = configService.selectConfigByKey("companymenu.config");
+                if (StringUtils.isNotEmpty(json)) {
+                    CompanyMenuConfig config = JSONUtil.toBean(json, CompanyMenuConfig.class);
+                    salesRole.setMenuIds(config.getMenuIds());
+                    roleService.insertRole(salesRole);
+                }
+            } catch (Exception e) {
+                logger.error("获取菜单配置失败", e);
+            }
             //添加用户
             CompanyUser user=new CompanyUser();
             user.setCompanyId(company.getCompanyId());

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

@@ -22,6 +22,7 @@ import com.fs.his.vo.CitysAreaVO;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.vo.CompanyUserQwVO;
+import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
@@ -530,4 +531,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public List<CompanyUser> getCompanyUserList(CompanyUser companyUser) {
         return companyUserMapper.getCompanyUserList(companyUser);
     }
+
+    @Override
+    public List<QwOptionsVO> selectQwUserListLikeName(Map<String, Object> params) {
+        return companyUserMapper.selectQwUserListLikeName(params);
+    }
 }

+ 17 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCompanyUser.java

@@ -40,5 +40,22 @@ public class FsUserCompanyUser extends BaseEntity{
     @ApiModelProperty(value = "重粉所属销售,多个用逗号隔开")
     private String repeatCompanyUserName;
 
+    /**
+     * 项目ID
+     */
+    private Long projectId;
+    /**
+     * 企微用户ID
+     */
+    private Long qwUserId;
+    /**
+     * 企微外部联系人ID
+     */
+    private Long qwExternalContactId;
+    /**
+     * 企微主体ID
+     */
+    private Long qwCompanyId;
+
 
 }

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

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -70,11 +71,13 @@ public class FsUserCoursePeriod
 
     /** 开营日期-开始时间 */
     @JsonFormat(pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
     @Excel(name = "开营日期-开始时间", width = 30, dateFormat = "yyyy-MM-dd")
     private LocalDate periodStartingTime;
 
     /** 开营日期-结束时间 */
     @JsonFormat(pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
     @Excel(name = "开营日期-结束时间", width = 30, dateFormat = "yyyy-MM-dd")
     private LocalDate periodEndTime;
 

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

@@ -133,7 +133,7 @@ public interface FsCourseRedPacketLogMapper
 
     List<FsCourseRedPacketLogListPVO> selectFsCourseRedPacketLogListVONew(FsCourseRedPacketLogParam fsCourseRedPacketLog);
 
-    @Select("select * from fs_course_red_packet_log where video_id = #{videoId} and user_id = #{userId} limit 1")
+    @Select("select * from fs_course_red_packet_log where video_id = #{videoId} and user_id = #{userId} order by log_id desc limit 1")
     FsCourseRedPacketLog selectFsCourseRedPacketLogByTemporary(@Param("videoId") Long videoId, @Param("userId")Long userId);
 
     /**

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

@@ -2,6 +2,7 @@ package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsUserCompanyUser;
+import com.fs.qw.dto.FsUserTransferParamDTO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -76,4 +77,6 @@ public interface FsUserCompanyUserMapper extends BaseMapper<FsUserCompanyUser>{
      * @return
      */
     List<FsUserCompanyUser> selectRepeatCompanyUserName(@Param("userIds") List<Long> userIds);
+
+    void transfer(@Param("param") FsUserTransferParamDTO transferParam);
 }

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

@@ -217,7 +217,7 @@ public interface FsUserCourseMapper
             "<if test = ' maps.isShow !=null '> " +
             "and c.is_show = #{maps.isShow} " +
             "</if>" +
-            " order by c.course_id  "+
+            " order by c.sort,c.course_id  "+
             "</script>"})
     List<FsUserCourseListPVO> selectFsUserCourseListCompanyPVO(@Param("maps")FsUserCourseParam fsUserCourse);
 

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

@@ -7,6 +7,7 @@ import com.fs.course.param.FsUserCourseVideoListUParam;
 import com.fs.course.param.FsUserCourseVideoParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.vo.FsCourseVideoListBySidebarVO;
+import com.fs.course.vo.FsUserCourseVO;
 import com.fs.course.vo.FsUserCourseVideoListUVO;
 import com.fs.course.vo.FsUserCourseVideoVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
@@ -34,6 +35,8 @@ public interface FsUserCourseVideoMapper
      */
     public FsUserCourseVideo selectFsUserCourseVideoByVideoId(Long videoId);
 
+    FsUserCourseVO selectFsUserCourseVideoVoByVideoId(@Param("videoId") Long videoId, @Param("periodId") Long periodId);
+
     /**
      * 查询课堂视频列表
      *

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

@@ -17,7 +17,6 @@ public class FsCourseWatchLogListParam implements Serializable {
 
     private Long qwUserId;
 
-
     private Long companyId;
 
     private Long companyUserId;

+ 47 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCompanyUserService.java

@@ -59,4 +59,51 @@ public interface IFsUserCompanyUserService extends IService<FsUserCompanyUser>{
      * @return 结果
      */
     int deleteFsUserCompanyUserById(Long id);
+    /**
+     * 根据用户ID和项目ID查询微信用户与销售的关系
+     * @param userId            用户ID
+     * @param projectId   项目ID
+     * @return FsUserCompanyUser
+     */
+    FsUserCompanyUser selectByUserIdAndProjectId(Long userId, Long projectId);
+
+    /**
+     * 查询是否已绑定关系
+     * @param userId            用户ID
+     * @param companyUserId     销售ID
+     * @return  boolean
+     */
+    boolean hasBind(Long userId, Long companyUserId);
+
+    /**
+     * 获取当前销售的所有重粉会员
+     * @param userId 销售ID
+     * @return  list
+     */
+    List<FsUserCompanyUser> selectRepeatUser(Long userId);
+
+    /**
+     * 获取会员的重粉的所属销售
+     * @param userIds   会员ID集合
+     * @return  list
+     */
+    List<FsUserCompanyUser> selectRepeatCompanyUserName(List<Long> userIds);
+
+    /**
+     * 绑定会员-项目-销售关系
+     * @param userId        会员ID
+     * @param projectId     项目ID
+     * @param companyId     公司ID
+     * @param companyUserId 销售ID
+     */
+    void bindRelationship(Long userId, Long projectId, Long companyId, Long companyUserId);
+
+    /**
+     * 修改会员-项目-销售关系
+     * @param userId        会员ID
+     * @param projectId     项目ID
+     * @param companyId     公司ID
+     * @param companyUserId 销售ID
+     */
+    void changeRelationship(Long userId, Long projectId, Long companyId, Long companyUserId);
 }

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

@@ -122,4 +122,7 @@ public interface IFsUserCourseService
     String createCourseImageQR(String url, String backgroundImagePath, InputStream file, String outputFormat, String title, String duration) throws Exception;
 
     String createUserImageQR(@NotNull(message = "链接不能为空") String realLink, String backgroundImagePath, InputStream inputStream, String png, @NotNull(message = "销售id不能为空") Long companyUserId) throws Exception;
+
+    int copyFsUserCourse(Long courseId);
+
 }

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

@@ -174,4 +174,6 @@ public interface IFsUserCourseVideoService
     List<FsUserCourseVideoPageListVO> selectCourseVideoListByMap(Map<String, Object> params);
 
     ResponseResult<Boolean> setWatchCourseTime(List<FsWatchCourseTimeParam> collect);
+
+    R createRoomMiniLink(FsCourseLinkMiniParam param);
 }

+ 91 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyUserServiceImpl.java

@@ -1,5 +1,7 @@
 package com.fs.course.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.course.domain.FsUserCompanyUser;
 import com.fs.course.mapper.FsUserCompanyUserMapper;
@@ -7,6 +9,7 @@ import com.fs.course.service.IFsUserCompanyUserService;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * 微信用户和销售关系Service业务层处理
@@ -88,4 +91,92 @@ public class FsUserCompanyUserServiceImpl extends ServiceImpl<FsUserCompanyUserM
     {
         return baseMapper.deleteFsUserCompanyUserById(id);
     }
+
+    /**
+     * 根据用户ID和项目ID查询微信用户与销售的关系
+     * @param userId            用户ID
+     * @param projectId   项目ID
+     * @return FsUserCompanyUser
+     */
+    @Override
+    public FsUserCompanyUser selectByUserIdAndProjectId(Long userId, Long projectId) {
+        LambdaQueryWrapper<FsUserCompanyUser> queryWrapper = Wrappers.<FsUserCompanyUser>lambdaQuery()
+                .eq(FsUserCompanyUser::getUserId, userId)
+                .eq(FsUserCompanyUser::getProjectId, projectId);
+        return getOne(queryWrapper);
+    }
+
+    /**
+     * 查询是否已绑定关系
+     * @param userId            用户ID
+     * @param companyUserId     销售ID
+     * @return  boolean
+     */
+    @Override
+    public boolean hasBind(Long userId, Long companyUserId) {
+        LambdaQueryWrapper<FsUserCompanyUser> queryWrapper = Wrappers.<FsUserCompanyUser>lambdaQuery()
+                .eq(FsUserCompanyUser::getUserId, userId)
+                .eq(FsUserCompanyUser::getCompanyUserId, companyUserId);
+        return count(queryWrapper) > 0;
+    }
+
+    /**
+     * 获取当前销售的所有重粉会员
+     * @param userId 销售ID
+     * @return list
+     */
+    @Override
+    public List<FsUserCompanyUser> selectRepeatUser(Long userId) {
+        return baseMapper.selectRepeatUser(userId);
+    }
+
+    /**
+     * 获取会员的重粉的所属销售
+     * @param userIds   会员ID集合
+     * @return  list
+     */
+    @Override
+    public List<FsUserCompanyUser> selectRepeatCompanyUserName(List<Long> userIds) {
+        return baseMapper.selectRepeatCompanyUserName(userIds);
+    }
+
+    /**
+     * 绑定会员-项目-销售关系
+     * @param userId        会员ID
+     * @param projectId     项目ID
+     * @param companyId     公司ID
+     * @param companyUserId 销售ID
+     */
+    @Override
+    public void bindRelationship(Long userId, Long projectId, Long companyId, Long companyUserId) {
+        FsUserCompanyUser userCompanyUser = new FsUserCompanyUser();
+        userCompanyUser.setUserId(userId);
+        userCompanyUser.setProjectId(projectId);
+        userCompanyUser.setCompanyId(companyId);
+        userCompanyUser.setCompanyUserId(companyUserId);
+        boolean hasBind = hasBind(userId, companyUserId);
+        userCompanyUser.setIsRepeatFans(hasBind ? 1 : 0);
+        save(userCompanyUser);
+    }
+
+    /**
+     * 修改会员-项目-销售关系
+     * @param userId        会员ID
+     * @param projectId     项目ID
+     * @param companyId     公司ID
+     * @param companyUserId 销售ID
+     */
+    @Override
+    public void changeRelationship(Long userId, Long projectId, Long companyId, Long companyUserId) {
+        LambdaQueryWrapper<FsUserCompanyUser> queryWrapper = Wrappers.<FsUserCompanyUser>lambdaQuery()
+                .eq(FsUserCompanyUser::getUserId, userId)
+                .eq(FsUserCompanyUser::getProjectId, projectId)
+                .last("limit 1");
+        FsUserCompanyUser userCompanyUser = getOne(queryWrapper);
+        if (Objects.nonNull(userCompanyUser)) {
+            userCompanyUser.setCompanyId(companyId);
+            userCompanyUser.setCompanyUserId(companyUserId);
+            updateById(userCompanyUser);
+        }
+    }
 }

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

@@ -12,6 +12,7 @@ import com.fs.course.domain.FsUserCoursePeriodDays;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
 import com.fs.course.mapper.FsUserCoursePeriodMapper;
+import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
 import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.param.CourseAnalysisParam;
 import com.fs.course.param.PeriodCountParam;
@@ -26,6 +27,7 @@ import com.fs.his.vo.OptionsVO;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -56,6 +58,8 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     private final FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;
     private final IFsUserCourseVideoService fsUserCourseVideoService;
 
+    @Autowired
+    private FsUserCourseVideoRedPackageMapper fsUserCourseVideoRedPackageMapper;
     /**
      * 查询营期课程
      *
@@ -115,7 +119,16 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     @Override
     public int deleteFsUserCoursePeriodDaysByIds(Long[] ids)
     {
-        return baseMapper.deleteFsUserCoursePeriodDaysByIds(ids);
+        int flag = 0;
+        List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectBatchIds(Arrays.asList(ids));
+        List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
+        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
+        if(!periodDayIds.isEmpty()){
+            flag = fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
+            //删除红包记录
+            fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+        }
+        return flag;
     }
 
     /**
@@ -133,7 +146,7 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
     @Override
     public R addCourse(FsUserCoursePeriodDays entity) {
         FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(entity.getPeriodId());
-        List<FsUserCoursePeriodDays> dayList = list(new QueryWrapper<FsUserCoursePeriodDays>().eq("period_id", period.getPeriodId()));
+        List<FsUserCoursePeriodDays> dayList = list(new QueryWrapper<FsUserCoursePeriodDays>().eq("period_id", period.getPeriodId()).eq("del_flag","0"));
         long days;
         if(period.getPeriodType() == 2){
             days = 1;

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

@@ -10,6 +10,7 @@ import com.fs.course.mapper.FsUserCoursePeriodMapper;
 import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.vo.FsUserCoursePeriodVO;
+import org.apache.commons.collections.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -145,6 +146,9 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
         int flag = fsUserCoursePeriodMapper.updateBatchDelFlag(periodIds,1);
         //删除课程
         Set<Long> set = Arrays.asList(periodIds).stream().collect(Collectors.toSet());
+        if (CollectionUtils.isEmpty(set)){
+            return flag;
+        }
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
         List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());

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

@@ -25,6 +25,7 @@ import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseListParam;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
 import com.fs.his.config.IntegralConfig;
@@ -36,6 +37,7 @@ import com.fs.his.service.IFsUserNewTaskService;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.domain.QwCompany;
 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.service.ISysConfigService;
@@ -102,8 +104,18 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private QwApiService qwApiService;
 
+    @Autowired
+    private IFsUserCourseService fsUserCourseService;
+
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
+
     @Autowired
     private RedisCache redisCache;
+
+    @Autowired
+    private AsyncUploadQwCourseImageService asyncUploadQwCourseImageService;
+
     private static final String realLink = "/courseH5/pages/course/learning?course=";
     public static final String shortLink = "/courseH5/pages/course/learning?s=";
 
@@ -141,8 +153,14 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Override
     public int insertFsUserCourse(FsUserCourse fsUserCourse)
     {
+
+        if (fsUserCourse.getIsPrivate()==1){
+            asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
+        }
+
+
         fsUserCourse.setCreateTime(DateUtils.getNowDate());
-        return fsUserCourseMapper.insertFsUserCourse(fsUserCourse);
+        return  fsUserCourseMapper.insertFsUserCourse(fsUserCourse);
     }
 
     /**
@@ -154,6 +172,10 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Override
     public int updateFsUserCourse(FsUserCourse fsUserCourse)
     {
+        if (fsUserCourse.getIsPrivate()==1) {
+            asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
+        }
+
         fsUserCourse.setUpdateTime(DateUtils.getNowDate());
         return fsUserCourseMapper.updateFsUserCourse(fsUserCourse);
     }
@@ -609,6 +631,33 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return convertToBase64(combined, outputFormat);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class) // 显式声明事务
+    public int copyFsUserCourse(Long courseId) {
+        FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(courseId);
+        if(fsUserCourse != null){
+            fsUserCourse.setCourseId(null);
+            fsUserCourseService.insertFsUserCourse(fsUserCourse);
+            Long newCourseId = fsUserCourse.getCourseId();
+
+            if (newCourseId == null) {
+                throw new RuntimeException("课程插入失败,无法获取新课程ID");
+            }
+
+            FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
+            fsUserCourseVideo.setCourseId(courseId);
+            List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+            for (FsUserCourseVideo courseVideo : list) {
+                courseVideo.setVideoId(null);
+                courseVideo.setCourseId(newCourseId);
+                fsUserCourseVideoService.insertFsUserCourseVideo(courseVideo);
+            }
+            return 1;
+        }
+
+        return 0;
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();
@@ -692,7 +741,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
      * @param corpId 企业ID
      * @throws Exception 上传过程中的异常
      */
-    private void uploadCourseImage(FsUserCourse course, String corpId) throws Exception {
+    public void uploadCourseImage(FsUserCourse course, String corpId) throws Exception {
         File imageFile = null;
         try {
             // 将图片URL转为本地临时文件

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

@@ -20,6 +20,7 @@ import com.fs.course.service.IFsUserCourseTrainingCampService;
 import com.fs.course.vo.FsUserCourseTrainingCampVO;
 import com.fs.his.vo.OptionsVO;
 import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -90,6 +91,9 @@ public class FsUserCourseTrainingCampServiceImpl extends ServiceImpl<FsUserCours
         }
         //删除课程
         Set<Long> set = periodIds.stream().collect(Collectors.toSet());
+        if (CollectionUtils.isEmpty(set)){
+            return;
+        }
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
         List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());

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

@@ -439,22 +439,28 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     private R handleRoom(FsUserCourseVideoAddKfUParam param,FsUser user) {
         //查询客户列表
-//        List<QwExternalContact> contacts = qwExternalContactMapper.selectQwExternalContactListVOByfsUserId(user.getUserId());
-//        if (!contacts.isEmpty()){
-//            //找出对应销售匹配的客户
-//            QwExternalContact matchedContact = contacts.stream()
-//                    .filter(contact -> contact.getQwUserId().equals(Long.parseLong(param.getQwUserId())))
-//                    .findFirst()
-//                    .orElse(null);
-//            if (matchedContact!=null){
-//                param.setQwExternalId(matchedContact.getId());
-//                FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(param.getQwExternalId(), param.getVideoId(),param.getQwUserId());
-//                if (log==null){
-//                    createWatchLog(param);
-//                }
-//                return R.ok().put("qwExternalId",matchedContact.getId());
-//            }
-//        }
+        List<QwExternalContact> contacts = qwExternalContactMapper.selectQwExternalContactListVOByfsUserId(user.getUserId());
+        log.info("查出来的企微客户数量:"+contacts.size());
+        if (!contacts.isEmpty()){
+            //找出对应销售匹配的客户
+            QwExternalContact matchedContact = contacts.stream()
+                    .filter(contact -> contact.getQwUserId().equals(Long.parseLong(param.getQwUserId())))
+                    .findFirst()
+                    .orElse(null);
+
+            if (matchedContact!=null){
+                log.info("匹配到的第一个企微用户:"+matchedContact.getUserId());
+                log.info("企微id:"+matchedContact.getId());
+                log.info("用户:"+param.getVideoId());
+                log.info("企微用户:"+param.getQwUserId());
+                param.setQwExternalId(matchedContact.getId());
+                FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(matchedContact.getId(), param.getVideoId(),param.getQwUserId());
+                if (log==null){
+                    createWatchLog(param);
+                }
+                return R.error(567,"群聊通用链接").put("qwExternalId", matchedContact.getId());
+            }
+        }
 
         FsCourseLink courseLink = courseLinkMapper.selectFsCourseLinkByLink(param.getLink());
         System.out.println("查询的链接参数"+courseLink);
@@ -531,7 +537,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             courseWatchLogMapper.updateFsCourseWatchLog(log);
         }
 
-        return R.ok().put("qwExternalId", qwExternalContact.getId());
+        return R.error(567,"群聊通用链接").put("qwExternalId", qwExternalContact.getId());
     }
 
     private void createWatchLog(FsUserCourseVideoAddKfUParam param) {
@@ -539,7 +545,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         BeanUtils.copyProperties(param,log);
         log.setQwExternalContactId(param.getQwExternalId());
         log.setSendType(2);
+        log.setUserId(param.getUserId());
+        log.setVideoId(param.getVideoId());
         log.setDuration(0L);
+        log.setQwUserId(Long.parseLong(param.getQwUserId()));
         log.setCreateTime(new Date());
         log.setLogType(3);
         logger.info("zyp \n【群聊生成看课记录】:{}",param);
@@ -967,7 +976,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         packetParam.setRedPacketMode(config.getRedPacketMode());
         packetParam.setCompanyId(param.getCompanyId());
 
-        System.out.println("红包商户号"+amount);
+        System.out.println("红包金额"+amount);
         System.out.println("红包商户号"+packetParam);
         //2025.6.19 红包金额为0的时候
         if (amount.compareTo(BigDecimal.ZERO)>0){
@@ -1569,7 +1578,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(),param.getFsUserId(),qwUser , param.getExternalUserId());
 
         //生成小程序链接
-        String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(),2,null);
+        String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(),2,null,0);
 
         JSONObject news = new JSONObject(true);
         news.put("miniprogramAppid", qwCompany.getMiniAppId());
@@ -1611,7 +1620,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(),param.getFsUserId(),qwUser , param.getExternalUserId());
 
-        String linkByCartLink = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(),1,domainName);
+        String linkByCartLink = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(),1,domainName,0);
 
 
         //生成卡片链接
@@ -1661,7 +1670,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     }
 
     private String createLinkByMiniApp(Date sendTime, Long courseId, Long videoId,
-                                       QwUser qwUser, Long externalId,int type,String domainName) {
+                                       QwUser qwUser, Long externalId,int type,String domainName,Integer isRoom) {
 
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(qwUser.getCompanyId());
@@ -1671,7 +1680,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         link.setCorpId(qwUser.getCorpId());
         link.setCourseId(courseId);
         link.setQwExternalId(externalId);
-
+        link.setIsRoom(isRoom);
         if (type == 1) {
             link.setLinkType(0);
         }else {
@@ -1826,6 +1835,32 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return ResponseResult.ok();
     }
 
+    @Override
+    public R createRoomMiniLink(FsCourseLinkMiniParam param) {
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser==null||qwUser.getCompanyId()==null||qwUser.getCompanyUserId()==null){
+            return R.error("员工未绑定 销售公司 或 销售 请先绑定");
+        }
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+
+        if (qwCompany == null ) {
+            return  R.error().put("msg","企业不存在,请联系管理员");
+        }
+
+        //生成小程序链接
+        String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(),2,null,1);
+
+        JSONObject news = new JSONObject(true);
+        news.put("miniprogramAppid", qwCompany.getMiniAppId());
+        news.put("miniprogramTitle", param.getTitle());
+        news.put("miniprogramPicUrl", "https://cos.his.cdwjyyh.com/fs/20250523/9c8af5735d784847818cada7fa776a7b.jpg");
+        news.put("miniprogramPage", linkByMiniApp);
+
+        return R.ok().put("data",news);
+    }
+
     /**
      * 获取视频时长(优先从Redis获取,不存在则查数据库)
      */

+ 40 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVO.java

@@ -0,0 +1,40 @@
+package com.fs.course.vo;
+
+import com.fs.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+
+@Data
+public class FsUserCourseVO extends BaseEntity {
+
+    @ApiModelProperty(value = "课程视频id")
+    private Long videoId;
+
+    @ApiModelProperty(value = "视频标题")
+    private String title;
+
+
+    @ApiModelProperty(value = "课程ID")
+    private Long courseId;
+
+
+    @ApiModelProperty(value = "课程名称")
+    private String courseName;
+
+
+    @ApiModelProperty(value = "营期id")
+    private Long periodId;
+
+    @ApiModelProperty(value = "营期id")
+    private String periodName;
+
+    @ApiModelProperty(value = "训练营id")
+    private Long trainingCampId;
+
+    @ApiModelProperty(value = "训练营名称")
+    private String trainingCampName;
+
+
+
+}

+ 37 - 0
fs-service/src/main/java/com/fs/his/domain/FsUserOperationLog.java

@@ -0,0 +1,37 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户操作日志对象 fs_user_operation_log
+ *
+ * @author fs
+ * @date 2025-07-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserOperationLog extends BaseEntity{
+
+    private Long logId;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 操作类型 */
+    @Excel(name = "操作类型")
+    private String operationType;
+
+    /** 详情 */
+    @Excel(name = "详情")
+    private String details;
+
+    @Excel(name = "参数")
+    private String param;
+
+
+}

+ 35 - 0
fs-service/src/main/java/com/fs/his/enums/FsUserOperationEnum.java

@@ -0,0 +1,35 @@
+package com.fs.his.enums;
+
+import java.util.stream.Stream;
+
+public enum FsUserOperationEnum {
+    MINLOGIN("小程序登录",1),
+    H5LOGIN("h5登录",2),
+    BEMEMBER("成为会员",3),
+    ISADDKF("判断是否成为会员",4),
+    STUDY("学习课程",5),
+    ANSWER("答题",6),
+    SENDREWARD("发送奖励",7);
+
+    private final String label;
+    private final Integer value;
+
+    FsUserOperationEnum(String label, Integer value) {
+        this.label = label;
+        this.value = value;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+    public static FsUserOperationEnum toType(String label) {
+        return Stream.of(FsUserOperationEnum.values())
+                .filter(p -> p.label.equals(label))
+                .findAny()
+                .orElse(null);
+    }
+}

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

@@ -0,0 +1,61 @@
+package com.fs.his.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsUserOperationLog;
+
+/**
+ * 用户操作日志Mapper接口
+ * 
+ * @author fs
+ * @date 2025-07-04
+ */
+public interface FsUserOperationLogMapper extends BaseMapper<FsUserOperationLog>{
+    /**
+     * 查询用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 用户操作日志
+     */
+    FsUserOperationLog selectFsUserOperationLogByLogId(Long logId);
+
+    /**
+     * 查询用户操作日志列表
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 用户操作日志集合
+     */
+    List<FsUserOperationLog> selectFsUserOperationLogList(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 新增用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int insertFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 修改用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int updateFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 删除用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogId(Long logId);
+
+    /**
+     * 批量删除用户操作日志
+     * 
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogIds(Long[] logIds);
+}

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

@@ -0,0 +1,61 @@
+package com.fs.his.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsUserOperationLog;
+
+/**
+ * 用户操作日志Service接口
+ * 
+ * @author fs
+ * @date 2025-07-04
+ */
+public interface IFsUserOperationLogService extends IService<FsUserOperationLog>{
+    /**
+     * 查询用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 用户操作日志
+     */
+    FsUserOperationLog selectFsUserOperationLogByLogId(Long logId);
+
+    /**
+     * 查询用户操作日志列表
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 用户操作日志集合
+     */
+    List<FsUserOperationLog> selectFsUserOperationLogList(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 新增用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int insertFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 修改用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int updateFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 批量删除用户操作日志
+     * 
+     * @param logIds 需要删除的用户操作日志主键集合
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogIds(Long[] logIds);
+
+    /**
+     * 删除用户操作日志信息
+     * 
+     * @param logId 用户操作日志主键
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogId(Long logId);
+}

+ 93 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserOperationLogServiceImpl.java

@@ -0,0 +1,93 @@
+package com.fs.his.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.his.mapper.FsUserOperationLogMapper;
+import com.fs.his.domain.FsUserOperationLog;
+import com.fs.his.service.IFsUserOperationLogService;
+
+/**
+ * 用户操作日志Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-07-04
+ */
+@Service
+public class FsUserOperationLogServiceImpl extends ServiceImpl<FsUserOperationLogMapper, FsUserOperationLog> implements IFsUserOperationLogService {
+
+    /**
+     * 查询用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 用户操作日志
+     */
+    @Override
+    public FsUserOperationLog selectFsUserOperationLogByLogId(Long logId)
+    {
+        return baseMapper.selectFsUserOperationLogByLogId(logId);
+    }
+
+    /**
+     * 查询用户操作日志列表
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 用户操作日志
+     */
+    @Override
+    public List<FsUserOperationLog> selectFsUserOperationLogList(FsUserOperationLog fsUserOperationLog)
+    {
+        return baseMapper.selectFsUserOperationLogList(fsUserOperationLog);
+    }
+
+    /**
+     * 新增用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserOperationLog(FsUserOperationLog fsUserOperationLog)
+    {
+        fsUserOperationLog.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserOperationLog(fsUserOperationLog);
+    }
+
+    /**
+     * 修改用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserOperationLog(FsUserOperationLog fsUserOperationLog)
+    {
+        return baseMapper.updateFsUserOperationLog(fsUserOperationLog);
+    }
+
+    /**
+     * 批量删除用户操作日志
+     * 
+     * @param logIds 需要删除的用户操作日志主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOperationLogByLogIds(Long[] logIds)
+    {
+        return baseMapper.deleteFsUserOperationLogByLogIds(logIds);
+    }
+
+    /**
+     * 删除用户操作日志信息
+     * 
+     * @param logId 用户操作日志主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOperationLogByLogId(Long logId)
+    {
+        return baseMapper.deleteFsUserOperationLogByLogId(logId);
+    }
+}

+ 1 - 1
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -270,7 +270,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
                 "and (cu.nick_name like concat('%', #{companyUser}, '%') or cu.phonenumber= #{companyUser})"+
             "</if> " +
             "        </where>"+
-            "order by ec.id desc"+
+            "order by ec.create_time desc,ec.id desc"+
             "</script>"})
     List<QwExternalContactVO> selectQwExternalContactListVO(QwExternalContactParam qwExternalContact);
 

+ 13 - 5
fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java

@@ -71,19 +71,27 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
 
     void insertQwWorkTaskBatch(@Param("qwWorkTasks")List<QwWorkTask> qwWorkTasks);
     @Select({"<script> " +
-             "select t.*,e.`name`,qw.qw_user_name from qw_work_task t LEFT JOIN qw_external_contact e on e.id=t.ext_id  LEFT JOIN qw_user qw ON qw.id = t.qw_user_id  "+
-             "where DATE(t.create_time) = CURDATE()" +
+            "select t.*,cu.nick_name,e.`description`,e.name,qw.qw_user_name qwUserName,e.last_watch_time from qw_work_task t LEFT JOIN qw_external_contact e on e.id=t.ext_id  LEFT JOIN qw_user qw ON qw.id = t.qw_user_id  "+
+            "  LEFT JOIN company_user cu on t.company_user_id=cu.user_id \n" +
+            "where DATE(t.create_time) = CURDATE()" +
             "            <if test=\"extId != null \"> and t.ext_id = #{extId}</if>\n" +
             "            <if test=\"qwUserId != null \"> and t.qw_user_id = #{qwUserId}</if>\n" +
             "            <if test=\"type != null \"> and t.type = #{type}</if>\n" +
             "            <if test=\"status != null \"> and t.status = #{status}</if>\n" +
             "            <if test=\"score != null \"> and t.score = #{score}</if>\n" +
             "            <if test=\"sopId != null  and sopId != ''\"> and t.sop_id = #{sopId}</if>\n" +
+            "            <if test=\"title != null  and title != ''\"> and t.title like CONCAT('%',#{title},'%') </if>\n" +
             "            <if test=\"companyId != null \"> and t.company_id = #{companyId}</if>\n" +
             "            <if test=\"companyUserId != null \"> and t.company_user_id = #{companyUserId}</if>\n" +
-            " " +
-            " " +
-            "order by t.score desc,t.id desc "+
+            "<if test = 'deptId != null    '>   AND (cu.dept_id = #{deptId} OR cu.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{deptId}, ancestors) )) </if>" +
+            "             <if test=\"qwUserName != null and qwUserName != ''\"> and qw.qw_user_name = #{qwUserName}</if>\n" +
+            "            <if test=\"cuDeptIdList != null and !cuDeptIdList.isEmpty() and  userType != '00' \">" +
+            "               AND cu.dept_id IN " +
+            "                   <foreach collection='cuDeptIdList' item='item' open='(' separator=',' close=')'> " +
+            "                       #{item} " +
+            "                   </foreach> " +
+            "            </if>" +
+            "order by t.score desc,e.last_watch_time ,t.id desc "+
             "</script>"})
     List<QwWorkTaskListVO> selectQwWorkTaskListVO(QwWorkTaskListParam qwWorkTask);
     @Select("select ext_id from qw_work_task where type=2 and DATE(create_time) = CURDATE() ")

+ 11 - 0
fs-service/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java

@@ -6,6 +6,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class QwWorkTaskListParam {
@@ -62,5 +63,15 @@ public class QwWorkTaskListParam {
     private Long pageNum;
     private Long pageSize;
 
+    /**
+     * 销售部门
+     */
+    private List<Long> cuDeptIdList;
+
+    /**
+     * 部门类型 00 管理员 01 员工
+     */
+    private String userType;
+
 
 }

+ 75 - 0
fs-service/src/main/java/com/fs/qw/service/impl/AsyncUploadQwCourseImageService.java

@@ -0,0 +1,75 @@
+package com.fs.qw.service.impl;
+
+import com.fs.common.utils.PubFun;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.service.impl.FsUserCourseServiceImpl;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.result.QwFilterSopCustomersResult;
+import com.fs.qw.service.IQwCompanyService;
+import com.fs.qw.vo.QwSopRuleTimeVO;
+import com.fs.sop.domain.*;
+import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.mapper.QwSopMapper;
+import com.fs.sop.mapper.QwSopTempMapper;
+import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.params.DeleteQwSopParam;
+import com.fs.sop.params.QwSopTagsParam;
+import com.fs.sop.params.SopUserLogsArray;
+import com.fs.sop.params.SopUserLogsList;
+import com.fs.sop.service.IQwSopTempVoiceService;
+import com.fs.sop.service.ISopUserLogsService;
+import com.fs.voice.utils.StringUtil;
+import com.fs.wxUser.domain.CompanyWxUser;
+import com.fs.wxUser.mapper.CompanyWxUserMapper;
+import com.fs.wxUser.param.CompanyWxUserSopParam;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+public class AsyncUploadQwCourseImageService {
+
+
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
+    @Autowired
+    private FsUserCourseServiceImpl fsUserCourseService;
+
+    /**
+     * 异步处理销售删除客户
+     */
+    @Async("scheduledExecutorService")
+    public void uploadQwCourseImage(FsUserCourse fsUserCourse) {
+
+        // 获取所有企业微信配置
+        List<QwCompany> companies = iQwCompanyService.selectQwCompanyList(new QwCompany());
+
+        // 遍历每个企业微信配置
+        for (QwCompany company : companies) {
+            String corpId = company.getCorpId();
+            if (corpId == null) {
+                continue;
+            }
+
+            //上传图片到对应企业的素材库
+            try {
+                fsUserCourseService.uploadCourseImage(fsUserCourse, corpId);
+            } catch (Exception e) {
+                log.error("处理课程图片失败: courseId={}, corpId={}, error={}", fsUserCourse.getCourseId(), corpId, e.getMessage());
+            }
+
+        }
+
+    }
+}

+ 11 - 2
fs-service/src/main/java/com/fs/qw/service/impl/CustomerTransferApprovalServiceImpl.java

@@ -8,6 +8,7 @@ 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.mapper.FsUserCompanyUserMapper;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.qw.domain.CustomerTransferApproval;
@@ -17,6 +18,7 @@ import com.fs.qw.service.ICustomerTransferApprovalService;
 import com.fs.qw.vo.TransferCustomDTO;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.hc.openapi.tool.util.StringUtils;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.http.util.Asserts;
 import org.springframework.aop.framework.AopContext;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -53,7 +55,8 @@ public class CustomerTransferApprovalServiceImpl implements ICustomerTransferApp
 
     @Autowired
     private IFsUserService fsUserService;
-
+    @Autowired
+    private FsUserCompanyUserMapper fsUserCompanyUserMapper;
     /**
      * 查询客户转移审批
      *
@@ -233,7 +236,13 @@ public class CustomerTransferApprovalServiceImpl implements ICustomerTransferApp
             transferParam.setUserIds(customerIds);
             transferParam.setSourceCompanyUserId(item.getOriginalUserId());
 
-            fsUserService.transfer(transferParam);
+            if(CollectionUtils.isNotEmpty(transferParam.getUserIds())) {
+                fsUserService.transfer(transferParam);
+            }
+
+            if(CollectionUtils.isNotEmpty(transferParam.getUserIds())) {
+                fsUserCompanyUserMapper.transfer(transferParam);
+            }
         }
         List<Long> customerIds = JSON.parseArray(item.getCustomerIds(), Long.class);
         List<TransferCustomDTO> customerList = getCustomerList(customerIds, item);

+ 36 - 5
fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java

@@ -2,14 +2,14 @@ package com.fs.qw.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.utils.DateUtils;
-import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.domain.QwUser;
-import com.fs.qw.domain.QwUserVoiceLog;
-import com.fs.qw.domain.QwWorkTask;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwUserVoiceLogMapper;
 import com.fs.qw.mapper.QwWorkTaskMapper;
+import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwUserVoiceLogService;
 import com.fs.qw.vo.QwUserVoiceLogTotalVo;
 import com.fs.qw.vo.QwUserVoiceLogVo;
@@ -23,6 +23,8 @@ import org.springframework.stereotype.Service;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 企微用户通话记录Service业务层处理
@@ -42,6 +44,12 @@ public class QwUserVoiceLogServiceImpl extends ServiceImpl<QwUserVoiceLogMapper,
     WxWorkService wxWorkService;
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private IQwCompanyService qwCompanyService;
+
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
     /**
      * 查询企微用户通话记录
      *
@@ -117,7 +125,30 @@ public class QwUserVoiceLogServiceImpl extends ServiceImpl<QwUserVoiceLogMapper,
 
     @Override
     public List<QwUserVoiceLogTotalVo> selectQwUserVoiceLogTotalList(QwUserVoiceLogTotalVo qwUserVoiceLog) {
-        return baseMapper.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
+        List<QwUserVoiceLogTotalVo> qwUserVoiceLogTotalVos = baseMapper.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
+        Set<Long> list = qwUserVoiceLogTotalVos.stream().map(QwUserVoiceLogTotalVo::getCompanyUserId).collect(Collectors.toSet());
+        if(list != null && !list.isEmpty()){
+            List<CompanyUser> companyUserList = companyUserMapper.selectCompanyUserByIds(list);
+
+            qwUserVoiceLogTotalVos.forEach(m-> {
+                companyUserList.forEach(n-> {
+                    if(m.getCompanyUserId().equals(n.getUserId())){
+                        m.setCompanyUserName(n.getNickName());
+                    }
+                });
+            });
+        }
+        List<QwCompany> companyList = qwCompanyService.selectQwCompanyList(new QwCompany());
+        qwUserVoiceLogTotalVos.forEach(m -> companyList.forEach(n ->{
+            if(m.getCorpId().equals(n.getCorpId())){
+                m.setCorpName(n.getCorpName());
+            }
+        }));
+        String companyUserName = qwUserVoiceLog.getCompanyUserName();
+        if(companyUserName != null && !companyUserName.isEmpty()){
+            qwUserVoiceLogTotalVos = qwUserVoiceLogTotalVos.stream().filter(n -> n.getCompanyUserName().contains(companyUserName)).collect(Collectors.toList());
+        }
+        return qwUserVoiceLogTotalVos;
     }
 
     @Override

+ 2 - 1
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java

@@ -63,9 +63,10 @@ public class QwExternalContactVO {
     private String description;
 
     /** 标签id */
-    @Excel(name = "标签id")
+    //@Excel(name = "标签id")
     private String tagIds;
 
+    @Excel(name = "标签名")
     private List<String> tagIdsName;
 
     /** 备注电话号码 */

+ 9 - 0
fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogTotalVo.java

@@ -73,6 +73,15 @@ public class QwUserVoiceLogTotalVo extends BaseEntity {
 
     private QwUser qwUser;
 
+    private String corpName;
+
+    public String getCorpName() {
+        return corpName;
+    }
+
+    public void setCorpName(String corpName) {
+        this.corpName = corpName;
+    }
 
     public Long getConnectCount() {
         return connectCount;

+ 2 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java

@@ -92,6 +92,8 @@ public class QwSopServiceImpl implements IQwSopService
 
     @Autowired
     private AsyncSopService asyncSopService;
+
+
     @Autowired
     private AsyncSopTestService asyncSopTestService;
     @Autowired

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

@@ -60,8 +60,8 @@ watch:
   password3: v9xsKuqn_$d2y
 
 fs :
-  commonApi: http://127.0.0.1:8010
-  h5CommonApi: http://127.0.0.1:8010
+  commonApi: http://192.168.0.238:8010
+  h5CommonApi: http://192.168.0.238:8010
 nuonuo:
   key: 10924508
   secret: A2EB20764D304D16

+ 18 - 4
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -317,12 +317,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectCompanyUserListByMap" resultType="com.fs.his.vo.OptionsVO">
         select
-        cu.user_id dictValue,
-        cu.nick_name dictLabel
+        concat(cu.nick_name,'(',cu.user_id,')') as dictLabel,
+        cu.user_id as dictValue
         from company_user cu
         <where>
-            <if test="params.companyUserName != null and params.companyUserName != ''">
-                and cu.nick_name like concat('%', #{params.companyUserName}, '%')
+            <if test="params.nickName != null and params.nickName != ''">
+                and cu.nick_name like concat(#{params.nickName}, '%')
             </if>
             <if test="params.companyId != null">
                 and cu.company_id = #{params.companyId}
@@ -493,6 +493,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <!-- 数据范围过滤 -->
         ${params.dataScope}
     </select>
+    <select id="selectQwUserListLikeName" resultType="com.fs.qw.vo.QwOptionsVO">
+        select
+        concat(qw.qw_user_name,'(',qw.id,')') as dictLabel,
+        qw.id as dictValue
+        from qw_user qw
+        <where>
+            <if test="params.qwUserName != null and params.qwUserName != ''">
+                and qw.qw_user_name like concat(#{params.qwUserName}, '%')
+            </if>
+            <if test="params.companyId != null">
+                and qw.company_id = #{params.companyId}
+            </if>
+        </where>
+    </select>
     <update id="batchUpdateUserDomain">
         update company_user set domain=#{domain} where user_id in <foreach collection="ids"  item="item" index="index" open="(" separator="," close=")">#{item}</foreach>
     </update>

+ 1 - 1
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -127,7 +127,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 </foreach>
             </if>
         </where>
-         order by l.finish_time asc,l.update_time desc
+         order by l.finish_time desc,l.update_time desc,l.create_time desc
     </select>
 
     <select id="selectFsCourseWatchLogListByParam" resultType="com.fs.course.vo.FsCourseWatchLogListVO">

+ 40 - 8
fs-service/src/main/resources/mapper/course/FsUserCompanyUserMapper.xml

@@ -10,10 +10,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="companyUserId"    column="company_user_id"    />
         <result property="companyId"    column="company_id"    />
         <result property="isRepeatFans"    column="is_repeat_fans"    />
+        <result property="projectId"    column="project_id"    />
+        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="qwExternalContactId"    column="qw_external_contact_id"    />
+        <result property="qwCompanyId"    column="qw_company_id"    />
     </resultMap>
 
     <sql id="selectFsUserCompanyUserVo">
-        select id, user_id, company_user_id, company_id, is_repeat_fans from fs_user_company_user
+        select * from fs_user_company_user
     </sql>
 
     <select id="selectFsUserCompanyUserList" parameterType="FsUserCompanyUser" resultMap="FsUserCompanyUserResult">
@@ -23,6 +27,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="isRepeatFans != null "> and is_repeat_fans = #{isRepeatFans}</if>
+            <if test="projectId != null "> and project_id = #{projectId}</if>
+            <if test="qwUserId != null "> and qw_user_id = #{qwUserId}</if>
+            <if test="qwExternalContactId != null "> and qw_external_contact_id = #{qwExternalContactId}</if>
+            <if test="qwCompanyId != null "> and qw_company_id = #{qwCompanyId}</if>
         </where>
     </select>
 
@@ -39,14 +47,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">company_user_id,</if>
             <if test="companyId != null">company_id,</if>
             <if test="isRepeatFans != null">is_repeat_fans,</if>
-         </trim>
+            <if test="projectId != null">project_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="qwExternalContactId != null">qw_external_contact_id,</if>
+            <if test="qwCompanyId != null">qw_company_id,</if>
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="userId != null">#{userId},</if>
             <if test="companyUserId != null">#{companyUserId},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="isRepeatFans != null">#{isRepeatFans},</if>
-         </trim>
+            <if test="projectId != null">#{projectId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="qwExternalContactId != null">#{qwExternalContactId},</if>
+            <if test="qwCompanyId != null">#{qwCompanyId},</if>
+        </trim>
     </insert>
 
     <update id="updateFsUserCompanyUser" parameterType="FsUserCompanyUser">
@@ -56,6 +72,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="isRepeatFans != null">is_repeat_fans = #{isRepeatFans},</if>
+            <if test="projectId != null">project_id = #{projectId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="qwExternalContactId != null">qw_external_contaact_id = #{qwExternalContactId},</if>
+            <if test="qwCompanyId != null">qw_company_id = #{qwCompanyId},</if>
         </trim>
         where id = #{id}
     </update>
@@ -73,11 +93,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectRepeatCompanyUserName" resultType="FsUserCompanyUser">
         SELECT
-            GROUP_CONCAT( company_user.nick_name ) AS repeatCompanyUserName,
-            fs_user_company_user.user_id
+        GROUP_CONCAT( company_user.nick_name ) AS repeatCompanyUserName,
+        fs_user_company_user.user_id
         FROM
-            fs_user_company_user
-                LEFT JOIN company_user ON company_user.user_id = fs_user_company_user.company_user_id
+        fs_user_company_user
+        LEFT JOIN company_user ON company_user.user_id = fs_user_company_user.company_user_id
         <where>
             fs_user_company_user.user_id IN
             <foreach item="userId" collection="userIds" open="(" separator="," close=")">
@@ -85,6 +105,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </foreach>
         </where>
         GROUP BY
-            fs_user_company_user.user_id
+        fs_user_company_user.user_id
     </select>
+
+    <update id="transfer">
+        update fs_user_company_user set company_user_id=#{targetCompanyUserId}
+        <where>
+            <if test="param.userIds != null and param.userIds.size() > 0">
+                user_id in
+                <foreach collection="param.userIds" item="item" separator="," open="(" close=")">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+    </update>
 </mapper>

+ 3 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseTrainingCampMapper.xml

@@ -21,6 +21,9 @@
             <if test="params.trainingCampName != null and params.trainingCampName != ''">
                 and ctc.training_camp_name like concat('%',#{params.trainingCampName},'%')
             </if>
+            <if test="params.companyId != null and params.companyId != ''">
+                and ctp.company_id like concat('%',#{params.companyId},'%')
+            </if>
         </where>
         group by ctc.training_camp_id, ctc.training_camp_name, ctc.order_number
         order by

+ 19 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -310,6 +310,25 @@
         </if>
         order by video.course_sort
     </select>
+    <select id="selectFsUserCourseVideoVoByVideoId" resultType="com.fs.course.vo.FsUserCourseVO">
+        select
+        video.video_id,
+        video.title,
+        course.course_id,
+        course.course_name,
+        fcp.period_id,
+        fcp.period_name,
+        c.training_camp_id,
+        c.training_camp_name
+        from `fs_user_course_video` video
+        left join fs_user_course course ON video.course_id = course.course_id
+        left join fs_user_course_period_days fcpd on fcpd.video_id = video.video_id
+        left join fs_user_course_period fcp on fcp.period_id = fcpd.period_id
+        left join fs_user_course_training_camp c on fcp.training_camp_id = c.training_camp_id
+        where course.is_del = 0 and video.video_id = #{videoId}
+         and fcp.period_id = #{periodId}
+
+    </select>
 
     <update id="updateRedPacketMoney">
         update fs_user_course_video set red_packet_money = #{redPacketMoney} where video_id = #{videoId}

+ 76 - 0
fs-service/src/main/resources/mapper/his/FsUserOperationLogMapper.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.his.mapper.FsUserOperationLogMapper">
+
+    <resultMap type="FsUserOperationLog" id="FsUserOperationLogResult">
+        <result property="logId"    column="log_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="operationType"    column="operation_type"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="details"    column="details"    />
+        <result property="param"    column="param"    />
+    </resultMap>
+
+    <sql id="selectFsUserOperationLogVo">
+        select log_id, user_id, operation_type, create_time, details,param from fs_user_operation_log
+    </sql>
+
+    <select id="selectFsUserOperationLogList" parameterType="FsUserOperationLog" resultMap="FsUserOperationLogResult">
+        <include refid="selectFsUserOperationLogVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="operationType != null  and operationType != ''"> and operation_type = #{operationType}</if>
+            <if test="details != null  and details != ''"> and details = #{details}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserOperationLogByLogId" parameterType="Long" resultMap="FsUserOperationLogResult">
+        <include refid="selectFsUserOperationLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertFsUserOperationLog" parameterType="FsUserOperationLog">
+        insert into fs_user_operation_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="logId != null">log_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="operationType != null and operationType != ''">operation_type,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="details != null">details,</if>
+            <if test="param != null">param,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="logId != null">#{logId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="operationType != null and operationType != ''">#{operationType},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="details != null">#{details},</if>
+            <if test="param != null">#{param},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsUserOperationLog" parameterType="FsUserOperationLog">
+        update fs_user_operation_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="operationType != null and operationType != ''">operation_type = #{operationType},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="details != null">details = #{details},</if>
+            <if test="param != null and param != ''">param = #{param},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteFsUserOperationLogByLogId" parameterType="Long">
+        delete from fs_user_operation_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteFsUserOperationLogByLogIds" parameterType="String">
+        delete from fs_user_operation_log where log_id in
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+</mapper>

+ 18 - 0
fs-user-app/src/main/java/com/fs/app/annotation/UserOperationLog.java

@@ -0,0 +1,18 @@
+package com.fs.app.annotation;
+
+
+import com.fs.his.enums.FsUserOperationEnum;
+
+import java.lang.annotation.*;
+
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface UserOperationLog {
+    /** 操作类型,比如 "新增", "删除", "更新", "查询" */
+    FsUserOperationEnum operationType();
+
+    /** 操作详情(可选) */
+    String detail() default "";
+}

+ 5 - 0
fs-user-app/src/main/java/com/fs/app/controller/CourseWxH5Controller.java

@@ -2,6 +2,7 @@ package com.fs.app.controller;
 
 
 import com.fs.app.annotation.Login;
+import com.fs.app.annotation.UserOperationLog;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
@@ -14,6 +15,7 @@ import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.service.*;
 import com.fs.course.vo.FsUserCourseVideoH5VO;
 import com.fs.course.vo.newfs.FsUserCourseVideoLinkDetailsVO;
+import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsUserService;
 import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.Api;
@@ -72,6 +74,7 @@ public class CourseWxH5Controller extends AppBaseController {
     @Login
     @ApiOperation("H5课程详情")
     @GetMapping("/videoDetails")
+    @UserOperationLog(operationType = FsUserOperationEnum.STUDY)
     public ResponseResult<FsUserCourseVideoLinkDetailsVO> getCourseVideoDetails(FsUserCourseVideoLinkParam param) {
         param.setFsUserId(Long.parseLong(getUserId()));
         return courseVideoService.getLinkCourseVideoDetails(param);
@@ -103,6 +106,7 @@ public class CourseWxH5Controller extends AppBaseController {
 
     @ApiOperation("答题")
     @PostMapping("/courseAnswer")
+    @UserOperationLog(operationType = FsUserOperationEnum.ANSWER)
     public R courseAnswer(@RequestBody FsCourseQuestionAnswerUParam param){
         param.setUserId(Long.parseLong(getUserId()));
         logger.info("zyp \n【答题】:{}",param.getQuestions());
@@ -115,6 +119,7 @@ public class CourseWxH5Controller extends AppBaseController {
     @ApiOperation("发放奖励")
     @PostMapping("/sendReward")
     @RepeatSubmit
+    @UserOperationLog(operationType = FsUserOperationEnum.SENDREWARD)
     public R sendReward(@RequestBody FsCourseSendRewardUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));

+ 3 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserController.java

@@ -5,6 +5,7 @@ import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
 import cn.hutool.core.date.DateTime;
 import com.alibaba.fastjson.JSON;
+import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.utils.JwtUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
@@ -19,6 +20,7 @@ import com.fs.company.service.ICompanyUserService;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.course.config.CourseMaConfig;
 import com.fs.his.domain.FsUser;
+import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsUserService;
 
 import com.fs.system.domain.SysConfig;
@@ -74,6 +76,7 @@ public class WxCompanyUserController extends AppBaseController {
 
     @ApiOperation("小程序-授权登录")
     @PostMapping("/loginByMa")
+    @UserOperationLog(operationType = FsUserOperationEnum.MINLOGIN)
     public R login(@RequestBody LoginMaWxParam param) {
         log.info("=====================进入小程序授权登录, 入参: {}", param);
         if (StringUtils.isBlank(param.getCode())) {

+ 3 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxMpController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 
 import cn.hutool.core.date.DateTime;
+import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.param.FsUserLoginByMpParam;
 import com.fs.app.utils.JwtUtils;
 import com.fs.common.core.domain.R;
@@ -8,6 +9,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.course.mapper.FsCourseSopLogsMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.his.domain.FsUser;
+import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.qw.mapper.QwExternalContactMapper;
@@ -157,6 +159,7 @@ public class WxMpController {
   @ApiOperation("课程短链公众号登录")
   @PostMapping("/loginByMp")
   @Transactional
+  @UserOperationLog(operationType = FsUserOperationEnum.H5LOGIN)
   public R loginByMp( @RequestBody FsUserLoginByMpParam param) {
 
     if (StringUtils.isBlank(param.getCode())) {

+ 283 - 0
fs-user-app/src/main/java/com/fs/framework/aspectj/UserOperationLogAspect.java

@@ -0,0 +1,283 @@
+package com.fs.framework.aspectj;
+
+import com.alibaba.druid.support.json.JSONUtils;
+import com.alibaba.fastjson.JSON;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.app.annotation.UserOperationLog;
+import com.fs.app.utils.JwtUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.param.LoginMaWxParam;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.course.domain.FsCourseQuestionBank;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.param.FsCourseQuestionAnswerUParam;
+import com.fs.course.param.FsCourseSendRewardUParam;
+import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
+import com.fs.course.vo.FsUserCourseVO;
+import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserOperationLog;
+import com.fs.his.enums.FsUserOperationEnum;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.mapper.FsUserOperationLogMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.*;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Aspect
+@Component
+public class UserOperationLogAspect {
+
+    @Autowired
+    private FsUserOperationLogMapper logMapper;
+    @Autowired
+    private FsUserMapper userMapper;
+    @Autowired
+    private FsUserCourseVideoMapper userCourseVideoMapper;
+    @Autowired
+    JwtUtils jwtUtils;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+    private static final ThreadLocal<FsUserOperationLog> LOG_HOLDER = new ThreadLocal<>();
+
+    @Pointcut("@annotation(com.fs.app.annotation.UserOperationLog)")
+    public void logPointcut() {}
+
+
+    @AfterReturning(pointcut = "logPointcut()", returning = "result")
+    public void doAfterReturning(JoinPoint joinPoint, Object result) {
+        handleLog(joinPoint, null);
+    }
+
+    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
+        handleLog(joinPoint, e);
+    }
+
+    @AfterReturning(pointcut = "@annotation(userOpLog)", returning = "result")
+    public void afterReturning(JoinPoint joinPoint,UserOperationLog userOpLog, Object result) {
+        FsUserOperationLog operationLog = LOG_HOLDER.get();
+        if (operationLog == null) return;
+
+        try {
+            if (operationLog.getUserId() == null){
+                FsUser fsUser = extractUserFromResult(result);
+                if (fsUser != null && operationLog.getUserId() == null) {
+                    operationLog.setUserId(fsUser.getUserId());
+                    StringBuilder details = getDetail(
+                            userOpLog,
+                            FsUserOperationEnum.toType(operationLog.getOperationType()),
+                            fsUser,
+                            joinPoint.getArgs());
+                    operationLog.setDetails(details.toString());
+                }
+            }
+            logMapper.insertFsUserOperationLog(operationLog);
+        } catch (Exception ex) {
+            log.error("操作日志插入异常", ex);
+        } finally {
+            LOG_HOLDER.remove();
+        }
+    }
+
+    private void handleLog(JoinPoint joinPoint, Exception e) {
+        FsUserOperationLog operationLog = new FsUserOperationLog();
+        LOG_HOLDER.set(operationLog);
+        try {
+            //时间
+            operationLog.setCreateTime(DateUtils.getNowDate());
+            //操作类型
+            Method method = getMethod(joinPoint);
+            UserOperationLog annotation = method.getAnnotation(UserOperationLog.class);
+            if (annotation == null) return;
+            operationLog.setOperationType(annotation.operationType().getLabel());
+
+            //用户
+            Long userId =null;
+            try {
+                userId = Long.valueOf(jwtUtils.getClaimByToken(ServletUtils.getRequest().getHeader("APPToken")).getSubject().toString());
+            } catch (Exception ie){
+                log.info("获取用户id失败");
+            }
+            if (userId == null) {
+                LOG_HOLDER.set(operationLog);
+                return;
+            }
+
+            FsUser fsUser = userMapper.selectFsUserByUserId(userId);
+            if (fsUser == null) {
+                LOG_HOLDER.set(operationLog);
+                return;
+            }
+            operationLog.setUserId(userId);
+
+
+            StringBuilder details = getDetail(annotation, annotation.operationType(), fsUser,joinPoint.getArgs());
+            operationLog.setDetails(details.toString());
+
+            if (e != null) {
+                details.append(",异常: ").append(e.getMessage());
+            }
+
+
+            LOG_HOLDER.set(operationLog);
+        } catch (Exception ex) {
+            log.error("记录操作日志异常", ex);
+        }
+    }
+
+    private StringBuilder getDetail(UserOperationLog annotation, FsUserOperationEnum opType, FsUser fsUser,Object[] args) {
+        StringBuilder details = new StringBuilder();
+        if (annotation.detail() == null || annotation.detail().isEmpty()) {
+            switch (opType.getValue()) {
+                case 1: // 小程序登录
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("登录了小程序");
+                    break;
+                case 2: // h5登录
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("登录了h5");
+                    break;
+                case 3: // 成为会员
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("注册成为会员");
+                    break;
+                case 4: // 判断是否成为会员
+                    break;
+                case 5: // 学习课程
+                    String courseInfo = extractCourseInfo(args);
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("观看 ").append(courseInfo);
+                    break;
+                case 6: // 答题
+                    String answerCourse = answerCourse(args);
+                    details.append(fsUser.getNickName())
+                            .append("在 ")
+                            .append(DateUtils.getTime())
+                            .append("\n")
+                            .append(answerCourse);
+                    break;
+                case 7: // 发送奖励
+                    String sendReward = sendReward(args);
+
+                    details.append(fsUser.getNickName())
+                            .append("在 ")
+                            .append(DateUtils.getTime())
+                            .append("领取红包")
+                            .append("\n")
+                            .append(sendReward);
+                    break;
+                default:
+                    details.append(opType.getLabel());
+                    break;
+            }
+        } else {
+            details.append(annotation.detail());
+        }
+        return details;
+    }
+
+    private Method getMethod(JoinPoint joinPoint) throws NoSuchMethodException {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        return joinPoint.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
+    }
+
+
+    private FsUser extractUserFromResult(Object result) {
+        if (result instanceof R) {
+            R r = (R) result;
+            Object user = r.get("user");
+            if (user instanceof FsUser) {
+                return (FsUser) user;
+            }
+        }
+        return null;
+    }
+    private String extractCourseInfo(Object[] args) {
+        if (args == null) return "未知课程";
+
+        for (Object arg : args) {
+            if (arg instanceof FsUserCourseVideoLinkParam) {
+                FsUserCourseVideoLinkParam param = (FsUserCourseVideoLinkParam) arg;
+                if (param.getVideoId() != null && param.getPeriodId() != null) {
+                    FsUserCourseVO vo = userCourseVideoMapper.selectFsUserCourseVideoVoByVideoId(param.getVideoId(), param.getPeriodId());
+                    if (vo != null){
+                        FsUserOperationLog operationLog = LOG_HOLDER.get();
+                        operationLog.setParam(JSON.toJSONString(vo));
+                        LOG_HOLDER.set(operationLog);
+                        return "课程:"+ vo.getCourseName() + " 的 " + vo.getTitle() + " 小节";
+                    }
+                }
+            }
+        }
+        return "未知课程";
+    }
+
+    private String answerCourse(Object[] args) {
+        if (args == null) return "未知课程";
+        for (Object arg : args) {
+            if (arg instanceof FsCourseQuestionAnswerUParam) {
+                FsCourseQuestionAnswerUParam param = (FsCourseQuestionAnswerUParam) arg;
+                if (param.getVideoId() != null && param.getPeriodId() != null) {
+                    FsUserCourseVO vo = userCourseVideoMapper.selectFsUserCourseVideoVoByVideoId(param.getVideoId(), param.getPeriodId());
+                    if (vo != null){
+                        FsUserOperationLog operationLog = LOG_HOLDER.get();
+                        operationLog.setParam(JSON.toJSONString(vo));
+                        LOG_HOLDER.set(operationLog);
+                        StringBuilder details =new StringBuilder("课程:"+ vo.getCourseName() + " 的 " + vo.getTitle() + " 小节 问题为:");
+                        List<FsCourseQuestionBank> questions = param.getQuestions();
+                        if (questions != null && !questions.isEmpty()) {
+                            for (int i = 0; i < questions.size(); i++) {
+                                details.append("\n").append(i+1).append(".").append(questions.get(i).getTitle());
+                                details.append(" 提交答案为:").append(questions.get(i).getAnswer());
+                            }
+                        }
+                        return details.toString();
+                    }
+                }
+            }
+        }
+        return "未知课程";
+    }
+
+    private String sendReward(Object[] args) {
+        if (args == null) return "未知课程";
+        for (Object arg : args) {
+            if (arg instanceof FsCourseSendRewardUParam) {
+                FsCourseSendRewardUParam param = (FsCourseSendRewardUParam) arg;
+                if (param.getVideoId() != null && param.getPeriodId() != null) {
+                    FsUserCourseVO vo = userCourseVideoMapper.selectFsUserCourseVideoVoByVideoId(param.getVideoId(), param.getPeriodId());
+                    if (vo != null){
+                        FsUserOperationLog operationLog = LOG_HOLDER.get();
+                        operationLog.setParam(JSON.toJSONString(vo));
+                        LOG_HOLDER.set(operationLog);
+                        StringBuilder details =new StringBuilder();
+                        details.append("课程:"+ vo.getCourseName() + " 的 " + vo.getTitle() + " 小节");
+                        return details.toString();
+                    }
+                }
+            }
+        }
+        return "未知课程";
+    }
+
+}