Преглед изворни кода

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

# Conflicts:
#	fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
#	fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
Long пре 1 недеља
родитељ
комит
55437102f1
42 измењених фајлова са 1202 додато и 164 уклоњено
  1. 4 1
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  2. 17 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  3. 19 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  4. 42 33
      fs-admin/src/main/java/com/fs/his/controller/FsUserController.java
  5. 3 1
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  6. 10 10
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  7. 6 2
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  8. 1 1
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  9. 2 1
      fs-qw-task/src/main/java/com/fs/app/taskService/SopLogsTaskService.java
  10. 29 10
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  11. 6 4
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  12. 1 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  13. 4 2
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  14. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  15. 8 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  16. 8 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  17. 108 42
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  18. 5 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  19. 58 0
      fs-service/src/main/java/com/fs/his/dto/FsUserDTO.java
  20. 6 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  21. 16 1
      fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java
  22. 40 26
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  23. 103 4
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  24. 7 5
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  25. 6 4
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  26. 1 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  27. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java
  28. 15 0
      fs-service/src/main/java/com/fs/qw/param/FsUserCourseRedPageParam.java
  29. 11 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  30. 6 3
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  31. 2 2
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java
  32. 5 5
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  33. 1 1
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  34. 104 0
      fs-service/src/main/resources/application-config-dev-czt.yml
  35. 94 0
      fs-service/src/main/resources/application-config-druid-heyantang.yml
  36. 157 0
      fs-service/src/main/resources/application-dev-czt.yml
  37. 159 0
      fs-service/src/main/resources/application-druid-heyantang.yml
  38. 13 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  39. 104 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  40. 4 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml
  41. 14 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java
  42. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

+ 4 - 1
fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java

@@ -48,11 +48,14 @@ public class FsCoursePlaySourceConfigController extends BaseController {
                               @RequestParam(required = false) String appid,
                               @RequestParam(required = false) Integer isMall,
                               @RequestParam(required = false, defaultValue = "1") Integer pageNum,
-                              @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+                              @RequestParam(required = false, defaultValue = "10") Integer pageSize,
+                              @RequestParam(required = false) Long companyId
+    ) {
         Map<String, Object> params = new HashMap<>();
         params.put("name", name);
         params.put("appid", appid);
         params.put("isMall", isMall);
+        params.put("companyId", companyId);
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);

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

@@ -8,10 +8,12 @@ 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.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseListPVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.OptionsVO;
+import com.fs.qw.param.FsUserCourseRedPageParam;
 import com.fs.system.service.ISysConfigService;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -45,6 +47,9 @@ public class FsUserCourseController extends BaseController
     @Autowired
     private IFsUserCourseService fsUserCourseService;
 
+    @Autowired
+    private IFsUserCourseVideoService courseVideoService;
+
     @Autowired
     private RedisCacheUtil redisCacheUtil;
 
@@ -207,6 +212,18 @@ public class FsUserCourseController extends BaseController
         return toAjax(1);
     }
 
+    /**
+     * 统一修改课程红包
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourse:editRedPage')")
+    @Log(title = "修改课程红包", businessType = BusinessType.UPDATE)
+    @PostMapping("/editRedPage")
+    public AjaxResult editRedPage(@RequestBody FsUserCourseRedPageParam redPageParam)
+    {
+        courseVideoService.updateFsUserCourseRedPage(redPageParam);
+        return toAjax(1);
+    }
+
     /**
      * 修改课程
      */

