Sfoglia il codice sorgente

Merge branch 'refs/heads/master' into 企微聊天

# Conflicts:
#	fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
#	fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
ct 2 settimane fa
parent
commit
2ce99fa2c6
69 ha cambiato i file con 2845 aggiunte e 333 eliminazioni
  1. 48 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyConfigController.java
  2. 572 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java
  3. 16 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  4. 21 0
      fs-admin/src/main/java/com/fs/course/params/FsUserCourseConfigParam.java
  5. 23 0
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java
  6. 49 9
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  7. 138 0
      fs-admin/src/main/java/com/fs/qw/controller/QwDeptController.java
  8. 309 5
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  9. 37 0
      fs-admin/src/test/java/com/fs/api/controller/IndexStatisticsControllerTest.java
  10. 1 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  11. 104 0
      fs-company/src/main/java/com/fs/company/controller/tag/FsVideoCourseTagController.java
  12. 2 2
      fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java
  13. 29 26
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  14. 1 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  15. 14 4
      fs-service/src/main/java/com/fs/company/domain/CompanyDeptTreeSelect.java
  16. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java
  17. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourse.java
  18. 0 27
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  19. 8 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  20. 3 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  21. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  22. 4 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  23. 2 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  24. 8 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  25. 1 28
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  26. 5 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseListPVO.java
  27. 2 2
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoH5VO.java
  28. 0 39
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoVO.java
  29. 12 0
      fs-service/src/main/java/com/fs/erp/service/impl/DfOrderServiceImpl.java
  30. 1 1
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  31. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralOrderMapper.java
  32. 4 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralOrderService.java
  33. 9 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  34. 52 1
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  35. 44 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  36. 1 1
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderPVO.java
  37. 5 0
      fs-service/src/main/java/com/fs/qw/mapper/QwCompanyMapper.java
  38. 4 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  39. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  40. 1 0
      fs-service/src/main/java/com/fs/qw/param/QwWatchLogStatisticsListParam.java
  41. 1 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  42. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  43. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  44. 16 10
      fs-service/src/main/java/com/fs/tag/domain/FsTagUpdateQueue.java
  45. 96 0
      fs-service/src/main/java/com/fs/tag/domain/FsVideoCourseTag.java
  46. 5 104
      fs-service/src/main/java/com/fs/tag/mapper/FsTagUpdateQueueMapper.java
  47. 73 0
      fs-service/src/main/java/com/fs/tag/mapper/FsVideoCourseTagMapper.java
  48. 61 0
      fs-service/src/main/java/com/fs/tag/service/IFsVideoCourseTagService.java
  49. 70 39
      fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java
  50. 137 0
      fs-service/src/main/java/com/fs/tag/service/impl/FsVideoCourseTagServiceImpl.java
  51. 28 0
      fs-service/src/main/java/com/fs/utils/DomainUtil.java
  52. 23 0
      fs-service/src/main/java/com/fs/utils/OrderContextHolder.java
  53. 29 0
      fs-service/src/main/java/com/fs/utils/QwStatusEnum.java
  54. 2 2
      fs-service/src/main/resources/application-config-druid-knt.yml
  55. 102 0
      fs-service/src/main/resources/application-config-druid-knt2.yml
  56. 95 0
      fs-service/src/main/resources/application-config-druid-yxj.yml
  57. 6 6
      fs-service/src/main/resources/application-druid-hst.yml
  58. 2 2
      fs-service/src/main/resources/application-druid-knt.yml
  59. 166 0
      fs-service/src/main/resources/application-druid-knt2.yml
  60. 2 2
      fs-service/src/main/resources/application-druid-sxjz.yml
  61. 172 0
      fs-service/src/main/resources/application-druid-yxj.yml
  62. 19 0
      fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml
  63. 4 4
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  64. 7 4
      fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml
  65. 15 5
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoRedPackageMapper.xml
  66. 9 1
      fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml
  67. 5 3
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml
  68. 119 0
      fs-service/src/main/resources/mapper/tag/FsVideoCourseTagMapper.xml
  69. 14 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseTransferController.java

+ 48 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyConfigController.java

@@ -0,0 +1,48 @@
+package com.fs.company.controller;
+
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.company.service.ICompanyConfigService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.service.ISysConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 系统配置
+ *
+
+ */
+@RestController
+@RequestMapping("/company/companyConfig")
+public class CompanyConfigController extends BaseController
+{
+
+    @Autowired
+    private ICompanyConfigService companyConfigService;
+
+
+    @Autowired
+    private ISysConfigService configService;
+
+
+
+    /**
+     * 根据参数键名查询总后台参数值
+     * @param configKey
+     * @return
+     */
+    @GetMapping(value = "/getConfigByKey/{configKey}")
+    public AjaxResult getConfigByKey(@PathVariable String configKey)
+    {
+        SysConfig config=configService.selectConfigByConfigKey(configKey);
+        return AjaxResult.success(config);
+    }
+
+
+
+}

+ 572 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java

@@ -0,0 +1,572 @@
+package com.fs.company.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONUtil;
+import com.baidu.dev2.thirdparty.jackson.databind.ObjectMapper;
+import com.fs.common.annotation.Log;
+import com.fs.common.constant.UserConstants;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.PatternUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.*;
+import com.fs.company.param.CompanyUserAreaParam;
+import com.fs.company.param.CompanyUserCodeParam;
+import com.fs.company.param.CompanyUserQwParam;
+import com.fs.company.service.*;
+import com.fs.company.vo.BatchUserRolesVO;
+import com.fs.company.vo.CompanyUserImportVO;
+import com.fs.company.vo.CompanyUserQwListVO;
+import com.fs.company.vo.CompanyUserVO;
+import com.fs.course.config.CourseConfig;
+import com.fs.framework.web.service.TokenService;
+import com.fs.his.utils.qrcode.QRCodeUtils;
+import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.vo.FsStoreProductExportVO;
+import com.fs.im.config.IMConfig;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.service.OpenIMService;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.service.IQwCompanyService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.vo.CompanyUserQwVO;
+import com.fs.qw.vo.QwUserVO;
+import com.fs.system.service.ISysConfigService;
+import com.fs.utils.DomainUtil;
+import com.fs.utils.QwStatusEnum;
+import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiOperation;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.Assert;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+
+/**
+ * 用户信息
+ */
+@RestController
+@RequestMapping("/company/CompanyUserAll")
+public class CompanyUserAllController extends BaseController {
+
+    @Autowired
+    private ICompanyRoleService roleService;
+
+    @Autowired
+    private ICompanyPostService postService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    private ICompanyUserDelayTimeService companyUserDelayTimeService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private OpenIMService openIMService;
+
+    @Autowired
+    IQwCompanyService iQwCompanyService;
+
+    @Autowired
+    private IQwUserService qwUserService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
+
+
+    @GetMapping("/getList")
+    public TableDataInfo getList(CompanyUser user)
+    {
+        startPage();
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(user);
+        return getDataTable(list);
+    }
+    @GetMapping("/qwList")
+    public TableDataInfo qwList(CompanyUserQwParam user) {
+        List<CompanyUserQwListVO> list = companyUserService.selectCompanyUserQwListVO(user);
+        if (!list.isEmpty()){
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+            Long sendDelayTime = config.getSendDelayTime();
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            for (CompanyUserQwListVO companyUserQwListVO : list) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
+                    if (ObjectUtil.isEmpty(companyUserDelayTime)) {
+                        companyUserQwListVO.setSendDelayTime(sendDelayTime);
+                    } else {
+                        companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
+                    }
+                    //是否绑定
+                    if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
+                        if(!companyUserQwListVO.getQwUserId().isEmpty()){
+                            Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
+                                    .map(Long::parseLong)
+                                    .toArray(Long[]::new);
+                            List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
+                            companyUserQwListVO.setQwUsers(qwUserVOS);
+                        }
+                    }
+                });
+                futures.add(future);
+            }
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+        }
+        return getDataTable(list);
+    }
+
+    @Log(title = "用户管理导出", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('company:user:export')")
+    @GetMapping("/export")
+    public AjaxResult export(CompanyUser user)
+    {
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(user);
+        ExcelUtil<CompanyUser> util = new ExcelUtil<CompanyUser>(CompanyUser.class);
+        return util.exportExcel(list, "用户数据");
+    }
+
+
+    @Log(title = "销售信息导入", businessType = BusinessType.IMPORT,isStoreLog = true,logParam = {"销售","信息导入"})
+    @PreAuthorize("@ss.hasPermi('company:user:import')")
+    @PostMapping("/importCompanyUser")
+    public AjaxResult importData(@RequestParam("file") MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<CompanyUserImportVO> util = new ExcelUtil<>(CompanyUserImportVO.class);
+        List<CompanyUserImportVO> list = util.importExcel(file.getInputStream());
+        String message = companyUserService.importCompanyUser(list, updateSupport);
+        return AjaxResult.success(message);
+    }
+
+
+    @GetMapping("/importTemplate")
+    public AjaxResult importTemplate()
+    {
+        ExcelUtil<CompanyUserImportVO> util = new ExcelUtil<>(CompanyUserImportVO.class);
+        return util.importTemplateExcel("销售数据");
+    }
+
+
+
+    /**
+     * 根据用户编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:query')")
+    @GetMapping(value = { "/", "/{userId}" })
+    public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        CompanyRole companyRoleMap=new CompanyRole();
+
+        CompanyUser userById = companyUserService.selectCompanyUserById(userId);
+        companyRoleMap.setCompanyId(userById.getCompanyId());
+        List<CompanyRole> roles = roleService.selectCompanyRoleList(companyRoleMap);
+
+        ajax.put("roles", CompanyUser.isAdmin(userById.getUserType()) ? roles : roles.stream().filter(r -> r.getRoleKey()!="admin").collect(Collectors.toList()));
+
+        CompanyPost postMap=new CompanyPost();
+        postMap.setCompanyId(userById.getCompanyId());
+        ajax.put("posts", postService.selectCompanyPostList(postMap));
+        if (StringUtils.isNotNull(userId))
+        {
+            CompanyUser companyUser=companyUserService.selectCompanyUserById(userId);
+            ajax.put(AjaxResult.DATA_TAG, companyUser);
+            ajax.put("postIds", postService.selectPostListByUserId(userId));
+            ajax.put("roleIds", roleService.selectRoleListByUserId(userId));
+
+
+        }
+        return ajax;
+    }
+
+    /**
+     * 根据用户编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:query')")
+    @GetMapping("/addInfo/{companyId}")
+    public AjaxResult addInfo(@PathVariable(value = "companyId", required = true) Long companyId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+
+        CompanyRole companyRoleMap=new CompanyRole();
+        companyRoleMap.setCompanyId(companyId);
+        List<CompanyRole> roles = roleService.selectCompanyRoleList(companyRoleMap);
+
+        ajax.put("roles", CompanyUser.isAdmin("01") ? roles : roles.stream().filter(r -> r.getRoleKey()!="admin").collect(Collectors.toList()));
+
+        CompanyPost postMap=new CompanyPost();
+        postMap.setCompanyId(companyId);
+        ajax.put("posts", postService.selectCompanyPostList(postMap));
+
+        return ajax;
+    }
+
+    /**
+     * 新增用户
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:add')")
+    @Log(title = "用户管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody CompanyUser user)
+    {
+        if (!PatternUtils.checkPassword(user.getPassword())) {
+            return AjaxResult.error("密码格式不正确,需包含字母、数字和特殊字符,长度为 8-20 位");
+        }
+
+        //判断用户数量是否已达到上线
+
+        Integer count=companyUserService.selectCompanyUserCountByCompanyId(user.getCompanyId());
+        Company company=companyService.selectCompanyById(user.getCompanyId());
+
+        if(count>company.getLimitUserCount()){
+            return AjaxResult.error("用户数量已达到上限");
+        }
+        user.setCompanyId(user.getCompanyId());
+        if (UserConstants.NOT_UNIQUE.equals(String.valueOf(companyUserService.checkUserName(user.getUserName()))))
+        {
+            return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
+        }
+        if (!StringUtil.strIsNullOrEmpty(user.getDomain())){
+            user.setDomain(user.getDomain().replaceAll("^[\\s\\u2005]+", ""));
+        }
+
+        user.setCreateBy(SecurityUtils.getUsername());
+        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
+        user.setCreateTime(new Date());
+        user.setUserType("01");//一般用户
+        user.setIsAudit(1);
+        return toAjax(companyUserService.insertUser(user));
+    }
+
+    /**
+     * 生成创建销售的二维码
+     */
+
+    @PreAuthorize("@ss.hasPermi('company:user:addCodeUrl')")
+    @Log(title = "生成创建销售的二维码", businessType = BusinessType.INSERT)
+    @PostMapping("/addCodeUrl")
+    public R addCodeUrl( @RequestBody CompanyUserCodeParam user) throws Exception {
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(user.getCorpId());
+        if (qwCompany == null || qwCompany.getCorpId() == null){
+            return R.error("企业信息不存在");
+        }
+
+        //部门
+        Long deptId = user.getDeptId();
+
+        //角色组
+        Long[] roleIds = user.getRoleIds();
+
+        //区域
+        String addressId = user.getAddressId();
+
+
+        Map<String, Object> stateMap = new HashMap<>();
+        stateMap.put("companyId", user.getCompanyId());
+        stateMap.put("deptId", deptId);
+        stateMap.put("roleIds", roleIds); // 数组可以直接存放
+        stateMap.put("addressId", addressId);
+
+        // 使用JSON库将Map转换为字符串
+        ObjectMapper objectMapper = new ObjectMapper();
+        String status = objectMapper.writeValueAsString(stateMap);
+
+        // 如果要将status作为URL参数传递,记得进行URL编码!
+        String encodedStatus = URLEncoder.encode(status, StandardCharsets.UTF_8.toString());
+
+        String url="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+user.getCorpId()+"&redirect_uri=" +
+                "http://"+qwCompany.getRealmNameUrl()+"/loginqw/pages/companyLogin/index?corpId="+user.getCorpId() +
+                "&response_type=code&scope=snsapi_base&state="+encodedStatus+"&agentid="+qwCompany.getServerAgentId()+"#wechat_redirect";
+
+        R andUpload = QRCodeUtils.createAndUpload(url);
+        return  R.ok().put("data",andUpload);
+    }
+    /**
+     * 修改用户
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody CompanyUser user)
+    {
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        user.setUpdateBy(SecurityUtils.getUsername());
+
+        if (!StringUtil.strIsNullOrEmpty(user.getDomain())){
+            user.setDomain(user.getDomain().replaceAll("^[\\s\\u2005]+", ""));
+        }
+
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(user.getUserId());
+
+        if (!companyUser.getUserName().equals(user.getUserName())){
+            if (UserConstants.NOT_UNIQUE.equals(String.valueOf(companyUserService.checkUserName(user.getUserName()))))
+            {
+                return AjaxResult.error("修改员工账号'" + user.getUserName() + "'失败,员工账号已存在");
+            }
+        }
+
+
+        logger.info("用户管理-修改用户:"+user.getUserName()+"/登陆人:"+loginUser.getUser().getUserId()+"/名字:"+loginUser.getUser().getUserName()+"/修改的角色:"+ Arrays.toString(user.getRoleIds()));
+        return toAjax(companyUserService.updateUser(user));
+    }
+
+    /**
+     * 删除用户
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:remove')")
+    @Log(title = "用户管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        for (Long userId : userIds) {
+            CompanyUser companyUser = companyUserService.selectCompanyUserById(userId);
+            CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUser.getCompanyId(),userId);
+            if (ObjectUtil.isNotEmpty(companyUserDelayTime)){
+                companyUserDelayTimeService.deleteCompanyUserDelayTimeById(companyUserDelayTime.getId());
+            }
+        }
+        return toAjax(companyUserService.deleteCompanyUserByIds(userIds));
+    }
+
+    /**
+     * 重置密码
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resetPwd")
+    public AjaxResult resetPwd(@RequestBody CompanyUser user)
+    {
+        if (!PatternUtils.checkPassword(user.getPassword())) {
+            return AjaxResult.error("密码格式不正确,需包含字母、数字和特殊字符,长度为 8-20 位");
+        }
+
+        String newPassword = SecurityUtils.encryptPassword(user.getPassword());
+        int i = companyUserService.resetUserPwdByUserId(user.getUserId(), newPassword);
+
+        if (i > 0) {
+            // 更新缓存用户密码
+            redisCache.deleteObject("newCompanyUser:" + user.getCompanyId() + ":" + user.getUserName());
+        }
+
+        return toAjax(i);
+    }
+
+    /**
+     * 状态修改
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody CompanyUser user)
+    {
+        //管理员的状态不能修改
+        CompanyUser companyUser=companyUserService.selectCompanyUserById(user.getUserId());
+        if(companyUser.isAdmin()){
+            return AjaxResult.error("不能修改管理员状态");
+        }
+        user.setUpdateBy(SecurityUtils.getUsername());
+        return toAjax(companyUserService.updateCompanyUser(user));
+    }
+
+
+
+
+    /**
+     * 获取区域
+     */
+    @GetMapping("/getCitysAreaList")
+    public R getCitysAreaList(){
+        return R.ok().put("data",companyUserService.getCitysAreaList());
+    }
+
+    /**
+     * 批量修改 销售的所属区域(临时的)
+     */
+    @PostMapping("/updateCompanyUserAreaList")
+    public R updateCompanyUserAreaList(@RequestBody CompanyUserAreaParam param)
+    {
+        return companyUserService.updateCompanyUserAreaList(param);
+    }
+
+
+    @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);
+    }
+
+    @Log(title = "设置是否需要单独注册会员", businessType = BusinessType.UPDATE)
+    @PutMapping("/setRegister")
+    public AjaxResult setIsRegisterMember(@RequestParam Boolean status, @RequestBody List<Long> userIds) {
+        Boolean r = companyUserService.setIsRegisterMember(status, userIds);
+        if (r) {
+            return AjaxResult.success();
+        } else {
+            return AjaxResult.error("操作失败");
+        }
+    }
+
+    @Log(title = "是否允许所有方式注册会员", businessType = BusinessType.UPDATE)
+    @PutMapping("/allowedAllRegister")
+    public AjaxResult isAllowedAllRegister(@RequestParam Boolean status, @RequestBody List<Long> userIds) {
+        Boolean r = companyUserService.isAllowedAllRegister(status, userIds);
+        if (r) {
+            return AjaxResult.success();
+        } else {
+            return AjaxResult.error("操作失败");
+        }
+    }
+    @PostMapping("/common/uploadOSS")
+    public R uploadOSS(@RequestParam("file") MultipartFile file,
+                       @RequestParam("userId") String userId) throws Exception {
+        String url = companyUserService.uploadQrCode(file, userId);
+        return R.ok().put("url", url);
+    }
+
+
+    /**
+     * 销售解除绑定医生
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUser:unBindDoctorId')")
+    @Log(title = "销售解除绑定医生", businessType = BusinessType.UPDATE)
+    @GetMapping("/unBindDoctorId/{userId}")
+    public R bindDoctorId(@PathVariable("userId") Long userId){
+        return companyUserService.unBindDoctor(userId);
+    }
+
+    /**
+     * 销售绑定医生
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUser:bindDoctorId')")
+    @Log(title = "销售绑定医生", businessType = BusinessType.UPDATE)
+    @PostMapping("/bindDoctorId")
+    public R unBindDoctorId(@RequestBody CompanyUser companyUser){
+        return companyUserService.bindDoctor(companyUser);
+    }
+
+    /**
+     * 批量修改角色
+     * @param batchUserRolesVO
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "批量修改角色", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateBatchUserRoles")
+    public R updateBatchUserRoles(@RequestBody BatchUserRolesVO batchUserRolesVO){
+        Assert.notEmpty(batchUserRolesVO.getRoleIds(), "角色不能为空");
+        Assert.notEmpty(batchUserRolesVO.getUserIds(), "用户不能为空");
+        return companyUserService.updateBatchUserRoles(batchUserRolesVO);
+    }
+
+
+    @ApiOperation("校验客服是否注册新的im")
+    @PostMapping("/accountCheck")
+    public R accountCheck(@RequestBody Map<String, String> userIdMap){
+        //获取管理员token
+        String userId = userIdMap.get("userId");
+        String adminToken = openIMService.getAdminToken();
+        JSONObject requestBody = new JSONObject();
+        // 解析响应
+        if (StringUtils.isNotEmpty(adminToken)) {
+            //查询用户是否注册
+            ArrayList<String> userIds = new ArrayList<>();
+            requestBody = new JSONObject();
+            userIds.add(userId);
+            requestBody.put("checkUserIDs", userIds);
+            String body = HttpRequest.post(IMConfig.URL+"/user/account_check")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(requestBody.toString())
+                    .execute()
+                    .body();
+            JSONObject jsonObject = new JSONObject(body);
+            JSONArray results = jsonObject.getJSONObject("data").getJSONArray("results");
+            if (results != null && results.length() > 0) {
+                JSONObject resultObj = results.getJSONObject(0);
+                int accountStatus = resultObj.getInt("accountStatus");
+                //未注册自动注册
+                if (accountStatus==0){
+                    String s = userId.replaceFirst("^C", "");
+                    CompanyUser companyUser = companyUserService.selectCompanyUserById(Long.parseLong(s));
+                    if (null==companyUser){
+                        return R.error("用户不存在");
+                    }
+                    ArrayList<Object> users = new ArrayList<>();
+                    HashMap<String, String> map = new HashMap<>();
+                    map.put("userID",userId);
+                    map.put("nickname",companyUser.getNickName());
+                    map.put("faceURL",companyUser.getAvatar());
+                    users.add(map);
+                    requestBody = new JSONObject();
+                    //userIds.add(userId);
+                    requestBody.put("users", users);
+                    String registerBody = HttpRequest.post(IMConfig.URL+"/user/user_register")
+                            .header("operationID", String.valueOf(System.currentTimeMillis()))
+                            .header("token", adminToken).body(requestBody.toString()).execute().body();
+                }
+            } else {
+                return R.error("返回结果为空");
+            }
+           /* HashMap<String, String> tokenMap = new HashMap<>();
+            tokenMap.put("platformID","1");
+            tokenMap.put("userID",userId);*/
+            requestBody = new JSONObject();
+            requestBody.put("platformID",5);
+            requestBody.put("userID",userId);
+            String body1 = HttpRequest.post(IMConfig.URL+"/auth/get_user_token")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(requestBody.toString()).execute().body();
+            JSONObject userJson = new JSONObject(body1);
+            JSONObject userData = userJson.getJSONObject("data");
+            String userToken = userData.getString("token");
+            return R.ok().put("token", userToken);
+        } else {
+            return R.error("获取管理员token失败");
+        }
+    }
+    @ApiOperation("添加好友")
+    @PostMapping("/importFriend")
+    public R importFriend(@RequestBody HashMap<String,Object> map){
+        String ownerUserID = (String) map.get("ownerUserID");
+        List<String> friendUserIDs = (List<String>)map.get("friendUserIDs");
+        OpenImResponseDTO openImResponseDTO = openIMService.importFriend(ownerUserID, friendUserIDs);
+        return R.ok().put("data",openImResponseDTO);
+    }
+}

