Ver código fonte

Merge branch 'refs/heads/master' into cg_live_dev

chenguo 1 semana atrás
pai
commit
cd91998964
61 arquivos alterados com 2283 adições e 141 exclusões
  1. 15 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyDeptController.java
  2. 32 3
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  3. 53 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  4. 20 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  5. 212 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserVoiceLogController.java
  6. 241 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwWorkTaskNewController.java
  7. 28 0
      fs-company/src/main/java/com/fs/company/utils/DomainUtil.java
  8. 2 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  9. 2 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  10. 5 3
      fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  11. 67 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisQwUserController.java
  12. 2 2
      fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  13. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  14. 4 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  15. 6 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  16. 17 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCompanyUser.java
  17. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  18. 0 1
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  19. 47 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCompanyUserService.java
  20. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  21. 91 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyUserServiceImpl.java
  22. 128 93
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  23. 40 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVO.java
  24. 37 0
      fs-service/src/main/java/com/fs/his/domain/FsUserOperationLog.java
  25. 35 0
      fs-service/src/main/java/com/fs/his/enums/FsUserOperationEnum.java
  26. 61 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserOperationLogMapper.java
  27. 61 0
      fs-service/src/main/java/com/fs/his/service/IFsUserOperationLogService.java
  28. 93 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserOperationLogServiceImpl.java
  29. 7 0
      fs-service/src/main/java/com/fs/qw/domain/QwTagGroup.java
  30. 9 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  31. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  32. 11 1
      fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java
  33. 8 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  34. 13 5
      fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java
  35. 11 0
      fs-service/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java
  36. 31 0
      fs-service/src/main/java/com/fs/qw/param/sidebar/ExternalContactInfoParam.java
  37. 24 0
      fs-service/src/main/java/com/fs/qw/param/sidebar/TagGroupListParam.java
  38. 24 0
      fs-service/src/main/java/com/fs/qw/param/sidebar/TagGroupUpdateParam.java
  39. 32 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  40. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwTagGroupService.java
  41. 110 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  42. 16 4
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java
  43. 36 5
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java
  44. 2 1
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  45. 9 0
      fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogTotalVo.java
  46. 59 0
      fs-service/src/main/java/com/fs/qw/vo/sidebar/ExternalContactInfoVO.java
  47. 63 0
      fs-service/src/main/java/com/fs/qw/vo/sidebar/ExternalContactQwUserVO.java
  48. 20 0
      fs-service/src/main/java/com/fs/qw/vo/sidebar/ExternalContactTagVO.java
  49. 3 3
      fs-service/src/main/resources/application-config-druid-bnkc.yml
  50. 18 4
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  51. 1 1
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  52. 28 8
      fs-service/src/main/resources/mapper/course/FsUserCompanyUserMapper.xml
  53. 19 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  54. 76 0
      fs-service/src/main/resources/mapper/his/FsUserOperationLogMapper.xml
  55. 11 0
      fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml
  56. 18 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  57. 18 0
      fs-user-app/src/main/java/com/fs/app/annotation/UserOperationLog.java
  58. 5 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseWxH5Controller.java
  59. 3 0
      fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserController.java
  60. 3 0
      fs-user-app/src/main/java/com/fs/app/controller/WxMpController.java
  61. 283 0
      fs-user-app/src/main/java/com/fs/framework/aspectj/UserOperationLogAspect.java

+ 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));
+    }
+
     /**
      * 查询部门列表(排除节点)
      */

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

@@ -18,24 +18,27 @@ import com.fs.company.mapper.CompanyUserDelayTimeMapper;
 import com.fs.company.param.CompanyUserAreaParam;
 import com.fs.company.param.CompanyUserQwParam;
 import com.fs.company.service.*;
+import com.fs.company.utils.DomainUtil;
 import com.fs.company.vo.CompanyUserQwListVO;
 import com.fs.company.vo.CompanyUserVO;
 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;
 
 
@@ -362,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));
+    }
+
 
     /**
      * 获取用户列表
@@ -376,4 +396,13 @@ public class CompanyUserController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/generateSubDomain")
+    public R generateSubDomain(CompanyUser user){
+        //获取后台配置
+        String json= configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        // 生成二级域名
+        String subDomain = "http://" + DomainUtil.generateSubDomain(config.getCourseDomainName(), 6, String.valueOf(SecurityUtils.getLoginUser().getUser().getUserId()));
+        return  R.ok().put("data",subDomain);
+    }
 }

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

@@ -226,11 +226,64 @@ public class QwExternalContactController extends BaseController
     @Log(title = "企业微信客户", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(QwExternalContactParam qwExternalContact)
+    {
+        if (qwExternalContact.getCorpId()==null){
+            return AjaxResult.success();
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+        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, "企业微信客户数据");
+    }
+
+
+    @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();
         }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+
         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));
+    }
+}

+ 28 - 0
fs-company/src/main/java/com/fs/company/utils/DomainUtil.java

@@ -0,0 +1,28 @@
+package com.fs.company.utils;
+
+
+import java.util.Random;
+
+public class DomainUtil {
+
+    private static final String LETTER = "abcdefghijklmnopqrstuvwxyz";
+    private static final Random RANDOM = new Random();
+
+    /**
+     * 生成二级域名
+     * @param domain 主域名
+     * @param length 名称长度
+     * @param suffix 后缀
+     * @return  二级域名
+     */
+    public static String generateSubDomain(String domain, int length, String suffix) {
+        if (length < 6) {
+            length = 6;
+        }
+        StringBuilder sub = new StringBuilder(length);
+        for (int i = 0; i < length; i++) {
+            sub.append(LETTER.charAt(RANDOM.nextInt(LETTER.length())));
+        }
+        return sub + "-" + suffix + "." + domain;
+    }
+}

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