+ 19 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -23,6 +23,7 @@ import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseVideoChooseVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.vo.OptionsVO;
+import com.fs.qw.vo.SortDayVo;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -176,6 +177,24 @@ public class FsUserCourseVideoController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/getVideoListByCourseIdAll")
+    public TableDataInfo getVideoListByCourseIdAll(Long courseId)
+    {
+
+        FsUserCourseVideo courseVideo=new FsUserCourseVideo();
+        courseVideo.setCourseId(courseId);
+        courseVideo.setIsDel(0);
+        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoList(courseVideo);
+        return getDataTable(list);
+    }
+
+    @PostMapping("/sortCourseVideo")
+    public AjaxResult sortCourseVideo(@RequestBody List<FsUserCourseVideo> list){
+        fsUserCourseVideoService.sortCourseVideo(list);
+        return toAjax(1);
+    }
+
+
     @GetMapping("/getSort/{courseId}")
     public R remove(@PathVariable("courseId") Long courseId)
     {

+ 42 - 33
fs-admin/src/main/java/com/fs/his/controller/FsUserController.java

@@ -18,6 +18,7 @@ import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.his.domain.FsUserAddress;
+import com.fs.his.dto.FsUserDTO;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsUserAddIntegralTemplateParam;
@@ -121,6 +122,47 @@ public class FsUserController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 导出用户列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:user:export')")
+    @Log(title = "用户", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserParam fsUser)
+    {
+        logger.info("导出用户列表:"+ SecurityUtils.getUserId());
+        if (fsUserService.isEntityNull(fsUser)){
+            return AjaxResult.error("请筛选数据导出");
+        }
+        Long count = fsUserService.selectFsUserExportListVOCount(fsUser);
+        if (count>10000){
+            return AjaxResult.error("导出数据不可超过1w条");
+        }
+        List<FsUserVO> list = fsUserService.selectFsUserListVO(fsUser);
+        SysRole sysRole = isCheckPermission();
+        List<FsUserDTO> listDTO = Lists.newArrayList();
+        for (FsUserVO fsUserVO : list) {
+            if(fsUserVO.getPhone() != null&&fsUserVO.getPhone()!=""){
+                if (!(sysRole.getIsCheckPhone()==1)){
+                    if (fsUserVO.getPhone().length()>11){
+                        fsUserVO.setPhone(decryptPhoneMk(fsUserVO.getPhone()));
+                    }else {
+                        fsUserVO.setPhone(fsUserVO.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                    }
+                } else {
+                    if (fsUserVO.getPhone().length()>11) {
+                        fsUserVO.setPhone(decryptPhone(fsUserVO.getPhone()));
+                    }
+                }
+            }
+            FsUserDTO fsUserDTO = new FsUserDTO();
+            BeanUtils.copyProperties(fsUserVO,fsUserDTO);
+            listDTO.add(fsUserDTO);
+        }
+        ExcelUtil<FsUserDTO> util = new ExcelUtil<FsUserDTO>(FsUserDTO.class);
+        return util.exportExcel(listDTO, "用户数据");
+    }
+
     @Autowired
     private ISysRoleService sysRoleService;
     private SysRole isCheckPermission() {
@@ -196,37 +238,6 @@ public class FsUserController extends BaseController
         return util.exportExcel(list, "项目会员数据");
     }
 
-    /**
-     * 导出用户列表
-     */
-    @PreAuthorize("@ss.hasPermi('his:user:export')")
-    @Log(title = "用户", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsUserParam fsUser)
-    {
-        logger.info("导出用户列表:"+ SecurityUtils.getUserId());
-        if (fsUserService.isEntityNull(fsUser)){
-            return AjaxResult.error("请筛选数据导出");
-        }
-        Long count = fsUserService.selectFsUserExportListVOCount(fsUser);
-        if (count>10000){
-            return AjaxResult.error("导出数据不可超过1w条");
-        }
-        List<FsUserExportListVO> list = fsUserService.selectFsUserExportListVO(fsUser);
-        SysRole sysRole = isCheckPermission();
-        for (FsUserExportListVO vo : list) {
-            if (vo.getMobile()!=null && !(sysRole.getIsCheckPhone()==1)){
-                vo.setMobile(vo.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
-            } else {
-                if (vo.getMobile().length()>11){
-                    vo.setMobile(decryptPhone(vo.getMobile()));
-                }
-            }
-        }
-        ExcelUtil<FsUserExportListVO> util = new ExcelUtil<FsUserExportListVO>(FsUserExportListVO.class);
-        return util.exportExcel(list, "用户数据");
-    }
-
     /**
      * 获取用户详细信息
      */
@@ -238,8 +249,6 @@ public class FsUserController extends BaseController
         return AjaxResult.success(fsUser);
     }
 
-
-
     @GetMapping(value = "/getUserAddr/{userId}")
     public AjaxResult getUserAddr(@PathVariable("userId") Long userId)
     {

+ 3 - 1
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -225,8 +225,10 @@ public class FsUserCourseVideoController extends AppBaseController {
     public ResponseResult<PageInfo<FsUserCourseVideoPageListVO>> todayCourseList(@RequestParam(defaultValue = "1") Integer pageNum,
                                                                                  @RequestParam(defaultValue = "10") Integer pageSize, String keyword) {
         Long companyId = getCompanyId();
+        log.info("销售小程序-今日课程,获取公司id:{}", companyId);
         if (Objects.isNull(companyId)) {
-            ResponseResult.fail(400, "未获取到公司ID,请重新登录后再试");
+            log.error("未获取到公司id,进入提示逻辑。。。。。。。。");
+            return ResponseResult.fail(400, "未获取到公司ID,请重新登录后再试");
         }
 
         Map<String, Object> params = new HashMap<>();

+ 10 - 10
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -264,16 +264,16 @@ public class SendMsg {
                 }
             }
             // 推送 APP
-//            if (!setting.getSetting().isEmpty()) {
-//                new Thread(() -> {
-//                    try {
-//                        List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
-//                        asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(settings, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId());
-//                    } catch (Exception e) {
-//                        log.error("推送APP失败", e);
-//                    }
-//                }).start();
-//            }
+            if (!setting.getSetting().isEmpty()) {
+                new Thread(() -> {
+                    try {
+                        List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
+                        asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(settings, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId());
+                    } catch (Exception e) {
+                        log.error("推送APP失败", e);
+                    }
+                }).start();
+            }
             qwSopLogs.setSend(true);
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             QwSopLogs updateQwSop = new QwSopLogs();

+ 6 - 2
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -259,7 +259,7 @@ public class CommonController {
     }
 
     @GetMapping("/test")
-    public R test(String time) throws Exception {
+    public R test(String time, String sopId) throws Exception {
         log.info("进入sop任务");
 //        LocalDateTime currentTime = DateUtil.parseLocalDateTime(time);
 //        // 计算下一个整点时间
@@ -268,7 +268,11 @@ public class CommonController {
 //        // 打印日志,确认时间
 //        log.info("任务实际执行时间: {}", currentTime);
 //        log.info("传递给任务的时间参数: {}", nextHourTime);
-        sopLogsTaskService.selectSopUserLogsListByTime(DateUtil.parseLocalDateTime(time));
+        List<String> sopidList = new ArrayList<>();
+        if(StringUtils.isNotEmpty(sopId)){
+            sopidList = Arrays.asList(sopId.split(","));
+        }
+        sopLogsTaskService.selectSopUserLogsListByTime(DateUtil.parseLocalDateTime(time), sopidList);
         return R.ok();
     }
     @GetMapping("/testWx")

+ 1 - 1
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -117,7 +117,7 @@ public class qwTask {
         log.info("任务实际执行时间: {}", currentTime);
 
         // 调用服务方法处理SOP用户日志
-        sopLogsTaskService.selectSopUserLogsListByTime(currentTime);
+        sopLogsTaskService.selectSopUserLogsListByTime(currentTime, null);
     }
 
     /**

+ 2 - 1
fs-qw-task/src/main/java/com/fs/app/taskService/SopLogsTaskService.java

@@ -1,10 +1,11 @@
 package com.fs.app.taskService;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 public interface SopLogsTaskService {
 
-    public void selectSopUserLogsListByTime(LocalDateTime currentTime) throws Exception;
+    public void selectSopUserLogsListByTime(LocalDateTime currentTime, List<String> sopidList) throws Exception;
 
 
     /**

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

@@ -5,8 +5,6 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.taskService.SopLogsTaskService;
-import com.fs.common.core.domain.R;
-import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.Company;
@@ -19,7 +17,6 @@ import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
-import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCompanyBindService;
 import com.fs.qw.domain.*;
@@ -286,7 +283,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
     @Override
-    public void selectSopUserLogsListByTime(LocalDateTime currentTime) throws Exception {
+    public void selectSopUserLogsListByTime(LocalDateTime currentTime, List<String> sopidList) throws Exception {
         long startTimeMillis = System.currentTimeMillis();
         log.info("====== 开始选择和处理 SOP 用户日志 ======");
 
@@ -296,7 +293,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             config = cachedCourseConfig;
         }
 
-        List<SopUserLogsVo> sopUserLogsVos = sopUserLogsMapper.selectSopUserLogsListByTime();
+        List<SopUserLogsVo> sopUserLogsVos = sopUserLogsMapper.selectSopUserLogsListByTime(sopidList);
         if (sopUserLogsVos.isEmpty()) {
             log.info("没有需要处理的 SOP 用户日志。");
             return;
@@ -1002,8 +999,22 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     break;
                 //小程序单独
                 case "4":
-
-                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                    if (isGroupChat) {
+                        try {
+                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
+                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
+                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
+                                if (vo != null && vo.getId() != null) {
+                                    sopLogs.setFsUserId(vo.getFsUserId());
+                                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo);
+                                }
+                            });
+                        } catch (Exception e) {
+                            log.error("群聊创建看课记录失败!", e);
+                        }
+                    } else {
+                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                    }
 
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                             qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId(), isGroupChat ? groupChat.getChatId() : null);
@@ -1075,6 +1086,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             }
 
         }
+        clonedContent.getSetting().stream().filter(e -> "1".equals(e.getIsBindUrl())).forEach(e -> {
+            e.setIsBindUrl("0");
+            e.setLinkDescribe(null);
+            e.setLinkUrl(null);
+            e.setLinkImageUrl(null);
+        });
         sopLogs.setContentJson(JSON.toJSONString(clonedContent));
         enqueueQwSopLogs(sopLogs);
     }
@@ -1364,10 +1381,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         link.setCompanyId(Long.parseLong(companyId));
         link.setQwUserId(Long.parseLong(qwUserId));
         link.setCompanyUserId(Long.parseLong(companyUserId));
-        link.setVideoId(videoId.longValue());
+        link.setVideoId(videoId);
         link.setCorpId(logVo.getCorpId());
-        link.setCourseId(courseId.longValue());
-        link.setQwExternalId(Long.parseLong(externalId));
+        link.setCourseId(courseId);
+        if(StringUtils.isEmpty(chatId)){
+            link.setQwExternalId(Long.parseLong(externalId));
+        }
         link.setProjectCode(cloudHostProper.getProjectCode());
         link.setChatId(chatId);
 

+ 6 - 4
fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java

@@ -84,11 +84,13 @@ public interface CompanyRechargeMapper
             "<if test = 'maps.payType != null  '> " +
             "and r.pay_type = #{maps.payType}" +
             "</if>" +
-            "<if test = 'maps.beginTime != null and maps.beginTime != \"\" '> " +
-            "and date_format(r.pay_time,'%y%m%d') &gt;= date_format(#{maps.beginTime},'%y%m%d') " +
+            "<if test= 'maps.params != null and maps.params !=\"\"'>"+
+            "<if test = 'maps.params.beginTime != null and maps.params.beginTime != \"\" '> " +
+            "and date_format(r.pay_time,'%y%m%d') &gt;= date_format(#{maps.params.beginTime},'%y%m%d') " +
             "</if>" +
-            "<if test = 'maps.endTime != null and maps.endTime != \"\" '> " +
-            "and date_format(r.pay_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
+            "<if test = 'maps.params.endTime != null and maps.params.endTime != \"\" '> " +
+            "and date_format(r.pay_time,'%y%m%d') &lt;= date_format(#{maps.params.endTime},'%y%m%d') " +
+            "</if>"+
             "</if>" +
             "order by r.recharge_id desc " +
             "</script>"})

+ 1 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -16,6 +16,7 @@ public class CourseConfig implements Serializable {
     private Integer maxBufferLength;//最大缓冲时长
     private Integer videoIntegral;//每十分钟获取多少积分
     private Integer answerIntegral;//答题获得积分
+    private Integer appAnswerIntegral; //app答题积分
     private Integer defaultLine;//默认看课线路
     private String realLinkDomainName;//真链域名
     private String authDomainName;//网页授权域名

+ 4 - 2
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -246,12 +246,14 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             " ,count(o.log_id) send_number" +
             " ,sum(if((o.user_id is not null or o.user_id>0) and o.log_type=3,1,0)) is_user_wait_number" +
             " ,sum(if((o.user_id is null or o.user_id=0) and o.log_type=3,1,0)) no_user_wait_number" +
-            " ,sum(ifnull(fcr.amount,0)) red_amount" +
+//            " ,sum(ifnull(fcr.amount,0)) red_amount" +
+            ",(SELECT SUM(amount) FROM fs_course_red_packet_log \n" +
+            "     WHERE user_id = o.user_id AND video_id = o.video_id) as red_amount " +
             "</if> " +
             "FROM fs_course_watch_log o " +
             "<if test= 'sendType != 1 '> " +
             " LEFT JOIN qw_user qu on qu.id=o.qw_user_id " +
-            " LEFT JOIN fs_course_red_packet_log fcr on o.user_id = fcr.user_id and fcr.video_id = o.video_id" +
+//            " LEFT JOIN fs_course_red_packet_log fcr on o.user_id = fcr.user_id and fcr.video_id = o.video_id" + //会有笛卡尔积问题
             "</if>\n" +
             "LEFT JOIN fs_user_course_video v on v.video_id=o.video_id \n" +
             "LEFT JOIN fs_user_course uc on uc.course_id=v.course_id\n" +

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

@@ -120,6 +120,6 @@ public interface FsUserCoursePeriodDaysMapper extends BaseMapper<FsUserCoursePer
 
     Long selectFsUserCoursePeriodDaysCount(FsUserCoursePeriodDays fsUserCoursePeriodDays);
 
-    @Select("SELECT distinct period_id from fs_user_course_period_days  where start_date_time >=#{periodSTime} and end_date_time <=#{periodETime} ")
+    @Select("SELECT distinct period_id from fs_user_course_period_days  where day_date >=#{periodSTime} and day_date <=#{periodETime} ")
     List<Long> selectFsUserCoursePeriodDaysByTime(@Param("periodSTime") String periodSTime,@Param("periodETime") String periodETime);
 }

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

@@ -9,6 +9,7 @@ import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.his.vo.OptionsVO;
+import com.fs.qw.param.FsUserCourseRedPageParam;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -59,6 +60,13 @@ public interface FsUserCourseVideoMapper
      */
     public int updateFsUserCourseVideo(FsUserCourseVideo fsUserCourseVideo);
 
+    @Update("<script> " +
+            "update fs_user_course_video set red_packet_money=#{data.redPacketMoney} where course_id=#{data.courseId} " +
+            "</script>")
+    public int updateFsUserCourseRedPage(@Param("data") FsUserCourseRedPageParam courseRedPageParam);
+
+    int batchUpdateByVideoId(@Param("list") List<Map<String, Object>> list);
+
     /**
      * 删除课堂视频
      *

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

@@ -1,5 +1,6 @@
 package com.fs.course.service;
 
+import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.course.domain.FsUserCourseVideo;
@@ -15,6 +16,8 @@ import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.course.vo.newfs.FsUserVideoListVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.OptionsVO;
+import com.fs.qw.param.FsUserCourseRedPageParam;
+import com.fs.sop.domain.QwSopTempDay;
 
 import java.util.List;
 import java.util.Map;
@@ -59,6 +62,9 @@ public interface IFsUserCourseVideoService
      */
     public int updateFsUserCourseVideo(FsUserCourseVideo fsUserCourseVideo);
 
+    public int updateFsUserCourseRedPage(FsUserCourseRedPageParam userCourseRedPageParam);
+    public void sortCourseVideo(List<FsUserCourseVideo> list);
+
     /**
      * 批量删除课堂视频
      *
@@ -203,6 +209,8 @@ public interface IFsUserCourseVideoService
      */
     List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params);
 
+    R sendAppReward(FsCourseSendRewardUParam param);
+
     /**
      * 企微聊天创建小程序链接
      */

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

@@ -15,8 +15,10 @@ import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.enums.BizResponseEnum;
 import com.fs.common.exception.CustomException;
+import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.company.constant.CompanyTrafficConstants;
@@ -46,6 +48,7 @@ import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.service.IFsUserWxService;
 import com.fs.his.utils.ConfigUtil;
@@ -60,12 +63,15 @@ import com.fs.qw.mapper.QwGroupChatMapper;
 import com.fs.qw.mapper.QwGroupChatUserMapper;
 import com.fs.qw.mapper.QwSessionMapper;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.param.FsUserCourseRedPageParam;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.vo.SortDayVo;
 import com.fs.qwApi.Result.QwAddContactWayResult;
 import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.sop.domain.QwSopTempDay;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
@@ -255,6 +261,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private BalanceRollbackErrorMapper balanceRollbackErrorMapper;
 
+    @Autowired
+    private IFsUserIntegralLogsService iFsUserIntegralLogsService;
+
 
 
 
@@ -316,6 +325,31 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return fsUserCourseVideoMapper.updateFsUserCourseVideo(fsUserCourseVideo);
     }
 
+    @Override
+    public int updateFsUserCourseRedPage(FsUserCourseRedPageParam userCourseRedPageParam) {
+
+        return fsUserCourseVideoMapper.updateFsUserCourseRedPage(userCourseRedPageParam);
+    }
+
+    @Override
+    public void sortCourseVideo(List<FsUserCourseVideo> list) {
+        if (list.isEmpty()){
+            return;
+        }
+        // 直接构建更新参数
+        List<Map<String, Object>> updateParams = list.stream()
+                .map(item -> {
+                    Map<String, Object> param = new HashMap<>();
+                    param.put("videoId", item.getVideoId());
+                    param.put("courseSort", item.getCourseSort());
+                    return param;
+                })
+                .collect(Collectors.toList());
+
+        // 批量更新
+        fsUserCourseVideoMapper.batchUpdateByVideoId(updateParams);
+    }
+
     /**
      * 批量删除课堂视频
      *
@@ -1230,7 +1264,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         try {
             // 尝试获取锁,等待时间5秒,锁过期时间30秒
-            boolean isLocked = lock.tryLock(5, 60, TimeUnit.SECONDS);
+            boolean isLocked = lock.tryLock(5, 300, TimeUnit.SECONDS);
             if (!isLocked) {
                 logger.warn("获取锁失败,用户ID:{},视频ID:{}", param.getUserId(), param.getVideoId());
                 return R.error("操作频繁,请稍后再试!");
@@ -1355,45 +1389,19 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         // 准备发送红包参数
         WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
 
+        //判断是否走服务号openId发红包
         if (user.getMpOpenId()!=null&&!isNewWxMerchant){
             packetParam.setOpenId(user.getMpOpenId());
         }else {
-            //修复数据
+            //查询是否绑定小程序
             FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
             if (fsUserWx ==null){
-                if (user.getCourseMaOpenId()==null){
-                    logger.error(" 【转账openId参数错误】:{}", user.getUserId());
-                    return R.error("openId参数错误,请清理缓存后重新授权!");
-                }
-                packetParam.setOpenId(user.getCourseMaOpenId());
-                try {
-                    handleFsUserWx(user,param.getAppId());
-                }catch (Exception e){
-                    logger.error(" 【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId(),e);
-                }
-
+                return R.error("openId参数错误,请清理缓存重新授权");
             }else {
                 packetParam.setOpenId(fsUserWx.getOpenId());
             }
         }
 
-//        packetParam.setOpenId(user.getMpOpenId());
-//        // 来源是小程序切换openId
-//        if (param.getSource() == 2) {
-//            //处理多小程序问题
-//            FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
-//            if (fsUserWx ==null){
-//                try {
-//                    handleFsUserWx(user,param.getAppId());
-//                }catch (Exception e){
-//                    logger.error("【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId());
-//                }
-//            }else {
-//                packetParam.setOpenId(fsUserWx.getOpenId());
-//            }
-//            //查出公司绑定openid并赋值
-//        }
-
         //判断服务号配置是否存在
         if (StringUtils.isNotEmpty(config.getMpAppId())){
             packetParam.setMpAppId(config.getMpAppId());
@@ -1541,20 +1549,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             if (user.getMpOpenId()!=null&&!isNewWxMerchant){
                 packetParam.setOpenId(user.getMpOpenId());
             }else {
-                //修复数据
+                //查询是否绑定小程序
                 FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
                 if (fsUserWx ==null){
-                    if (user.getCourseMaOpenId()==null){
-                        logger.error(" 【转账openId参数错误】:{}", user.getUserId());
-                        return R.error("openId参数错误,请清理缓存后重新授权!");
-                    }
-                    packetParam.setOpenId(user.getCourseMaOpenId());
-                    try {
-                        handleFsUserWx(user,param.getAppId());
-                    }catch (Exception e){
-                        logger.error(" 【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId(),e);
-                    }
-
+                    return R.error("openId参数错误,请清理缓存重新授权");
                 }else {
                     packetParam.setOpenId(fsUserWx.getOpenId());
                 }
@@ -3221,6 +3219,74 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return fsUserCourseVideoMapper.getChooseCourseVideoListByMap(params);
     }
 
+    @Override
+    public R sendAppReward(FsCourseSendRewardUParam param) {
+        // 获取用户信息
+        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        if (user==null){
+            return R.error("会员被停用,无权限,请联系客服!");
+        }
+        FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideo(param.getUserId(), param.getVideoId(), param.getQwUserId(), param.getQwExternalId());
+        if (log == null) {
+            return R.error("无记录");
+        }
+        if (log.getRewardType() != null) {
+            return R.error("奖励已发放");
+        }
+
+        // 获取配置信息
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        // 更新用户积分
+        FsUser userMap=new FsUser();
+        userMap.setUserId(user.getUserId());
+        Integer appAnswerIntegral = config.getAppAnswerIntegral();
+        if (appAnswerIntegral == null ){
+            appAnswerIntegral = config.getAnswerIntegral();
+        }
+        userMap.setIntegral(user.getIntegral()+(appAnswerIntegral==null?0:appAnswerIntegral));
+        fsUserMapper.updateFsUser(userMap);
+        CompletableFuture.runAsync(() -> {
+            FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+            integralLogs.setIntegral(config.getAppAnswerIntegral().longValue());
+            integralLogs.setUserId(user.getUserId());
+            integralLogs.setBalance(userMap.getIntegral());
+            integralLogs.setLogType(17);
+            integralLogs.setBusinessId(StringUtils.isNotEmpty(log.getLogId().toString()) ? log.getLogId().toString() : null);
+            integralLogs.setCreateTime(new Date());
+//            integralLogs.setNickName(user.getNickName());
+//            integralLogs.setPhone(user.getPhone());
+            //integralLogs.setId(integralLogsService.getFsUserIntegralLogsInsertId());
+//        fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
+//            iFsUserIntegralLogsService.insertFsUserIntegralLogsMySql(integralLogs);
+            iFsUserIntegralLogsService.insertFsUserIntegralLogs(integralLogs);
+            //asyncAddIntegralLogs.saveLogAsync(integralLogs);
+
+            // 更新观看记录的奖励类型
+            log.setRewardType(2);
+            courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+
+            //转换红包
+            FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+            redPacketLog.setCourseId(param.getCourseId());
+            redPacketLog.setOutBatchNo(integralLogs.getId().toString());
+            redPacketLog.setCompanyId(param.getCompanyId());
+            redPacketLog.setUserId(param.getUserId());
+            redPacketLog.setVideoId(param.getVideoId());
+            redPacketLog.setStatus(1);
+            redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+            redPacketLog.setCompanyUserId(param.getCompanyUserId());
+            redPacketLog.setCreateTime(new Date());
+            redPacketLog.setAmount(BigDecimal.valueOf(config.getAppAnswerIntegral()).divide(BigDecimal.valueOf(1000)));
+            redPacketLog.setRemark("点播答题领取积分转");
+            redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+            redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+        });
+        return R.ok("奖励发放成功");
+    }
+
     /**
      * 企微聊天创建小程序链接
      */

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

@@ -122,6 +122,11 @@ public class FsUser extends BaseEntity
     private Date  vipStartDate;
     private Date  vipEndDate;
     private Integer vipLevel;
+
+    /**
+     * 会员等级
+     */
+    private Integer level;
     private Integer vipStatus;
 
     private Integer sex;

+ 58 - 0
fs-service/src/main/java/com/fs/his/dto/FsUserDTO.java

@@ -0,0 +1,58 @@
+package com.fs.his.dto;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/29 上午9:40
+ */
+@Data
+public class FsUserDTO {
+
+    @Excel(name = "用户ID")
+    private Long userId;
+
+    @Excel(name = "用户昵称")
+    private String nickName;
+
+    @Excel(name = "手机号码")
+    private String phone;
+
+    @Excel(name = "用户积分")
+    private Long integral;
+
+    @Excel(name = "用户状态:1为正常,0为禁止")
+    private Integer status;
+
+    @Excel(name = "用户备注")
+    private String remark;
+
+    @Excel(name = "上级昵称")
+    private String tuiName;
+
+    @Excel(name = "app来源")
+    private String source;
+
+    @Excel(name = "登陆设备")
+    private String loginDevice;
+
+    @Excel(name = "上级手机号码")
+    private String tuiPhone;
+
+    @Excel(name = "下级人数")
+    private Integer tuiUserCount;
+
+    @Excel(name = "最后一次登录ip")
+    private String lastIp;
+
+    @Excel(name = "余额")
+    private BigDecimal balance;
+
+    @Excel(name = "会员注册时间", dateFormat = "yyyy-MM-dd HH:mm:ss" , sort = 6)
+    private Date createTime;
+}

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

@@ -219,6 +219,12 @@ public interface FsStoreOrderMapper
             "            <if test=\"maps.packageSecondName != null and maps.packageSecondName != '' \"> and so.package_second_name like concat('%', #{maps.packageSecondName}, '%')</if>"+
             "            <if test=\"maps.storeId != null \"> and so.store_id = #{maps.storeId}</if>\n" +
             "            <if test=\"maps.orderCode != null  and maps.orderCode != ''\"> and so.order_code = #{maps.orderCode}</if>\n" +
+            "            <if test=\"maps.orderCodes != null  and maps.orderCodes.size > 0\">\n" +
+            "                and so.order_code in\n" +
+            "                <foreach collection=\"maps.orderCodes\" item=\"orderCode\" open=\"(\" close=\")\" separator=\",\">\n" +
+            "                    #{orderCode}\n" +
+            "                </foreach>\n" +
+            "            </if>" +
             "            <if test=\"maps.prescribeCode != null  and maps.prescribeCode != ''\"> and p.prescribe_code = #{maps.prescribeCode}</if>\n" +
             "            <if test=\"maps.userName != null  and maps.userName != ''\"> and so.user_name like concat('%', #{maps.userName}, '%')</if>\n" +
             "            <if test=\"maps.userPhone != null  and maps.userPhone != ''\"> and so.user_phone = #{maps.userPhone}</if>\n" +

+ 16 - 1
fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java

@@ -181,7 +181,22 @@ public interface FsStorePaymentMapper
     @Select("select * from fs_store_payment where business_type=#{type} and  business_id=#{businessId} and (status=1 or starus=-1)")
     List<FsStorePayment> selectFsStorePaymentByPayOrRefund(int i, Long orderId);
     @Select({"<script> " +
-            " SELECT CONCAT('package-', sp.pay_code) AS pay_code,sp.*,u.nick_name,u.phone,s.store_name,c.company_name,cu.nick_name as companyUserName,fso.delivery_name,fso.package_name,fso.package_second_name ,csc.name miniProgramName " +
+            " SELECT " +
+            " CASE " +
+            "   WHEN sp.status = -1 THEN CONCAT('refund-', sp.pay_code) " +
+            "   ELSE " +
+            "     CASE " +
+            "       WHEN sp.business_type = 1 THEN CONCAT('inquiry-', sp.pay_code) " +
+            "       WHEN sp.business_type = 2 THEN CONCAT('store-', sp.pay_code) " +
+            "       WHEN sp.business_type = 3 THEN CONCAT('package-', sp.pay_code) " +
+            "       WHEN sp.business_type = 4 THEN CONCAT('course-', sp.pay_code) " +
+            "       WHEN sp.business_type = 5 THEN CONCAT('appvip-', sp.pay_code) " +
+            "       WHEN sp.business_type = 6 THEN CONCAT('integral-', sp.pay_code) " +
+            "       WHEN sp.business_type = 7 THEN CONCAT('payment-', sp.pay_code) " +
+            "       ELSE sp.pay_code " +
+            "     END " +
+            " END AS pay_code, " +
+            "sp.*,u.nick_name,u.phone,s.store_name,c.company_name,cu.nick_name as companyUserName,fso.delivery_name,fso.package_name,fso.package_second_name ,csc.name miniProgramName " +
             " FROM fs_store_payment sp " +
             " LEFT JOIN  fs_user u ON u.user_id=sp.user_id " +
             " LEFT JOIN fs_store s ON s.store_id=sp.store_id " +

+ 40 - 26
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -105,6 +105,32 @@ public interface FsUserMapper
     })
     List<FsUserVO> selectFsUserListVO(FsUserParam fsUser);
 
+    @Select({"<script> " +
+            "SELECT  \n" +
+            "    fp.create_time ,\n" +
+            "    cu.nick_name ,\n" +
+            "    fu.user_id ,\n" +
+            "    fp.patient_name ,\n" +
+            "    fp.mobile ,\n" +
+            "    fu.is_buy \n" +
+            "FROM fs_user fu\n" +
+            "LEFT JOIN company_user_user cuu ON fu.user_id = cuu.user_id\n" +
+            "LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id\n" +
+            "LEFT JOIN fs_patient fp ON fu.user_id = fp.user_id\n" +
+            "WHERE fp.create_time IS NOT NULL and fu.is_del = 0 \n" +
+            "  <if test=\"companyUserId != null \"> and cuu.company_user_id=#{companyUserId} </if>\n" +
+            "  <if test=\"companyId != null \"> and cuu.company_id=#{companyId} </if>\n" +
+            "  <if test=\"nickName != null  and nickName != ''\"> and fu.nick_name like concat( #{nickName}, '%')</if>\n" +
+            "   <if test=\"phone != null  and phone != ''\"> and fu.phone like concat( #{phone}, '%')</if>\n" +
+            "   <if test=\"status != null \"> and fu.status = #{status}</if>\n" +
+            "            <if test=\"isBuy != null \"> and fu.is_buy = #{isBuy}</if>\n" +
+            "            <if test=\"userId != null \"> and fu.user_id = #{userId}</if>\n" +
+            "            <if test=\"sTime != null \">  and DATE(fu.create_time) &gt;= DATE(#{sTime})</if>\n" +
+            "            <if test=\"eTime != null \">  and DATE(fu.create_time) &lt;= DATE(#{eTime})</if>\n" +
+            "ORDER BY fu.user_id DESC "+
+            "</script>"})
+    List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser);
+
     @Update("update fs_user set is_del=1 where user_id=#{userId}")
     int updateFsUserByUserId(Long userId);
 
@@ -144,32 +170,6 @@ public interface FsUserMapper
             "</script>"})
     List<FsUserVO> selectFsUserListVOByComponentsUser(FsUserParam fsUser);
 
-    @Select({"<script> " +
-            "SELECT  \n" +
-            "    fp.create_time ,\n" +
-            "    cu.nick_name ,\n" +
-            "    fu.user_id ,\n" +
-            "    fp.patient_name ,\n" +
-            "    fp.mobile ,\n" +
-            "    fu.is_buy \n" +
-            "FROM fs_user fu\n" +
-            "LEFT JOIN company_user_user cuu ON fu.user_id = cuu.user_id\n" +
-            "LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id\n" +
-            "LEFT JOIN fs_patient fp ON fu.user_id = fp.user_id\n" +
-            "WHERE fp.create_time IS NOT NULL and fu.is_del = 0 \n" +
-            "  <if test=\"companyUserId != null \"> and cuu.company_user_id=#{companyUserId} </if>\n" +
-            "  <if test=\"companyId != null \"> and cuu.company_id=#{companyId} </if>\n" +
-            "  <if test=\"nickName != null  and nickName != ''\"> and fu.nick_name like concat( #{nickName}, '%')</if>\n" +
-            "   <if test=\"phone != null  and phone != ''\"> and fu.phone like concat( #{phone}, '%')</if>\n" +
-            "   <if test=\"status != null \"> and fu.status = #{status}</if>\n" +
-            "            <if test=\"isBuy != null \"> and fu.is_buy = #{isBuy}</if>\n" +
-            "            <if test=\"userId != null \"> and fu.user_id = #{userId}</if>\n" +
-            "            <if test=\"sTime != null \">  and DATE(fu.create_time) &gt;= DATE(#{sTime})</if>\n" +
-            "            <if test=\"eTime != null \">  and DATE(fu.create_time) &lt;= DATE(#{eTime})</if>\n" +
-            "ORDER BY fu.user_id DESC "+
-            "</script>"})
-    List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser);
-
     @Select("SELECT user_id\n" +
             "FROM fs_user_integral_logs\n" +
             "WHERE business_type = 2\n" +
@@ -410,6 +410,20 @@ public interface FsUserMapper
     Map<String, Object> countUserStats(
             UserStatisticsCommonParam param);
 
+    /**
+     * 查询观看和完成人数
+     */
+    Map<String, Object> countUserWatchStats(UserStatisticsCommonParam param);
+
+    /**
+     * 统计用户答题数据(答题次数、正确次数)
+     */
+    Map<String, Object> countUserAnswerStats(UserStatisticsCommonParam param);
+
+
+    /**查询红包数量和红包金额 */
+    Map<String, Object> countUserRedPacketStats(UserStatisticsCommonParam param);
+
     /**
      * 统计用户领取红包数据(红包个数、红包金额)
      * */

+ 103 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -271,11 +271,26 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.updateFsUserByUserId(userId);
     }
 