+ 16 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -8,6 +8,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.config.CourseConfig;
+import com.fs.course.params.FsUserCourseConfigParam;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseListPVO;
 import com.fs.framework.web.service.TokenService;
@@ -341,4 +342,19 @@ public class FsUserCourseController extends BaseController
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
     }
+
+    /**
+     * 修改配置
+     **/
+    @PreAuthorize("@ss.hasPermi('course:userCourse:editConfig')")
+    @Log(title = "课程配置", businessType = BusinessType.UPDATE)
+    @PostMapping("/editConfig")
+    public R editConfig(@RequestBody FsUserCourseConfigParam params){
+        fsUserCourseService.editConfig(params.getId(), params.getConfigJson());
+        redisCacheUtil.delRedisKey("getCourseList");
+        redisCacheUtil.delRedisKey("h5user:course:video:list:all");
+        redisCacheUtil.delRedisKey("h5user:course:list:all");
+        redisCacheUtil.delRedisKey("cache:video");
+        return R.ok();
+    }
 }

+ 21 - 0
fs-admin/src/main/java/com/fs/course/params/FsUserCourseConfigParam.java

@@ -0,0 +1,21 @@
+package com.fs.course.params;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@ApiModel("过程页配置参数实体")
+@Data
+public class FsUserCourseConfigParam {
+
+    @NotNull(message = "课程ID不能为空")
+    @ApiModelProperty("课程ID")
+    private Long id;
+
+    @NotBlank(message = "配置信息不能为空")
+    @ApiModelProperty("配置信息")
+    private String configJson;
+}

+ 23 - 0
fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java

@@ -26,6 +26,9 @@ import com.fs.his.service.IFsDoctorService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
 
+import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
+import static com.fs.his.utils.PhoneUtil.encryptPhone;
+
 /**
  * 医生管理Controller
  *
@@ -291,4 +294,24 @@ public class FsDoctorController extends BaseController
         return R.ok().put("data", new PageInfo<>(list));
     }
 
+
+    @GetMapping("/getDocVoList")
+    public TableDataInfo getDocVoList(FsDoctorParam param) {
+        startPage();
+        List<FsDoctorVO> list = fsDoctorService.selectDocVOByNameAndPhone(param);
+        if (list == null || list.isEmpty()) {
+            param.setMobile(encryptPhone(param.getMobile()));
+            list = fsDoctorService.selectDocVOByNameAndPhone(param);
+        }
+
+        if (list != null) {
+            list.forEach( item -> {
+                if (item.getMobile() != null)
+                    item.setMobile(decryptAutoPhoneMk(item.getMobile()));
+            });
+        }
+        return getDataTable(list);
+    }
+
+
 }

+ 49 - 9
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -1,13 +1,13 @@
 package com.fs.his.controller;
 
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsIntegralOrder;
@@ -17,7 +17,8 @@ import com.fs.his.enums.ShipperCodeEnum;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsIntegralOrderService;
-import com.fs.his.utils.PhoneUtil;
+import com.fs.his.service.IFsStoreOrderService;
+import com.fs.utils.OrderContextHolder;
 import com.fs.his.vo.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -44,6 +45,9 @@ public class FsIntegralOrderController extends BaseController
     private IFsIntegralOrderService fsIntegralOrderService;
     @Autowired
     private IFsExpressService expressService;
+
+    @Autowired
+    private IFsStoreOrderService fsStoreOrderService;
     /**
      * 查询积分商品订单列表
      */