+ 67 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisQwUserController.java

@@ -1,16 +1,26 @@
 package com.fs.app.controller;
 
+import com.fs.common.BeanCopyUtils;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.qw.domain.QwTagGroup;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.ExternalContactDetailsParam;
+import com.fs.qw.param.sidebar.ExternalContactInfoParam;
+import com.fs.qw.param.sidebar.TagGroupListParam;
+import com.fs.qw.param.sidebar.TagGroupUpdateParam;
 import com.fs.qw.service.IQwExternalContactInfoService;
 import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwTagGroupService;
+import com.fs.qw.service.IQwTagService;
 import com.fs.qw.vo.ExternalContactDetailsVO;
 import com.fs.qw.vo.QwExternalListByHeavyVO;
+import com.fs.qw.vo.QwTagGroupListVO;
+import com.fs.qw.vo.sidebar.ExternalContactInfoVO;
+import com.fs.qw.vo.sidebar.ExternalContactTagVO;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -36,6 +46,9 @@ public class ApisQwUserController extends BaseController {
     @Autowired
     private IQwExternalContactInfoService qwExternalContactInfoService;
 
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+
     @GetMapping("/details")
     @ApiOperation("会员看课详情")
     public R getUserDetails(@ApiParam(value = "外部联系人id", required = true) @RequestParam Long contactId,
@@ -98,4 +111,58 @@ public class ApisQwUserController extends BaseController {
         PageInfo<QwExternalListByHeavyVO> result = new PageInfo<>(qwExternalListByHeavy);
         return R.ok().put("data", result);
     }
+
+    @GetMapping("/externalContact")
+    @ApiOperation("获取侧边栏外部联系人信息")
+    public R getExternalContactInfo(@RequestParam(value = "qwExternalContactId") Long qwExternalContactId) {
+        ExternalContactInfoVO externalContactInfo = qwExternalContactService.getExternalContactInfo(qwExternalContactId);
+        return R.ok().put("data", externalContactInfo);
+    }
+
+
+    @GetMapping("/externalContact/tag")
+    @ApiOperation("获取侧边栏外部联系人标签")
+    public R getExternalContactTag(@RequestParam(value = "qwExternalContactId") Long qwExternalContactId) {
+        List<ExternalContactTagVO> tagList = qwExternalContactService.getExternalContactTag(qwExternalContactId);
+        return R.ok().put("data", tagList);
+    }
+
+    @PutMapping("/externalContact")
+    @ApiOperation("编辑外部联系人信息")
+    public R updateExternalContactInfo(@RequestBody ExternalContactInfoParam param) {
+        return qwExternalContactService.updateExternalContactInfo(param);
+    }
+
+    @GetMapping("/tagGroupList")
+    @ApiOperation("获取所有标签组和其下标签")
+    public R getTagGroupList(TagGroupListParam param) {
+        QwTagGroup qwTagGroup = new QwTagGroup();
+        BeanCopyUtils.copy(param, qwTagGroup);
+        qwTagGroup.setName(param.getTagName());
+
+        PageHelper.startPage(qwTagGroup.getPageNum(), qwTagGroup.getPageSize());
+        List<QwTagGroupListVO> list = qwTagGroupService.selectQwGroupTagList(qwTagGroup);
+
+        PageInfo<QwTagGroupListVO> result = new PageInfo<>(list);
+        return R.ok().put("data", result);
+    }
+
+//    @GetMapping("/searchTags")
+//    @ApiOperation("搜索标签-跟管理端保持一致")
+//    public R searchTagList(QwTagParam param) {
+//        List<QwTagGroupListVO> list = qwTagService.searchTags(param);
+//        return R.ok().put("data", list);
+//    }
+
+    @PutMapping("/externalContact/tag")
+    @ApiOperation("编辑标签")
+    public R updateExternalContactTag(@RequestBody TagGroupUpdateParam param) {
+        int i = qwExternalContactService.updateExternalContactTag(param);
+        if (i > 0) {
+            return R.ok();
+        }
+        return R.error();
+    }
+
+
 }

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

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

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

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

@@ -439,99 +439,105 @@ 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);
         String msg = "<div style=\"color: red;margin-bottom: 15px;font-weight: bold;\">本课程为群会员独享<br>请长按二维码</div>\n" +
                 "\t\t\t\t\t<div style=\"color: #999;font-size: 14px;font-weight: bold;\">添加伴学助手免费领取会员权限</div>";