+    /**
+     * 列表查询
+     * @param fsUser
+     * @return
+     */
     @Override
     public List<FsUserVO> selectFsUserListVO(FsUserParam fsUser) {
         return fsUserMapper.selectFsUserListVO(fsUser);
     }
 
+    /**
+     * 导出用户列表
+     * @param fsUser
+     * @return
+     */
+    @Override
+    public List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser) {
+        return fsUserMapper.selectFsUserExportListVO(fsUser);
+    }
+
     @Override
     public FsUser selectFsUserByOpenId(String openId) {
         return fsUserMapper.selectFsUserByOpenId(openId);
@@ -313,10 +328,6 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.selectFsUserListVOByComponentsUser(fsUser);
     }
 
-    @Override
-    public List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser) {
-        return fsUserMapper.selectFsUserExportListVO(fsUser);
-    }
     @Autowired
     private FsUserIntegralLogsMapper integralLogsMapper;
 
@@ -1190,7 +1201,95 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.selectFsUserListByJointUserNameKey(userNameKey);
     }
 
+
+    /**
+     * @Description: 该方法原方法是 getUserStatisticsOld
+     * 目的为了优化sql查询 fsUserMapper.countUserStats 现拆分成三次异步查询
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/29 15:34
+     */
     private FsUserStatisticsVO getUserStatistics(UserStatisticsCommonParam param) {
+
+        FsUserStatisticsVO fsUserStatisticsVO = new FsUserStatisticsVO();
+
+        // 判断是否是管理员
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserById(param.getUserId());
+        if (companyUser != null && companyUser.isAdmin()){
+            param.setUserId(0L);
+            param.setCompanyId(companyUser.getCompanyId());
+        }
+
+        // 异步调用三个查询方法
+        CompletableFuture<Map<String, Object>> watchStatsFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.countUserWatchStats(param));
+
+        CompletableFuture<Map<String, Object>> answerStatsFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.countUserAnswerStats(param));
+
+        CompletableFuture<Map<String, Object>> redPacketStatsFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.countUserRedPacketStats(param));
+
+        try {
+            // 等待所有查询完成
+            Map<String, Object> watchMap = watchStatsFuture.get();
+            Map<String, Object> answerMap = answerStatsFuture.get();
+            Map<String, Object> redPacketMap = redPacketStatsFuture.get();
+
+            // 处理观看和完成人数统计
+            if (watchMap != null) {
+                fsUserStatisticsVO.setCourseWatchNum(Integer.valueOf(watchMap.getOrDefault("courseWatchNum", 0L).toString()))
+                        .setCourseCompleteNum(Integer.valueOf(watchMap.getOrDefault("courseCompleteNum", 0L).toString()));
+
+                Long completeNum = (Long) watchMap.get("courseCompleteNum");
+                Long watchNum = (Long) watchMap.get("courseWatchNum");
+                if (completeNum != null && watchNum != null && watchNum > 0) {
+                    int courseCompleteRate = BigDecimal.valueOf(completeNum)
+                            .divide(BigDecimal.valueOf(watchNum), 2, RoundingMode.HALF_UP)
+                            .multiply(BigDecimal.valueOf(100))
+                            .intValue();
+                    fsUserStatisticsVO.setCourseCompleteRate(courseCompleteRate);
+                }
+            }
+
+            // 处理答题统计
+            if (answerMap != null) {
+                Long answerRightNum = (Long) answerMap.get("answerRightNum");
+                Long answerNum = (Long) answerMap.get("answerNum");
+
+                fsUserStatisticsVO.setAnswerNum(answerNum.intValue()).setAnswerRightNum(answerRightNum.intValue());
+
+                if (answerRightNum != null && answerNum != null && answerNum > 0) {
+                    int answerCompleteRate = BigDecimal.valueOf(answerRightNum)
+                            .divide(BigDecimal.valueOf(answerNum), 2, RoundingMode.HALF_UP)
+                            .multiply(BigDecimal.valueOf(100))
+                            .intValue();
+                    fsUserStatisticsVO.setAnswerRightRate(answerCompleteRate);
+                }
+            }
+
+            // 处理红包统计
+            if (redPacketMap != null) {
+                Object numObj = redPacketMap.get("redPacketNum");
+                Object amtObj = redPacketMap.get("redPacketAmount");
+                if (numObj != null && amtObj != null) {
+                    fsUserStatisticsVO.setRedPacketNum(Integer.parseInt(numObj.toString()))
+                            .setRedPacketAmount(new BigDecimal(amtObj.toString()));
+                }
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            // 异常处理
+            logger.error("异步查询用户统计数据失败", e);
+        }
+
+        return fsUserStatisticsVO;
+    }
+
+
+
+
+    private FsUserStatisticsVO getUserStatisticsNew(UserStatisticsCommonParam param) {
         FsUserStatisticsVO fsUserStatisticsVO = new FsUserStatisticsVO();
 
         // 判断是否是管理员

+ 7 - 5
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java

@@ -119,15 +119,17 @@ public interface FsStoreAfterSalesScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
-            "<if test = 'maps.beginTime != null and maps.beginTime != \"\"   '> " +
-            " AND date_format(s.create_time,'%y%m%d') &gt;= date_format(#{maps.beginTime},'%y%m%d') " +
+            "<if test = 'maps.params != null and maps.params != \"\"   '> " +
+            "<if test = 'maps.params.beginTime != null and maps.params.beginTime != \"\"   '> " +
+            " AND date_format(s.create_time,'%y%m%d') &gt;= date_format(#{maps.params.beginTime},'%y%m%d') " +
+            "</if>" +
+            "<if test = 'maps.params.endTime != null and maps.params.endTime != \"\"   '> " +
+            " AND date_format(s.create_time,'%y%m%d') &lt;= date_format(#{maps.params.endTime},'%y%m%d') " +
+            "</if>" +
             "</if>" +
             "<if test = 'maps.consigneePhone != null and  maps.consigneePhone !=\"\"     '> " +
             "and o.user_phone like CONCAT('%',#{maps.consigneePhone},'%') " +
             "</if>" +
-            "<if test = 'maps.endTime != null and maps.endTime != \"\"   '> " +
-            " AND date_format(s.create_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
-            "</if>" +
             "<if test = 'maps.deptId != null    '> " +
             "  AND (o.dept_id = #{maps.deptId} OR o.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{maps.deptId}, ancestors) )) " +
             "</if>" +

+ 6 - 4
fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java

@@ -103,11 +103,13 @@ public interface FsStorePaymentScrmMapper
 //            "<if test = 'maps.createTime != null    '> " +
 //            "and DATE_FORMAT(p.create_time,'%Y-%m-%d') = DATE_FORMAT(#{maps.createTime},'%Y-%m-%d')  " +
 //            "</if>" +
-            "<if test = 'maps.beginTime != null and maps.beginTime != \"\"   '> " +
-            " AND date_format(p.pay_time,'%y%m%d') &gt;= date_format(#{maps.beginTime},'%y%m%d') " +
+            "<if test = 'maps.params != null and maps.params != \"\"   '> " +
+            "<if test = 'maps.params.beginTime != null and maps.params.beginTime != \"\"   '> " +
+            " AND date_format(p.pay_time,'%y%m%d') &gt;= date_format(#{maps.params.beginTime},'%y%m%d') " +
+            "</if>" +
+            "<if test = 'maps.params.endTime != null and maps.params.endTime != \"\"   '> " +
+            " AND date_format(p.pay_time,'%y%m%d') &lt;= date_format(#{maps.params.endTime},'%y%m%d') " +
             "</if>" +
-            "<if test = 'maps.endTime != null and maps.endTime != \"\"   '> " +
-            " AND date_format(p.pay_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
             "</if>" +
 
             "<if test = 'maps.refundBeginTime != null  and maps.refundBeginTime != \"\"    '> " +

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

@@ -743,7 +743,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             //绑定销售
             FsUserScrm fsuser = userService.selectFsUserById(userId);
             if (param.getCompanyUserId() != null) {
-                if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId()) && !Objects.equals(fsuser.getCompanyUserId(), param.getCompanyUserId())) {
+                if (!CloudHostUtils.hasCloudHostName("鸿森堂") && ObjectUtil.isNotEmpty(fsuser.getCompanyUserId()) && !Objects.equals(fsuser.getCompanyUserId(), param.getCompanyUserId())) {
                     CompanyUser companyUser = companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
                     return R.error(String.format("请联系%s销售进行购买商品!", companyUser.getNickName()));
                 } else {

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

@@ -86,7 +86,7 @@ public interface QwTagMapper
     QwTagVO selectQwTagByName(@Param("trimTag") String trimTag, @Param("corpId")String corpId );
 
     @Select("<script>" +
-            "SELECT name FROM qw_tag WHERE tag_id IN " +
+            "SELECT distinct name FROM qw_tag WHERE tag_id IN " +
             "<foreach collection='date.tagIds' item='tagId' open='(' separator=',' close=')'>" +
             "#{tagId}" +
             "</foreach>" +

+ 15 - 0
fs-service/src/main/java/com/fs/qw/param/FsUserCourseRedPageParam.java

@@ -0,0 +1,15 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class FsUserCourseRedPageParam {
+
+    //课程
+    private Long courseId;
+    //红包
+    private BigDecimal redPacketMoney;
+}
+

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

@@ -3929,9 +3929,10 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                         qw.setDescription(followUser.getDescription()); // 设置描述信息
                         List<Tag> tags = followUser.getTags();
 
+                        List<String> tagArr = new ArrayList<>();
+
                         if (tags != null && tags.size() > 0) {
 
-                            List<String> tagArr = new ArrayList<>();
                             for (Tag tag : tags) {
                                 if (tag != null && tag.getTag_id() != null) {
                                     tagArr.add(tag.getTag_id());
@@ -3944,10 +3945,18 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
                             //新客对话
 //                            processTagsAllByAiChat(qwExternalContact1,corpId,tagArr);
+                        }else {
 
-                            qw.setTagIds(JSON.toJSONString(tagArr)); // 设置标签ID
+                            //客户自带标签空干净了
+                            logger.info("客户自带标签空干净了:"+qwExternalContact1.getName()+"|公司"+qwExternalContact1.getCorpId()+"|员工"+qwExternalContact1.getUserId()+"|总标签"+tagArr+"|原标签"+qwExternalContact1.getTagIds());
+
+                            //销售删除客户-找这个销售的sop任务中所有的营期里有没有这个客户,删了
+                            sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactId(userID,corpId, externalUserID);
+                            sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactIdByChat(userID,corpId, externalUserID);
 
                         }
+
+                        qw.setTagIds(JSON.toJSONString(tagArr)); // 设置标签ID
                         if (followUser.getRemark_mobiles() != null && followUser.getRemark_mobiles().size() > 0) {
                             List<String> remarkMobiles = followUser.getRemark_mobiles();
                             qw.setRemarkMobiles(JSON.toJSONString(remarkMobiles));

+ 6 - 3
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -54,7 +54,7 @@ public interface SopUserLogsMapper {
 //    int updateSopUserLogsDistinctByList(@Param("data") SopUserLogsArray userLogsArray);
 
     @DataSource(DataSourceType.SOP)
-    public List<SopUserLogsVo> selectSopUserLogsListByTime();
+    public List<SopUserLogsVo> selectSopUserLogsListByTime(@Param("sopIds") List<String> sopidList);
 
     @DataSource(DataSourceType.SOP)
     public List<SopUserLogs> meetsTheRatingByUserInfo();
@@ -389,6 +389,9 @@ List<SopUserLogsVO> selectSopUserLogsGroupListByParam(@Param("maps") SopUserLogs
     void replaceUser(@Param("vo") ReplaceUserDto vo);
 
     @DataSource(DataSourceType.SOP)
-    @Select("SELECT * FROM sop_user_logs WHERE sop_id = #{sopId} AND qw_user_id = #{qwUserId}")
-    SopUserLogs queryUserBySopId(@Param("qwUserId")String qwUserId,@Param("sopId") String sopId);
+    @Select("<script>" +"SELECT * FROM sop_user_logs WHERE sop_id = #{sopId} AND qw_user_id = #{qwUserId} " +
+            "<if test=\"chatId != null and chatId != ''\"> and chat_id = #{chatId} </if>" +
+            " limit 1 " +
+            "</script>")
+    SopUserLogs queryUserBySopId(@Param("qwUserId")String qwUserId,@Param("sopId") String sopId,@Param("chatId") String chatId);
 }

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

@@ -1099,8 +1099,8 @@ public class QwSopLogsServiceImpl extends ServiceImpl<QwSopLogsMapper, QwSopLogs
 
                                 //有app的异步推送消息
                                 if (!setting.isEmpty()) {
-                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
-                                    //asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(setting, param.getCorpId(),qwUser.getCompanyUserId(),log.getFsUserId());
+//                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
+                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(setting, param.getCorpId(),qwUser.getCompanyUserId(),log.getFsUserId());
                                 }
 
                                 if (log.getExpiryTime() == null) {

+ 5 - 5
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -558,7 +558,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setQwUserKey(qwUser.getId());
 
                     // 设置实际发送人
-                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
+                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupUser.getChatId());
                     //域名
                     String companyUserId = qwUser.getCompanyUserId().toString();
                     String domainName = companyUserMapper.selectDomainByUserId(Long.parseLong(companyUserId));
@@ -696,7 +696,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setExternalUserName(groupChat.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
                     // 设置实际发送人
-                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
+                    updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupChat.getChatId());
 
                     QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
 
@@ -1009,7 +1009,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }
                 sopLogs.setContentJson(JSON.toJSONString(setting));
                 // 设置实际发送人
-                updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId());
+                updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),null);
 
                 sopLogsList.add(sopLogs);
             });
@@ -1023,9 +1023,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return R.ok();
     }
 