@@ -98,18 +102,33 @@ public class FsIntegralOrderController extends BaseController
     @GetMapping(value = "/getExpress/{id}")
     public R getExpress(@PathVariable("id") Long id)
     {
+        /**
+         * selectFsIntegralOrderByOrderId查询的数据表:fs_integral_order
+         * 需要执行添加字段的DDL语句
+         * ALTER TABLE `fs_integral_order`
+         * ADD COLUMN `login_account` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '代服物流查询账号';
+         * */
         FsIntegralOrder fsIntegralOrder = fsIntegralOrderService.selectFsIntegralOrderByOrderId(id);
         ExpressInfoDTO expressInfoDTO=null;
-        if(StringUtils.isNotEmpty(fsIntegralOrder.getDeliverySn())){
-            String lastFourNumber = "";
-            if (fsIntegralOrder.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
+        //代服管家 查询自己的物流
+        if (CloudHostUtils.hasCloudHostName("金牛明医")) {
+            FsStoreOrder fsStoreOrder = new FsStoreOrder();
+            fsStoreOrder.setOrderCode(fsIntegralOrder.getOrderCode());
+            //线程携带订单信息
+            OrderContextHolder.setIntegralOrder(fsIntegralOrder);
+            expressInfoDTO = fsStoreOrderService.getDfExpressInfoDTO(fsStoreOrder);
+        }else {
+            if (StringUtils.isNotEmpty(fsIntegralOrder.getDeliverySn())) {
+                String lastFourNumber = "";
+                if (fsIntegralOrder.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
 
-                lastFourNumber = fsIntegralOrder.getUserPhone();
-                if (lastFourNumber.length() == 11) {
-                    lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                    lastFourNumber = fsIntegralOrder.getUserPhone();
+                    if (lastFourNumber.length() == 11) {
+                        lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                    }
                 }
+                expressInfoDTO = expressService.getExpressInfo(fsIntegralOrder.getOrderCode(), fsIntegralOrder.getDeliveryCode(), fsIntegralOrder.getDeliverySn(), lastFourNumber);
             }
-            expressInfoDTO=expressService.getExpressInfo(fsIntegralOrder.getOrderCode(),fsIntegralOrder.getDeliveryCode(),fsIntegralOrder.getDeliverySn(),lastFourNumber);
         }
         return R.ok().put("data",expressInfoDTO);
     }
@@ -178,4 +197,25 @@ public class FsIntegralOrderController extends BaseController
         String orderCode = requestBody.get("orderCode");
         return toAjax(fsIntegralOrderService.cancelOrder(orderCode));
     }
+    /**
+     * 申请退款
+     * 接口不校验是否发货
+     * */
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:cancel')")
+    @Log(title = "积分商品订单", businessType = BusinessType.UPDATE)
+    @PostMapping("/mandatoryRefunds")
+    public AjaxResult mandatoryRefunds(@RequestBody Map<String, String> requestBody){
+        String orderCode = requestBody.get("orderCode");
+        return toAjax(fsIntegralOrderService.mandatoryRefunds(orderCode));
+    }
+
+    /**
+     * 确认收货
+     * todo:权限标识符待定
+     */
+    @Log(title = "积分商品订单", businessType = BusinessType.UPDATE)
+    @GetMapping("/finishOrder/{orderCode}")
+    public AjaxResult finishOrder(@PathVariable("orderCode") String orderCode) {
+        return toAjax(fsIntegralOrderService.finishOrder(orderCode));
+    }
 }

+ 138 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwDeptController.java

@@ -0,0 +1,138 @@
+package com.fs.qw.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.qw.domain.QwDept;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.service.IQwDeptService;
+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 2024-08-27
+ */
+@RestController
+@RequestMapping("/qw/qwDept")
+public class QwDeptController extends BaseController
+{
+    @Autowired
+    private IQwDeptService qwDeptService;
+
+
+    /**
+     * 查询企业微信部门列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwDept qwDept)
+    {
+        startPage();
+        List<QwDept> list = qwDeptService.selectQwDeptList(qwDept);
+        return getDataTable(list);
+    }
+    @Autowired
+    QwCompanyMapper qwCompanyMapper;
+
+    /**
+     * 同步企业微信 部门信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:list')")
+    @GetMapping("/syncDept/{companyId}")
+    public R syncDept(@PathVariable("companyId") Long companyId){
+
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByCompanyId(companyId);
+        for (String string : strings) {
+            qwDeptService.insertOrUpdateQwDept(string);
+        }
+
+        return  R.ok();
+    }
+
+    /**
+     * 导出企业微信部门列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:export')")
+    @Log(title = "企业微信部门", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwDept qwDept)
+    {
+        List<QwDept> list = qwDeptService.selectQwDeptList(qwDept);
+        ExcelUtil<QwDept> util = new ExcelUtil<QwDept>(QwDept.class);
+        return util.exportExcel(list, "企业微信部门数据");
+    }
+
+    /**
+     * 获取企业微信部门详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwDeptService.selectQwDeptById(id));
+    }
+
+    /**
+     * 新增企业微信部门
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:add')")
+    @Log(title = "企业微信部门", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwDept qwDept)
+    {
+        return toAjax(qwDeptService.insertQwDept(qwDept));
+    }
+
+    /**
+     * 修改企业微信部门
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:edit')")
+    @Log(title = "企业微信部门", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwDept qwDept)
+    {
+        return toAjax(qwDeptService.updateQwDept(qwDept));
+    }
+
+    /**
+     * 删除企业微信部门
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:remove')")
+    @Log(title = "企业微信部门", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwDeptService.deleteQwDeptByIds(ids));
+    }
+
+    /**
+     * @Description: 获取企微部门 按Treeselect返回 每一个企微主体有自己的部门,按企微主体查询
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/30 9:33
+     */
+    @GetMapping("/treeselect")
+    public AjaxResult treeselect(QwDept qwDept)
+    {
+        if(StringUtils.isEmpty(qwDept.getCorpId())){
+            return AjaxResult.error("请选择企微主体");
+        }
+        List<QwDept> depts = qwDeptService.selectQwDeptList(qwDept);
+        return AjaxResult.success(qwDeptService.buildDeptTreeSelect(depts));
+    }
+
+}

+ 309 - 5
fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java

@@ -1,22 +1,48 @@
 package com.fs.qw.controller;
 
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Log;
+import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactTransferCompanyAudit;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.param.QwFsUserParam;
+import com.fs.qw.param.QwUserBingParam;
+import com.fs.qw.param.QwUserListParam;
+import com.fs.qw.service.IQwDeptService;
 import com.fs.qw.service.IQwExternalContactTransferCompanyAuditService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.QwOptionsVO;
+import com.fs.qw.vo.QwUserVO;
+import com.fs.qwApi.domain.QwExternalContactAllListResult;
+import com.fs.qwApi.domain.inner.ExternalContact;
+import com.fs.qwApi.domain.inner.ExternalContactInfo;
+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 org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 企微用户Controller
@@ -29,8 +55,28 @@ import java.util.List;
 public class QwUserController extends BaseController {
     @Autowired
     private IQwUserService qwUserService;
+
     @Autowired
     private IQwExternalContactTransferCompanyAuditService auditService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+
+    @Autowired
+    private QwApiService qwApiService;
+
+    @Autowired
+    private QwCompanyMapper qwCompanyMapper;
+
+    @Autowired
+    private IQwDeptService qwDeptService;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
     @GetMapping("/getQwUserAll")
     public AjaxResult getQwUserAll(){
         return AjaxResult.success(qwUserService.getQwUserAll());
@@ -47,7 +93,265 @@ public class QwUserController extends BaseController {
    @GetMapping("/getMyQwCompanyList")
     public R getMyQwCompanyList()
     {
-        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOBySys();
+        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOAll();
         return  R.ok().put("data",list);
     }
+
+    /**
+     * 绑定企微用户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:bind')")
+    @Log(title = "企微用户", businessType = BusinessType.UPDATE)
+    @PutMapping("/bindQwUser")
+    @RepeatSubmit
+    public R bindQwUser(@RequestBody QwUserBingParam qwUserParam)
+    {
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(qwUserParam.getCompanyUserId());
+
+        String qwUserIdCompanyStr = companyUser.getQwUserId();
+        Set<String> qwUserIdCompanySet = new HashSet<>();
+        if (!StringUtil.strIsNullOrEmpty(qwUserIdCompanyStr)) {
+            String[] qwUserId = qwUserIdCompanyStr.split(",");
+            qwUserIdCompanySet = Arrays.stream(qwUserId)
+                    .filter(id -> !id.isEmpty())
+                    .collect(Collectors.toSet());
+        }
+
+        if (companyUser!=null){
+
+            //选择的企业微信账号为“”
+            if (StringUtil.strIsNullOrEmpty(qwUserParam.getId())&&qwUserParam.getCompanyUserId()!=null){
+                //制空企业微信的绑定
+                qwUserService.updateUserByUserId(qwUserParam.getCompanyUserId());
+
+                //制空销售的绑定
+                companyUserMapper.updateCompanyUserByNullQwUserID(companyUser.getUserId());
+
+            }else {
+                String idParam = qwUserParam.getId();
+                String[] splitParam = idParam.split(",");
+                Set<String> splitParamSet = Arrays.stream(splitParam)
+                        .filter(s -> !s.isEmpty())
+                        .collect(Collectors.toSet());
+
+                // 找出在 qwUserIdCompanySet 中存在但在 splitParamSet 中不存在的元素
+                Set<String> difference = new HashSet<>(qwUserIdCompanySet);
+                difference.removeAll(splitParamSet);
+
+                //制空绑定
+                if (!difference.isEmpty()){
+                    difference.forEach(id->{
+                        qwUserService.updateUnBindUserById(id);
+                    });
+
+                }
+
+
+                for (String paramId : splitParamSet) {
+                    QwUser qu= qwUserService.selectQwUserById(Long.parseLong(paramId));
+                    if (qu.getCompanyUserId()!=null&&!qu.getCompanyUserId().equals(qwUserParam.getCompanyUserId())){
+                        return R.error( qu.getQwUserName()+"已经被其他人绑定,请先解绑");
+                    }
+
+
+                    CompanyUser user = new CompanyUser();
+                    user.setQwUserId(qwUserParam.getId());
+                    user.setUserId(companyUser.getUserId());
+                    user.setQwStatus(1);
+                    companyUserMapper.updateCompanyUser(user);
+
+
+
+                    qu.setCompanyUserId(qwUserParam.getCompanyUserId());
+                    qu.setStatus(1);
+                    qu.setCompanyId(companyUser.getCompanyId());
+                    qwUserService.updateQwUser(qu);
+
+                }
+
+                //同步最后单独跑/先不异步了pp
+                for (String paramId : splitParamSet){
+
+                    //如果销售没绑定过,全刷,否则只同步新的增加的
+                    if (StringUtil.strIsNullOrEmpty(qwUserIdCompanyStr)){
+                        updateAndSyncQwUser(paramId, companyUser);
+
+                    }
+                    else if (!StringUtil.strIsNullOrEmpty(qwUserIdCompanyStr) && !qwUserIdCompanySet.contains(paramId)){
+                        updateAndSyncQwUser(paramId, companyUser);
+                    }
+                }
+            }
+
+
+            return R.ok();
+        }
+        return R.error("绑定失败");
+
+    }
+
+
+    private void updateAndSyncQwUser(String paramId, CompanyUser companyUser) {
+
+        QwUser qu= qwUserService.selectQwUserById(Long.parseLong(paramId));
+
+        qwExternalContactMapper.updateBindUserByQwUser(qu.getCorpId(),qu.getQwUserId(),companyUser.getCompanyId(),companyUser.getUserId());
+
+        //根据企微账号和公司id 同步企业微信账号(游标初始为空)
+        syncMyQwExternalContact(qu,null);
+    }
+
+    public R  syncMyQwExternalContact(QwUser qwUser,String getNextCursor )  {
+
+        String qwUserId = qwUser.getQwUserId();
+        String corpId = qwUser.getCorpId();
+        Long companyId = qwUser.getCompanyId();
+
+        QwExternalListParam param = new QwExternalListParam();
+        param.setLimit(100);
+        param.setUserid_list(Arrays.asList(qwUserId));
+        param.setCursor(getNextCursor);
+
+        QwExternalContactAllListResult list = qwApiService.getAllExternalcontactList(param, corpId);
+
+        logger.info("批量获取客户详情" + list);
+
+        if (list.getErrcode() == 0) {
+            List<ExternalContactInfo> externalContactList = list.getExternal_contact_list();
+            for (ExternalContactInfo externalContactInfo : externalContactList) {
+
+                ExternalContact externalContact = externalContactInfo.getExternal_contact();
+                FollowInfo followInfo = externalContactInfo.getFollow_info();
+                QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(externalContact.getExternal_userid(), qwUserId, corpId);
+                logger.info("客户详情" + qwExternalContact);
+
+                if (qwExternalContact == null) {
+                    qwExternalContact = new QwExternalContact();
+                    qwExternalContact.setUserId(qwUserId); // 设置属于用户ID
+                    qwExternalContact.setExternalUserId(externalContact.getExternal_userid()); // 设置外部联系人ID
+
+
+                    qwExternalContact.setCorpId(corpId); // 设置企业ID
+                    qwExternalContact.setCompanyId(companyId); // 设置公司ID
+                }
+
+
+                // 设置公共属性
+                qwExternalContact.setCompanyUserId(qwUser.getCompanyUserId());
+                qwExternalContact.setQwUserId(qwUser.getId());
+                qwExternalContact.setName(externalContact.getName()); // 设置名称
+                qwExternalContact.setAvatar(externalContact.getAvatar()); // 设置头像
+                qwExternalContact.setType(externalContact.getType()); // 设置外部联系人类型(1微信用户,2企业微信用户)
+                qwExternalContact.setGender(externalContact.getGender()); // 设置性别 (0-未知, 1-男性, 2-女性)
+                qwExternalContact.setDescription(followInfo.getDescription()); // 设置描述信息
+                qwExternalContact.setRemark(followInfo.getRemark());
+
+//                        if (followInfo.getTag_id() != null && !followInfo.getTag_id().isEmpty()) {
+                qwExternalContact.setTagIds(JSON.toJSONString(followInfo.getTag_id())); // 设置标签ID
+//                        }
+
+//                        if (followInfo.getRemark_mobiles() != null && !followInfo.getRemark_mobiles().isEmpty()) {
+                qwExternalContact.setRemarkMobiles(JSON.toJSONString(followInfo.getRemark_mobiles())); // 设置备注电话号码
+//                        }
+
+                qwExternalContact.setState(followInfo.getState());
+
+                if (followInfo.getState() != null && !followInfo.getState().isEmpty()) {
+                    String s = "way:" + corpId + ":";
+                    if (followInfo.getState().contains(s)) {
+                        String wayId = followInfo.getState().substring(followInfo.getState().indexOf(s) + s.length());
+                        qwExternalContact.setWayId(Long.parseLong(wayId));
+                    }
+                }
+
+                qwExternalContact.setRemarkCorpName(followInfo.getRemark_corp_name()); // 设置备注企业名称
+                qwExternalContact.setAddWay(followInfo.getAdd_way()); // 设置来源
+                qwExternalContact.setOperUserid(followInfo.getOper_userid()); // 设置oper用户ID
+
+                // 根据是否存在记录进行更新或插入
+                if (qwExternalContact.getId() != null) {
+                    qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+                } else {
+                    qwExternalContactMapper.insertQwExternalContact(qwExternalContact);
+                }
+            }
+
+        }else {
+            return R.error("同步失败:"+list.getErrmsg());
+        }
+
+        if (!StringUtil.strIsNullOrEmpty(list.getNext_cursor())){
+            syncMyQwExternalContact(qwUser,list.getNext_cursor());
+        }
+        return R.ok();
+    }
+
+
+    /**
+     * 同步企微用户
+     */
+    @RepeatSubmit
+    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
+    @Log(title = "企微用户", businessType = BusinessType.INSERT)
+    @PostMapping("sync/{corpId}")
+    public R sync(@PathVariable("corpId") String corpId)
+    {
+
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByAll();
+        for (String string : strings) {
+
+            if (string.equals(corpId)){
+                qwUserService.syncQwUser(string);
+                qwDeptService.insertOrUpdateQwDept(string);
+                logger.info("同步完成");
+            }
+        }
+        return R.ok();
+    }
+
+    /**
+     * 获取企微用户详细信息
+     */
+
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwUserService.selectQwUserVOById(id));
+    }
+    /**
+     * 批量查询 企微用户详细信息
+     */
+    @GetMapping(value = "/getInfo/{ids}")
+    public AjaxResult getInfoByIds(@PathVariable("ids") Long[] ids)
+    {
+        return AjaxResult.success(qwUserService.selectQwUserVOByIds(ids));
+    }
+
+    @RepeatSubmit
+    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
+    @Log(title = "同步企微用户名称", businessType = BusinessType.INSERT)
+    @PostMapping("syncName/{corpId}")
+    public R syncName(@PathVariable("corpId") String corpId)
+    {
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByAll();
+        for (String string : strings) {
+            if (string.equals(corpId)){
+                qwUserService.syncQwUserName(string);
+            }
+        }
+        return R.ok();
+    }
+
+
+    /**
+     * 查询企微用户列表
+     */
+
+    @GetMapping("/userList")
+    public TableDataInfo userList(QwUserListParam qwUser)
+    {
+        startPage();
+        List<QwUserVO> list = qwUserService.selectAllQwUserListVO(qwUser);
+        return getDataTable(list);
+    }
 }

+ 37 - 0
fs-admin/src/test/java/com/fs/api/controller/IndexStatisticsControllerTest.java

@@ -0,0 +1,37 @@
+package com.fs.api.controller;
+
+import com.fs.FSApplication;
+import com.fs.common.core.domain.R;
+import com.fs.statis.dto.AnalysisPreviewQueryDTO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = FSApplication.class)
+@RequiredArgsConstructor
+@Slf4j
+public class IndexStatisticsControllerTest {
+
+    @Autowired
+    private IndexStatisticsController indexStatisticsController;
+
+    @Test
+    public void analysisPreview() {
+        AnalysisPreviewQueryDTO dto = new AnalysisPreviewQueryDTO();
+        dto.setCompanyId(null);
+        dto.setDeptId(1L);
+        dto.setEndTime("2025-10-31 23:59:59");
+        dto.setStartTime("2025-10-01 00:00:00");
+        dto.setType(4);
+        dto.setUserType(1);
+        R r = indexStatisticsController.analysisPreview(dto);
+        log.info("r: {}",r);
+    }
+}

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

@@ -621,7 +621,7 @@ public class QwUserController extends BaseController
     }
     @RepeatSubmit
     @PreAuthorize("@ss.hasPermi('qw:user:sync')")