-        System.out.println("企微传参:"+courseLink.getChatId()+"corpId:"+param.getCorpId());
-        QwGroupChatDetailsResult result = qwApiService.groupChatDetails(courseLink.getChatId(), param.getCorpId());
-        if(result.getErrCode() != 0){
-            log.info("企微接口请求失败,请联系管理员:" +result.getErrMsg());
-            return R.error("不是此群成员");
-        }
-        List<QwGroupChatDetailsResult.Member> collect = result.getGroupChat().getMemberList().stream().filter(e -> e.getType() == 2).collect(Collectors.toList());
-        if(collect.isEmpty()){
-            logger.info("群聊里面为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
-            return addCustomerService(param.getQwUserId(),msg);
-        }
-        Optional<QwGroupChatDetailsResult.Member> optional = collect.stream().filter(e -> e.getName().equals(user.getNickName()) || e.getName().equals(param.getNickName())).findFirst();
-        if(!optional.isPresent()){
-            logger.info("昵称未匹配上弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
 
-            return addCustomerService(param.getQwUserId(),msg);
-        }
-        QwGroupChatDetailsResult.Member member = optional.get();
-        QwExternalContact qwExternalContact = qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>().eq("user_id", result.getGroupChat().getOwner()).eq("external_user_id", member.getUserId()));
-        if(qwExternalContact==null){
-            logger.info("外部联系人为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId()+":"+member.getUserId()+param.getNickName());
-            return addCustomerService(param.getQwUserId(),msg);
-        }
-        Long qwExternalId = qwExternalContact.getId();
-        log.info("外部联系人数据:{}", qwExternalContact);
-//        addCompanyCompanyFsUser(param);
-        FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(qwExternalId, param.getVideoId(),param.getQwUserId());
-        if (log==null ){
-            logger.info("看课记录为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId()+qwExternalId+":"+param.getVideoId()+":"+param.getQwUserId());
-            return addCustomerService(param.getQwUserId(),msg);
-        }
-        //判断外部联系人有没有绑定userId
-        if (qwExternalContact.getFsUserId()!=null){
-            //有客户有小程序id  但 登录的小程序id和根据外部联系人id查出来的小程序id不一致
-            if (!qwExternalContact.getFsUserId().equals(param.getUserId())) {
-                logger.info("小程序id不一致空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
-                return addCustomerService(param.getQwUserId(),msg);
-            }
-            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByMiniUserId(param.getUserId());
-            //匹配客户公司id
-            if (qwExternalContacts.stream().noneMatch(contact -> contact.getCorpId().equals(param.getCorpId()))){
-                logger.info("未匹配上公司空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
-                return addCustomerService(param.getQwUserId(),msg);
-            }
+        return addCustomerService(param.getQwUserId(),msg);
 
-            //看课记录中userId为0绑定userId
-            if (log.getUserId()==null||log.getUserId().equals(0L) || !log.getUserId().equals(param.getUserId())){
-                log.setUserId(param.getUserId());
-            }
-
-            log.setUpdateTime(new Date());
-            courseWatchLogMapper.updateFsCourseWatchLog(log);
-
-            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
-        }else {
-            //没绑定fsUser直接绑定fsUser
-            QwExternalContact contact = new QwExternalContact();
-            contact.setId(qwExternalId);
-            contact.setFsUserId(param.getUserId());
-            qwExternalContactMapper.updateQwExternalContact(contact);
-            FsUser fsUser = new FsUser();
-            fsUser.setUserId(user.getUserId());
-            fsUser.setIsAddQw(1);
-            fsUserMapper.updateFsUser(fsUser);
-            //绑定上之后 更新观看记录
-            //看课记录中userId为0绑定userId
-            log.setUserId(param.getUserId());
-            log.setUpdateTime(new Date());
-            courseWatchLogMapper.updateFsCourseWatchLog(log);
-        }
-
-        return R.ok().put("qwExternalId", qwExternalContact.getId());
+//        QwGroupChatDetailsResult result = qwApiService.groupChatDetails(courseLink.getChatId(), param.getCorpId());
+//        if(result.getErrCode() != 0){
+//            log.info("企微接口请求失败,请联系管理员:" +result.getErrMsg());
+//            return R.error("不是此群成员");
+//        }
+//        List<QwGroupChatDetailsResult.Member> collect = result.getGroupChat().getMemberList().stream().filter(e -> e.getType() == 2).collect(Collectors.toList());
+//        if(collect.isEmpty()){
+//            logger.info("群聊里面为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        Optional<QwGroupChatDetailsResult.Member> optional = collect.stream().filter(e -> e.getName().equals(user.getNickName()) || e.getName().equals(param.getNickName())).findFirst();
+//        if(!optional.isPresent()){
+//            logger.info("昵称未匹配上弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        QwGroupChatDetailsResult.Member member = optional.get();
+//        QwExternalContact qwExternalContact = qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>().eq("user_id", result.getGroupChat().getOwner()).eq("external_user_id", member.getUserId()));
+//        if(qwExternalContact==null){
+//            logger.info("外部联系人为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId()+":"+member.getUserId()+param.getNickName());
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        Long qwExternalId = qwExternalContact.getId();
+//        log.info("外部联系人数据:{}", qwExternalContact);
+////        addCompanyCompanyFsUser(param);
+//        FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(qwExternalId, param.getVideoId(),param.getQwUserId());
+//        if (log==null ){
+//            logger.info("看课记录为空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId()+qwExternalId+":"+param.getVideoId()+":"+param.getQwUserId());
+//            return addCustomerService(param.getQwUserId(),msg);
+//        }
+//        //判断外部联系人有没有绑定userId
+//        if (qwExternalContact.getFsUserId()!=null){
+//            //有客户有小程序id  但 登录的小程序id和根据外部联系人id查出来的小程序id不一致
+//            if (!qwExternalContact.getFsUserId().equals(param.getUserId())) {
+//                logger.info("小程序id不一致空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//                return addCustomerService(param.getQwUserId(),msg);
+//            }
+//            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByMiniUserId(param.getUserId());
+//            //匹配客户公司id
+//            if (qwExternalContacts.stream().noneMatch(contact -> contact.getCorpId().equals(param.getCorpId()))){
+//                logger.info("未匹配上公司空弹二维码:"+param.getCorpId()+":"+param.getQwUserId()+":"+param.getChatId()+":"+param.getUserId());
+//                return addCustomerService(param.getQwUserId(),msg);
+//            }
+//
+//            //看课记录中userId为0绑定userId
+//            if (log.getUserId()==null||log.getUserId().equals(0L) || !log.getUserId().equals(param.getUserId())){
+//                log.setUserId(param.getUserId());
+//            }
+//
+//            log.setUpdateTime(new Date());
+//            courseWatchLogMapper.updateFsCourseWatchLog(log);
+//
+//            iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
+//        }else {
+//            //没绑定fsUser直接绑定fsUser
+//            QwExternalContact contact = new QwExternalContact();
+//            contact.setId(qwExternalId);
+//            contact.setFsUserId(param.getUserId());
+//            qwExternalContactMapper.updateQwExternalContact(contact);
+//            FsUser fsUser = new FsUser();
+//            fsUser.setUserId(user.getUserId());
+//            fsUser.setIsAddQw(1);
+//            fsUserMapper.updateFsUser(fsUser);
+//            //绑定上之后 更新观看记录
+//            //看课记录中userId为0绑定userId
+//            log.setUserId(param.getUserId());
+//            log.setUpdateTime(new Date());
+//            courseWatchLogMapper.updateFsCourseWatchLog(log);
+//        }
+//
+//        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);
+    }
+}

+ 7 - 0
fs-service/src/main/java/com/fs/qw/domain/QwTagGroup.java

@@ -1,5 +1,6 @@
 package com.fs.qw.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
@@ -40,4 +41,10 @@ public class QwTagGroup extends BaseEntity
 
     private Integer groupFrom;
 
+    @TableField(exist = false)
+    private Integer pageNum = 1;
+
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+
 }

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

@@ -16,6 +16,7 @@ import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 import org.springframework.stereotype.Repository;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -270,7 +271,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);
 
@@ -396,4 +397,11 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     int batchUpdateContactByIds(List<Map<Long, Integer>> list);
 
     int updateContactByIds(List<String> list, int i);
+
+    @Select("select count( DISTINCT fs_course_answer_logs.log_id ) AS answerTime from fs_course_answer_logs where user_id = #{userId}")
+    int getAnswerTime(@Param("userId") Long userId);
+
+    @Select("select sum(amount) as redPacketAmount from fs_course_red_packet_log where user_id = #{userId}")
+    BigDecimal getRedPacketAmount(@Param("userId") Long userId);
+
 }

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