-    private void updateQwUserKey(QwSopLogs sopLogs, String qwUserId, String sopId) {
+    private void updateQwUserKey(QwSopLogs sopLogs, String qwUserId, String sopId,String chatId) {
         // 设置实际发送人
-        SopUserLogs qwSopLogs = sopUserLogsMapper.queryUserBySopId(qwUserId,sopId);
+        SopUserLogs qwSopLogs = sopUserLogsMapper.queryUserBySopId(qwUserId,sopId,chatId);
         if (qwSopLogs != null && ObjectUtil.isNotEmpty(qwSopLogs.getActualQwId())){
             // sopLogs.setQwUserid(qwUser.getQwUserId());
             sopLogs.setQwUserKey(qwSopLogs.getActualQwId());

+ 1 - 1
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java

@@ -165,7 +165,7 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
         // 当前时间
         LocalDateTime currentDateTime = LocalDateTime.now();
 
-        List<SopUserLogsVo> sopUserLogsVos = sopUserLogsMapper.selectSopUserLogsListByTime();
+        List<SopUserLogsVo> sopUserLogsVos = sopUserLogsMapper.selectSopUserLogsListByTime(null);
         // 创建一个 Map,用于分组:key 是sop_id,value 是对应的 QwSopLogs 列表
         Map<String, List<SopUserLogsVo>> stringListMap = sopUserLogsVos.stream()
                 .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));

+ 104 - 0
fs-service/src/main/resources/application-config-dev-czt.yml

@@ -0,0 +1,104 @@
+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: wxa6a215ad7353fd8a   #纯正堂药速通
+        secret: 4aa21869d9ed5bfc9477afb231a30f05 #纯正堂药速通
+        token: Ncbnd7lJvkripxxna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wwa46ffb9ff6ac35b8 #企业ID北京存在文化
+    appConfigs:
+      - agentId: 1000070       #北京存在文化
+        secret: pu2EFz6gY2Fo2K-aRUxLPaAkKIaMJJRp8ES9JdpHkp4 #北京存在文化
+        token: PPKOdAlCoMO
+        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId:  #微信公众号或者小程序等的appid
+    mchId:  #微信支付商户号
+    mchKey:  #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://userapp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx6d3706feab2b9926 # 第一个公众号的appid  //公众号名称:纯正堂大药房
+        secret: eedddc683062b258625f036a71d7cbc0 # 公众号的appsecret--纯正堂大药房
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+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://127.0.0.1:7771
+  h5CommonApi: http://127.0.0.1:7771
+  jwt:
+    # 加密秘钥
+    secret: e10adc3949ba59abbe56e057f20f883e
+    # token有效时长,7天,单位秒
+    expire: 31536000
+    header: AppToken
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: czt-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: czt
+tmp_secret_config:
+  secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIxX
+  secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgBT
+  bucket: fs-1319721001
+  app_id: 1319721001
+  region: ap-chongqing
+  proxy: fs
+cloud_host:
+  company_name: 纯正堂
+  projectCode: CZT
+headerImg:
+  imgUrl:
+
+ipad:
+  ipadUrl: http://ipad.cykbja.cn
+  aiApi: 1212121212
+  commonApi:
+  voiceApi:
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+
+# 0 代表关闭 1代表开启(润天老商户号的扣款限制)
+enableRedPackAccount: 0
+video:
+  videoUploadDir:
+  frameOutputDir:

+ 94 - 0
fs-service/src/main/resources/application-config-druid-heyantang.yml

@@ -0,0 +1,94 @@
+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:
+    appConfigs:
+      - agentId:
+        secret: ec7okROXJqkNafq66-L6aKNv0asTzQIG0CYrj3vyBbo
+        token: PPKOdAlCoMO
+        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId:  #微信公众号或者小程序等的appid
+    mchId:  #微信支付商户号
+    mchKey:  #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://userapp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx9df02b2236d4fbf2 # 第一个公众号的appid
+        secret: 4f685750d40ab334b6f2f54b7d121389 # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+aifabu:  #爱链接
+  appKey: 7b471be905ab17e00f3b858c6710dd117601d00
+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://172.27.0.17:8010
+  h5CommonApi: http://172.27.0.11:7771
+  jwt:
+    # 加密秘钥
+    secret: f4e2e52034348f86b67cde581c0f9eb5
+    # token有效时长,7天,单位秒
+    expire: 31536000
+    header: AppToken
+nuonuo:
+  key: 10924509
+  secret: A2EB20764D304D16
+
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: cdhyt-1323137866
+#  bucket: cdhyt-1383856587
+  app_id: 1323137866
+  region: ap-chongqing
+#  region: ap-chengdu
+  proxy: cdhyt
+cloud_host:
+  company_name: 鹤颜堂
+  projectCode: HEYANTANG
+headerImg:
+  imgUrl: https
+ipad:
+  ipadUrl: https://qwipad.yytcdtb.cn
+  aiApi: http://127.0.0.1:3000/api
+  voiceApi:
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id: -SjnK9K6cNKASa6AD9Q_c0YT7J1lPTEpPIpqbMJF8F0
+  inquiry_temp_id: hwFXVh0AWqeasBsZpa0-urb3CrPeYEwBiy3P6AMMGFQ
+
+

+ 157 - 0
fs-service/src/main/resources/application-dev-czt.yml

@@ -0,0 +1,157 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-dev-czt,common
+    # redis 配置
+    redis:
+        host: localhost
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password:
+        # 连接超时时间
+        timeout: 10s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 100
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                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://nj-cdb-5rexc1if.sql.tencentcdb.com:28670/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                  username: root
+                  password: Ylrz_1q2w3e4r5t6y
+                # 从库数据源
+                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://nj-cdb-5rexc1if.sql.tencentcdb.com:28670/fs_his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_1q2w3e4r5t6y
+                # 初始连接数
+                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: rmq-16xj8o92zp.rocketmq.cd.qcloud.tencenttdmq.com:8080
+    producer:
+        group: my-producer-group
+        access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
+        secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
+    consumer:
+        group: common-group
+        access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
+        secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
+openIM:
+    secret: openIM123
+    userID: imAdmin
+    url: https://web.im.fbylive.com/api
+#是否使用新im
+im:
+    type: NONE
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: true

+ 159 - 0
fs-service/src/main/resources/application-druid-heyantang.yml

@@ -0,0 +1,159 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-heyantang,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 172.27.0.13
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: Ylrz_c123232014^$
+        # 连接超时时间
+        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://172.27.0.7:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                # 从库数据源
+                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://172.27.0.7:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                # 初始连接数
+                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: 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: test-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+openIM:
+    secret: openIM123
+    userID: imAdmin
+    url: https://web.im.fbylive.com/api
+#是否使用新im
+im:
+    type: NONE
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false

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

@@ -60,6 +60,7 @@
             <if test="userId != null "> and user_id = #{userId}</if>
             <if test="projectId != null "> and project_id = #{projectId}</if>
             <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="isDel != null "> and is_del = #{isDel}</if>
         </where>
     </select>
 
@@ -387,6 +388,18 @@
         update fs_user_course_video set red_packet_money = #{redPacketMoney} where video_id = #{videoId}
     </update>
 
+    <update id="batchUpdateByVideoId">
+        UPDATE fs_user_course_video
+        SET course_sort =
+        <foreach collection="list" item="item" index="index" separator=" " open="CASE video_id" close="END">
+            WHEN #{item.videoId} THEN #{item.courseSort}
+        </foreach>
+        WHERE video_id IN
+        <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
+            #{item.videoId}
+        </foreach>
+    </update>
+
 
     <select id="selectByFileKey"  resultMap="FsUserCourseVideoResult">
         <include refid="selectFsUserCourseVideoVo"/>

+ 104 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -48,10 +48,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="courseMaOpenId"    column="course_ma_open_id"    />
         <result property="qwExtId"    column="qw_ext_id"    />
         <result property="qwUserId"    column="qw_user_id"    />
+        <result property="level" column="level"/>
     </resultMap>
 
     <sql id="selectFsUserVo">
-        select user_id,qw_ext_id,sex,is_buy,course_ma_open_id,is_push,is_add_qw,source,login_device,is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, create_time, update_time, last_ip, balance,is_weixin_auth,parent_id,qw_user_id,company_id,company_user_id from fs_user
+        select user_id,qw_ext_id,sex,is_buy,`level`,course_ma_open_id,is_push,is_add_qw,source,login_device,is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, create_time, update_time, last_ip, balance,is_weixin_auth,parent_id,qw_user_id,company_id,company_user_id from fs_user
     </sql>
 
     <select id="selectFsUserList" parameterType="FsUser" resultMap="FsUserResult">
@@ -557,6 +558,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="orderCount != null">order_count,</if>
             <if test="companyId != null">company_id,</if>
             <if test="companyUserId != null">company_user_id,</if>
+            <if test="level != null">`level`,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="nickName != null">#{nickName},</if>
@@ -600,6 +602,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="orderCount != null">#{orderCount},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="level != null">#{level},</if>
          </trim>
     </insert>
 
@@ -1206,6 +1209,106 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
 
+    <!-- 查询观看和完成人数 -->
+    <select id="countUserWatchStats" resultType="Map">
+        SELECT
+        COUNT(DISTINCT CASE WHEN l.log_type != 3 AND l.send_type = 1 THEN l.user_id END) AS courseWatchNum,
+        COUNT(DISTINCT CASE WHEN l.log_type = 2 AND l.send_type = 1 THEN l.user_id END) AS courseCompleteNum
+        FROM  fs_course_watch_log l
+        LEFT JOIN fs_user u  ON u.user_id = l.user_id
+        LEFT JOIN company_user cu_l ON l.company_user_id = cu_l.user_id
+        <where>
+            <if test="userId != null and userId != 0">
+                AND (cu_l.user_id = #{userId} OR cu_l.parent_id = #{userId})
+            </if>
+            <if test="userId != null and userId == 0">
+                AND l.company_id = #{companyId}
+            </if>
+            <if test="periodId != null and periodId != ''">
+                AND l.period_id = #{periodId}
+            </if>
+            <if test="videoId != null and videoId != ''">
+                AND l.video_id = #{videoId}
+            </if>
+            <if test="startTime != null and startTime != ''">
+                AND l.create_time &gt;= #{startTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                AND l.create_time &lt;= #{endTime}
+            </if>
+            <if test="companyUserId != null and companyUserId != ''">
+                AND l.company_user_id = #{companyUserId}
+            </if>
+        </where>
+    </select>
+
+    <!-- 查询答题人数和答对人数 -->
+    <select id="countUserAnswerStats" resultType="Map">
+        SELECT
+        COUNT(DISTINCT CASE WHEN a.user_id THEN a.user_id END) AS answerNum,
+        COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS answerRightNum
+        FROM  fs_course_answer_logs a
+        LEFT JOIN fs_user u  ON u.user_id = a.user_id
+        LEFT JOIN company_user cu_a ON a.company_user_id = cu_a.user_id
+        <where>
+            <if test="userId != null and userId != 0">
+                AND (cu_a.user_id = #{userId} OR cu_a.parent_id = #{userId})
+            </if>
+            <if test="userId != null and userId == 0">
+                AND a.company_id = #{companyId}
+            </if>
+            <if test="periodId != null and periodId != ''">
+                AND a.period_id = #{periodId}
+            </if>
+            <if test="videoId != null and videoId != ''">
+                AND a.video_id = #{videoId}
+            </if>
+            <if test="startTime != null and startTime != ''">
+                AND a.create_time &gt;= #{startTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                AND a.create_time &lt;= #{endTime}
+            </if>
+            <if test="companyUserId != null and companyUserId != ''">
+                AND a.company_user_id = #{companyUserId}
+            </if>
+        </where>
+    </select>
+
+    <!-- 查询红包数量和红包金额 -->
+    <select id="countUserRedPacketStats" resultType="Map">
+        SELECT
+        COUNT(CASE WHEN flog.status = 1 THEN flog.log_id END) AS redPacketNum,
+        IFNULL(SUM(CASE WHEN flog.status = 1 THEN flog.amount END), 0) AS redPacketAmount
+        FROM  fs_course_red_packet_log flog
+        LEFT JOIN fs_user u  ON u.user_id = flog.user_id
+        LEFT JOIN company_user cu_flog ON flog.company_user_id = cu_flog.user_id
+        <where>
+            <if test="userId != null and userId != 0">
+                AND (cu_flog.user_id = #{userId} OR cu_flog.parent_id = #{userId})
+            </if>
+            <if test="userId != null and userId == 0">
+                AND flog.company_id = #{companyId}
+            </if>
+            <if test="periodId != null and periodId != ''">
+                AND flog.period_id = #{periodId}
+            </if>
+            <if test="videoId != null and videoId != ''">
+                AND flog.video_id = #{videoId}
+            </if>
+            <if test="startTime != null and startTime != ''">
+                AND flog.create_time &gt;= #{startTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                AND flog.create_time &lt;= #{endTime}
+            </if>
+            <if test="companyUserId != null and companyUserId != ''">
+                AND flog.company_user_id = #{companyUserId}
+            </if>
+        </where>
+    </select>
+
+
     <select id="countUserAnswer" resultType="Map">
         SELECT
         (

+ 4 - 0
fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -207,6 +207,10 @@
         where a.start_time &lt;= Now()
           and a.status = 1
           and b.send_type != 4 and b.status in (2,3)
+        <if test="sopIds != null and !sopIds.isEmpty()">
+            and a.sop_id in
+            <foreach collection="sopIds" open="(" close=")" index="index" item="item" separator=",">#{item}</foreach>
+        </if>
     </select>
 
     <select id="meetsTheRatingByUserInfo"   resultMap="SopUserLogsResult">

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

@@ -2,6 +2,7 @@ package com.fs.app.controller;
 
 
 import com.fs.app.annotation.Login;
+import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.ServletUtils;
@@ -17,6 +18,7 @@ import io.jsonwebtoken.Claims;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.transaction.annotation.Transactional;
@@ -26,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
 import java.util.*;
 
 @Api("课堂接口")
+@Slf4j
 @RestController
 @RequestMapping(value="/app/course")
 public class CourseController extends  AppBaseController{
@@ -341,4 +344,15 @@ public class CourseController extends  AppBaseController{
     public void updateUrl(){
         tencentCloudCosService.updateUrl();
     }
+
+    @Login
+    @ApiOperation("发放奖励App")
+    @PostMapping("/sendAppReward")
+    @RepeatSubmit
+    public R sendAppReward(@RequestBody FsCourseSendRewardUParam param)
+    {
+        param.setUserId(Long.parseLong(getUserId()));
+        log.info("zyp \n【发放APP奖励】:{}",param);
+        return courseVideoService.sendAppReward(param);
+    }
 }

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -104,6 +104,7 @@ public class CourseFsUserController extends AppBaseController {
 
     @ApiOperation("获取缓冲流量")
     @PostMapping("/getInternetTraffic")
+    @Login
     public R getInternetTraffic(@RequestBody FsUserCourseVideoFinishUParam param) {
         param.setUserId(Long.parseLong(getUserId()));
         return courseVideoService.getInternetTraffic(param);