-    @Log(title = "企微用户", businessType = BusinessType.INSERT)
+    @Log(title = "同步企微用户名称", businessType = BusinessType.INSERT)
     @PostMapping("syncName/{corpId}")
     public R syncName(@PathVariable String corpId)
     {

+ 104 - 0
fs-company/src/main/java/com/fs/company/controller/tag/FsVideoCourseTagController.java

@@ -0,0 +1,104 @@
+package com.fs.company.controller.tag;
+
+import java.util.List;
+
+import com.fs.tag.domain.FsVideoCourseTag;
+import com.fs.tag.service.IFsVideoCourseTagService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 视频小节看课标签关联Controller
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+@RestController
+@RequestMapping("/shop/tag")
+public class FsVideoCourseTagController extends BaseController
+{
+    @Autowired
+    private IFsVideoCourseTagService fsVideoCourseTagService;
+
+    /**
+     * 查询视频小节看课标签关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsVideoCourseTag fsVideoCourseTag)
+    {
+        startPage();
+        List<FsVideoCourseTag> list = fsVideoCourseTagService.selectFsVideoCourseTagList(fsVideoCourseTag);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出视频小节看课标签关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:export')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsVideoCourseTag fsVideoCourseTag)
+    {
+        List<FsVideoCourseTag> list = fsVideoCourseTagService.selectFsVideoCourseTagList(fsVideoCourseTag);
+        ExcelUtil<FsVideoCourseTag> util = new ExcelUtil<FsVideoCourseTag>(FsVideoCourseTag.class);
+        return util.exportExcel(list, "视频小节看课标签关联数据");
+    }
+
+    /**
+     * 获取视频小节看课标签关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsVideoCourseTagService.selectFsVideoCourseTagById(id));
+    }
+
+    /**
+     * 新增视频小节看课标签关联
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:add')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsVideoCourseTag fsVideoCourseTag)
+    {
+        return toAjax(fsVideoCourseTagService.insertFsVideoCourseTag(fsVideoCourseTag));
+    }
+
+    /**
+     * 修改视频小节看课标签关联
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:edit')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsVideoCourseTag fsVideoCourseTag)
+    {
+        return toAjax(fsVideoCourseTagService.updateFsVideoCourseTag(fsVideoCourseTag));
+    }
+
+    /**
+     * 删除视频小节看课标签关联
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:remove')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsVideoCourseTagService.deleteFsVideoCourseTagByIds(ids));
+    }
+}

+ 2 - 2
fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java

@@ -37,11 +37,11 @@ public class SendMsgTest {
 
     @Test
     public void testLogWrite(){
-        List<Long> testLogIds = Arrays.asList(177L,180L,182L,183L,184L,185L);
+        List<Long> testLogIds = Arrays.asList(194L,195L);
 
         for(Long logId : testLogIds){
             FsCourseWatchLog fsCourseWatchLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(logId);
-            fsTagUpdateService.onCourseWatchingBatch(Collections.singletonList(fsCourseWatchLog));
+            fsTagUpdateService.onCourseWatchFinishedBatch(Collections.singletonList(fsCourseWatchLog));
         }
     }
 

+ 29 - 26
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -332,8 +332,7 @@ public class QwMsgController {
                     sendType = 1;
                     userId = sender;
                 }
-
-                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104||wxWorkMessageDTO.getMsgtype()==141){
+                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
 
                     String content = wxWorkMessageDTO.getContent();
                     log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
@@ -371,30 +370,6 @@ public class QwMsgController {
                         content = wxWorkMessageDTO.getUrl();
                         log.info("id:{}, 用户发送表情"+content, id);
                     }//视频号
-                    else if (wxWorkMessageDTO.getMsgtype()==141){
-                        QwUser qwUserByAppKey = qwUserMapper.selectQwUserById(id);
-                        if(qwUserByAppKey.getVideoGetStatus() != null && qwUserByAppKey.getVideoGetStatus() == 1){
-                            QwUserVideo qwUserVideo = qwUserVideoService.selectByObjectId(wxWorkMessageDTO.getObjectId(), qwUserByAppKey.getId());
-                            if(qwUserVideo == null){
-                                QwUserVideo userVideo=new QwUserVideo();
-                                userVideo.setSenderName(wxWorkMessageDTO.getSender_name());
-                                userVideo.setAppKey(qwUserByAppKey.getAppKey());
-                                userVideo.setNickName(wxWorkMessageDTO.getNickname());
-                                userVideo.setObjectId(wxWorkMessageDTO.getObjectId());
-                                userVideo.setCoverUrl(wxWorkMessageDTO.getCover_url());
-                                userVideo.setThumbUrl(wxWorkMessageDTO.getThumb_url());
-                                userVideo.setAvatar(wxWorkMessageDTO.getAvatar());
-                                userVideo.setDesc(wxWorkMessageDTO.getDesc());
-                                userVideo.setUrl(wxWorkMessageDTO.getUrl());
-                                userVideo.setExtras(wxWorkMessageDTO.getExtras());
-                                userVideo.setObjectNonceId(wxWorkMessageDTO.getObjectNonceId());
-                                userVideo.setQwUserId(qwUserByAppKey.getId());
-                                userVideo.setCompanyUserId(qwUserByAppKey.getCompanyUserId());
-                                userVideo.setCompanyId(qwUserByAppKey.getCompanyId());
-                                qwUserVideoService.insertQwUserVideo(userVideo);
-                            }
-                        }
-                    }
 
                     if (2000000000000000L-receiver>0){
                         log.info("id:{}, 客户发送", id);
@@ -405,6 +380,34 @@ public class QwMsgController {
                     }
 
                 }
+                //视频号
+                if (wxWorkMessageDTO.getMsgtype()==141){
+                    QwUser qwUserByAppKey = qwUserMapper.selectQwUserById(id);
+                    log.info("进入到视频号:{}",qwUserByAppKey);
+
+                    if(qwUserByAppKey.getVideoGetStatus() != null && qwUserByAppKey.getVideoGetStatus() == 1){
+                        QwUserVideo qwUserVideo = qwUserVideoService.selectByObjectId(wxWorkMessageDTO.getObjectId(), qwUserByAppKey.getId());
+                        if(qwUserVideo == null){
+                            QwUserVideo userVideo=new QwUserVideo();
+                            userVideo.setSenderName(wxWorkMessageDTO.getSender_name());
+                            userVideo.setAppKey(qwUserByAppKey.getAppKey());
+                            userVideo.setNickName(wxWorkMessageDTO.getNickname());
+                            userVideo.setObjectId(wxWorkMessageDTO.getObjectId());
+                            userVideo.setCoverUrl(wxWorkMessageDTO.getCover_url());
+                            userVideo.setThumbUrl(wxWorkMessageDTO.getThumb_url());
+                            userVideo.setAvatar(wxWorkMessageDTO.getAvatar());
+                            userVideo.setDesc(wxWorkMessageDTO.getDesc());
+                            userVideo.setUrl(wxWorkMessageDTO.getUrl());
+                            userVideo.setExtras(wxWorkMessageDTO.getExtras());
+                            userVideo.setObjectNonceId(wxWorkMessageDTO.getObjectNonceId());
+                            userVideo.setQwUserId(qwUserByAppKey.getId());
+                            userVideo.setCompanyUserId(qwUserByAppKey.getCompanyUserId());
+                            userVideo.setCompanyId(qwUserByAppKey.getCompanyId());
+                            qwUserVideoService.insertQwUserVideo(userVideo);
+                            log.info("存储完成:userVideo={}",userVideo);
+                        }
+                    }
+                }
                 //语音通话
                 if (wxWorkMessageDTO.getMsgtype()==40){
                     if (wxWorkMessageDTO.getRecordtype()==null){

+ 1 - 0
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -207,6 +207,7 @@ public class QwDataCallbackService {
                             if(WelcomeCodeList.getLength() > 0) {
                                 WelcomeCode = WelcomeCodeList.item(0).getTextContent();
                             }
+
                             String userId = root.getElementsByTagName("UserID").item(0).getTextContent();
                             String externalUserId = root.getElementsByTagName("ExternalUserID").item(0).getTextContent();
                             String cacheKey = "qwApiExternal:" + userId + ":" + corpId + ":" + externalUserId;

+ 14 - 4
fs-service/src/main/java/com/fs/company/domain/CompanyDeptTreeSelect.java

@@ -14,21 +14,31 @@ public class CompanyDeptTreeSelect {
     /** 节点名称 */
     private String label;
 
+    /**
+    * 销售公司
+    */
+    private Long companyId;
+
     /** 子节点 */
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private List<CompanyDeptTreeSelect> children;
 
-    public CompanyDeptTreeSelect()
-    {
-
-    }
     public CompanyDeptTreeSelect(CompanyDept dept) {
         this.id = dept.getDeptId();
         this.label = dept.getDeptName();
+        this.companyId = dept.getCompanyId();
         this.children = dept.getChildren().stream().map(CompanyDeptTreeSelect::new).collect(Collectors.toList());
 
     }
 
+    public Long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(Long companyId) {
+        this.companyId = companyId;
+    }
+
     public Long getId()
     {
         return id;

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java

@@ -120,4 +120,6 @@ public interface CompanyDeptMapper
     void deleteCompanyDeptByCompanyIds(Long[] companyIds);
 
     List<CompanyDept> selectCompanyDeptByIds(@Param("depts")List<Long> deptIds);
+
+    List<Long> getCurrentDeptIdDownTreeIds(@Param("deptId") Long deptId);
 }

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

@@ -152,4 +152,9 @@ public class FsUserCourse extends BaseEntity
     @TableField(exist = false)
     private Long[] companyIdsList;
 
+    /**
+     * 课堂配置
+     */
+    private String configJson;
+
 }

+ 0 - 27
fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java

@@ -112,31 +112,4 @@ public class FsUserCourseVideo extends BaseEntity
 
     private Long listingEndTime;//商品结束售卖时间
 
-
-    /**
-     * 看课中标签ID
-     */
-    private String watchingTagId;
-    /**
-     * 完课标签ID
-     */
-    private String watchedTagId;
-    /**
-     * 标签组ID
-     */
-    private String tagGroupId;
-
-    /**
-     * 标签组表中的ID
-     */
-    private Long tgId;
-    /**
-     * 看课标签 表中的ID
-     */
-    private Long watchingTgId;
-    /**
-     * 完课标签 表中的ID
-     */
-    private Long watchedTgId;
-
 }

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

@@ -193,7 +193,7 @@ public interface FsUserCourseMapper
 
 
     @Select("select v.video_id,v.title,v.course_id,v.video_url,v.question_bank_id," +
-            "SEC_TO_TIME(c.duration) as total_duration,c.views,c.course_name,c.description,c.img_url " +
+            "SEC_TO_TIME(c.duration) as total_duration,c.views,c.course_name,c.description,c.img_url,c.config_json  " +
             " from fs_user_course_video v " +
             "left join fs_user_course c on v.course_id = c.course_id " +
             "where v.video_id = #{videoId}")
@@ -328,4 +328,11 @@ public interface FsUserCourseMapper
 
     @Select("select course_id,course_name,description,img_url,second_img secondImg,views from fs_user_course where course_id = #{courseId} and is_del = 0")
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(@Param("courseId") Long courseId);
+
+
+    /**
+     * 修改课堂配置
+     */
+    @Update("update fs_user_course set config_json = #{configJson} where course_id = #{id}")
+    void editConfig(@Param("id") Long id, @Param("configJson") String configJson);
 }

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

@@ -67,7 +67,9 @@ public interface FsUserCourseVideoRedPackageMapper
      * @return 结果
      */
     public int deleteFsUserCourseVideoRedPackageByIds(Long[] ids);
-    public int deleteFsUserCourseVideoRedPackageByVedioIds(Long[] ids);
+    public int deleteFsUserCourseVideoRedPackageByVedioIds(
+            @Param("videoIds") Long[] videoIds,
+            @Param("periodIds") Long[] periodIds);
     @Update("INSERT INTO fs_user_course_video_red_package (company_id, video_id, red_packet_money) " +
             "VALUES (#{companyId}, #{videoId}, #{redPacketMoney}) " +
             "ON DUPLICATE KEY UPDATE red_packet_money = VALUES(red_packet_money);")

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

@@ -13,6 +13,7 @@ import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
 import com.fs.his.vo.OptionsVO;
 
+import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 
 /**
@@ -134,4 +135,9 @@ public interface IFsUserCourseService
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(Long courseId);
 
     R createAppCourseSortLink(FsCourseLinkCreateParam fsCourseLinkCreateParam);
+
+    /**
+     * 修改课堂配置
+     */
+    void editConfig(Long id, String configJson);
 }

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

@@ -645,8 +645,10 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVO(FsCourseWatchLogListParam param) {
 
         // 因为selectFsCourseWatchLogListVO 这个方法中查询了看课日志记录表 需要限制创建时间必传
-        if(StringUtils.isEmpty(param.getSTime()) || StringUtils.isEmpty(param.getETime())){
-            throw new RuntimeException("请输入创建时间");
+        if((StringUtils.isEmpty(param.getSTime()) || StringUtils.isEmpty(param.getETime())) &&
+                (StringUtils.isEmpty(param.getUpSTime()) || StringUtils.isEmpty(param.getUpETime())) &&
+                (StringUtils.isEmpty(param.getScheduleEndTime()) || StringUtils.isEmpty(param.getScheduleStartTime()))){
+            throw new RuntimeException("请输入创建时间或营期时间或更新时间其中一个");
         }
 
         // 待看课-未注册

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

@@ -129,11 +129,12 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         int flag = 0;
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectBatchIds(Arrays.asList(ids));
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
+        List<Long> getPeriodIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getPeriodId).collect(Collectors.toList());
         List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             flag = fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
             //删除红包记录
-            fsUserCourseVideoRedPackageMapper.deleteFsUserCourseVideoRedPackageByVedioIds(videoIds.toArray(new Long[0]));
+            fsUserCourseVideoRedPackageMapper.deleteFsUserCourseVideoRedPackageByVedioIds(videoIds.toArray(new Long[0]),getPeriodIds.toArray(new Long[0]));
         }
         return flag;
     }

+ 8 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -762,6 +762,14 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return R.error("生成链接失败!");
     }
 
+    /**
+     * 修改课堂配置
+     */
+    @Override
+    public void editConfig(Long id, String configJson) {
+        fsUserCourseMapper.editConfig(id, configJson);
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();

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

@@ -953,30 +953,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     @Override
     public List<FsUserCourseVideoVO> selectFsUserCourseVideoListByCourseIdAndCompany(FsUserCourseVideoParam fsUserCourseVideo) {
-        List<FsUserCourseVideoVO> fsUserCourseVideoVOS = fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
-        for (FsUserCourseVideoVO item : fsUserCourseVideoVOS) {
-            if(ObjectUtils.isNotNull(item.getTgId())){
-                QwTagGroup qwTagGroup = qwTagGroupMapper.selectQwTagGroupById(item.getTgId());
-                if(ObjectUtils.isNotNull(qwTagGroup)){
-                    item.setTagGroupName(qwTagGroup.getName());
-                }
-            }
-
-            if(ObjectUtils.isNotNull(item.getWatchingTgId())){
-                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchingTgId());
-                if(ObjectUtils.isNotNull(qwTag)){
-                    item.setWatchingTagName(qwTag.getName());
-                }
-            }
-
-            if(ObjectUtils.isNotNull(item.getWatchedTgId())) {
-                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchedTgId());
-                if(ObjectUtils.isNotNull(qwTag)){
-                    item.setWatchedTagName(qwTag.getName());
-                }
-            }
-        }
-        return fsUserCourseVideoVOS;
+        return fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
     }
 
     @Override
@@ -2741,10 +2718,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }else {
             link.setLinkType(3);
         }
-        link.setProjectCode(cloudHostProper.getProjectCode());
-
-        link.setProjectCode(cloudHostProper.getProjectCode());
-
         String randomString = generateRandomStringWithLock();
         if (StringUtil.strIsNullOrEmpty(randomString)){
             link.setLink(UUID.randomUUID().toString().replace("-", ""));

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

@@ -52,4 +52,9 @@ public class FsUserCourseListPVO extends BaseEntity
      * 项目名称
      */
     private String projectName;
+
+    /**
+     * 课堂配置
+     */
+    private String configJson;
 }

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

@@ -35,7 +35,7 @@ public class FsUserCourseVideoH5VO extends BaseEntity
     /** 总播放量 */
     private Long views;
 
-
-
+    /** 课堂配置 **/
+    private String configJson;
 
 }

+ 0 - 39
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoVO.java

@@ -68,43 +68,4 @@ public class FsUserCourseVideoVO extends BaseEntity {
     private String redPacketMoney;
 
     private String companyRedPacketMoney;
-    /**
-     * 标签组表中的ID
-     */
-    private Long tgId;
-    /**
-     * 看课标签 表中的ID
-     */
-    private Long watchingTgId;
-    /**
-     * 完课标签 表中的ID
-     */
-    private Long watchedTgId;
-
-    /**
-     * 看课中标签ID
-     */
-    private String watchingTagId;
-    /**
-     * 完课标签ID
-     */
-    private String watchedTagId;
-    /**
-     * 标签组ID
-     */
-    private String tagGroupId;
-
-    /**
-     * 标签组名称
-     */
-    private String tagGroupName;
-    /**
-     * 看课标签
-     */
-    private String watchingTagName;
-    /**
-     * 完课标签
-     */
-    private String watchedTagName;
-
 }

+ 12 - 0
fs-service/src/main/java/com/fs/erp/service/impl/DfOrderServiceImpl.java

@@ -47,6 +47,7 @@ import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.vo.FsStoreOrderItemVO;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.utils.OrderContextHolder;
 import com.hc.openapi.tool.util.StringUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.util.Asserts;