@@ -80,4 +80,7 @@ public interface QwTagGroupMapper
 
     @Select("select * from qw_tag_group where id=#{id}")
     QwTagGroupVO selectQwTagGroupByIdVO(Long id);
+
+    List<QwTagGroupListVO> selectQwTagGroups(QwTagGroup qwTagGroup);
+
 }

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

@@ -4,6 +4,7 @@ import com.fs.qw.domain.QwTag;
 import com.fs.qw.param.QwTagSearchParam;
 import com.fs.qw.param.newparam.ContactTagListParam;
 import com.fs.qw.vo.QwTagVO;
+import com.fs.qw.vo.sidebar.ExternalContactTagVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -72,7 +73,7 @@ public interface QwTagMapper
             "select * from qw_tag "+
             "<where>\n" +
             "            <if test=\"tagId != null  and tagId != ''\"> and tag_id = #{tagId}</if>\n" +
-            "            <if test=\"name != null  and name != ''\"> and name like concat( #{name}, '%')</if>\n" +
+            "            <if test=\"name != null  and name != ''\"> and name like concat('%', #{name}, '%')</if>\n" +
             "            <if test=\"groupId != null  and groupId != ''\"> and group_id = #{groupId}</if>\n" +
             "            <if test=\"order != null  and order != ''\"> and `order` = #{order}</if>\n" +
             "            <if test=\"corpId != null  and corpId != ''\"> and corp_id = #{corpId}</if>\n" +