@@ -255,9 +256,20 @@ public class DfOrderServiceImpl implements IErpOrderService {
         String orderCode = request.getCode();
         if (StrUtil.isNotBlank(orderCode)) {
             FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(orderCode);
+            if (OrderContextHolder.hasIntegralOrder())
+                order = new FsStoreOrder();
             if (order != null) {
                 String mailNumber = order.getDeliverySn();
                 Long dfAccountId = getSFAccountIndex(order.getOrderId());
+                //积分商城逻辑
+                if (OrderContextHolder.hasIntegralOrder()){
+                    FsIntegralOrder integralOrder = OrderContextHolder.getIntegralOrder();
+                    mailNumber = integralOrder.getDeliverySn();
+                    FsDfAccount fsDfAccount = fsDfAccountMapper.selectFsDfAccountByAccount(integralOrder.getLoginAccount());
+                    if (fsDfAccount != null)
+                        dfAccountId = fsDfAccount.getId();
+                    OrderContextHolder.clear();//清理threadlocalmap引用。
+                }
                 if (StringUtils.isNotBlank(mailNumber) && dfAccountId != null) {
                     try {
                         Map<String, Object> map = new HashMap<>();

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

@@ -114,6 +114,6 @@ public class FsIntegralOrder extends BaseEntity
     @Excel(name = "销售公司ID")
     private Long companyId;
 
-
+    private String loginAccount;
 
 }

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

@@ -108,4 +108,6 @@ public interface FsIntegralOrderMapper
     FsIntegralOrderPVO selectFsIntegralOrderPVO(Long orderId);
 
     int cancelOrder(@Param("orderId") Long orderId);
+
+    int finishOrder(@Param("orderId") Long orderId,@Param("oldStatus") Integer status);
 }

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

@@ -105,4 +105,8 @@ public interface IFsIntegralOrderService
     AjaxResult export(FsIntegralOrder fsIntegralOrder);
 
     int cancelOrder(String orderCode);
+
+    int mandatoryRefunds(String orderCode);
+
+    int finishOrder(String orderCode);
 }

+ 9 - 0
fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java

@@ -103,6 +103,15 @@ public interface IFsStorePaymentService
 
     String transferNotify(String notifyData, HttpServletRequest request);
 
+    /**
+     * 分公司配置回调地址
+     * @param companyId
+     * @param notifyData
+     * @param request
+     * @return
+     */
+    String TransferNotifyWithCompanyId(Long companyId,String notifyData, HttpServletRequest request);
+
     Boolean isEntityNull(FsStorePaymentParam fsStorePayment);
 
     Long selectFsStorePaymentExcelVOCount(FsStorePaymentParam fsStorePayment);

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

@@ -44,6 +44,7 @@ import com.fs.ybPay.dto.OrderQueryDTO;
 import com.fs.ybPay.service.IPayService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
+import org.redisson.api.RObjectAsync;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -192,7 +193,8 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         o1.setDeliveryName(fsIntegralOrder.getDeliveryName());
         o1.setDeliverySn(fsIntegralOrder.getDeliverySn());
         o1.setDeliveryTime(DateUtils.getNowDate());
-
+        //增加字段,表示代服物流的账号,后续会使用该账号信息来查询物流。
+        o1.setLoginAccount(fsIntegralOrder.getLoginAccount());
         return fsIntegralOrderMapper.updateFsIntegralOrder(o1);
     }
 
@@ -652,6 +654,55 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         return i;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int mandatoryRefunds(String orderCode) {
+        //查询订单是否存在且是否未发货
+        FsIntegralOrder fsIntegralOrder = fsIntegralOrderMapper.selectFsIntegralOrderByOrderCode(orderCode);
+        if (null==fsIntegralOrder){
+            throw new ServiceException("订单不存在");
+        }
+        //用户说自己去对接物流,不必校验是否发货。
+//        if (fsIntegralOrder.getStatus()!=1){
+//            throw new ServiceException("订单已发货或已完成");
+//        }
+        int i = 0;
+        //修改订单状态
+        i = fsIntegralOrderMapper.cancelOrder(fsIntegralOrder.getOrderId());
+        if (i>0){
+            //原路退回积分
+            FsUser fsUser = fsUserMapper.selectFsUserByUserId(fsIntegralOrder.getUserId());
+            fsUser.setIntegral(fsUser.getIntegral()+Long.parseLong(fsIntegralOrder.getIntegral()));
+            i = fsUserMapper.updateFsUser(fsUser);
+            //新增积分记录
+            FsUserIntegralLogs fsUserIntegralLogs = new FsUserIntegralLogs();
+            fsUserIntegralLogs.setBalance(fsUser.getIntegral());
+            fsUserIntegralLogs.setBusinessId(fsIntegralOrder.getOrderId().toString());
+            fsUserIntegralLogs.setUserId(fsIntegralOrder.getUserId());
+            fsUserIntegralLogs.setLogType(20);
+            fsUserIntegralLogs.setIntegral(Long.parseLong(fsIntegralOrder.getIntegral()));
+            fsUserIntegralLogs.setBusinessType(2);
+            fsUserIntegralLogs.setStatus(0);
+            i = fsUserIntegralLogsMapper.insertFsUserIntegralLogs(fsUserIntegralLogs);
+            //todo:库存是否需要退还,待定
+        }
+        return i;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int finishOrder(String orderCode) {
+        //查询订单是否存在且是否未发货
+        FsIntegralOrder fsIntegralOrder = fsIntegralOrderMapper.selectFsIntegralOrderByOrderCode(orderCode);
+        if (null==fsIntegralOrder){
+            throw new ServiceException("订单不存在");
+        }
+        if (fsIntegralOrder.getStatus()!=2){
+            throw new ServiceException("订单未发货或者未支付,无法完成订单");
+        }
+        return fsIntegralOrderMapper.finishOrder(fsIntegralOrder.getOrderId(), fsIntegralOrder.getStatus());
+    }
+
     /**
      * 处理手机号脱敏
      */

+ 44 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -940,6 +940,50 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         }
     }
 
+    /**
+     * 分公司配置回调地址
+     * @param companyId
+     * @param notifyData
+     * @param request
+     * @return
+     */
+    @Override
+    public String TransferNotifyWithCompanyId(Long companyId,String notifyData, HttpServletRequest request) {
+        logger.info("分公司回调::companyId:{}",companyId);
+        logger.info("zyp \n【收到转账回调::分公司】:{}",notifyData);
+        try {
+            String json = companyConfigMapper.selectRedPacketConfigByKey(companyId);
+//            String json = configService.selectConfigByKey("redPacket.config");
+            RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+            //创建微信订单
+            WxPayConfig payConfig = new WxPayConfig();
+            BeanUtils.copyProperties(config,payConfig);
+            WxPayService wxPayService = new WxPayServiceImpl();
+            wxPayService.setConfig(payConfig);
+            SignatureHeader signatureHeader = new SignatureHeader();
+            signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
+            signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
+            signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
+            signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
+            WxPayTransferBatchesNotifyV3Result result = wxPayService.parseTransferBatchesNotifyV3Result(notifyData,signatureHeader);
+            logger.info("到零钱回调::分公司:{}",result.getResult());
+            if (result.getResult().getBatchStatus().equals("FINISHED") && result.getResult().getFailNum()==0) {
+                R r = redPacketLogService.syncRedPacket(result.getResult().getOutBatchNo(),result.getResult().getBatchId());
+                logger.info("result:{}",r);
+                if (r.get("code").equals(200)){
+                    return WxPayNotifyResponse.success("处理成功");
+                }else {
+                    return WxPayNotifyResponse.fail("");
+                }
+            }else {
+                return WxPayNotifyResponse.fail("");
+            }
+        } catch (WxPayException e) {
+            logger.error("zyp \n【转账回调异常】:{}", e.getReturnMsg());
+            return WxPayNotifyResponse.fail(e.getMessage());
+        }
+    }
+
     @Override
     public Boolean isEntityNull(FsStorePaymentParam param) {
         if (param == null) {

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

@@ -75,5 +75,5 @@ public class FsIntegralOrderPVO extends BaseEntity
 
     private String phone;
 
-
+    private String loginAccount;
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwCompanyMapper.java

@@ -62,9 +62,14 @@ public interface QwCompanyMapper
      * @return 结果
      */
     public int deleteQwCompanyByIds(Long[] ids);
+
     @Select("SELECT corp_id from qw_company where FIND_IN_SET(#{companyId}, company_ids)")
     List<String> selectQwCompanyCorpIdListByCompanyId(Long companyId);
 
+    @Select("SELECT corp_id from qw_company where status = 1 ")
+    List<String> selectQwCompanyCorpIdListByAll();
+
+
     @Select("SELECT * from qw_company where corp_id=#{corpId} limit 1")
     QwCompany selectQwCompanyByCorpId(String corpId);
 

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

@@ -156,6 +156,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "            <if test=\"nickName != null  and nickName != ''\"> and cu.nick_name like concat( #{nickName}, '%') </if>\n" +
             "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_name like concat('%', #{qwUserName}, '%') </if> " +
             "            <if test=\"corpId != null \"> and qu.corp_id = #{corpId}</if>\n" +
+            "            <if test=\"companyUserId != null \"> and qu.company_user_id = #{companyUserId}</if>\n" +
             "            <if test=\"companyId != null \"> and qu.company_id = #{companyId}</if>\n " +
             "            <if test=\"isDel != null \"> and qu.is_del = #{isDel}</if>\n "+
             "            <if test=\"cuDeptIdList != null and !cuDeptIdList.isEmpty() and  userType != '00' \">" +
@@ -301,6 +302,9 @@ public interface QwUserMapper extends BaseMapper<QwUser>
     @Select("select corp_id as dictValue,corp_name as dictLabel from qw_company where FIND_IN_SET(#{companyId},company_ids)")
     List<QwOptionsVO> selectQwCompanyListOptionsVOByCompanyId(Long companyId);
 
+    @Select("select corp_id as dictValue,corp_name as dictLabel from qw_company where status=1")
+    List<QwOptionsVO> selectQwCompanyListOptionsVOAll();
+
     @Select("select  *  from qw_user where qw_hook_id=#{qwHookId} ")
     QwUser selectQwUserByQwHookId(String qwHookId);
 

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

@@ -148,6 +148,12 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "<if test ='deptId !=null and deptId!=\"\"'>\n" +
             "   and cu.dept_id = #{deptId}\n" +
             "</if>" +
+            " <if test=\"filterDeptIds != null and filterDeptIds.size() != 0\">\n" +
+            "     and cu.dept_id  in\n" +
+            "      <foreach collection=\"deptIds\" item=\"item\" open=\"(\" close=\")\" separator=\",\">\n" +
+            "         ${item}\n" +
+            "      </foreach>\n" +
+            " </if> " +
             "<if test ='ids !=null and ids!=\"\"'>\n" +
             "   and qec.qw_user_id in (${ids})\n" +
             "</if>" +
@@ -191,6 +197,12 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "<if test ='(deptIds == null or deptIds.size == 0) and deptId !=null and deptId!=\"\"'>\n" +
             "   and cu.dept_id = #{deptId}\n" +
             "</if>" +
+            " <if test=\"deptIds != null and deptIds.size() != 0\">\n" +
+            "     and cu.dept_id  in\n" +
+            "      <foreach collection=\"deptIds\" item=\"item\" open=\"(\" close=\")\" separator=\",\">\n" +
+            "         ${item}\n" +
+            "      </foreach>\n" +
+            " </if> " +
             "<if test ='ids !=null and ids!=\"\"'>\n" +
             "   and qec.qw_user_id in (${ids})\n" +
             "</if>" +

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

@@ -40,4 +40,5 @@ public class QwWatchLogStatisticsListParam {
 
     private Long pageNum;
     private Long pageSize;
+    private List<Long> filterDeptIds;
 }

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

@@ -113,6 +113,7 @@ public interface IQwUserService
     void updateUnBindUserById(String id);
 
     List<QwOptionsVO> selectQwUserListOptionsVOByCompanyUserId(Long userId);
+    List<QwOptionsVO> selectQwCompanyListOptionsVOAll();
 
     QwUser selectQwUserByAppKey(String key);
 

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

@@ -1057,6 +1057,11 @@ public class QwUserServiceImpl implements IQwUserService
         return qwUserMapper.selectQwUserListOptionsVOByCompanyUserId(userId);
     }
 
+    @Override
+    public List<QwOptionsVO> selectQwCompanyListOptionsVOAll() {
+        return qwUserMapper.selectQwCompanyListOptionsVOAll();
+    }
+
     @Override
     public QwUser selectQwUserByAppKey(String key) {
         return qwUserMapper.selectQwUserByAppKey(key);

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java

@@ -249,6 +249,11 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
         CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(param.getDeptId());
         if (ObjectUtils.isNotEmpty(companyDept)&&companyDept.getParentId()==0L){
             param.setDeptId(null);
+        }else {
+            //通过部门id去找到所有子部门id
+            List<Long> currentDeptIdDownTreeIds = companyDeptMapper.getCurrentDeptIdDownTreeIds(param.getDeptId());
+            param.setFilterDeptIds(currentDeptIdDownTreeIds);
+            param.setDeptId(null);
         }
         TableDataInfo rspData = new TableDataInfo();
         rspData.setCode(HttpStatus.SUCCESS);

+ 16 - 10
fs-service/src/main/java/com/fs/tag/domain/FsTagUpdateQueue.java

@@ -1,5 +1,6 @@
 package com.fs.tag.domain;
 
+import com.fs.common.annotation.Excel;
 import lombok.Data;
 
 import java.time.LocalDateTime;
@@ -27,11 +28,6 @@ public class FsTagUpdateQueue {
     /** 课程ID */
     private Long courseId;
 
-    /** 标签id */
-    private String tagId;
-
-    /** 标签名称 */
-    private String tagName;
 
     /** 操作类型(0 ADD 1 REMOVE) 默认0 */
     private Integer operationType;
@@ -96,18 +92,28 @@ public class FsTagUpdateQueue {
      */
     private String watchedTagId;
     /**
-     * 标签组ID
+     * 看课标签组ID
      */
-    private String tagGroupId;
+    private String watchingTgGroupId;
 
     /**
-     * 标签组表中的ID
+     * 完课标签组ID
      */
-    private Long tgId;
+    private String watchedTgGroupId;
+    /** 看课中-标签组ID */
+    private Long watchingGroupId;
+
     /**
-     * 看课标签 表中的ID
+     * 完课标签组-标签组表中的ID
+     */
+    private Long watchedTagGroupId;
+    /**
+     * 看课中标签-表中的ID
      */
     private Long watchingTgId;
+
+    /** 完课-标签组ID */
+    private Long watchedGroupId;
     /**
      * 完课标签 表中的ID
      */

+ 96 - 0
fs-service/src/main/java/com/fs/tag/domain/FsVideoCourseTag.java

@@ -0,0 +1,96 @@
+package com.fs.tag.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+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_video_course_tag
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsVideoCourseTag extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 企微公司 */
+    @Excel(name = "企微公司")
+    private String corpId;
+    /**
+     * 企微公司名
+     */
+    private String qwCompanyName;
+    /** 视频ID */
+    @Excel(name = "视频ID")
+    private Long videoId;
+    /**
+     * 小节名称
+     */
+    private String videoName;
+
+    /** 看课中-标签组ID */
+    @Excel(name = "看课中-标签组ID")
+    private Long watchingGroupId;
+    /**
+     * 看课中-标签组名
+     */
+    private String watchingGroupName;
+
+    /** 完课-标签组ID */
+    @Excel(name = "完课-标签组ID")
+    private Long watchedGroupId;
+    /**
+     * 完课-标签组名
+     */
+    private String watchedGroupName;
+
+    /** 看课中-标签组ID */
+    @Excel(name = "看课中-标签组ID")
+    private Long watchingTgId;
+    /**
+     * 看课中标签名
+     */
+    private String watchingTgName;
+
+    /** 完课-标签组ID */
+    @Excel(name = "完课-标签组ID")
+    private Long watchedTgId;
+
+    /**
+     * 完课-标签名
+     */
+    private String watchedTgName;
+
+    /** 看课中-标签组ID */
+    @Excel(name = "看课中-标签组ID")
+    private String watchingGroupTgId;
+
+
+    /** 完课-标签组ID */
+    @Excel(name = "完课-标签组ID")
+    private String watchedGroupTgId;
+
+
+    /** 看课中-标签ID */
+    @Excel(name = "看课中-标签ID")
+    private String watchingTagId;
+
+    /** 完课-标签组 */
+    @Excel(name = "完课-标签组")
+    private String watchedTagId;
+
+
+    /**
+     * corp_id, '_', video_id
+     */
+    @TableField(exist = false)
+    private String compositeKey;
+
+}

+ 5 - 104
fs-service/src/main/java/com/fs/tag/mapper/FsTagUpdateQueueMapper.java

@@ -11,123 +11,26 @@ import java.util.List;
 @Mapper
 public interface FsTagUpdateQueueMapper {
 
-    @Select("select * from fs_tag_update_queue where retry_count < 3 and status in (0,3) and (next_execute_time < now() or next_execute_time is null) limit 500")
+    @Select("select * from fs_tag_update_queue where retry_count < 3 and status in (0,3) and (next_execute_time < now() or next_execute_time is null) limit 1000")
     List<FsTagUpdateQueue> selectPending();
 
-    @Select("<script>" +
-            "SELECT * FROM fs_tag_update_queue " +
-            "<where>" +
-            "<if test='id != null'> AND id = #{id} </if>" +
-            "<if test='courseLogId != null'> AND course_log_id = #{courseLogId} </if>" +
-            "<if test='courseId != null'> AND course_id = #{courseId} </if>" +
-            "<if test='tagId != null'> AND tag_id = #{tagId} </if>" +
-            "<if test='tagName != null'> AND tag_name = #{tagName} </if>" +
-            "<if test='operationType != null'> AND operation_type = #{operationType} </if>" +
-            "<if test='videoId != null'> AND video_id = #{videoId} </if>" +
-            "<if test='status != null'> AND status = #{status} </if>" +
-            "<if test='retryCount != null'> AND retry_count = #{retryCount} </if>" +
-            "<if test='corpId != null'> AND corp_id = #{corpId} </if>" +
-            "<if test='qwUserId != null'> AND qw_user_id = #{qwUserId} </if>" +
-            "</where>" +
-            "</script>")
-    List<FsTagUpdateQueue> selectByConditions(FsTagUpdateQueue condition);
-
-    @Insert("<script>" +
-            "INSERT INTO fs_tag_update_queue " +
-            "(course_log_id, course_id, tag_id, tag_name, operation_type, video_id, status, retry_count, corp_id, qw_user_id, fail_msg, payload, response, create_time, update_time, update_by, create_by,log_type) " +
-            "VALUES " +
-            "<trim prefix='(' suffix=')' suffixOverrides=','>" +
-            "<if test='courseLogId != null'>course_log_id,</if>" +
-            "<if test='courseId != null'>course_id,</if>" +
-            "<if test='tagId != null'>tag_id,</if>" +
-            "<if test='tagName != null'>tag_name,</if>" +
-            "<if test='operationType != null'>operation_type,</if>" +
-            "<if test='videoId != null'>video_id,</if>" +
-            "<if test='status != null'>status,</if>" +
-            "<if test='retryCount != null'>retry_count,</if>" +
-            "<if test='corpId != null'>corp_id,</if>" +
-            "<if test='qwUserId != null'>qw_user_id,</if>" +
-            "<if test='qwExternalContactId != null'>qw_external_contact_id,</if>" +
-            "<if test='failMsg != null'>fail_msg,</if>" +
-            "<if test='payload != null'>payload,</if>" +
-            "<if test='response != null'>response,</if>" +
-            "<if test='createTime != null'>create_time,</if>" +
-            "<if test='updateTime != null'>update_time,</if>" +
-            "<if test='updateBy != null'>update_by,</if>" +
-            "<if test='createBy != null'>create_by,</if>" +
-            "<if test='logType != null'>log_type,</if>" +
-            "</trim>" +
-            "<trim prefix='VALUES (' suffix=')' suffixOverrides=','>" +
-            "<if test='courseLogId != null'>#{courseLogId},</if>" +
-            "<if test='courseId != null'>#{courseId},</if>" +
-            "<if test='tagId != null'>#{tagId},</if>" +
-            "<if test='tagName != null'>#{tagName},</if>" +
-            "<if test='operationType != null'>#{operationType},</if>" +
-            "<if test='videoId != null'>#{videoId},</if>" +
-            "<if test='status != null'>#{status},</if>" +
-            "<if test='retryCount != null'>#{retryCount},</if>" +
-            "<if test='corpId != null'>#{corpId},</if>" +
-            "<if test='qwExternalContactId != null'>#{qwExternalContactId},</if>" +
-            "<if test='qwUserId != null'>#{qwUserId},</if>" +
-            "<if test='failMsg != null'>#{failMsg},</if>" +
-            "<if test='payload != null'>#{payload},</if>" +
-            "<if test='response != null'>#{response},</if>" +
-            "<if test='createTime != null'>#{createTime},</if>" +
-            "<if test='updateTime != null'>#{updateTime},</if>" +
-            "<if test='updateBy != null'>#{updateBy},</if>" +
-            "<if test='createBy != null'>#{createBy},</if>" +
-            "<if test='log_type != null'>#{logType},</if>" +
-            "</trim>" +
-            "</script>")
-    @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
-    int insertSelective(FsTagUpdateQueue record);
-
-
 
     @Insert("<script>" +
             "INSERT IGNORE INTO fs_tag_update_queue (" +
-            "course_log_id, is_first, course_id, tag_id, tag_name, operation_type, video_id, status, retry_count, " +
-            "corp_id, qw_user_id, qw_external_contact_id, fail_msg, payload, response, create_time, update_time, update_by, create_by, log_type,tg_id,watching_tg_id,watched_tg_id,watching_tag_id,watched_tag_id,tag_group_id" +
+            "course_log_id, is_first, course_id,  operation_type, video_id, status, retry_count, " +
+            "corp_id, qw_user_id, qw_external_contact_id, fail_msg, payload, response, create_time, update_time, update_by, create_by, log_type,watching_tg_id,watched_tg_id,watching_tag_id,watched_tag_id,watching_tg_group_id,watched_tg_group_id" +
             ") VALUES " +
             "<foreach collection='list' item='item' separator=','>" +
             "(" +
-            "#{item.courseLogId}, #{item.isFirst}, #{item.courseId}, #{item.tagId}, #{item.tagName}, #{item.operationType}, #{item.videoId}, #{item.status}, #{item.retryCount}, " +
+            "#{item.courseLogId}, #{item.isFirst}, #{item.courseId},  #{item.operationType}, #{item.videoId}, #{item.status}, #{item.retryCount}, " +
             "#{item.corpId}, #{item.qwUserId}, #{item.qwExternalContactId}, #{item.failMsg}, #{item.payload}, #{item.response}, #{item.createTime}," +
-            " #{item.updateTime}, #{item.updateBy}, #{item.createBy}, #{item.logType},#{item.tgId},#{item.watchingTgId},#{item.watchedTgId},#{item.watchingTagId},#{item.watchedTagId},#{item.tagGroupId}" +
+            " #{item.updateTime}, #{item.updateBy}, #{item.createBy}, #{item.logType},#{item.watchingTgId},#{item.watchedTgId},#{item.watchingTagId},#{item.watchedTagId},#{item.watchingTgGroupId},#{item.watchedTgGroupId}" +
             ")" +
             "</foreach>" +
             "</script>")
     int batchInsert(@Param("list") List<FsTagUpdateQueue> list);
 
 
-    @Update("<script>" +
-            "UPDATE fs_tag_update_queue " +
-            "<set>" +
-            "<if test='courseLogId != null'>course_log_id = #{courseLogId},</if>" +
-            "<if test='courseId != null'>course_id = #{courseId},</if>" +
-            "<if test='tagId != null'>tag_id = #{tagId},</if>" +
-            "<if test='tagName != null'>tag_name = #{tagName},</if>" +
-            "<if test='operationType != null'>operation_type = #{operationType},</if>" +
-            "<if test='videoId != null'>video_id = #{videoId},</if>" +
-            "<if test='status != null'>status = #{status},</if>" +
-            "<if test='retryCount != null'>retry_count = #{retryCount},</if>" +
-            "<if test='corpId != null'>corp_id = #{corpId},</if>" +
-            "<if test='qwUserId != null'>qw_user_id = #{qwUserId},</if>" +
-            "<if test='failMsg != null'>fail_msg = #{failMsg},</if>" +
-            "<if test='payload != null'>payload = #{payload},</if>" +
-            "<if test='response != null'>response = #{response},</if>" +
-            "<if test='createTime != null'>create_time = #{createTime},</if>" +
-            "<if test='updateTime != null'>update_time = #{updateTime},</if>" +
-            "<if test='updateBy != null'>update_by = #{updateBy},</if>" +
-            "<if test='createBy != null'>create_by = #{createBy},</if>" +
-            "<if test='logType != null'>log_type = #{logType},</if>" +
-            "</set> " +
-            "WHERE id = #{id}" +
-            "</script>")
-    int updateSelective(FsTagUpdateQueue record);
-
-
-
     @Update("<script>" +
             "<foreach collection='list' item='item' separator=';'>" +
             "UPDATE fs_tag_update_queue" +
@@ -135,8 +38,6 @@ public interface FsTagUpdateQueueMapper {
             "<if test='item.courseLogId != null'>course_log_id = #{item.courseLogId},</if>" +
             "<if test='item.isFirst != null'>is_first = #{item.isFirst},</if>" +
             "<if test='item.courseId != null'>course_id = #{item.courseId},</if>" +
-            "<if test='item.tagId != null'>tag_id = #{item.tagId},</if>" +
-            "<if test='item.tagName != null'>tag_name = #{item.tagName},</if>" +
             "<if test='item.operationType != null'>operation_type = #{item.operationType},</if>" +
             "<if test='item.videoId != null'>video_id = #{item.videoId},</if>" +
             "<if test='item.status != null'>status = #{item.status},</if>" +

+ 73 - 0
fs-service/src/main/java/com/fs/tag/mapper/FsVideoCourseTagMapper.java

@@ -0,0 +1,73 @@
+package com.fs.tag.mapper;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.tag.domain.FsVideoCourseTag;
+import org.apache.ibatis.annotations.MapKey;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 视频小节看课标签关联Mapper接口
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+public interface FsVideoCourseTagMapper extends BaseMapper<FsVideoCourseTag>{
+    /**
+     * 查询视频小节看课标签关联
+     *
+     * @param id 视频小节看课标签关联主键
+     * @return 视频小节看课标签关联
+     */
+    FsVideoCourseTag selectFsVideoCourseTagById(Long id);
+
+    /**
+     * 查询视频小节看课标签关联列表
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 视频小节看课标签关联集合
+     */
+    List<FsVideoCourseTag> selectFsVideoCourseTagList(FsVideoCourseTag fsVideoCourseTag);
+
+    /**
+     * 新增视频小节看课标签关联
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 结果
+     */
+    int insertFsVideoCourseTag(FsVideoCourseTag fsVideoCourseTag);
+
+    /**
+     * 修改视频小节看课标签关联
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 结果
+     */
+    int updateFsVideoCourseTag(FsVideoCourseTag fsVideoCourseTag);
+
+    /**
+     * 删除视频小节看课标签关联
+     *
+     * @param id 视频小节看课标签关联主键
+     * @return 结果
+     */
+    int deleteFsVideoCourseTagById(Long id);
+
+    /**
+     * 批量删除视频小节看课标签关联
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsVideoCourseTagByIds(Long[] ids);
+
+    @Select("select * from fs_video_course_tag where corp_id=#{corpId} and video_id=#{videoId}")
+    FsVideoCourseTag selectFsVideoCourseTagByCorpIdAndVideoId(@Param("corpId") String corpId, @Param("videoId") Long videoId);
+
+    @Select("SELECT id,corp_id,video_id,watching_group_id,watched_group_id,watching_tg_id,watched_tg_id,watching_group_tag_id as watching_group_tg_id,watched_group_tag_id as watched_group_tg_id,watching_tag_id,watched_tag_id, CONCAT(corp_id, '_', video_id) AS composite_key FROM fs_video_course_tag")
+    @MapKey("compositeKey")
+    Map<String,FsVideoCourseTag> selectAll();
+}

+ 61 - 0
fs-service/src/main/java/com/fs/tag/service/IFsVideoCourseTagService.java

@@ -0,0 +1,61 @@
+package com.fs.tag.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.tag.domain.FsVideoCourseTag;
+
+/**
+ * 视频小节看课标签关联Service接口
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+public interface IFsVideoCourseTagService extends IService<FsVideoCourseTag>{
+    /**
+     * 查询视频小节看课标签关联
+     *
+     * @param id 视频小节看课标签关联主键
+     * @return 视频小节看课标签关联
+     */
+    FsVideoCourseTag selectFsVideoCourseTagById(Long id);
+
+    /**
+     * 查询视频小节看课标签关联列表
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 视频小节看课标签关联集合
+     */
+    List<FsVideoCourseTag> selectFsVideoCourseTagList(FsVideoCourseTag fsVideoCourseTag);
+
+    /**
+     * 新增视频小节看课标签关联
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 结果
+     */
+    int insertFsVideoCourseTag(FsVideoCourseTag fsVideoCourseTag);
+
+    /**
+     * 修改视频小节看课标签关联
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 结果
+     */
+    int updateFsVideoCourseTag(FsVideoCourseTag fsVideoCourseTag);
+
+    /**
+     * 批量删除视频小节看课标签关联
+     *
+     * @param ids 需要删除的视频小节看课标签关联主键集合
+     * @return 结果
+     */
+    int deleteFsVideoCourseTagByIds(Long[] ids);
+
+    /**
+     * 删除视频小节看课标签关联信息
+     *
+     * @param id 视频小节看课标签关联主键
+     * @return 结果
+     */
+    int deleteFsVideoCourseTagById(Long id);
+}

+ 70 - 39
fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java

@@ -2,6 +2,7 @@ package com.fs.tag.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.course.domain.FsCourseWatchLog;
@@ -28,7 +29,9 @@ import com.fs.qwApi.param.QwAddTagParam;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.tag.domain.FsTagUpdateQueue;
+import com.fs.tag.domain.FsVideoCourseTag;
 import com.fs.tag.mapper.FsTagUpdateQueueMapper;
+import com.fs.tag.mapper.FsVideoCourseTagMapper;
 import com.fs.tag.service.FsTagUpdateService;
 import com.google.common.util.concurrent.RateLimiter;
 import lombok.extern.slf4j.Slf4j;
@@ -65,17 +68,7 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
     private QwApiService qwApiService;
 
     @Autowired
-    private QwTagMapper qwTagMapper;
-
-    @Autowired
-    private QwTagGroupMapper qwTagGroupMapper;
-
-    @Autowired
-    private IQwTagGroupService qwTagGroupService;
-
-    @Autowired
-    private FsUserCourseMapper fsUserCourseMapper;
-
+    private FsVideoCourseTagMapper fsVideoCourseTagMapper;
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
 
@@ -84,10 +77,6 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
 
     @Value("${tag.rate.limit:30}")
     private Integer RATE_LIMIT_NUM;
-    /**
-     * 标签组最大数量
-     */
-    private static final Integer TAG_MAX_NUM = 100;
 
     /**
      * 接口限流
@@ -115,6 +104,8 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
         Map<Long, FsUserCourseVideo> courseVideoMap = fsUserCourseVideoMapper.selectAllMap();
         // 用户(这里用户用的是企微外部联系人ID)+videoId+status 唯一
 
+        Map<String, FsVideoCourseTag> fsVideoCourseTagMap = fsVideoCourseTagMapper.selectAll();
+
         // 先导课看课记录
         List<FsTagUpdateQueue> batchData = new ArrayList<>();
         for (FsCourseWatchLog item : logs) {
@@ -122,8 +113,6 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             task.setCourseId(item.getCourseId());
             task.setVideoId(item.getVideoId());
             task.setCourseLogId(item.getLogId());
-            task.setTagId(null);
-            task.setTagName(null);
 
             task.setLogType(0);
             task.setOperationType(0);
@@ -150,12 +139,31 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
                 batchData.add(task);
                 continue;
             }
-            task.setTagGroupId(fsUserCourseVideo.getTagGroupId());
-            task.setTgId(fsUserCourseVideo.getTgId());
-            task.setWatchingTagId(fsUserCourseVideo.getWatchingTagId());
-            task.setWatchedTagId(fsUserCourseVideo.getWatchedTagId());
-            task.setWatchingTgId(fsUserCourseVideo.getWatchingTgId());
-            task.setWatchedTgId(fsUserCourseVideo.getWatchedTgId());
+            String compositeKey = String.format("%s_%s", corpId, fsUserCourseVideo.getVideoId());
+
+            FsVideoCourseTag fsVideoCourseTag = fsVideoCourseTagMap.get(compositeKey);
+            if(ObjectUtil.isNull(fsVideoCourseTag)) {
+                log.info("{},对应记录不存在!",compositeKey);
+                continue;
+            }
+            // 看课中-标签ID(表中的)
+            task.setWatchingTgId(fsVideoCourseTag.getWatchingTgId());
+            // 看课中-标签组ID(表中的)
+            task.setWatchingGroupId(fsVideoCourseTag.getWatchingGroupId());
+            // 完课-标签组ID(表中的)
+            task.setWatchedGroupId(fsVideoCourseTag.getWatchedGroupId());
+            // 完课-完课ID(表中的)
+            task.setWatchedTgId(fsVideoCourseTag.getWatchedTgId());
+
+            // 看课中-标签ID(企微)
+            task.setWatchingTagId(fsVideoCourseTag.getWatchingTagId());
+            // 看课中-标签组ID(企微)
+            task.setWatchingTgGroupId(fsVideoCourseTag.getWatchingGroupTgId());
+
+            // 完课-完课标签ID(企微)
+            task.setWatchedTagId(fsVideoCourseTag.getWatchedTagId());
+            // 完课-标签组ID(企微)
+            task.setWatchedTgGroupId(fsVideoCourseTag.getWatchedGroupTgId());
 
             if(ObjectUtil.equal(fsUserCourseVideo.getIsFirst(),1)) {
                 task.setIsFirst(1);
@@ -178,6 +186,7 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             return;
         }
         Map<Long, FsUserCourseVideo> courseVideoMap = fsUserCourseVideoMapper.selectAllMap();
+        Map<String, FsVideoCourseTag> fsVideoCourseTagMap = fsVideoCourseTagMapper.selectAll();
 
         // 先导课看课记录
         List<FsTagUpdateQueue> batchData = new ArrayList<>();
@@ -186,8 +195,7 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             task.setCourseId(item.getCourseId());
             task.setVideoId(item.getVideoId());
             task.setCourseLogId(item.getLogId());
-            task.setTagId(null);
-            task.setTagName(null);
+
             task.setOperationType(0);
             task.setStatus(0);
             task.setRetryCount(0);
@@ -213,12 +221,31 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
                 batchData.add(task);
                 continue;
             }
-            task.setTagGroupId(fsUserCourseVideo.getTagGroupId());
-            task.setTgId(fsUserCourseVideo.getTgId());
-            task.setWatchingTagId(fsUserCourseVideo.getWatchingTagId());
-            task.setWatchedTagId(fsUserCourseVideo.getWatchedTagId());
-            task.setWatchingTgId(fsUserCourseVideo.getWatchingTgId());
-            task.setWatchedTgId(fsUserCourseVideo.getWatchedTgId());
+            String compositeKey = String.format("%s_%s", corpId, fsUserCourseVideo.getVideoId());
+
+            FsVideoCourseTag fsVideoCourseTag = fsVideoCourseTagMap.get(compositeKey);
+            if(ObjectUtil.isNull(fsVideoCourseTag)) {
+                log.info("{},对应记录不存在!",compositeKey);
+                continue;
+            }
+            // 看课中-标签ID(表中的)
+            task.setWatchingTgId(fsVideoCourseTag.getWatchingTgId());
+            // 看课中-标签组ID(表中的)
+            task.setWatchingGroupId(fsVideoCourseTag.getWatchingGroupId());
+            // 完课-标签组ID(表中的)
+            task.setWatchedGroupId(fsVideoCourseTag.getWatchedGroupId());
+            // 完课-完课ID(表中的)
+            task.setWatchedTgId(fsVideoCourseTag.getWatchedTgId());
+
+            // 看课中-标签ID(企微)
+            task.setWatchingTagId(fsVideoCourseTag.getWatchingTagId());
+            // 看课中-标签组ID(企微)
+            task.setWatchingTgGroupId(fsVideoCourseTag.getWatchingGroupTgId());
+
+            // 完课-完课标签ID(企微)
+            task.setWatchedTagId(fsVideoCourseTag.getWatchedTagId());
+            // 完课-标签组ID(企微)
+            task.setWatchedTgGroupId(fsVideoCourseTag.getWatchedGroupTgId());
 
             if(ObjectUtil.equal(fsUserCourseVideo.getIsFirst(),1)) {
                 task.setIsFirst(1);
@@ -236,7 +263,7 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
     @Override
     public void handleData() {
         List<FsTagUpdateQueue> tasks = fsTagUpdateQueueMapper.selectPending();
-        if(CollectionUtils.isEmpty(tasks)){
+        if (CollectionUtils.isEmpty(tasks)) {
             log.info("找不到可处理的任务,已跳过!");
             return;
         }
@@ -276,7 +303,7 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             Thread.currentThread().interrupt();
         }
 
-        if(CollectionUtils.isNotEmpty(tasks)){
+        if (CollectionUtils.isNotEmpty(tasks)) {
             fsTagUpdateQueueMapper.batchUpdateSelective(tasks);
         }
 
@@ -288,7 +315,7 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
             QwExternalContact qwExternalContact = qwExternalContactMapper
                     .selectQwExternalContactById(fsTagUpdateQueue.getQwExternalContactId());
-            if(qwExternalContact == null) {
+            if (qwExternalContact == null) {
                 throw new IllegalArgumentException(String.format("企微外部联系人 %s 未找到!", fsTagUpdateQueue.getQwExternalContactId()));
             }
             qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
@@ -297,7 +324,7 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             rateLimiter.acquire();
 
             // 如果是看课中
-            if(ObjectUtil.equal(fsTagUpdateQueue.getLogType(),0)){
+            if (ObjectUtil.equal(fsTagUpdateQueue.getLogType(), 0)) {
                 qwEditUserTagParam.setAdd_tag(Collections.singletonList(fsTagUpdateQueue.getWatchingTagId()));
             } else {
                 // 已完课
@@ -305,19 +332,23 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
                 qwEditUserTagParam.setRemove_tag(Collections.singletonList(fsTagUpdateQueue.getWatchingTagId()));
             }
 
+            if (ObjectUtil.isNull(fsTagUpdateQueue.getCorpId())) {
+                throw new IllegalArgumentException("corpId为空!请检查一下");
+            }
             QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, fsTagUpdateQueue.getCorpId());
+            log.info("返回结果: {}", JSON.toJSONString(qwResult));
             fsTagUpdateQueue.setPayload(JSON.toJSONString(qwEditUserTagParam));
             fsTagUpdateQueue.setResponse(JSON.toJSONString(qwResult));
             // 打标签成功
-            if(ObjectUtil.equal(qwResult.getErrcode(),0)) {
+            if (ObjectUtil.equal(qwResult.getErrcode(), 0)) {
                 fsTagUpdateQueue.setStatus(2);
-                fsTagUpdateQueue.setRetryCount(0);
+                fsTagUpdateQueue.setFailMsg("");
             } else {
                 throw new RuntimeException(String.format("打标签失败 原因: %s", JSON.toJSONString(qwResult)));
             }
-        } catch (Exception e){
+        } catch (Exception e) {
             fsTagUpdateQueue.setStatus(3);
-            fsTagUpdateQueue.setRetryCount(fsTagUpdateQueue.getRetryCount()+1);
+            fsTagUpdateQueue.setRetryCount(fsTagUpdateQueue.getRetryCount() + 1);
             fsTagUpdateQueue.setFailMsg(ExceptionUtils.getFullStackTrace(e));
             fsTagUpdateQueue.setNextExecuteTime(LocalDateTime.now().plusHours(1));
         }

+ 137 - 0
fs-service/src/main/java/com/fs/tag/service/impl/FsVideoCourseTagServiceImpl.java

@@ -0,0 +1,137 @@
+package com.fs.tag.service.impl;
+
+import java.util.List;
+
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.course.vo.FsUserCourseVideoVO;
+import com.fs.qw.domain.QwTag;
+import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.mapper.QwTagGroupMapper;
+import com.fs.qw.mapper.QwTagMapper;
+import com.fs.tag.domain.FsVideoCourseTag;
+import com.fs.tag.mapper.FsVideoCourseTagMapper;
+import com.fs.tag.service.IFsVideoCourseTagService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 视频小节看课标签关联Service业务层处理
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+@Service
+public class FsVideoCourseTagServiceImpl extends ServiceImpl<FsVideoCourseTagMapper, FsVideoCourseTag> implements IFsVideoCourseTagService {
+
+    /**
+     * 查询视频小节看课标签关联
+     *
+     * @param id 视频小节看课标签关联主键
+     * @return 视频小节看课标签关联
+     */
+    @Override
+    public FsVideoCourseTag selectFsVideoCourseTagById(Long id)
+    {
+        return baseMapper.selectFsVideoCourseTagById(id);
+    }
+
+
+    @Autowired
+    private QwTagGroupMapper qwTagGroupMapper;
+
+    @Autowired
+    private QwTagMapper qwTagMapper;
+    /**
+     * 查询视频小节看课标签关联列表
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 视频小节看课标签关联
+     */
+    @Override
+    public List<FsVideoCourseTag> selectFsVideoCourseTagList(FsVideoCourseTag fsVideoCourseTag)
+    {
+        List<FsVideoCourseTag> fsVideoCourseTags = baseMapper.selectFsVideoCourseTagList(fsVideoCourseTag);
+        for (FsVideoCourseTag item : fsVideoCourseTags) {
+            if(ObjectUtils.isNotNull(item.getWatchingGroupId())){
+                QwTagGroup qwTagGroup = qwTagGroupMapper.selectQwTagGroupById(item.getWatchingGroupId());
+                if(ObjectUtils.isNotNull(qwTagGroup)){
+                    item.setWatchingGroupName(qwTagGroup.getName());
+                }
+            }
+            if(ObjectUtils.isNotNull(item.getWatchedGroupId())){
+                QwTagGroup qwTagGroup = qwTagGroupMapper.selectQwTagGroupById(item.getWatchedGroupId());
+                if(ObjectUtils.isNotNull(qwTagGroup)){
+                    item.setWatchedGroupName(qwTagGroup.getName());
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getWatchingTgId())){
+                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchingTgId());
+                if(ObjectUtils.isNotNull(qwTag)){
+                    item.setWatchingTgName(qwTag.getName());
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getWatchedTgId())) {
+                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchedTgId());
+                if(ObjectUtils.isNotNull(qwTag)){
+                    item.setWatchedTgName(qwTag.getName());
+                }
+            }
+        }
+
+        return fsVideoCourseTags;
+    }
+
+    /**
+     * 新增视频小节看课标签关联
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 结果
+     */
+    @Override
+    public int insertFsVideoCourseTag(FsVideoCourseTag fsVideoCourseTag)
+    {
+        fsVideoCourseTag.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsVideoCourseTag(fsVideoCourseTag);
+    }
+
+    /**
+     * 修改视频小节看课标签关联
+     *
+     * @param fsVideoCourseTag 视频小节看课标签关联
+     * @return 结果
+     */
+    @Override
+    public int updateFsVideoCourseTag(FsVideoCourseTag fsVideoCourseTag)
+    {
+        fsVideoCourseTag.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsVideoCourseTag(fsVideoCourseTag);
+    }
+
+    /**
+     * 批量删除视频小节看课标签关联
+     *
+     * @param ids 需要删除的视频小节看课标签关联主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsVideoCourseTagByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsVideoCourseTagByIds(ids);
+    }
+
+    /**
+     * 删除视频小节看课标签关联信息
+     *
+     * @param id 视频小节看课标签关联主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsVideoCourseTagById(Long id)
+    {
+        return baseMapper.deleteFsVideoCourseTagById(id);
+    }
+}

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

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

+ 23 - 0
fs-service/src/main/java/com/fs/utils/OrderContextHolder.java

@@ -0,0 +1,23 @@
+package com.fs.utils;
+
+import com.fs.his.domain.FsIntegralOrder;
+
+public class OrderContextHolder {
+    private static final ThreadLocal<FsIntegralOrder> CONTEXT = new ThreadLocal<>();
+
+    public static void setIntegralOrder(FsIntegralOrder order) {
+        CONTEXT.set(order);
+    }
+
+    public static FsIntegralOrder getIntegralOrder() {
+        return CONTEXT.get();
+    }
+
+    public static boolean hasIntegralOrder() {
+        return CONTEXT.get() != null;
+    }
+
+    public static void clear() {
+        CONTEXT.remove();
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/utils/QwStatusEnum.java

@@ -0,0 +1,29 @@
+package com.fs.utils;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/16 下午3:00
+ */
+public enum QwStatusEnum {
+
+    BOUND(1, "已绑定"),
+    UNBOUND(0, "未绑定");
+
+    private Integer code;
+
+    private String detail;
+
+    QwStatusEnum(Integer code, String detail) {
+        this.code = code;
+        this.detail = detail;
+    }
+
+    public int getCode(){
+        return this.code;
+    }
+
+    public String getDetail() {
+        return this.detail;
+    }
+}

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

@@ -58,8 +58,8 @@ watch:
   password3: v9xsKuqn_$d2y
 
 fs :
-  commonApi: http://192.168.0.196:7771
-  h5CommonApi: http://192.168.0.196:7771
+  commonApi: http://192.168.0.114:7771
+  h5CommonApi: http://192.168.0.114:7771
   jwt:
     # 加密秘钥
     secret: f4e2e52034348f86b67cde581c0f9eb5

+ 102 - 0
fs-service/src/main/resources/application-config-druid-knt2.yml

@@ -0,0 +1,102 @@
+baidu:
+  token: 12313231232
+  back-domain: https://www.xxxx.com
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  miniapp:
+    configs:
+      - appid:
+        secret:
+        token:
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wxd2edd379beb6581b
+    appConfigs:
+      - agentId: 1000005
+        secret: ec7okROXJqkNafq66-L6aKNv0asTzQIG0CYrj3vyBbo
+        token: PPKOdAlCoMO
+        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wxd2edd379beb6581b #微信公众号或者小程序等的appid
+    mchId: 1723480901 #微信支付商户号:陕西康年堂医药连锁有限公司
+    mchKey: 8cab128997a3547c1363b0898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://userappB.kangniantangyiyao.top/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx6ee517a8d8743f88  # 第一个公众号的appid
+        secret: 1fac75465a61f9259a0fe19795d9e80d # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+  open:
+    appId: wxd2edd379beb6581b
+    secret: 99e8312f6f7297c7d41196f5bb045053
+aifabu:  #爱链接
+  appKey: 7b471be905ab17e00f3b858c6710dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+  #  account: tcloud
+  #  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://192.168.0.114:7771
+  h5CommonApi: http://192.168.0.114:7771
+  jwt:
+    # 加密秘钥
+    secret: f4e2e52034348f86b67cde581c0f9eb5
+    # token有效时长,7天,单位秒
+    expire: 31536000
+    header: AppToken
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: jnmy-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: jnmy
+tmp_secret_config:
+  secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIx
+  secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgB
+  bucket: fs-131972100
+  app_id: 1319721001
+  region: ap-chongqing
+  proxy: fs
+cloud_host:
+  company_name: 康年堂
+  projectCode: KNT
+headerImg:
+  imgUrl: https
+ipad:
+  ipadUrl: http://qwipad.jnmyunl.com
+  aiApi: http://49.232.181.28:3000/api
+  voiceApi: http://139.186.176.122:8009
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id: -SjnK9K6cNKASa6AD9Q_c0YT7J1lPTEpPIpqbMJF8F0
+  inquiry_temp_id: hwFXVh0AWqeasBsZpa0-urb3CrPeYEwBiy3P6AMMGFQ
+
+

+ 95 - 0
fs-service/src/main/resources/application-config-druid-yxj.yml

@@ -0,0 +1,95 @@
+baidu:
+  token: 1
+  back-domain: https://www.xxxx.com
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  miniapp:
+    configs:
+      - appid: w   #中康智慧
+        secret: 5
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+      - appid: w   #中康未来智慧药房
+        secret: 9
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wwb
+    appConfigs:
+      - agentId: 100005
+        secret: ec7okROXJqkNafq66aKNv0asTzQIG0CYrj3vyBbo
+        token: PPKOdAloMO
+        aesKey: PKvaxtpSvNGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wx #微信公众号或者小程序等的appid
+    mchId: 1611045 #微信支付商户号
+    mchKey: 8cab128997a3547c10898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://usepp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx17f36a56c701bdea # 第一个公众号的appid   //公众号名称
+        secret: 185030bbe7f8d7a0c16b94dd9d4ea542 # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+aifabu:  #爱链接
+  appKey: 7b471be905ab17ef358c610dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+#  account: tcloud
+#  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://159.75.111.224:8010
+  h5CommonApi: http://159.75.111.224:8010
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: syysy-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: syysy
+cloud_host:
+  company_name: 易行健
+  projectCode: whyxj
+#看课授权时显示的头像
+headerImg:
+  imgUrl: https://yxj-1323137866.cos.ap-chongqing.myqcloud.com/app/yxj.jpg
+ipad:
+  ipadUrl: http://ipad.ysya.top
+  aiApi: http://49.232.181.28:3000/api
+  voiceApi:
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+# 聚水潭API配置
+jst:
+  app_key: 5dea3c46f0214985b1fd43d6428b2b12 #聚水潭2025-09-03
+  app_secret: 3f382758a4f4470e80932a24912be0ce #聚水潭2025-09-0
+  authorization_code: 999999
+  shop_code: "18886784"

+ 6 - 6
fs-service/src/main/resources/application-druid-hst.yml

@@ -129,15 +129,15 @@ spring:
             config:
               multi-statement-allow: true
 rocketmq:
-  name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+  name-server: rmq-1vjak37re.rocketmq.gz.qcloud.tencenttdmq.com:8080 # RocketMQ NameServer 地址
   producer:
-    group: my-producer-group
-    access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-    secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+    group: course-finish-group
+    access-key: ak1vjak37reb7b19a2b09d1 # 替换为实际的 accessKey
+    secret-key: sk3987beb638e3414f # 替换为实际的 secretKey
   consumer:
     group: test-group
-    access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-    secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+    access-key: ak1vjak37reb7b19a2b09d1 # 替换为实际的 accessKey
+    secret-key: sk3987beb638e3414f # 替换为实际的 secretKey
 openIM:
   secret: openIM123
   userID: imAdmin

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

@@ -96,11 +96,11 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://192.168.0.171:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    url: jdbc:mysql://192.168.0.171:3306/knt_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                     username: root
                     password: Ylrztek250218!3@.
                 read:
-                    url: jdbc:mysql://192.168.0.171:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    url: jdbc:mysql://192.168.0.171:3306/knt_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                     username: root
                     password: Ylrztek250218!3@.
                 # 初始连接数

+ 166 - 0
fs-service/src/main/resources/application-druid-knt2.yml

@@ -0,0 +1,166 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-knt2,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 192.168.0.131
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 2
+        # 密码
+        password: Ylrztek250218!3@.
+        # 连接超时时间
+        timeout: 20s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 8
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        #        clickhouse:
+        #            type: com.alibaba.druid.pool.DruidDataSource
+        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+        #            username: rt_2024
+        #            password: Yzx_19860213
+        #            initialSize: 10
+        #            maxActive: 100
+        #            minIdle: 10
+        #            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://192.168.0.171:3306/fs_knt_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250218!3@.
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://192.168.0.171:3306/knt_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250218!3@.
+                read:
+                    url: jdbc:mysql://192.168.0.171:3306/knt_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250218!3@.
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: 192.168.0.176:8100 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group
+        access-key: jnmyunl # 替换为实际的 accessKey
+        secret-key: 73a!ul~xQl@-6u1 # 替换为实际的 secretKey
+        tls-enable: false
+    consumer:
+        topic: course-finish-notes
+        group: course-finish-group
+        access-key: jnmyunl # 替换为实际的 accessKey
+        secret-key: 73a!ul~xQl@-6u1 # 替换为实际的 secretKey
+        tls-enable: false
+openIM:
+    secret: openIM123
+    userID: imAdmin
+    url: https://web.im.fbylive.com/api
+#是否使用新im
+im:
+    type: TENCENT
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: true

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

@@ -159,8 +159,8 @@ token:
     header: Authorization
     # 令牌密钥
     secret: abcdefghijklmnopqrstuvwxyz
-    # 令牌有效期(默认30分钟)
-    expireTime: 180
+    # 令牌有效期(默认30分钟,秒
+    expireTime: 86400
 openIM:
     secret: openIM123
     userID: imAdmin

+ 172 - 0
fs-service/src/main/resources/application-druid-yxj.yml

@@ -0,0 +1,172 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-syysy,common
+    # redis 配置
+    redis:
+        host: 172.17.0.4
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: Ylrz_tM8/oW6$pU9|tJ#&
+        # 连接超时时间
+        timeout: 10s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 8
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        #        clickhouse:
+        #            type: com.alibaba.druid.pool.DruidDataSource
+        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+        #            url: jdbc:clickhouse://1.14.104.71:8123/sop_test?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+        #            username: rt_2024
+        #            password: Yzx_19860213
+        #            initialSize: 10
+        #            maxActive: 100
+        #            minIdle: 10
+        #            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.7:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_tM818782145I@
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    url: jdbc:mysql://172.16.0.16:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_tM818782145I@
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 2000
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.7:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_tM818782145I@
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+    consumer:
+        group: voice-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+custom:
+    token: "1o62d3YxvdHd4LEUiltnu7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXtp1YuzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d5e2d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
+# token配置
+token:
+    # 令牌自定义标识
+    header: Authorization
+    # 令牌密钥
+    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌有效期(默认30分钟)
+    expireTime: 180
+openIM:
+    secret: openIM123
+    userID: imAdmin
+    url: https://web.im.ysya.top/api
+#是否使用新im
+im:
+    type: OPENIM
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: true
+

+ 19 - 0
fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml

@@ -179,4 +179,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </select>
 
+    <select id="getCurrentDeptIdDownTreeIds" parameterType="Long"  resultType="Long">
+        WITH RECURSIVE cte AS (
+            SELECT
+                *,
+                0 AS level
+            FROM company_dept
+            WHERE dept_id = #{deptId}
+
+            UNION ALL
+
+            SELECT
+                t.*,
+                cte.level + 1 AS level
+            FROM company_dept t
+            INNER JOIN cte
+            ON t.parent_id = cte.dept_id
+        )
+        SELECT dept_id FROM cte ORDER BY level;
+    </select>
 </mapper>

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

@@ -128,16 +128,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and l.create_time &lt;= #{maps.eTime}
             </if>
             <if test= 'maps.scheduleStartTime != null '>
-                and DATE(l.camp_period_time) &gt;= DATE(#{maps.scheduleStartTime})
+                and l.camp_period_time &gt;= #{maps.scheduleStartTime}
             </if>
             <if test='maps.scheduleEndTime != null '>
-                and DATE(l.camp_period_time) &lt;= DATE(#{maps.scheduleEndTime})
+                and l.camp_period_time &lt;= #{maps.scheduleEndTime}
             </if>
             <if test= 'maps.upSTime != null '>
-                and DATE(l.update_time) &gt;= DATE(#{maps.upSTime})
+                and l.update_time &gt;= #{maps.upSTime}
             </if>
             <if test='maps.upETime != null '>
-                and DATE(l.update_time) &lt;= DATE(#{maps.upETime})
+                and l.update_time &lt;= #{maps.upETime}
             </if>
             <if test="maps.sopIds != null and maps.sopIds.size() > 0">
                 and l.sop_id in

+ 7 - 4
fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml

@@ -43,11 +43,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="isPrivate"    column="is_private"    />
         <result property="secondImg"    column="second_img"    />
         <result property="companyIds"    column="company_ids"    />
-
+        <result property="configJson"    column="config_json"    />
     </resultMap>
 
     <sql id="selectFsUserCourseVo">
-        select course_id,is_private,company_ids,is_next,talent_id,second_img,is_del, cate_id,sub_cate_id, course_name, title, img_url, sort, create_time, update_time, status, is_vip, is_hot, is_show, views, duration, description, hot_ranking, integral, price, sell_price, project, tags, likes, favorite_num, shares, is_auto_play, is_fast, is_best, is_tui, hot_num, is_integral, course_type from fs_user_course
+        select course_id,is_private,company_ids,is_next,talent_id,second_img,is_del, cate_id,sub_cate_id, course_name, title, img_url, sort, create_time, update_time, status, is_vip, is_hot, is_show, views, duration, description, hot_ranking, integral, price, sell_price, project, tags, likes, favorite_num, shares, is_auto_play, is_fast, is_best, is_tui, hot_num, is_integral, course_type, config_json from fs_user_course
     </sql>
 
     <select id="selectFsUserCourseList" parameterType="FsUserCourse" resultMap="FsUserCourseResult">
@@ -86,6 +86,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isNext != null "> and is_next = #{isNext}</if>
             <if test="isPrivate != null "> and is_private = #{isPrivate}</if>
             <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="configJson != null "> and config_json = #{configJson}</if>
         </where>
     </select>
 
@@ -159,7 +160,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         inner join fs_user_course_video ucv on ucv.video_id = cwl.video_id
         inner join fs_user_course uc on uc.course_id = ucv.course_id
         where cwl.user_id = #{userId} and uc.project = #{projectId}
-          and cwl.create_time between curdate() and date_add(curdate(), interval 1 day)
+          and cwl.create_time between curdate() and date_add(curdate(), interval 1 day) and cwl.send_type = 1
     </select>
 
 
@@ -204,6 +205,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="secondImg != null">second_img,</if>
             <if test="companyIds != null">company_ids,</if>
             <if test="userId != null">user_id,</if>
+            <if test="configJson != null">config_json,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="cateId != null">#{cateId},</if>
@@ -244,7 +246,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="secondImg != null">#{secondImg},</if>
             <if test="companyIds != null">#{companyIds},</if>
             <if test="userId != null">#{userId},</if>
-         </trim>
+            <if test="configJson != null">config_json = #{configJson},</if>
+        </trim>
     </insert>
 
     <update id="updateFsUserCourse" parameterType="FsUserCourse">

+ 15 - 5
fs-service/src/main/resources/mapper/course/FsUserCourseVideoRedPackageMapper.xml

@@ -63,11 +63,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
-    <delete id="deleteFsUserCourseVideoRedPackageByVedioIds" parameterType="String">
-        delete from fs_user_course_video_red_package where video_id in
-        <foreach item="id" collection="array" open="(" separator="," close=")">
-            #{id}
-        </foreach>
+    <delete id="deleteFsUserCourseVideoRedPackageByVedioIds">
+        DELETE FROM fs_user_course_video_red_package
+        WHERE 1=1
+        <if test="videoIds != null and videoIds.length > 0">
+            AND video_id IN
+            <foreach item="id" collection="videoIds" open="(" separator="," close=")">
+                #{id}
+            </foreach>
+        </if>
+        <if test="periodIds != null and periodIds.length > 0">
+            AND period_id IN
+            <foreach item="id" collection="periodIds" open="(" separator="," close=")">
+                #{id}
+            </foreach>
+        </if>
     </delete>
     <!-- 批量查询匹配的红包数据 -->
     <select id="selectByParamsList" resultMap="FsUserCourseVideoRedPackageResult">

+ 9 - 1
fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml

@@ -25,10 +25,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="createTime"    column="create_time"    />
         <result property="remark"    column="remark"    />
         <result property="barCode"    column="bar_code"    />
+        <result property="loginAccount"    column="login_account"    />
     </resultMap>
 
     <sql id="selectFsIntegralOrderVo">
-        select order_id, order_code, user_id,bar_code, user_name, user_phone, user_address, item_json, integral,pay_money,is_pay,pay_time,pay_type, status, delivery_code, delivery_name, delivery_sn, delivery_time, create_time,qw_user_id,company_user_id,company_id,remark from fs_integral_order
+        select order_id, order_code, user_id,bar_code, user_name, user_phone, user_address, item_json, integral,pay_money,is_pay,pay_time,pay_type, status, delivery_code, delivery_name, delivery_sn, delivery_time, create_time,qw_user_id,company_user_id,company_id,remark,login_account from fs_integral_order
     </sql>
 
     <select id="selectFsIntegralOrderList" parameterType="FsIntegralOrder" resultMap="FsIntegralOrderResult">
@@ -131,6 +132,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="remark != null">remark = #{remark},</if>
             <if test="barCode != null">bar_code = #{barCode},</if>
+            <if test="loginAccount != '' and loginAccount != null">login_account = #{loginAccount},</if>
         </trim>
         where order_id = #{orderId}
     </update>
@@ -149,4 +151,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         update fs_integral_order set status = -1
         where order_id = #{orderId}
     </update>
+    <update id="finishOrder">
+        UPDATE fs_integral_order
+        SET status = 3
+        WHERE order_id = #{orderId}
+          AND status = #{oldStatus}
+    </update>
 </mapper>

+ 5 - 3
fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -203,10 +203,12 @@
                b.filter_mode,
                b.is_samp_send
         from sop_user_logs a
-                 inner join qw_sop b on a.sop_id = b.id
+        inner join qw_sop b on a.sop_id = b.id
+        inner join qw_sop_temp c on b.temp_id = c.id
         where a.start_time &lt;= Now()
-          and a.status = 1
-          and b.send_type != 4 and b.status in (2,3)
+            and a.status = 1
+            and b.send_type != 4 and b.status in (2,3)
+            and c.`status` = 1
         <if test="sopIds != null and !sopIds.isEmpty()">
             and a.sop_id in
             <foreach collection="sopIds" open="(" close=")" index="index" item="item" separator=",">#{item}</foreach>

+ 119 - 0
fs-service/src/main/resources/mapper/tag/FsVideoCourseTagMapper.xml

@@ -0,0 +1,119 @@
+<?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.tag.mapper.FsVideoCourseTagMapper">
+
+    <resultMap type="FsVideoCourseTag" id="FsVideoCourseTagResult">
+        <result property="id"    column="id"    />
+        <result property="corpId"    column="corp_id"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="watchingGroupId"    column="watching_group_id"    />
+        <result property="watchedGroupId"    column="watched_group_id"    />
+        <result property="watchingTgId"    column="watching_tg_id"    />
+        <result property="watchedTgId"    column="watched_tg_id"    />
+        <result property="watchingGroupTgId"    column="watching_group_tag_id"    />
+        <result property="watchedGroupTgId"    column="watched_group_tag_id"    />
+        <result property="watchingTagId"    column="watching_tag_id"    />
+        <result property="watchedTagId"    column="watched_tag_id"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+    </resultMap>
+
+    <sql id="selectFsVideoCourseTagVo">
+        select id, corp_id, video_id, watching_group_id, watched_group_id, watching_tg_id, watched_tg_id, watching_group_tag_id, watched_group_tag_id, watching_tag_id, watched_tag_id, create_time, update_time, create_by, update_by from fs_video_course_tag
+    </sql>
+
+    <select id="selectFsVideoCourseTagList" parameterType="FsVideoCourseTag" resultMap="FsVideoCourseTagResult">
+        <include refid="selectFsVideoCourseTagVo"/>
+        <where>
+            <if test="corpId != null  and corpId != ''"> and corp_id = #{corpId}</if>
+            <if test="videoId != null "> and video_id = #{videoId}</if>
+            <if test="watchingGroupId != null "> and watching_group_id = #{watchingGroupId}</if>
+            <if test="watchedGroupId != null "> and watched_group_id = #{watchedGroupId}</if>
+            <if test="watchingTgId != null "> and watching_tg_id = #{watchingTgId}</if>
+            <if test="watchedTgId != null "> and watched_tg_id = #{watchedTgId}</if>
+            <if test="watchingGroupTgId != null  and watchingGroupTgId != ''"> and watching_group_tag_id = #{watchingGroupTgId}</if>
+            <if test="watchedGroupTgId != null  and watchedGroupTgId != ''"> and watched_group_tag_id = #{watchedGroupTgId}</if>
+            <if test="watchingTagId != null  and watchingTagId != ''"> and watching_tag_id = #{watchingTagId}</if>
+            <if test="watchedTagId != null  and watchedTagId != ''"> and watched_tag_id = #{watchedTagId}</if>
+        </where>
+    </select>
+
+    <select id="selectFsVideoCourseTagById" parameterType="Long" resultMap="FsVideoCourseTagResult">
+        <include refid="selectFsVideoCourseTagVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFsVideoCourseTag" parameterType="FsVideoCourseTag">
+        insert into fs_video_course_tag
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="corpId != null">corp_id,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="watchingGroupId != null">watching_group_id,</if>
+            <if test="watchedGroupId != null">watched_group_id,</if>
+            <if test="watchingTgId != null">watching_tg_id,</if>
+            <if test="watchedTgId != null">watched_tg_id,</if>
+            <if test="watchingGroupTgId != null">watching_group_tag_id,</if>
+            <if test="watchedGroupTgId != null">watched_group_tag_id,</if>
+            <if test="watchingTagId != null">watching_tag_id,</if>
+            <if test="watchedTagId != null">watched_tag_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="corpId != null">#{corpId},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="watchingGroupId != null">#{watchingGroupId},</if>
+            <if test="watchedGroupId != null">#{watchedGroupId},</if>
+            <if test="watchingTgId != null">#{watchingTgId},</if>
+            <if test="watchedTgId != null">#{watchedTgId},</if>
+            <if test="watchingGroupTgId != null">#{watchingGroupTgId},</if>
+            <if test="watchedGroupTgId != null">#{watchedGroupTgId},</if>
+            <if test="watchingTagId != null">#{watchingTagId},</if>
+            <if test="watchedTagId != null">#{watchedTagId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+        </trim>
+    </insert>
+
+    <update id="updateFsVideoCourseTag" parameterType="FsVideoCourseTag">
+        update fs_video_course_tag
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="corpId != null">corp_id = #{corpId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="watchingGroupId != null">watching_group_id = #{watchingGroupId},</if>
+            <if test="watchedGroupId != null">watched_group_id = #{watchedGroupId},</if>
+            <if test="watchingTgId != null">watching_tg_id = #{watchingTgId},</if>
+            <if test="watchedTgId != null">watched_tg_id = #{watchedTgId},</if>
+            <if test="watchingGroupTgId != null">watching_group_tag_id = #{watchingGroupTgId},</if>
+            <if test="watchedGroupTgId != null">watched_group_tag_id = #{watchedGroupTgId},</if>
+            <if test="watchingTagId != null">watching_tag_id = #{watchingTagId},</if>
+            <if test="watchedTagId != null">watched_tag_id = #{watchedTagId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsVideoCourseTagById" parameterType="Long">
+        delete from fs_video_course_tag where id = #{id}
+    </delete>
+
+    <delete id="deleteFsVideoCourseTagByIds" parameterType="String">
+        delete from fs_video_course_tag where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 14 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseTransferController.java

@@ -56,5 +56,19 @@ public class CourseTransferController {
     }
 
 
+    /**
+     * 分公司配置回调地址
+     * @param companyId
+     * @param notifyData
+     * @param request
+     * @param response
+     * @return
+     * @throws Exception
+     */
+    @PostMapping( "/transferNotifyWithCompanyId/{companyId}")
+    public String transferNotifyWithCompanyId(@PathVariable("companyId") Long companyId,@RequestBody String notifyData,HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+        return paymentService.TransferNotifyWithCompanyId(companyId,notifyData,request);
+    }
 
 }