@@ -95,4 +96,13 @@ public interface QwTagMapper
     List<QwTag> selectQwTagListByTagIdsNew(@Param("tagIds") List<String> tagIds);
 
     List<QwTagVO> selectTagListByUserId(@Param("param") ContactTagListParam param,  @Param("keywords") String[] keywords);
+
+    @Select("<script>" +
+            "SELECT tag_id, name FROM qw_tag WHERE tag_id IN " +
+            "<foreach collection='tagIds' item='tagId' open='(' separator=',' close=')'>" +
+            "#{tagId}" +
+            "</foreach>" +
+            "</script>")
+    List<ExternalContactTagVO> selectQwTagListByTagIds(@Param("tagIds") List<String> tagIds);
+
 }

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

@@ -9,6 +9,7 @@ import com.fs.qw.dto.QwUserDTO;
 import com.fs.qw.dto.QwUserKeyDTO;
 import com.fs.qw.param.*;
 import com.fs.qw.vo.*;
+import com.fs.qw.vo.sidebar.ExternalContactQwUserVO;
 import com.fs.sop.domain.QwSop;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.ibatis.annotations.MapKey;
@@ -415,4 +416,11 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "</script>")
     List<Long> selectQwUserListByCuDeptIdList(@Param("map") QwSop qwSop);
 
+    /**
+     * 根据外部联系人的fsUserId查询关联的企微员工信息
+     * @param fsUserId 会员id
+     * @return
+     */
+    List<ExternalContactQwUserVO> selectQwUserByFsUserId(@Param("fsUserId") Long fsUserId);
+
 }

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

+ 31 - 0
fs-service/src/main/java/com/fs/qw/param/sidebar/ExternalContactInfoParam.java

@@ -0,0 +1,31 @@
+package com.fs.qw.param.sidebar;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 侧边栏-外部联系人编辑信息-入参
+ */
+@Data
+@ApiModel
+public class ExternalContactInfoParam {
+
+    @ApiModelProperty(value = "外部联系人id")
+    @NotNull(message = "外部联系人id不能为空")
+    private Long qwExternalContactId;
+
+    @ApiModelProperty(value = "名称")
+    private String name;
+
+    @ApiModelProperty(value = "描述")
+    private String remark;
+
+    @ApiModelProperty(value = "性别")
+    private Integer gender;
+
+    @ApiModelProperty(value = "电话")
+    private String phone;
+
+}

+ 24 - 0
fs-service/src/main/java/com/fs/qw/param/sidebar/TagGroupListParam.java

@@ -0,0 +1,24 @@
+package com.fs.qw.param.sidebar;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 侧边栏-标签列表-入参
+ */
+@Data
+@ApiModel
+public class TagGroupListParam {
+
+    @ApiModelProperty(value = "页码,默认为1", required = true)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = true)
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "标签名称")
+    private String tagName;
+
+
+}

+ 24 - 0
fs-service/src/main/java/com/fs/qw/param/sidebar/TagGroupUpdateParam.java

@@ -0,0 +1,24 @@
+package com.fs.qw.param.sidebar;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 侧边栏-标签列表-入参
+ */
+@Data
+@ApiModel
+public class TagGroupUpdateParam {
+
+    @ApiModelProperty(value = "外部联系人id")
+    @NotNull(message = "外部联系人id不能为空")
+    private Long qwExternalContactId;
+
+    @ApiModelProperty(value = "标签ids")
+    List<String> tagIds;
+
+}

+ 32 - 0
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java

@@ -8,6 +8,8 @@ import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.*;
 import com.fs.qw.param.newparam.ExternalContactPageListParam;
+import com.fs.qw.param.sidebar.ExternalContactInfoParam;
+import com.fs.qw.param.sidebar.TagGroupUpdateParam;
 import com.fs.qw.result.QwExternalContactLogVo;
 import com.fs.qw.result.QwExternalContactVo;
 import com.fs.qw.vo.ExternalContactDetailsVO;
@@ -16,6 +18,8 @@ import com.fs.qw.vo.QwExternalListByHeavyVO;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.newvo.ExternalContactListVO;
 import com.fs.qw.vo.newvo.ExternalContactPageVO;
+import com.fs.qw.vo.sidebar.ExternalContactInfoVO;
+import com.fs.qw.vo.sidebar.ExternalContactTagVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import org.codehaus.jettison.json.JSONException;
 
@@ -203,4 +207,32 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
     ExternalContactPageVO getContactNumber(Long l);
 
     Boolean disabledUser(String[] ids, boolean b);
+
+    /**
+     * 获取外部联系人基本信息
+     * @param qwExternalContactId 联系人主键id
+     * @return
+     */
+    ExternalContactInfoVO getExternalContactInfo(Long qwExternalContactId);
+
+    /**
+     * 获取侧边栏外部联系人标签
+     * @param qwExternalContactId 联系人主键id
+     * @return
+     */
+    List<ExternalContactTagVO> getExternalContactTag(Long qwExternalContactId);
+
+    /**
+     * 侧边栏-编辑外部联系人信息
+     * @param param 入参
+     * @return
+     */
+    R updateExternalContactInfo(ExternalContactInfoParam param);
+
+    /**
+     * 修改外部联系人的标签
+     * @param param
+     */
+    int updateExternalContactTag(TagGroupUpdateParam param);
+
 }

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

@@ -68,6 +68,8 @@ public interface IQwTagGroupService
 
     List<QwTagGroupListVO> selectQwTagGroupListVO(QwTagGroup qwTagGroup);
 
+    List<QwTagGroupListVO> selectQwGroupTagList(QwTagGroup qwTagGroup);
+
     QwTagGroupVO selectQwTagGroupByIdVO(Long id);
 
     int insertQwTagGroupParam(QwTagGroupAddParam qwTagGroup);

+ 110 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.qw.service.impl;
 
 
+import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONException;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -26,10 +27,13 @@ import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.mapper.CrmCustomerMapper;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.utils.PhoneUtil;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.*;
 import com.fs.qw.param.newparam.ExternalContactPageListParam;
+import com.fs.qw.param.sidebar.ExternalContactInfoParam;
+import com.fs.qw.param.sidebar.TagGroupUpdateParam;
 import com.fs.qw.result.QwExternalContactLogVo;
 import com.fs.qw.result.QwExternalContactVo;
 import com.fs.qw.service.*;
@@ -37,6 +41,9 @@ import com.fs.qw.vo.*;
 import com.fs.qw.vo.newvo.ExternalContactListVO;
 import com.fs.qw.vo.newvo.ExternalContactNumVO;
 import com.fs.qw.vo.newvo.ExternalContactPageVO;
+import com.fs.qw.vo.sidebar.ExternalContactInfoVO;
+import com.fs.qw.vo.sidebar.ExternalContactQwUserVO;
+import com.fs.qw.vo.sidebar.ExternalContactTagVO;
 import com.fs.qwApi.Result.QwOpenidResult;
 import com.fs.qwApi.domain.*;
 import com.fs.qwApi.domain.inner.*;
@@ -54,10 +61,14 @@ import com.fs.sop.service.IQwSopService;
 import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.sop.service.ISopUserLogsService;
 import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysDictTypeService;
 import com.fs.voice.utils.StringUtil;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
 import lombok.extern.log4j.Log4j;
 import lombok.extern.log4j.Log4j2;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -191,8 +202,11 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Autowired
     private QwTagMapper qwTagMapper;
 
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
 
-    org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
+    Logger logger = LoggerFactory.getLogger(getClass());
 
     @Override
     public void addQwCourseJob() {
@@ -5379,6 +5393,101 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         }
     }
 
+    @Override
+    public ExternalContactInfoVO getExternalContactInfo(Long qwExternalContactId) {
+        QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(qwExternalContactId);
+        ExternalContactInfoVO externalContactInfoVO = new ExternalContactInfoVO();
+        BeanUtils.copyProperties(qwExternalContact, externalContactInfoVO);
+        externalContactInfoVO.setQwExternalContactId(qwExternalContact.getId());
+
+        // 设置性别
+        if(externalContactInfoVO.getGender() == 1){
+            externalContactInfoVO.setGenderName("男性");
+        } else if(externalContactInfoVO.getGender() == 2) {
+            externalContactInfoVO.setGenderName("女性");
+        } else {
+            externalContactInfoVO.setGenderName("未知");
+        }
+
+        // 设置电话
+        if(qwExternalContact.getFsUserId() != null) {
+            FsUser fsUser = fsUserMapper.selectFsUserByUserId(qwExternalContact.getFsUserId());
+            if(fsUser != null && StringUtils.isNotEmpty(fsUser.getPhone())){
+                externalContactInfoVO.setPhone(PhoneUtil.decryptPhone(fsUser.getPhone()));
+                externalContactInfoVO.setContactLevel("注册会员");
+            } else {
+                externalContactInfoVO.setContactLevel("未注册会员");
+            }
+        }
+
+        // 设置红包和答题
+        int answerTime = 0;
+        BigDecimal redPacketAmount = BigDecimal.ZERO;
+        if(qwExternalContact.getFsUserId() != null){
+            answerTime = qwExternalContactMapper.getAnswerTime(qwExternalContact.getFsUserId());
+            redPacketAmount = qwExternalContactMapper.getRedPacketAmount(qwExternalContact.getFsUserId());
+        }
+        externalContactInfoVO.setAnswerTime(answerTime);
+        externalContactInfoVO.setRedPacketAmount(redPacketAmount);
+
+        // 设置添加员工信息
+        List<ExternalContactQwUserVO> emptyList = Collections.emptyList();
+        List<ExternalContactQwUserVO> qwUsers = qwUserMapper.selectQwUserByFsUserId(qwExternalContact.getFsUserId());
+        externalContactInfoVO.setQwUserList(qwUsers.isEmpty() ? emptyList : qwUsers);
+
+        return externalContactInfoVO;
+    }
+
+    @Override
+    public List<ExternalContactTagVO> getExternalContactTag(Long qwExternalContactId) {
+        QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(qwExternalContactId);
+
+        if (StringUtils.isNotEmpty(qwExternalContact.getTagIds()) && !Objects.equals(qwExternalContact.getTagIds(), "[]")) {
+            Gson gson = new Gson();
+            List<String> tagIds = gson.fromJson(
+                    qwExternalContact.getTagIds(),
+                    new TypeToken<List<String>>() {
+                    }.getType()
+            );
+
+            return qwTagMapper.selectQwTagListByTagIds(tagIds);
+        }
+        return Collections.emptyList();
+    }
+
+    @Override
+    public R updateExternalContactInfo(ExternalContactInfoParam param) {
+        QwExternalContact qwExternalContact;
+        if(param.getQwExternalContactId() == null) {
+            return R.error(400, "外部联系人id不能为空");
+        }
+
+        qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(param.getQwExternalContactId());
+        BeanUtils.copyProperties(param, qwExternalContact);
+        qwExternalContact.setId(param.getQwExternalContactId());
+
+        qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+
+        // 修改电话
+        if(qwExternalContact.getFsUserId() != null) {
+            FsUser fsUser = fsUserMapper.selectFsUserByUserId(qwExternalContact.getFsUserId());
+            if(fsUser != null && StringUtils.isNotEmpty(param.getPhone())){
+                fsUser.setPhone(PhoneUtil.encryptPhone(param.getPhone()));
+                fsUserMapper.updateFsUser(fsUser);
+            }
+        }
+        return R.ok();
+    }
+
+    @Override
+    public int updateExternalContactTag(TagGroupUpdateParam param) {
+        QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(param.getQwExternalContactId());
+        if(qwExternalContact != null){
+            qwExternalContact.setTagIds(JSONUtil.toJsonStr(param.getTagIds()));
+            return qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+        }
+        return 0;
+    }
 
 
     //发送好友欢迎语

+ 16 - 4
fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java

@@ -36,10 +36,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 /**
  * 企微客户标签组Service业务层处理
@@ -266,6 +263,21 @@ public class QwTagGroupServiceImpl implements IQwTagGroupService
         return vo;
     }
 
+    @Override
+    public List<QwTagGroupListVO> selectQwGroupTagList(QwTagGroup qwTagGroup) {
+        List<QwTagGroupListVO> vo = qwTagGroupMapper.selectQwTagGroups(qwTagGroup);
+
+        for (QwTagGroupListVO qwTagGroupListVO : vo) {
+            QwTag qwTag = new QwTag();
+            qwTag.setGroupId(qwTagGroupListVO.getGroupId());
+            qwTag.setCompanyId(qwTagGroupListVO.getCompanyId());
+            qwTag.setName(qwTagGroup.getName());
+            List<QwTagVO> qwTags = qwTagMapper.selectQwTagListVO(qwTag);
+            qwTagGroupListVO.setTag(qwTags);
+        }
+        return vo;
+    }
+
     @Override
     public QwTagGroupVO selectQwTagGroupByIdVO(Long id) {
         QwTagGroupVO qwTagGroupVO = qwTagGroupMapper.selectQwTagGroupByIdVO(id);

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

+ 59 - 0
fs-service/src/main/java/com/fs/qw/vo/sidebar/ExternalContactInfoVO.java

@@ -0,0 +1,59 @@
+package com.fs.qw.vo.sidebar;
+
+import com.fs.common.annotation.Excel;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.vo.QwUserVO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.models.auth.In;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 侧边栏-外部联系人相关信息
+ */
+@Data
+@ApiModel
+public class ExternalContactInfoVO {
+
+    @ApiModelProperty(value = "外部联系人id")
+    private Long qwExternalContactId;
+
+    @ApiModelProperty(value = "名称")
+    private String name;
+
+    @ApiModelProperty(value = "头像")
+    private String avatar;
+
+    @ApiModelProperty(value = "企微用户主键id")
+    private Long qwUserId;
+
+    @ApiModelProperty(value = "描述")
+    private String remark;
+
+    @ApiModelProperty(value = "性别")
+    private Integer gender;
+
+    @ApiModelProperty(value = "性别名称")
+    private String genderName;
+
+    @ApiModelProperty(value = "电话")
+    private String phone;
+
+    @ApiModelProperty(value = "用户等级")
+    private String contactLevel;
+
+    @ApiModelProperty(value = "累计答题次数")
+    private Integer answerTime;
+
+    @ApiModelProperty(value = "累计红包金额")
+    private BigDecimal redPacketAmount;
+
+    @ApiModelProperty(value = "企微员工列表")
+    private List<ExternalContactQwUserVO> qwUserList;
+
+
+}

+ 63 - 0
fs-service/src/main/java/com/fs/qw/vo/sidebar/ExternalContactQwUserVO.java

@@ -0,0 +1,63 @@
+package com.fs.qw.vo.sidebar;
+
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 外部联系人的所属企微-出参
+ */
+@Data
+public class ExternalContactQwUserVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 企微用户id
+     */
+    @Excel(name = "企微用户id")
+    private String qwUserId;
+
+    /**
+     * 企微用户名
+     */
+    @Excel(name = "企微用户名")
+    private String qwUserName;
+
+    /**
+     * 所属部门id
+     */
+    @Excel(name = "所属部门id")
+    private String department;
+
+    /**
+     * 公司id
+     */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /**
+     * 公司员工id
+     */
+    @Excel(name = "公司员工id")
+    private Long companyUserId;
+
+    /**
+     * 公司名称
+     */
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    /**
+     * 销售昵称
+     */
+    @Excel(name = "销售昵称")
+    private String companyUserName;
+
+}

+ 20 - 0
fs-service/src/main/java/com/fs/qw/vo/sidebar/ExternalContactTagVO.java

@@ -0,0 +1,20 @@
+package com.fs.qw.vo.sidebar;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+/**
+ * 侧边栏-外部联系人标签
+ */
+@Data
+@ApiModel
+public class ExternalContactTagVO {
+
+    @ApiModelProperty(value = "标签id")
+    private String tagId;
+
+    @ApiModelProperty(value = "标签名称")
+    private String name;
+
+
+}

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

@@ -21,10 +21,10 @@ wx:
         aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
         msgDataFormat: JSON
   cp:
-    corpId: wwb2a1055fb6c9a7c2
+    corpId: ww7cac03119330b501
     appConfigs:
-      - agentId: 1000005
-        secret: ec7okROXJqkNafq66-L6aKNv0asTzQIG0CYrj3vyBbo
+      - agentId: 1000002
+        secret: N3S6FsLjcbBFnLJ4zlHEqwKy1SbWvBi43vWacuGM89k
         token: PPKOdAlCoMO
         aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
 #  pay:

+ 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">

+ 28 - 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,7 +105,7 @@ 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">

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

+ 11 - 0
fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml

@@ -97,4 +97,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
+
+    <select id="selectQwTagGroups" resultType="com.fs.qw.vo.QwTagGroupListVO">
+        select * from qw_tag_group  where group_id in (
+            select DISTINCT group_id from qw_tag
+     <where>
+        <if test="name != null and name != '' ">
+         and name like concat( '%', #{name}, '%')
+        </if>
+     </where>
+        )
+    </select>
 </mapper>

+ 18 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -230,4 +230,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           and qwt.status = 0
     </select>
 
+    <select id="selectQwUserByFsUserId" resultType="com.fs.qw.vo.sidebar.ExternalContactQwUserVO">
+        SELECT
+            qw_user.id,
+            qw_user.qw_user_id,
+            qw_user.qw_user_name,
+            qw_user.department,
+            qw_user.company_user_id,
+            qw_user.company_id,
+            company.company_name,
+            company_user.nick_name as companyUserName
+        FROM
+            qw_user
+                LEFT JOIN company_user ON company_user.user_id = qw_user.company_user_id
+                LEFT JOIN company ON company.company_id = qw_user.company_id
+        WHERE
+            qw_user.qw_user_id IN ( SELECT user_id FROM qw_external_contact WHERE fs_user_id = #{fsUserId} )
+    </select>
+
 </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 "未知课程";
+    }
+
+}