Browse Source

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 1 week ago
parent
commit
db5eb60aae
82 changed files with 3178 additions and 108 deletions
  1. 21 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  2. 19 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  3. 26 4
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java
  4. 20 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java
  5. 24 8
      fs-admin/src/main/java/com/fs/his/controller/FsPackageController.java
  6. 71 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveController.java
  7. 41 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveLogController.java
  8. 154 0
      fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java
  9. 25 0
      fs-common/src/main/java/com/fs/common/utils/DateUtils.java
  10. 67 18
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  11. 36 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  12. 1 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserImportVO.java
  13. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  14. 10 4
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  15. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  16. 6 4
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  17. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  18. 9 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  19. 23 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoChooseVO.java
  20. 60 0
      fs-service/src/main/java/com/fs/event/ActiveClickEvent.java
  21. 27 0
      fs-service/src/main/java/com/fs/event/ActiveClickListener.java
  22. 2 0
      fs-service/src/main/java/com/fs/his/domain/FsAdv.java
  23. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActive.java
  24. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveLog.java
  25. 39 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveResource.java
  26. 38 0
      fs-service/src/main/java/com/fs/his/dto/FsPromotionalActiveDTO.java
  27. 11 0
      fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java
  28. 14 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralGoodsMapper.java
  29. 11 0
      fs-service/src/main/java/com/fs/his/mapper/FsPackageMapper.java
  30. 17 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveLogMapper.java
  31. 29 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveMapper.java
  32. 15 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveResourceMapper.java
  33. 6 0
      fs-service/src/main/java/com/fs/his/service/IFsDoctorService.java
  34. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralGoodsService.java
  35. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsPackageService.java
  36. 21 0
      fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveLogService.java
  37. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveResourceService.java
  38. 48 0
      fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveService.java
  39. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsDoctorServiceImpl.java
  40. 10 3
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  41. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPackageServiceImpl.java
  42. 41 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveLogServiceImpl.java
  43. 12 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveResourceServiceImpl.java
  44. 276 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveServiceImpl.java
  45. 7 7
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  46. 23 0
      fs-service/src/main/java/com/fs/his/vo/FsDoctorChooseVO.java
  47. 31 0
      fs-service/src/main/java/com/fs/his/vo/FsGoodsVO.java
  48. 31 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralGoodsChooseVO.java
  49. 26 0
      fs-service/src/main/java/com/fs/his/vo/FsPackageChooseVO.java
  50. 38 0
      fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveDetailVO.java
  51. 23 0
      fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveStatVO.java
  52. 41 0
      fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveVO.java
  53. 5 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  54. 47 0
      fs-service/src/main/java/com/fs/qw/domain/QwPushCount.java
  55. 39 0
      fs-service/src/main/java/com/fs/qw/domain/QwRestrictionPushRecord.java
  56. 78 0
      fs-service/src/main/java/com/fs/qw/mapper/QwPushCountMapper.java
  57. 49 0
      fs-service/src/main/java/com/fs/qw/mapper/QwRestrictionPushRecordMapper.java
  58. 68 0
      fs-service/src/main/java/com/fs/qw/service/IQwPushCountService.java
  59. 106 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwPushCountServiceImpl.java
  60. 40 4
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  61. 3 0
      fs-service/src/main/resources/application-config-druid-czt.yml
  62. 94 0
      fs-service/src/main/resources/application-config-druid-ddgy.yml
  63. 94 0
      fs-service/src/main/resources/application-config-druid-hat.yml
  64. 176 0
      fs-service/src/main/resources/application-druid-ddgy.yml
  65. 176 0
      fs-service/src/main/resources/application-druid-hat.yml
  66. 46 0
      fs-service/src/main/resources/db/20250924-宣传活动.sql
  67. 68 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  68. 5 1
      fs-service/src/main/resources/mapper/his/FsAdvMapper.xml
  69. 58 0
      fs-service/src/main/resources/mapper/his/FsDoctorMapper.xml
  70. 41 0
      fs-service/src/main/resources/mapper/his/FsIntegralGoodsMapper.xml
  71. 47 0
      fs-service/src/main/resources/mapper/his/FsPackageMapper.xml
  72. 30 0
      fs-service/src/main/resources/mapper/his/FsPromotionalActiveLogMapper.xml
  73. 18 0
      fs-service/src/main/resources/mapper/his/FsPromotionalActiveMapper.xml
  74. 15 0
      fs-service/src/main/resources/mapper/his/FsPromotionalActiveResourceMapper.xml
  75. 71 0
      fs-service/src/main/resources/mapper/qw/QwPushCountMapper.xml
  76. 7 0
      fs-service/src/main/resources/mapper/qw/QwRestrictionPushRecordMapper.xml
  77. 64 44
      fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml
  78. 34 4
      fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java
  79. 63 0
      fs-user-app/src/main/java/com/fs/app/controller/PromotionalActiveController.java
  80. 15 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java
  81. 16 0
      fs-user-app/src/main/java/com/fs/app/exception/FSExceptionHandler.java
  82. 24 0
      fs-user-app/src/main/java/com/fs/app/param/PromotionalActiveClickParam.java

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

@@ -20,6 +20,7 @@ import com.fs.course.param.BatchVideoSvae;
 import com.fs.course.param.CourseVideoUpdates;
 import com.fs.course.service.IFsUserCourseService;
 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.system.service.ISysConfigService;
@@ -222,4 +223,24 @@ public class FsUserCourseVideoController extends BaseController
         List<OptionsVO> periodList = fsUserCourseVideoService.selectVideoListByMap(params);
         return R.ok().put("data", new PageInfo<>(periodList));
     }
+
+    @GetMapping("/getChooseCourseVideoList")
+    public R getChooseCourseVideoList(@RequestParam(required = false) Long courseId,
+                                      @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                      @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        Map<String,Object> params = new HashMap<>();
+        params.put("courseId", courseId);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            params.put("userId", userId);
+        }
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsUserCourseVideoChooseVO> list = fsUserCourseVideoService.getChooseCourseVideoListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
 }

+ 19 - 2
fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java

@@ -180,8 +180,11 @@ public class FsUserVideoController extends BaseController
         return toAjax(fsUserVideoService.updateFsUserVideoIsShow(videoIds,0));
     }
 
-    private static final String VIDEO_UPLOAD_DIR = "C:\\fs\\uploadPath\\userVideo\\video";  // 上传目录
-    private static final String FRAME_OUTPUT_DIR = "C:\\fs\\uploadPath\\userVideo\\frame";  // 输出帧的目录
+//    private static final String VIDEO_UPLOAD_DIR = "C:\\fs\\uploadPath\\userVideo\\video";  // 上传目录
+//    private static final String FRAME_OUTPUT_DIR = "C:\\fs\\uploadPath\\userVideo\\frame";  // 输出帧的目录
+    // 改为使用系统临时目录或相对路径
+    private static final String VIDEO_UPLOAD_DIR = System.getProperty("java.io.tmpdir") + File.separator + "fs_upload" + File.separator + "userVideo" + File.separator + "video";
+    private static final String FRAME_OUTPUT_DIR = System.getProperty("java.io.tmpdir") + File.separator + "fs_upload" + File.separator + "userVideo" + File.separator + "frame";
 
 
     /**
@@ -198,16 +201,19 @@ public class FsUserVideoController extends BaseController
 
         // 保存上传的视频文件
         String videoFileName = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
+        createDir(VIDEO_UPLOAD_DIR);
         File videoFile = new File(VIDEO_UPLOAD_DIR, videoFileName);
         try {
             file.transferTo(videoFile);
         } catch (IOException e) {
+            e.printStackTrace();
             // 记录错误日志
             return R.error("获取封面失败");
         }
 
         // 提取视频第一帧
         String frameFileName = FilenameUtils.removeExtension(videoFileName) + "_frame.jpg";
+        createDir(FRAME_OUTPUT_DIR);
         File frameFile = new File(FRAME_OUTPUT_DIR, frameFileName);
         try {
             extractFirstFrame(videoFile.getAbsolutePath(), frameFile.getAbsolutePath());
@@ -268,6 +274,17 @@ public class FsUserVideoController extends BaseController
         }
     }
 
+    private void createDir(String path){
+        File videoUploadDir = new File(path);
+        if (!videoUploadDir.exists()) {
+            boolean created = videoUploadDir.mkdirs();
+            if (!created) {
+                log.error("创建视频上传目录失败: {}", path);
+            }
+            log.info("创建视频上传目录: {}", path);
+        }
+    }
+
     @PostMapping("/updateUrl")
     public R updateUrl()
     {

+ 26 - 4
fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java

@@ -1,17 +1,19 @@
 package com.fs.his.controller;
 
 import java.util.Base64;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.sign.Md5Utils;
 import com.fs.his.param.*;
 import com.fs.his.utils.RedisCacheUtil;
-import com.fs.his.vo.FsDoctorListVO;
-import com.fs.his.vo.FsDoctorVO;
-import com.fs.his.vo.OptionsVO;
-import com.fs.his.vo.UserVo;
+import com.fs.his.vo.*;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -269,4 +271,24 @@ public class FsDoctorController extends BaseController
         return toAjax(fsDoctorService.updateFsDoctor(doc));
     }
 
+    @GetMapping("/getChooseDoctorList")
+    public R getChooseDoctorList(@RequestParam(required = false) String doctorName,
+                                 @RequestParam(required = false) Long hospitalId,
+                                 @RequestParam(required = false) Long deptId,
+                                 @RequestParam(required = false) String position,
+                                 @RequestParam(required = false) String mobile,
+                                 @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                 @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("doctorName", doctorName);
+        params.put("hospitalId", hospitalId);
+        params.put("deptId", deptId);
+        params.put("position", position);
+        params.put("mobile", mobile);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsDoctorChooseVO> list = fsDoctorService.getChooseDoctorListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
 }

+ 20 - 0
fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java

@@ -3,20 +3,26 @@ package com.fs.his.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.his.utils.RedisCacheUtil;
+import com.fs.his.vo.FsIntegralGoodsChooseVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import com.fs.his.vo.FsStoreProductExcelVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 积分商品Controller
@@ -127,4 +133,18 @@ public class FsIntegralGoodsController extends BaseController
         redisCacheUtil.delRedisKey("getIntegralGoodsById");
         return toAjax(fsIntegralGoodsService.deleteFsIntegralGoodsByGoodsIds(goodsIds));
     }
+
+    @GetMapping("/getChooseIntegralGoodsList")
+    public R getChooseIntegralGoodsList(@RequestParam(required = false) String goodsName,
+                                        @RequestParam(required = false) Integer goodsType,
+                                        @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                        @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("goodsName", goodsName);
+        params.put("goodsType", goodsType);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsIntegralGoodsChooseVO> list = fsIntegralGoodsService.getChooseIntegralGoodsListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
 }

+ 24 - 8
fs-admin/src/main/java/com/fs/his/controller/FsPackageController.java

@@ -1,7 +1,9 @@
 package com.fs.his.controller;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
@@ -12,20 +14,16 @@ import com.fs.his.param.FsPackageParam;
 import com.fs.his.param.FsStoreProductPackageModifyParam;
 import com.fs.his.service.IFsFollowTempService;
 import com.fs.his.utils.RedisCacheUtil;
+import com.fs.his.vo.FsPackageChooseVO;
 import com.fs.his.vo.FsPackageExcelVO;
 import com.fs.his.vo.FsPackageListVO;
 import com.fs.his.vo.OptionsVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -191,4 +189,22 @@ public class FsPackageController extends BaseController
         return toAjax(fsPackageService.updatePackagesStatus(param.getPackageIds(),param.getStatus()));
     }
 
+    @GetMapping("/getChoosePackageList")
+    public R getChoosePackageList(@RequestParam(required = false) String packageName,
+                                  @RequestParam(required = false) String secondName,
+                                  @RequestParam(required = false) Integer packageType,
+                                  @RequestParam(required = false) Integer packageSubType,
+                                  @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                  @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("packageName", packageName);
+        params.put("secondName", secondName);
+        params.put("packageType", packageType);
+        params.put("packageSubType", packageSubType);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsPackageChooseVO> list = fsPackageService.getChoosePackageListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
 }

+ 71 - 0
fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveController.java

@@ -0,0 +1,71 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.his.domain.FsPromotionalActive;
+import com.fs.his.dto.FsPromotionalActiveDTO;
+import com.fs.his.service.IFsPromotionalActiveService;
+import com.fs.his.vo.FsPromotionalActiveVO;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 宣传活动控制类
+ */
+@RestController
+@RequestMapping("/his/promotionActive")
+@AllArgsConstructor
+public class FsPromotionalActiveController extends BaseController {
+
+    private final IFsPromotionalActiveService fsPromotionalActiveService;
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsPromotionalActive active) {
+        startPage();
+        List<FsPromotionalActiveVO> list = fsPromotionalActiveService.selectPromotionalActiveVOList(active);
+        return getDataTable(list);
+    }
+
+    @GetMapping(value = "/{activeId}")
+    public AjaxResult getInfo(@PathVariable Long activeId) {
+        return AjaxResult.success(fsPromotionalActiveService.selectPromotionalActiveVOById(activeId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:add')")
+    @Log(title = "宣传活动", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Valid @RequestBody FsPromotionalActiveDTO param) {
+        fsPromotionalActiveService.addPromotionalActive(param);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:edit')")
+    @Log(title = "宣传活动", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Valid @RequestBody FsPromotionalActiveDTO param) {
+        fsPromotionalActiveService.editPromotionalActive(param);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:remove')")
+    @Log(title = "宣传活动", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{activeIds}")
+    public AjaxResult remove(@PathVariable Long[] activeIds) {
+        fsPromotionalActiveService.logicalRemove(activeIds);
+        return AjaxResult.success();
+    }
+
+    @GetMapping("/getPromotionalActiveOption")
+    public R getPromotionalActiveOption() {
+        return R.ok().put("list", fsPromotionalActiveService.getPromotionalActiveOption());
+    }
+}

+ 41 - 0
fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveLogController.java

@@ -0,0 +1,41 @@
+package com.fs.his.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.his.service.IFsPromotionalActiveLogService;
+import com.fs.his.vo.FsPromotionalActiveStatVO;
+import lombok.AllArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/his/promotionActiveLog")
+@AllArgsConstructor
+public class FsPromotionalActiveLogController extends BaseController {
+
+    private final IFsPromotionalActiveLogService promotionalActiveLogService;
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActiveLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String name,
+                              @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startTime,
+                              @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endTime) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("name", name);
+        params.put("startTime", startTime);
+        params.put("endTime", endTime);
+
+        startPage();
+        List<FsPromotionalActiveStatVO> list = promotionalActiveLogService.getPromotionalActiveLogStatByMap(params);
+        return getDataTable(list);
+    }
+}

+ 154 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java

@@ -0,0 +1,154 @@
+package com.fs.qw.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.qw.domain.QwPushCount;
+import com.fs.qw.service.IQwPushCountService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 定义销售推送不同类型的企业消息的次数Controller
+ *
+ * @author fs
+ * @date 2025-08-22
+ */
+@RestController
+@RequestMapping("/qw/qwPushCount")
+public class QwPushCountController extends BaseController {
+    @Autowired
+    private IQwPushCountService qwPushCountService;
+    @Autowired
+    private ResourceLoader resourceLoader;
+
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwPushCount qwPushCount) {
+        startPage();
+        List<QwPushCount> list = qwPushCountService.selectQwPushCountList(qwPushCount);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定义销售推送不同类型的企业消息的次数列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:export')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwPushCount qwPushCount) {
+        List<QwPushCount> list = qwPushCountService.selectQwPushCountList(qwPushCount);
+        ExcelUtil<QwPushCount> util = new ExcelUtil<QwPushCount>(QwPushCount.class);
+        return util.exportExcel(list, "定义销售推送不同类型的企业消息的次数数据");
+    }
+
+    /**
+     * 获取定义销售推送不同类型的企业消息的次数详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        List<Long> companyIdList=new ArrayList<>();
+        QwPushCount qwPushCount = qwPushCountService.selectQwPushCountById(id);
+        companyIdList.add(qwPushCount.getCompanyId());
+        qwPushCount.setCompanyIdList(companyIdList);
+        return AjaxResult.success(qwPushCount);
+    }
+
+
+    /**
+     * 新增定义销售推送不同类型的企业消息的次数
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:add')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwPushCount qwPushCount) {
+        List<Long> existingIds = new ArrayList<>();
+        Map<String, Long> existingDataMap = new HashMap<>();
+        List<QwPushCount> existingPushCounts = qwPushCountService.selectQwPushCountLists();
+        if (existingPushCounts != null && !existingPushCounts.isEmpty()) {
+            existingPushCounts.forEach(item -> {
+                String key = buildDataKey(item.getType(), item.getCompanyId());
+                existingDataMap.put(key, item.getId());
+            });
+        }
+        // 处理公司ID列表(可能为null或空)
+        List<Long> companyIdList = qwPushCount.getCompanyIdList();
+        boolean isEmptyList = companyIdList == null || companyIdList.isEmpty();
+
+        if (isEmptyList) {
+            // 处理无公司ID列表的情况
+            String key = buildDataKey(qwPushCount.getType(), null);
+            if (existingDataMap.containsKey(key)) {
+                existingIds.add(qwPushCount.getId());
+            } else {
+                qwPushCountService.insertQwPushCount(qwPushCount);
+            }
+        } else {
+            // 处理有公司ID列表的情况
+            companyIdList.forEach(companyId -> {
+                String key = buildDataKey(qwPushCount.getType(), companyId);
+                if (existingDataMap.containsKey(key)) {
+                    existingIds.add(companyId);
+                } else {
+                    qwPushCount.setCompanyId(companyId);
+                    qwPushCountService.insertQwPushCount(qwPushCount);
+                }
+            });
+        }
+
+        // 统一处理返回结果
+        if (!existingIds.isEmpty()) {
+            return error("新增限定类型已存在:失败条数" + existingIds.size());
+        } else {
+            return toAjax(1);
+        }
+    }
+    private String buildDataKey(Integer type, Long companyId) {
+        return type + "_" + (companyId == null ? "null" : companyId);
+    }
+
+    /**
+     * 修改定义销售推送不同类型的企业消息的次数
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:edit')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwPushCount qwPushCount) {
+        QwPushCount pushCount;
+        if (qwPushCount.getCompanyId() != null) {
+            pushCount = qwPushCountService.SelectQwPushCountByCompanyId(qwPushCount.getType(), qwPushCount.getCompanyId());
+        } else {
+            pushCount = qwPushCountService.SelectQwPushCountByType(qwPushCount.getType());
+        }
+        if (pushCount != null) {
+            if (!Objects.equals(pushCount.getId(), qwPushCount.getId()) && Objects.equals(pushCount.getCompanyId(), qwPushCount.getCompanyId()) && Objects.equals(pushCount.getType(), qwPushCount.getType())) {
+                return toAjax(0);
+            }
+            if (Objects.equals(pushCount.getPushCount(), qwPushCount.getPushCount())) {
+                return toAjax(0);
+            }
+        }
+        return toAjax(qwPushCountService.updateQwPushCount(qwPushCount));
+    }
+
+    /**
+     * 删除定义销售推送不同类型的企业消息的次数
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:remove')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(qwPushCountService.deleteQwPushCountByIds(ids));
+    }
+}

+ 25 - 0
fs-common/src/main/java/com/fs/common/utils/DateUtils.java

@@ -270,5 +270,30 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         cal.add(Calendar.DATE, days);
         return new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime());
     }
+    /**
+     * 获取到当天时间的开始:当天0时0分0秒0毫秒
+     * @return
+     */
+    public static Long toStartTime( ) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar.getTimeInMillis();
+    }
+
+    /**
+     * 获取到当天时间的结束:当天23时59分59秒999毫秒
+     * @return
+     */
+    public static Long toEndTime() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
+        calendar.set(Calendar.MINUTE, 59);
+        calendar.set(Calendar.SECOND, 59);
+        calendar.set(Calendar.MILLISECOND, 999);
+        return calendar.getTimeInMillis();
+    }
 
 }

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

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.service.IpadSendServer;
 import com.fs.common.core.redis.RedisCacheT;
+import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.config.CourseConfig;
@@ -12,8 +13,12 @@ import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.ipad.vo.BaseVo;
 import com.fs.qw.domain.QwIpadServer;
+import com.fs.qw.domain.QwPushCount;
+import com.fs.qw.domain.QwRestrictionPushRecord;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwIpadServerMapper;
+import com.fs.qw.mapper.QwPushCountMapper;
+import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.impl.AsyncSopTestService;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
@@ -55,6 +60,8 @@ public class SendMsg {
     private final AsyncSopTestService asyncSopTestService;
     private final ICompanyMiniappService companyMiniappService;
     private final IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
+    private final QwPushCountMapper qwPushCountMapper;
+    private final QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
 
     @Value("${group-no}")
     private String groupNo;
@@ -65,7 +72,7 @@ public class SendMsg {
     @Qualifier("customThreadPool")
     private ThreadPoolTaskExecutor customThreadPool;
 
-    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService) {
+    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper) {
         this.qwUserMapper = qwUserMapper;
         this.qwSopLogsMapper = qwSopLogsMapper;
         this.sendServer = sendServer;
@@ -76,6 +83,8 @@ public class SendMsg {
         this.asyncSopTestService = asyncSopTestService;
         this.companyMiniappService = companyMiniappService;
         this.fsCoursePlaySourceConfigService = fsCoursePlaySourceConfigService;
+        this.qwPushCountMapper = qwPushCountMapper;
+        this.qwRestrictionPushRecordMapper = qwRestrictionPushRecordMapper;
     }
     private List<QwUser> getQwUserList() {
         if (qwUserList.isEmpty()) {
@@ -191,27 +200,67 @@ public class SendMsg {
                 continue;
             }
             redisCache.setCacheObject(key, System.currentTimeMillis(), 24, TimeUnit.HOURS);
+            List<QwPushCount> pushCountList = qwPushCountMapper.selectQwPushCountLists();
+            Map<Integer, List<QwPushCount>> pushMap = pushCountList.stream().collect(Collectors.groupingBy(QwPushCount::getType));
             // 循环发送消息里面的每一条消息
             for (QwSopCourseFinishTempSetting.Setting content : setting.getSetting()) {
                 long start4 = System.currentTimeMillis();
-                // 发送
-                sendServer.send(content, user, qwSopLogs, miniMap, parentVo);
-                long end4 = System.currentTimeMillis();
-                log.info("请求pad发送完成:{}, {}, 时长4:{}", user.getQwUserName(), qwSopLogs.getId(), end4 - start4);
-                if(content.getSendStatus() == 2 && ("请求失败:消息发送过于频繁,请稍后再试".equals(content.getSendRemarks()) || "请求失败:请求频率异常".equals(content.getSendRemarks()))){
-                    QwUser update = new QwUser();
-                    update.setRemark("请求频率异常,暂停发送,三小时后恢复继续发送");
-                    update.setUpdateTime(new Date());
-                    qwUserMapper.update(update, new QueryWrapper<QwUser>().eq("id", user.getId()));
-                    redisCache.setCacheObject("qw:user:id:" + user.getId(), user.getId(), 3, TimeUnit.HOURS);
-                    return;
+                //判断当前销售推送客户消息限制
+                Long qwUserId = qwUser.getId();//销售的Id
+                Integer type = Integer.valueOf(content.getContentType());//发送消息的类型
+                Long customerId = qwSopLogs.getExternalId();//客户ID
+                Long companyId = qwSopLogs.getCompanyId();//公司ID
+                Integer pushCount = -99;
+                if(pushMap.containsKey(type)){
+                    List<QwPushCount> qwPushCounts = pushMap.get(type);
+                    Optional<QwPushCount> optional = qwPushCounts.stream().filter(e -> Objects.equals(e.getCompanyId(), companyId)).findFirst();
+                    if(optional.isPresent()){
+                        pushCount = optional.get().getPushCount();
+                    }else{
+                        Optional<QwPushCount> nullCount = qwPushCounts.stream().filter(e -> e.getCompanyId() == null).findFirst();
+                        if(nullCount.isPresent()){
+                            pushCount = nullCount.get().getPushCount();
+                        }
+                    }
                 }
-                try {
-                    int delay = ThreadLocalRandom.current().nextInt(300, 1000);
-                    log.debug("pad发送消息等待:{}ms", delay);
-                    Thread.sleep(delay);
-                } catch (InterruptedException e) {
-                    log.error("线程等待错误!");
+                //查询是否有设置限制客服推送消息次数
+//                    Integer pushCount=pushCountMap.containsKey(String.valueOf(companyId)) ? pushCountMap.get(String.valueOf(companyId)): pushCountMap.getOrDefault(String.valueOf(type), -99);
+                int salesPushCustomerMessageCount = qwRestrictionPushRecordMapper.selectQwRestrictionPushRecord(qwUserId, customerId, type, DateUtils.toStartTime(), DateUtils.toEndTime());
+                if (pushCount != -99 && salesPushCustomerMessageCount >= pushCount) {
+                    content.setSendStatus(2);//设置发送失败状态
+                    content.setSendRemarks("发送次数达到上限");
+                } else {
+                    // 发送
+                    sendServer.send(content, user, qwSopLogs, miniMap, parentVo);
+                    //判断销售推送成功:保存记录
+                    if (content.getSendStatus() != 2) {
+                        QwRestrictionPushRecord qrpr = new QwRestrictionPushRecord();
+                        qrpr.setType(type);
+                        qrpr.setQwUserId(qwUserId);
+                        qrpr.setQwExternalId(customerId);
+                        qrpr.setCompanyId(companyId);
+                        qrpr.setStatus(1);
+                        qrpr.setCreateTime(DateUtils.getTime());
+                        qrpr.setTime(System.currentTimeMillis());
+                        qwRestrictionPushRecordMapper.insert(qrpr);
+                    }
+                    long end4 = System.currentTimeMillis();
+                    log.info("请求pad发送完成:{}, {}, 时长4:{}", user.getQwUserName(), qwSopLogs.getId(), end4 - start4);
+                    if(content.getSendStatus() == 2 && ("请求失败:消息发送过于频繁,请稍后再试".equals(content.getSendRemarks()) || "请求失败:请求频率异常".equals(content.getSendRemarks()))){
+                        QwUser update = new QwUser();
+                        update.setRemark("请求频率异常,暂停发送,三小时后恢复继续发送");
+                        update.setUpdateTime(new Date());
+                        qwUserMapper.update(update, new QueryWrapper<QwUser>().eq("id", user.getId()));
+                        redisCache.setCacheObject("qw:user:id:" + user.getId(), user.getId(), 3, TimeUnit.HOURS);
+                        return;
+                    }
+                    try {
+                        int delay = ThreadLocalRandom.current().nextInt(300, 1000);
+                        log.debug("pad发送消息等待:{}ms", delay);
+                        Thread.sleep(delay);
+                    } catch (InterruptedException e) {
+                        log.error("线程等待错误!");
+                    }
                 }
             }
             // 推送 APP

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

@@ -674,11 +674,40 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         if(content.getSetting() == null){
             return;
         }
+        List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "7".equals(e.getContentType())).collect(Collectors.toList());
+        if (!setting.isEmpty()) {
+            List<String> valuesList = PubFun.listToNewList(setting, QwSopTempSetting.Content.Setting::getValue);
+            if (valuesList != null && !valuesList.isEmpty()) {
+                try {
+                    List<QwSopTempVoice> voiceList = qwSopTempVoiceService.getVoiceByText(Long.parseLong(companyUserId), valuesList);
+                    if (voiceList != null && !voiceList.isEmpty()) {
+                        Map<String, QwSopTempVoice> collect = voiceList.stream().collect(Collectors.toMap(QwSopTempVoice::getVoiceTxt, e -> e));
+                        setting.parallelStream().filter(e -> "7".equals(e.getContentType())).forEach(st -> {
+                            QwSopTempVoice voice = collect.get(st.getValue());
+                            if (voice.getVoiceUrl() == null) {
+                                return;
+                            }
+                            st.setVoiceUrl(voice.getVoiceUrl());
+                            st.setVoiceDuration(voice.getDuration() + "");
+                        });
+                    }
+                } catch (NumberFormatException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
 //        // 发送语音 end
         if (content.getType()==5){
             sopAddTag(logVo,content,sendTime);
         }
 
+        //当语音模板的qw_sop_temp_voice中无对应语音,就不生成qw_sop_logs记录
+        if (content.getType() == 7 && content.getSetting() != null && !content.getSetting().isEmpty()) {
+            if (content.getSetting().get(0).getVoiceUrl() == null) {
+                return;
+            }
+        }
+
         if (StringUtils.isNotEmpty(logVo.getChatId())) {
             QwGroupChat groupChat = groupChatMap.get(logVo.getChatId());
             ruleTimeVO.setSendType(6);
@@ -839,11 +868,18 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             case 5:
 //                handleTagMessage(sopLogs, content);
                 break;
+            case 7:
+                handleVoiceMessage(sopLogs, content, companyUserId);
+                break;
             default:
                 log.error("未知的消息类型 {},跳过处理。", type);
                 break;
         }
     }
+    private void handleVoiceMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId) {
+        sopLogs.setContentJson(JSON.toJSONString(content));
+        enqueueQwSopLogs(sopLogs);
+    }
 
     private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,String companyUserId) {
 

+ 1 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyUserImportVO.java

@@ -117,6 +117,7 @@ public class CompanyUserImportVO extends BaseEntity {
 
     private String voicePrintUrl;
 
+    @Excel(name = "销售区域编号")
     private String addressId;
 
     /** 看课域名 */

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

@@ -297,4 +297,7 @@ public interface FsUserCourseMapper
             "         AND user_id =#{userId}" +
             "         ORDER BY create_time DESC")
     List<OptionsVO> selectFsUserCourseAllListByUserId(@Param("userId") Long userId);
+
+    @Select("select course_id,course_name,description,img_url,second_img secondImg,views from fs_user_course where course_id = #{courseId} and is_del = 0")
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(@Param("courseId") Long courseId);
 }

+ 10 - 4
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -6,10 +6,7 @@ import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.param.FsUserCourseVideoListUParam;
 import com.fs.course.param.FsUserCourseVideoParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
-import com.fs.course.vo.FsCourseVideoListBySidebarVO;
-import com.fs.course.vo.FsUserCourseVO;
-import com.fs.course.vo.FsUserCourseVideoListUVO;
-import com.fs.course.vo.FsUserCourseVideoVO;
+import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.Param;
@@ -222,4 +219,13 @@ public interface FsUserCourseVideoMapper
 
     FsUserCourseVideo selectFsUserCourseVideoByVideoIdAndUserId(@Param("videoId") Long videoId,@Param("userId") Long userId);
 
+    /**
+     * 查询选择使用的视频列表
+     */
+    List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据视频id集合查询列表
+     */
+    List<FsUserCourseVideoAppletVO> getFsUserCourseVideoAppletVOListByIds(@Param("videoIds") List<Long> videoIds);
 }

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

@@ -128,4 +128,6 @@ public interface IFsUserCourseService
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
 
     List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId);
+
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(Long courseId);
 }

+ 6 - 4
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -8,10 +8,7 @@ import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
 import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
-import com.fs.course.vo.FsCourseVideoListBySidebarVO;
-import com.fs.course.vo.FsUserCourseVideoListUVO;
-import com.fs.course.vo.FsUserCourseVideoQVO;
-import com.fs.course.vo.FsUserCourseVideoVO;
+import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoDetailsVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoLinkDetailsVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
@@ -193,4 +190,9 @@ public interface IFsUserCourseVideoService
     R isAddKfIsOpen(FsUserCourseVideoAddKfUParam param);
 
     R getInternetTrafficIsOpen(FsUserCourseVideoFinishUParam param);
+
+    /**
+     * 查询选择使用的视频列表
+     */
+    List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params);
 }

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

@@ -710,6 +710,11 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return fsUserCourseMapper.selectFsUserCourseVideoAppletByCourseId(courseId);
     }
 
+    @Override
+    public List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(Long courseId) {
+        return fsUserCourseMapper.selectFsUserCourseVideoAppletListByCourseId(courseId);
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();

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

@@ -34,10 +34,7 @@ import com.fs.course.param.newfs.*;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsVideoResourceService;
-import com.fs.course.vo.FsCourseVideoListBySidebarVO;
-import com.fs.course.vo.FsUserCourseVideoListUVO;
-import com.fs.course.vo.FsUserCourseVideoQVO;
-import com.fs.course.vo.FsUserCourseVideoVO;
+import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.*;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserIntegralLogs;
@@ -2774,5 +2771,13 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return R.ok("生成看课记录成功!");
     }
 
+    /**
+     * 查询选择使用的视频列表
+     */
+    @Override
+    public List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params) {
+        return fsUserCourseVideoMapper.getChooseCourseVideoListByMap(params);
+    }
+
 }
 

+ 23 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoChooseVO.java

@@ -0,0 +1,23 @@
+package com.fs.course.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class FsUserCourseVideoChooseVO {
+
+    @ApiModelProperty("小节ID")
+    private Long videoId;
+
+    @ApiModelProperty("课程名称")
+    private String courseName;
+
+    @ApiModelProperty("小节名称")
+    private String courseVideoName;
+
+    @ApiModelProperty("文件名称")
+    private String videoName;
+
+    @ApiModelProperty("时长")
+    private Integer duration;
+}

+ 60 - 0
fs-service/src/main/java/com/fs/event/ActiveClickEvent.java

@@ -0,0 +1,60 @@
+package com.fs.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+import java.util.Arrays;
+
+@Getter
+public class ActiveClickEvent extends ApplicationEvent {
+
+    private final Long activeId;
+    private final Long userId;
+    private final Type type;
+    private final ProductType productType;
+    private final Long resourceId;
+
+    public ActiveClickEvent(Object source, Long activeId, Long userId, Type type) {
+        super(source);
+        this.activeId = activeId;
+        this.userId = userId;
+        this.type = type;
+        this.productType = null;
+        this.resourceId = null;
+    }
+
+    public ActiveClickEvent(Object source, Long activeId, Long userId, Type type, ProductType productType, Long resourceId) {
+        super(source);
+        this.activeId = activeId;
+        this.userId = userId;
+        this.type = type;
+        this.productType = productType;
+        this.resourceId = resourceId;
+    }
+
+    @Getter
+    @AllArgsConstructor
+    public enum Type {
+        HOME(1), VIDEO(2), DOCTOR(3), PRODUCT(4);
+        private final Integer value;
+        public static Type valueOf(Integer value) {
+            if (value == null)
+                return null;
+            return Arrays.stream(Type.values()).filter(t -> t.value.equals(value)).findFirst().orElse(null);
+        }
+    }
+
+    @Getter
+    @AllArgsConstructor
+    public enum ProductType {
+        PACKAGE(1), INTEGRAL(2);
+        private final Integer value;
+        public static ProductType valueOf(Integer value) {
+            if (value == null)
+                return null;
+            return Arrays.stream(ProductType.values()).filter(productType -> productType.value.equals(value)).findFirst().orElse(null);
+        }
+    }
+
+}

+ 27 - 0
fs-service/src/main/java/com/fs/event/ActiveClickListener.java

@@ -0,0 +1,27 @@
+package com.fs.event;
+
+import com.fs.his.service.IFsPromotionalActiveLogService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+@Slf4j
+@Component
+public class ActiveClickListener {
+
+    @Autowired
+    private IFsPromotionalActiveLogService activeLogService;
+
+    @Async
+    @EventListener
+    public void handleActiveClick(ActiveClickEvent event) {
+        log.debug("用户点击行为: event: {}", event);
+
+        Integer productType = Objects.isNull(event.getProductType()) ? null : event.getProductType().getValue();
+        activeLogService.saveClickActionLog(event.getActiveId(), event.getUserId(), event.getType().getValue(), productType, event.getResourceId());
+    }
+}

+ 2 - 0
fs-service/src/main/java/com/fs/his/domain/FsAdv.java

@@ -55,5 +55,7 @@ public class FsAdv extends BaseEntity
     @Excel(name = "显示类型 1公众号链接 2 小程序页面地址 3文章内容")
     private Integer showType;
 
+    /** 宣传活动ID **/
+    private Long activeId;
 
 }

+ 42 - 0
fs-service/src/main/java/com/fs/his/domain/FsPromotionalActive.java

@@ -0,0 +1,42 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("fs_promotional_active")
+public class FsPromotionalActive {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 活动标题文案
+     */
+    private String title;
+    /**
+     * 活动主题
+     */
+    private String theme;
+    /**
+     * 活动内容
+     */
+    private String content;
+    /**
+     * 是否删除 0正常 1删除
+     */
+    private Integer isDel;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+}

+ 42 - 0
fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveLog.java

@@ -0,0 +1,42 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("fs_promotional_active_log")
+public class FsPromotionalActiveLog {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 活动ID
+     */
+    private Long activeId;
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    /**
+     * 资源ID
+     */
+    private Long resourceId;
+    /**
+     * 模块类型 1.首页 2.视频 3.医生 4.产品
+     */
+    private Integer type;
+    /**
+     * 产品类型 1.疗法 2.积分商品
+     */
+    private Integer productType;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 39 - 0
fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveResource.java

@@ -0,0 +1,39 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("fs_promotional_active_resource")
+public class FsPromotionalActiveResource {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 活动ID
+     */
+    private Long activeId;
+    /**
+     * 资源ID
+     */
+    private Long resourceId;
+    /**
+     * 资源类型 1.视频 2.医生 3.产品
+     */
+    private Integer type;
+    /**
+     * 产品类型 1.疗法 2.积分商品
+     */
+    private Integer productType;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 38 - 0
fs-service/src/main/java/com/fs/his/dto/FsPromotionalActiveDTO.java

@@ -0,0 +1,38 @@
+package com.fs.his.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+@Data
+public class FsPromotionalActiveDTO {
+
+    @ApiModelProperty("活动ID")
+    private Long id;
+
+    @NotBlank(message = "活动标题不能为空")
+    @ApiModelProperty("活动标题")
+    private String title;
+
+    @NotBlank(message = "活动主题不能为空")
+    @ApiModelProperty("活动主题")
+    private String theme;
+
+    @NotBlank(message = "活动内容不能为空")
+    @ApiModelProperty("活动内容")
+    private String content;
+
+    @ApiModelProperty("积分商品")
+    private List<Long> goodsIds;
+
+    @ApiModelProperty("套餐包商品")
+    private List<Long> packageIds;
+
+    @ApiModelProperty("问诊医生")
+    private List<Long> doctorIds;
+
+    @ApiModelProperty("视频小节")
+    private List<Long> videoIds;
+}

+ 11 - 0
fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java

@@ -1,6 +1,7 @@
 package com.fs.his.mapper;
 
 import java.util.List;
+import java.util.Map;
 
 import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsDoctor;
@@ -209,4 +210,14 @@ public interface FsDoctorMapper
             "order by doctor_id desc"+
             "</script>"})
     List<FsDoctorVO> selectDocVOByNameAndPhone(@Param("param") FsDoctorParam param);
+
+    /**
+     * 查询医生选择列表
+     */
+    List<FsDoctorChooseVO> getChooseDoctorListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据医生id集合查询列表
+     */
+    List<FsDoctorListUVO> getFsDoctorListUVOListByIds(@Param("doctorIds") List<Long> doctorIds);
 }

+ 14 - 0
fs-service/src/main/java/com/fs/his/mapper/FsIntegralGoodsMapper.java

@@ -2,13 +2,17 @@ package com.fs.his.mapper;
 
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.param.FsIntegralGoodsListUParam;
+import com.fs.his.vo.FsGoodsVO;
+import com.fs.his.vo.FsIntegralGoodsChooseVO;
 import com.fs.his.vo.FsIntegralGoodsListUVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 积分商品Mapper接口
@@ -97,4 +101,14 @@ public interface FsIntegralGoodsMapper
 
     @Update("update fs_integral_goods set stock = stock + #{num} where goods_id = #{goodsId}")
     int addStock(@Param("goodsId") Long goodsId, @Param("num") int num);
+
+    /**
+     * 获取选择积分商品列表
+     */
+    List<FsIntegralGoodsChooseVO> getChooseIntegralGoodsListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据id集合查询积分商品列表
+     */
+    List<FsGoodsVO> getFsGoodsVOListByIds(@Param("goodsIds") List<Long> goodsIds);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPackageMapper.java

@@ -1,6 +1,8 @@
 package com.fs.his.mapper;
 
 import java.util.List;
+import java.util.Map;
+
 import com.fs.his.domain.FsPackage;
 import com.fs.his.param.FsPackageListUParam;
 import com.fs.his.param.FsPackageParam;
@@ -155,4 +157,13 @@ public interface FsPackageMapper
 
     List<FsPackage> selectFsPackageListByIds(Long[] packageIds);
 
+    /**
+     * 获取套餐包选择列表
+     */
+    List<FsPackageChooseVO> getChoosePackageListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据套餐包id集合查询列表
+     */
+    List<FsGoodsVO> getFsGoodsVOListByIds(@Param("packageIds") List<Long> packageIds);
 }

+ 17 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveLogMapper.java

@@ -0,0 +1,17 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsPromotionalActiveLog;
+import com.fs.his.vo.FsPromotionalActiveStatVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+public interface FsPromotionalActiveLogMapper extends BaseMapper<FsPromotionalActiveLog> {
+
+    /**
+     * 活动行为统计
+     */
+    List<FsPromotionalActiveStatVO> getPromotionalActiveLogStatByMap(@Param("params") Map<String, Object> params);
+}

+ 29 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveMapper.java

@@ -0,0 +1,29 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsPromotionalActive;
+import com.fs.his.vo.FsPromotionalActiveVO;
+import com.fs.his.vo.OptionsVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+public interface FsPromotionalActiveMapper extends BaseMapper<FsPromotionalActive> {
+
+    /**
+     * 查询活动列表
+     */
+    List<FsPromotionalActiveVO> selectPromotionalActiveVOList(FsPromotionalActive active);
+
+    /**
+     * 根据ID查询活动详情
+     */
+    FsPromotionalActiveVO selectPromotionalActiveVOById(@Param("activeId") Long activeId);
+
+    /**
+     * 获取活动选项列表
+     */
+    @Select("select title as dictLabel, id as dictValue from fs_promotional_active where is_del = 0")
+    List<OptionsVO> getPromotionalActiveOption();
+}

+ 15 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveResourceMapper.java

@@ -0,0 +1,15 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsPromotionalActiveResource;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface FsPromotionalActiveResourceMapper extends BaseMapper<FsPromotionalActiveResource> {
+
+    /**
+     * 批量添加
+     */
+    void insertBatch(@Param("resources") List<FsPromotionalActiveResource> resources);
+}

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

@@ -7,6 +7,7 @@ import com.fs.his.param.FsUpdateFollowParam;
 import com.fs.his.vo.*;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 医生管理Service接口
@@ -109,4 +110,9 @@ public interface IFsDoctorService
     String selectDoctorByIds(String doctorId);
 
     List<FsDoctorVO> selectDocVOByNameAndPhone(FsDoctorParam param);
+
+    /**
+     * 查询医生选择列表
+     */
+    List<FsDoctorChooseVO> getChooseDoctorListByMap(Map<String, Object> params);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/his/service/IFsIntegralGoodsService.java

@@ -3,10 +3,12 @@ package com.fs.his.service;
 import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.param.FsIntegralGoodsListUParam;
+import com.fs.his.vo.FsIntegralGoodsChooseVO;
 import com.fs.his.vo.FsIntegralGoodsListUVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 积分商品Service接口
@@ -71,4 +73,9 @@ public interface IFsIntegralGoodsService
     String importIntegralGoodsService(List<FsIntegralGoods> list);
 
     R getCourseIntegralGoods(Long userId);
+
+    /**
+     * 获取选择积分商品列表
+     */
+    List<FsIntegralGoodsChooseVO> getChooseIntegralGoodsListByMap(Map<String, Object> params);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/his/service/IFsPackageService.java

@@ -1,6 +1,8 @@
 package com.fs.his.service;
 
 import java.util.List;
+import java.util.Map;
+
 import com.fs.his.domain.FsPackage;
 import com.fs.his.param.FsPackageListUParam;
 import com.fs.his.param.FsPackageParam;
@@ -90,4 +92,9 @@ public interface IFsPackageService
     List<FsPackage> selectFsPackageListByIds(Long[] packageIds);
 
     int bulkCopyFsPackageByPackage(Long[] packageIds);
+
+    /**
+     * 获取套餐包选择列表
+     */
+    List<FsPackageChooseVO> getChoosePackageListByMap(Map<String, Object> params);
 }

+ 21 - 0
fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveLogService.java

@@ -0,0 +1,21 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsPromotionalActiveLog;
+import com.fs.his.vo.FsPromotionalActiveStatVO;
+
+import java.util.List;
+import java.util.Map;
+
+public interface IFsPromotionalActiveLogService extends IService<FsPromotionalActiveLog> {
+
+    /**
+     * 记录用户点击行为
+     */
+    void saveClickActionLog(Long activeId, Long userId, Integer type, Integer productType, Long resourceId);
+
+    /**
+     * 活动行为统计
+     */
+    List<FsPromotionalActiveStatVO> getPromotionalActiveLogStatByMap(Map<String, Object> params);
+}

+ 7 - 0
fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveResourceService.java

@@ -0,0 +1,7 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsPromotionalActiveResource;
+
+public interface IFsPromotionalActiveResourceService extends IService<FsPromotionalActiveResource> {
+}

+ 48 - 0
fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveService.java

@@ -0,0 +1,48 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsPromotionalActive;
+import com.fs.his.dto.FsPromotionalActiveDTO;
+import com.fs.his.vo.FsPromotionalActiveDetailVO;
+import com.fs.his.vo.FsPromotionalActiveVO;
+import com.fs.his.vo.OptionsVO;
+
+import java.util.List;
+
+public interface IFsPromotionalActiveService extends IService<FsPromotionalActive> {
+
+    /**
+     * 添加活动
+     */
+    void addPromotionalActive(FsPromotionalActiveDTO param);
+
+    /**
+     * 修改活动
+     */
+    void editPromotionalActive(FsPromotionalActiveDTO param);
+
+    /**
+     * 查询活动列表
+     */
+    List<FsPromotionalActiveVO> selectPromotionalActiveVOList(FsPromotionalActive active);
+
+    /**
+     * 根据ID查询活动详情
+     */
+    FsPromotionalActiveVO selectPromotionalActiveVOById(Long activeId);
+
+    /**
+     * 逻辑删除
+     */
+    void logicalRemove(Long[] activeIds);
+
+    /**
+     * 根据活动ID查询活动详情
+     */
+    FsPromotionalActiveDetailVO getActiveDetail(Long activeId);
+
+    /**
+     * 获取活动选项列表
+     */
+    List<OptionsVO> getPromotionalActiveOption();
+}

+ 8 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsDoctorServiceImpl.java

@@ -496,4 +496,12 @@ public class FsDoctorServiceImpl implements IFsDoctorService
         return originalImage.getSubimage(x, y, targetWidth, targetHeight);
     }
 
+    /**
+     * 查询医生选择列表
+     */
+    @Override
+    public List<FsDoctorChooseVO> getChooseDoctorListByMap(Map<String, Object> params) {
+        return fsDoctorMapper.getChooseDoctorListByMap(params);
+    }
+
 }

+ 10 - 3
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java

@@ -13,6 +13,7 @@ import com.fs.his.mapper.FsIntegralGoodsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsIntegralGoodsListUParam;
 import com.fs.his.service.IFsIntegralGoodsService;
+import com.fs.his.vo.FsIntegralGoodsChooseVO;
 import com.fs.his.vo.FsIntegralGoodsListUVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import com.fs.system.service.ISysConfigService;
@@ -20,9 +21,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
+import java.util.*;
 
 /**
  * 积分商品Service业务层处理
@@ -249,4 +248,12 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
 
         return selectedGoods;
     }
+
+    /**
+     * 获取选择积分商品列表
+     */
+    @Override
+    public List<FsIntegralGoodsChooseVO> getChooseIntegralGoodsListByMap(Map<String, Object> params) {
+        return fsIntegralGoodsMapper.getChooseIntegralGoodsListByMap(params);
+    }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsPackageServiceImpl.java

@@ -344,5 +344,13 @@ public class FsPackageServiceImpl implements IFsPackageService {
         }
         return 1;
     }
+
+    /**
+     * 获取套餐包选择列表
+     */
+    @Override
+    public List<FsPackageChooseVO> getChoosePackageListByMap(Map<String, Object> params) {
+        return fsPackageMapper.getChoosePackageListByMap(params);
+    }
 }
 

+ 41 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveLogServiceImpl.java

@@ -0,0 +1,41 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.his.domain.FsPromotionalActiveLog;
+import com.fs.his.mapper.FsPromotionalActiveLogMapper;
+import com.fs.his.service.IFsPromotionalActiveLogService;
+import com.fs.his.vo.FsPromotionalActiveStatVO;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class FsPromotionalActiveLogServiceImpl extends ServiceImpl<FsPromotionalActiveLogMapper, FsPromotionalActiveLog>
+        implements IFsPromotionalActiveLogService {
+
+    /**
+     * 记录用户点击行为
+     */
+    @Override
+    public void saveClickActionLog(Long activeId, Long userId, Integer type, Integer productType, Long resourceId) {
+        FsPromotionalActiveLog activeLog = new FsPromotionalActiveLog();
+        activeLog.setActiveId(activeId);
+        activeLog.setUserId(userId);
+        activeLog.setType(type);
+        activeLog.setProductType(productType);
+        activeLog.setResourceId(resourceId);
+        activeLog.setCreateTime(LocalDateTime.now());
+        baseMapper.insert(activeLog);
+    }
+
+    /**
+     * 活动行为统计
+     */
+    @Override
+    public List<FsPromotionalActiveStatVO> getPromotionalActiveLogStatByMap(Map<String, Object> params) {
+        return baseMapper.getPromotionalActiveLogStatByMap(params);
+    }
+}

+ 12 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveResourceServiceImpl.java

@@ -0,0 +1,12 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.his.domain.FsPromotionalActiveResource;
+import com.fs.his.mapper.FsPromotionalActiveResourceMapper;
+import com.fs.his.service.IFsPromotionalActiveResourceService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FsPromotionalActiveResourceServiceImpl extends ServiceImpl<FsPromotionalActiveResourceMapper, FsPromotionalActiveResource>
+        implements IFsPromotionalActiveResourceService {
+}

+ 276 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveServiceImpl.java

@@ -0,0 +1,276 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.bean.BeanUtils;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.his.domain.FsPromotionalActive;
+import com.fs.his.domain.FsPromotionalActiveResource;
+import com.fs.his.dto.FsPromotionalActiveDTO;
+import com.fs.his.mapper.*;
+import com.fs.his.service.IFsPromotionalActiveService;
+import com.fs.his.vo.FsGoodsVO;
+import com.fs.his.vo.FsPromotionalActiveDetailVO;
+import com.fs.his.vo.FsPromotionalActiveVO;
+import com.fs.his.vo.OptionsVO;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Service
+public class FsPromotionalActiveServiceImpl extends ServiceImpl<FsPromotionalActiveMapper, FsPromotionalActive> implements IFsPromotionalActiveService {
+
+    @Resource
+    private FsPromotionalActiveResourceMapper fsPromotionalActiveResourceMapper;
+    @Resource
+    private FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Resource
+    private FsDoctorMapper fsDoctorMapper;
+    @Resource
+    private FsIntegralGoodsMapper fsIntegralGoodsMapper;
+    @Resource
+    private FsPackageMapper fsPackageMapper;
+
+    /**
+     * 添加活动
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void addPromotionalActive(FsPromotionalActiveDTO param) {
+        FsPromotionalActive promotionalActive = new FsPromotionalActive();
+        promotionalActive.setTitle(param.getTitle());
+        promotionalActive.setTheme(param.getTheme());
+        promotionalActive.setContent(param.getContent());
+        promotionalActive.setCreateTime(LocalDateTime.now());
+        baseMapper.insert(promotionalActive);
+
+        List<FsPromotionalActiveResource> resources = buildResourceParam(param, promotionalActive.getId());
+        if (!resources.isEmpty()) {
+            fsPromotionalActiveResourceMapper.insertBatch(resources);
+        }
+    }
+
+    /**
+     * 组装参数
+     */
+    private List<FsPromotionalActiveResource> buildResourceParam(FsPromotionalActiveDTO param, Long activeId) {
+        List<FsPromotionalActiveResource> resources = new ArrayList<>();
+        if (Objects.nonNull(param.getVideoIds()) && !param.getVideoIds().isEmpty()) {
+            param.getVideoIds().forEach(videoId -> {
+               FsPromotionalActiveResource resource = new FsPromotionalActiveResource();
+               resource.setActiveId(activeId);
+               resource.setType(1);
+               resource.setResourceId(videoId);
+               resource.setCreateTime(LocalDateTime.now());
+               resources.add(resource);
+            });
+        }
+
+        if (Objects.nonNull(param.getDoctorIds()) && !param.getDoctorIds().isEmpty()) {
+            param.getDoctorIds().forEach(doctorId -> {
+                FsPromotionalActiveResource resource = new FsPromotionalActiveResource();
+                resource.setActiveId(activeId);
+                resource.setType(2);
+                resource.setResourceId(doctorId);
+                resource.setCreateTime(LocalDateTime.now());
+                resources.add(resource);
+            });
+        }
+
+        if (Objects.nonNull(param.getPackageIds()) && !param.getPackageIds().isEmpty()) {
+            param.getPackageIds().forEach(packageId -> {
+                FsPromotionalActiveResource resource = new FsPromotionalActiveResource();
+                resource.setActiveId(activeId);
+                resource.setType(3);
+                resource.setProductType(1);
+                resource.setResourceId(packageId);
+                resource.setCreateTime(LocalDateTime.now());
+                resources.add(resource);
+            });
+        }
+
+        if (Objects.nonNull(param.getGoodsIds()) && !param.getGoodsIds().isEmpty()) {
+            param.getGoodsIds().forEach(goodsId -> {
+                FsPromotionalActiveResource resource = new FsPromotionalActiveResource();
+                resource.setActiveId(activeId);
+                resource.setType(3);
+                resource.setProductType(2);
+                resource.setResourceId(goodsId);
+                resource.setCreateTime(LocalDateTime.now());
+                resources.add(resource);
+            });
+        }
+
+        return resources;
+    }
+
+    /**
+     * 修改活动
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void editPromotionalActive(FsPromotionalActiveDTO param) {
+        if (Objects.isNull(param.getId())) {
+            throw new ServiceException("活动ID不能为空");
+        }
+
+        FsPromotionalActive promotionalActive = baseMapper.selectById(param.getId());
+        promotionalActive.setTitle(param.getTitle());
+        promotionalActive.setTheme(param.getTheme());
+        promotionalActive.setContent(param.getContent());
+        promotionalActive.setUpdateTime(LocalDateTime.now());
+        baseMapper.updateById(promotionalActive);
+
+        Wrapper<FsPromotionalActiveResource> deleteWrapper = Wrappers.<FsPromotionalActiveResource>lambdaQuery()
+                .eq(FsPromotionalActiveResource::getActiveId, param.getId());
+        fsPromotionalActiveResourceMapper.delete(deleteWrapper);
+
+        List<FsPromotionalActiveResource> resources = buildResourceParam(param, promotionalActive.getId());
+        if (!resources.isEmpty()) {
+            fsPromotionalActiveResourceMapper.insertBatch(resources);
+        }
+    }
+
+    /**
+     * 查询活动列表
+     */
+    @Override
+    public List<FsPromotionalActiveVO> selectPromotionalActiveVOList(FsPromotionalActive active) {
+        return baseMapper.selectPromotionalActiveVOList(active);
+    }
+
+    /**
+     * 根据ID查询活动详情
+     */
+    @Override
+    public FsPromotionalActiveVO selectPromotionalActiveVOById(Long activeId) {
+        FsPromotionalActiveVO vo = baseMapper.selectPromotionalActiveVOById(activeId);
+
+        Wrapper<FsPromotionalActiveResource> queryWrapper = Wrappers.<FsPromotionalActiveResource>lambdaQuery()
+                .eq(FsPromotionalActiveResource::getActiveId, activeId);
+        List<FsPromotionalActiveResource> resources = fsPromotionalActiveResourceMapper.selectList(queryWrapper);
+
+        List<Long> videoIds = resources.stream()
+                .filter(resource -> resource.getType() == 1)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+        if (!videoIds.isEmpty()) {
+            Map<String, Object> params = new HashMap<>();
+            params.put("videoIds", videoIds);
+            vo.setVideoList(fsUserCourseVideoMapper.getChooseCourseVideoListByMap(params));
+        }
+
+        List<Long> doctorIds = resources.stream()
+                .filter(resource -> resource.getType() == 2)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+        if (!doctorIds.isEmpty()) {
+            Map<String, Object> params = new HashMap<>();
+            params.put("doctorIds", doctorIds);
+            vo.setDoctorList(fsDoctorMapper.getChooseDoctorListByMap(params));
+        }
+
+        List<Long> packageIds = resources.stream()
+                .filter(resource -> resource.getType() == 3 && resource.getProductType() == 1)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+        if (!packageIds.isEmpty()) {
+            Map<String, Object> params = new HashMap<>();
+            params.put("packageIds", packageIds);
+            vo.setPackageList(fsPackageMapper.getChoosePackageListByMap(params));
+        }
+
+        List<Long> goodsIds = resources.stream()
+                .filter(resource -> resource.getType() == 3 && resource.getProductType() == 2)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+        if (!goodsIds.isEmpty()) {
+            Map<String, Object> params = new HashMap<>();
+            params.put("goodsIds", goodsIds);
+            vo.setGoodsList(fsIntegralGoodsMapper.getChooseIntegralGoodsListByMap(params));
+        }
+        return vo;
+    }
+
+    /**
+     * 逻辑删除
+     */
+    @Override
+    public void logicalRemove(Long[] activeIds) {
+        if (activeIds.length == 0) {
+            return;
+        }
+
+        baseMapper.update(
+                null,
+                Wrappers.<FsPromotionalActive>lambdaUpdate()
+                .set(FsPromotionalActive::getIsDel, 1)
+                .set(FsPromotionalActive::getUpdateTime, LocalDateTime.now())
+                .in(FsPromotionalActive::getId, Arrays.asList(activeIds))
+        );
+    }
+
+    /**
+     * 根据活动ID查询活动详情
+     */
+    @Override
+    public FsPromotionalActiveDetailVO getActiveDetail(Long activeId) {
+        FsPromotionalActiveVO vo = baseMapper.selectPromotionalActiveVOById(activeId);
+
+        Wrapper<FsPromotionalActiveResource> queryWrapper = Wrappers.<FsPromotionalActiveResource>lambdaQuery()
+                .eq(FsPromotionalActiveResource::getActiveId, activeId);
+        List<FsPromotionalActiveResource> resources = fsPromotionalActiveResourceMapper.selectList(queryWrapper);
+
+        FsPromotionalActiveDetailVO detailVO = new FsPromotionalActiveDetailVO();
+        BeanUtils.copyProperties(vo, detailVO);
+
+        List<Long> videoIds = resources.stream()
+                .filter(resource -> resource.getType() == 1)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+        if (!videoIds.isEmpty()) {
+            detailVO.setVideoList(fsUserCourseVideoMapper.getFsUserCourseVideoAppletVOListByIds(videoIds));
+        }
+
+        List<Long> doctorIds = resources.stream()
+                .filter(resource -> resource.getType() == 2)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+        if (!doctorIds.isEmpty()) {
+            detailVO.setDoctorList(fsDoctorMapper.getFsDoctorListUVOListByIds(doctorIds));
+        }
+
+        List<Long> packageIds = resources.stream()
+                .filter(resource -> resource.getType() == 3 && resource.getProductType() == 1)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+
+        List<Long> goodsIds = resources.stream()
+                .filter(resource -> resource.getType() == 3 && resource.getProductType() == 2)
+                .map(FsPromotionalActiveResource::getResourceId)
+                .collect(Collectors.toList());
+
+        List<FsGoodsVO> goodsList = Stream.concat(
+                packageIds.isEmpty() ? Stream.empty() : fsPackageMapper.getFsGoodsVOListByIds(packageIds).stream(),
+                goodsIds.isEmpty() ? Stream.empty() : fsIntegralGoodsMapper.getFsGoodsVOListByIds(goodsIds).stream()
+        ).collect(Collectors.toList());
+
+        detailVO.setGoodsList(goodsList);
+        return detailVO;
+    }
+
+    /**
+     * 获取活动选项列表
+     */
+    @Override
+    public List<OptionsVO> getPromotionalActiveOption() {
+        return baseMapper.getPromotionalActiveOption();
+    }
+}

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

@@ -656,6 +656,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
 
             // 用户领取红包次数+1
             redisTemplateInteger.opsForValue().increment(userLimitKey, 1);
+
             return result;
         }catch (Exception e){
             logger.error("领取红包失败原因:{}", ExceptionUtils.getFullStackTrace(e),e);
@@ -729,10 +730,9 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             TransferBillsResult transferBillsResult = transferService.transferBills(request);
             logger.info("商家转账支付完成:[msg:{}]", transferBillsResult);
             return R.ok("发送红包成功").put("data", transferBillsResult);
-        } catch (WxPayException e) {
-            e.printStackTrace();
-            logger.info("商家转账支付失败:[msg:{}]", e.getMessage());
-            return R.error("发送失败");
+        } catch (Exception e) {
+            logger.error("商家转账支付失败:参数: {} :原因: {}",JSON.toJSONString(param), e.getMessage(),e);
+            throw new RuntimeException(e);
         }
     }
 
@@ -781,9 +781,9 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         try {
             TransferBatchesResult transferBatchesResult = transferService.transferBatches(request);
             return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo()).put("batchId", transferBatchesResult.getBatchId());
-        } catch (WxPayException e) {
-            e.printStackTrace();
-            return R.error("发送失败");
+        } catch (Exception e) {
+            logger.error("商家转账支付失败:参数: {} :原因: {}",JSON.toJSONString(param), e.getMessage(),e);
+            throw new RuntimeException(e);
         }
     }
 

+ 23 - 0
fs-service/src/main/java/com/fs/his/vo/FsDoctorChooseVO.java

@@ -0,0 +1,23 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class FsDoctorChooseVO {
+
+    @ApiModelProperty("医生ID")
+    private Long doctorId;
+
+    @ApiModelProperty("医生姓名")
+    private String doctorName;
+
+    @ApiModelProperty("所属医院")
+    private String hospitalName;
+
+    @ApiModelProperty("所属科室")
+    private String deptName;
+
+    @ApiModelProperty("职称")
+    private String position;
+}

+ 31 - 0
fs-service/src/main/java/com/fs/his/vo/FsGoodsVO.java

@@ -0,0 +1,31 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class FsGoodsVO {
+
+    @ApiModelProperty("类型 1.疗法 2.积分商品")
+    private Integer type;
+
+    @ApiModelProperty("ID")
+    private Long id;
+
+    @ApiModelProperty("名称")
+    private String name;
+
+    @ApiModelProperty("封面图")
+    private String imgUrl;
+
+    @ApiModelProperty("价格")
+    private BigDecimal price;
+
+    @ApiModelProperty("销量")
+    private BigDecimal sales;
+
+    @ApiModelProperty("所需积分")
+    private Integer integral;
+}

+ 31 - 0
fs-service/src/main/java/com/fs/his/vo/FsIntegralGoodsChooseVO.java

@@ -0,0 +1,31 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class FsIntegralGoodsChooseVO {
+
+    @ApiModelProperty("商品ID")
+    private Long goodsId;
+
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+
+    @ApiModelProperty("商品分类")
+    private String goodsTypeName;
+
+    @ApiModelProperty("商品封面图片")
+    private String goodsImg;
+
+    @ApiModelProperty("商品价格")
+    private BigDecimal cash;
+
+    @ApiModelProperty("所需积分")
+    private Integer integral;
+
+    @ApiModelProperty("库存")
+    private Integer stock;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/his/vo/FsPackageChooseVO.java

@@ -0,0 +1,26 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class FsPackageChooseVO {
+
+    @ApiModelProperty("套餐包ID")
+    private Long packageId;
+
+    @ApiModelProperty("套餐包封面图片")
+    private String packageImg;
+
+    @ApiModelProperty("套餐包名称")
+    private String packageName;
+
+    @ApiModelProperty("套餐包别名")
+    private String secondName;
+
+    @ApiModelProperty("套餐包类型")
+    private String packageTypeName;
+
+    @ApiModelProperty("套餐包子类型")
+    private String packageSubTypeName;
+}

+ 38 - 0
fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveDetailVO.java

@@ -0,0 +1,38 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.course.vo.FsUserCourseVideoAppletVO;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class FsPromotionalActiveDetailVO {
+
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    @ApiModelProperty("活动标题")
+    private String title;
+
+    @ApiModelProperty("活动主题")
+    private String theme;
+
+    @ApiModelProperty("活动内容")
+    private String content;
+
+    @ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("积分商品")
+    List<FsGoodsVO> goodsList;
+
+    @ApiModelProperty("问诊医生")
+    List<FsDoctorListUVO> doctorList;
+
+    @ApiModelProperty("视频小节")
+    List<FsUserCourseVideoAppletVO> videoList;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveStatVO.java

@@ -0,0 +1,23 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class FsPromotionalActiveStatVO {
+
+    @ApiModelProperty("活动标题")
+    private String title;
+
+    @ApiModelProperty("首页浏览量")
+    private Integer homeViews;
+
+    @ApiModelProperty("视频区点击量")
+    private Integer videoClick;
+
+    @ApiModelProperty("问诊区点击量")
+    private Integer doctorClick;
+
+    @ApiModelProperty("产品区点击量")
+    private Integer goodsClick;
+}

+ 41 - 0
fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveVO.java

@@ -0,0 +1,41 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.course.vo.FsUserCourseVideoChooseVO;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class FsPromotionalActiveVO {
+
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    @ApiModelProperty("活动标题")
+    private String title;
+
+    @ApiModelProperty("活动主题")
+    private String theme;
+
+    @ApiModelProperty("活动内容")
+    private String content;
+
+    @ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("积分商品")
+    private List<FsIntegralGoodsChooseVO> goodsList;
+
+    @ApiModelProperty("疗法")
+    private List<FsPackageChooseVO> packageList;
+
+    @ApiModelProperty("问诊医生")
+    private List<FsDoctorChooseVO> doctorList;
+
+    @ApiModelProperty("视频小节")
+    private List<FsUserCourseVideoChooseVO> videoList;
+}

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

@@ -338,7 +338,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         product.setStoreId(param.getStoreId());
         product.setIsDrug(param.getIsDrug().toString());
         //校验店铺资质信息
-        if(!("益善缘".equals(cloudHostProper.getCompanyName())) && !("康年堂".equals(cloudHostProper.getCompanyName()))){
+        if(!("益善缘".equals(cloudHostProper.getCompanyName())) && !("康年堂".equals(cloudHostProper.getCompanyName())) && !("纯正堂".equals(cloudHostProper.getCompanyName()))){
             //获取店铺
             FsStoreScrm store = fsStoreScrmService.selectFsStoreByStoreId(product.getStoreId());
             if(store == null || 1 != store.getStatus()){
@@ -369,6 +369,10 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             }
         }
 
+        if("纯正堂".equals(cloudHostProper.getCompanyName())){
+            product.setIsAudit("1");
+        }
+
         if(param.getProductId() != null && param.getProductId() > 0){
             //对已上架的商品进行修改需要重新审核
             if(1 == product.getIsShow() && "1".equals(product.getIsAudit())){

+ 47 - 0
fs-service/src/main/java/com/fs/qw/domain/QwPushCount.java

@@ -0,0 +1,47 @@
+package com.fs.qw.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 定义销售推送不同类型的企业消息的次数对象 qw_push_count
+ *
+ * @author fs
+ * @date 2025-08-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QwPushCount extends BaseEntity{
+
+    /** $column.columnComment */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 推送类型 */
+    @Excel(name = "推送类型")
+    private Integer type;
+
+    /** 销售推送企业微信消息的限定次数 */
+    @Excel(name = "销售推送企业微信消息的限定次数")
+    private Integer pushCount;
+
+    /** 推送公司id */
+    @Excel(name = "推送公司id")
+    private Long companyId;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Integer status;
+
+    // 标记该字段不与数据库字段对应
+    @TableField(exist = false)
+    private List<Long> companyIdList;
+
+}

+ 39 - 0
fs-service/src/main/java/com/fs/qw/domain/QwRestrictionPushRecord.java

@@ -0,0 +1,39 @@
+package com.fs.qw.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 记录销售推送成功信息
+ *
+ * @author liupeng
+ * @date 2025-08-20
+ */
+@Data
+public class QwRestrictionPushRecord implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    private Integer type;
+
+    private Long qwUserId;
+
+    private Long qwExternalId;
+
+    private Long companyId;
+
+    private Integer status;
+
+    private String createTime;
+
+    private Long time;
+
+    private String remarks;
+
+}

+ 78 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwPushCountMapper.java

@@ -0,0 +1,78 @@
+package com.fs.qw.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwPushCount;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 销售推送不同类型消息次数Mapper接口
+ *
+ * @author liupeng
+ * @date 2025-08-20
+ */
+@Repository
+public interface QwPushCountMapper extends BaseMapper<QwPushCount> {
+
+    @Select("select id,type,push_count,company_id,status from qw_push_count WHERE status=0")
+    public List<QwPushCount> selectQwPushCountLists();
+
+    @Select("select id,type,push_count,company_id,status from qw_push_count WHERE type = #{type} and company_id=#{companyId} and status=0")
+    public QwPushCount SelectQwPushCountByCompanyId(@Param("type") Integer type, @Param("companyId") Long companyId);  // 注解和SQL中的参数名都使用companyId
+
+    @Select("select id,type,push_count,company_id,status from qw_push_count WHERE type = #{type} and company_id is null and status=0")
+    public QwPushCount SelectQwPushCountByType(@Param("type") Integer type);
+
+
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数
+     *
+     * @param id 定义销售推送不同类型的企业消息的次数主键
+     * @return 定义销售推送不同类型的企业消息的次数
+     */
+    QwPushCount selectQwPushCountById(Long id);
+
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数列表
+     *
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 定义销售推送不同类型的企业消息的次数集合
+     */
+    List<QwPushCount> selectQwPushCountList(QwPushCount qwPushCount);
+
+    /**
+     * 新增定义销售推送不同类型的企业消息的次数
+     *
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 结果
+     */
+    int insertQwPushCount(QwPushCount qwPushCount);
+
+    /**
+     * 修改定义销售推送不同类型的企业消息的次数
+     *
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 结果
+     */
+    int updateQwPushCount(QwPushCount qwPushCount);
+
+    /**
+     * 删除定义销售推送不同类型的企业消息的次数
+     *
+     * @param id 定义销售推送不同类型的企业消息的次数主键
+     * @return 结果
+     */
+    int deleteQwPushCountById(Long id);
+
+    /**
+     * 批量删除定义销售推送不同类型的企业消息的次数
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteQwPushCountByIds(Long[] ids);
+
+}

+ 49 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwRestrictionPushRecordMapper.java

@@ -0,0 +1,49 @@
+package com.fs.qw.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwRestrictionPushRecord;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 记录销售推送成功信息Mapper接口
+ *
+ * @author liupeng
+ * @date 2025-08-20
+ */
+@Repository
+public interface QwRestrictionPushRecordMapper extends BaseMapper<QwRestrictionPushRecord> {
+
+    int insert(QwRestrictionPushRecord qwRestrictionPushRecord);
+    /**
+     * 查询当前销售ID对应类型的QW推送成功的记录
+     *
+     * @param type
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    @Select("select count(id) from qw_restriction_push_record where qw_user_id=#{qwUserId} and qw_external_id=#{qwExternalId} and type=#{type} and time>#{startTime} and time<=#{endTime}")
+    int selectQwRestrictionPushRecord(@Param("qwUserId") Long qwUserId,
+                                      @Param("qwExternalId") Long qwExternalId,
+                                      @Param("type") Integer type,
+                                      @Param("startTime") Long startTime,
+                                      @Param("endTime") Long endTime);
+
+    @Select("select id from qw_restriction_push_record where time<#{time}")
+    List<Long> selectExpirePushRecord(@Param("time") Long time);
+
+    @Delete("<script>" +
+            "delete from qw_restriction_push_record " +
+            "where id in " +
+            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    public int deleteQwRestrictionPushRecordIds(@Param("ids") Long[] ids);
+}
+

+ 68 - 0
fs-service/src/main/java/com/fs/qw/service/IQwPushCountService.java

@@ -0,0 +1,68 @@
+package com.fs.qw.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.QwPushCount;
+
+import java.util.List;
+
+/**
+ * 定义销售推送不同类型的企业消息的次数Service接口
+ * 
+ * @author fs
+ * @date 2025-08-22
+ */
+public interface IQwPushCountService extends IService<QwPushCount>{
+
+    public List<QwPushCount> selectQwPushCountLists();
+
+    public QwPushCount SelectQwPushCountByCompanyId( Integer type,Long companyId);  // 注解和SQL中的参数名都使用companyId
+
+    public QwPushCount SelectQwPushCountByType(Integer type);
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数
+     * 
+     * @param id 定义销售推送不同类型的企业消息的次数主键
+     * @return 定义销售推送不同类型的企业消息的次数
+     */
+    QwPushCount selectQwPushCountById(Long id);
+
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数列表
+     * 
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 定义销售推送不同类型的企业消息的次数集合
+     */
+    List<QwPushCount> selectQwPushCountList(QwPushCount qwPushCount);
+
+    /**
+     * 新增定义销售推送不同类型的企业消息的次数
+     * 
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 结果
+     */
+    int insertQwPushCount(QwPushCount qwPushCount);
+
+    /**
+     * 修改定义销售推送不同类型的企业消息的次数
+     * 
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 结果
+     */
+    int updateQwPushCount(QwPushCount qwPushCount);
+
+    /**
+     * 批量删除定义销售推送不同类型的企业消息的次数
+     * 
+     * @param ids 需要删除的定义销售推送不同类型的企业消息的次数主键集合
+     * @return 结果
+     */
+    int deleteQwPushCountByIds(Long[] ids);
+
+    /**
+     * 删除定义销售推送不同类型的企业消息的次数信息
+     * 
+     * @param id 定义销售推送不同类型的企业消息的次数主键
+     * @return 结果
+     */
+    int deleteQwPushCountById(Long id);
+}

+ 106 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwPushCountServiceImpl.java

@@ -0,0 +1,106 @@
+package com.fs.qw.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.qw.domain.QwPushCount;
+import com.fs.qw.mapper.QwPushCountMapper;
+import com.fs.qw.service.IQwPushCountService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 定义销售推送不同类型的企业消息的次数Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-08-22
+ */
+@Service
+public class QwPushCountServiceImpl extends ServiceImpl<QwPushCountMapper, QwPushCount> implements IQwPushCountService {
+
+    @Override
+    public List<QwPushCount> selectQwPushCountLists() {
+        return baseMapper.selectQwPushCountLists();
+    }
+
+    @Override
+    public QwPushCount SelectQwPushCountByCompanyId(Integer type, Long companyId) {
+        return baseMapper.SelectQwPushCountByCompanyId(type, companyId);
+    }
+
+    @Override
+    public QwPushCount SelectQwPushCountByType(Integer type) {
+        return baseMapper.SelectQwPushCountByType(type);
+    }
+
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数
+     * 
+     * @param id 定义销售推送不同类型的企业消息的次数主键
+     * @return 定义销售推送不同类型的企业消息的次数
+     */
+    @Override
+    public QwPushCount selectQwPushCountById(Long id)
+    {
+        return baseMapper.selectQwPushCountById(id);
+    }
+
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数列表
+     * 
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 定义销售推送不同类型的企业消息的次数
+     */
+    @Override
+    public List<QwPushCount> selectQwPushCountList(QwPushCount qwPushCount)
+    {
+        return baseMapper.selectQwPushCountList(qwPushCount);
+    }
+
+    /**
+     * 新增定义销售推送不同类型的企业消息的次数
+     * 
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 结果
+     */
+    @Override
+    public int insertQwPushCount(QwPushCount qwPushCount)
+    {
+        return baseMapper.insertQwPushCount(qwPushCount);
+    }
+
+    /**
+     * 修改定义销售推送不同类型的企业消息的次数
+     * 
+     * @param qwPushCount 定义销售推送不同类型的企业消息的次数
+     * @return 结果
+     */
+    @Override
+    public int updateQwPushCount(QwPushCount qwPushCount)
+    {
+        return baseMapper.updateQwPushCount(qwPushCount);
+    }
+
+    /**
+     * 批量删除定义销售推送不同类型的企业消息的次数
+     * 
+     * @param ids 需要删除的定义销售推送不同类型的企业消息的次数主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwPushCountByIds(Long[] ids)
+    {
+        return baseMapper.deleteQwPushCountByIds(ids);
+    }
+
+    /**
+     * 删除定义销售推送不同类型的企业消息的次数信息
+     * 
+     * @param id 定义销售推送不同类型的企业消息的次数主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwPushCountById(Long id)
+    {
+        return baseMapper.deleteQwPushCountById(id);
+    }
+}

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

@@ -40,10 +40,7 @@ import com.fs.qw.vo.GroupUserExternalVo;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.QwTagGroupListVO;
-import com.fs.sop.domain.QwSop;
-import com.fs.sop.domain.QwSopLogs;
-import com.fs.sop.domain.SopUserLogs;
-import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.domain.*;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
@@ -53,6 +50,7 @@ import com.fs.sop.params.BatchSopUserLogsInfoParamU;
 import com.fs.sop.params.SendUserLogsInfoMsgParam;
 import com.fs.sop.params.SopUserLogsParamByDate;
 import com.fs.sop.service.IQwSopService;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.sop.service.ISopUserLogsService;
 import com.fs.sop.vo.ExtCourseSopWatchLogVO;
@@ -167,6 +165,10 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     @Autowired
     private CompanyMapper companyMapper;
 
+    @Autowired
+    private IQwSopTempVoiceService sopTempVoiceService;
+
+
 
     @Override
     public void save(SopUserLogsInfo sopUserLogsInfo) {
@@ -617,6 +619,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                                 st.setMiniprogramPage(linkByMiniApp);
                                 break;
+
+                            //语音
+                            case "7":
+                                createVoiceUrl(st, companyUserId, qwSop);
+                                break;
                         }
                     }
                     setting.setSetting(list);
@@ -716,6 +723,12 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                                 st.setMiniprogramPage(linkByMiniApp);
                                 break;
+                            //语音
+                            case "7":
+                                if (qwUser.getCompanyUserId() != null) {
+                                    createVoiceUrl(st, String.valueOf(qwUser.getCompanyUserId()), qwSop);
+                                }
+                                break;
                         }
                     }
                     setting.setSetting(list);
@@ -889,6 +902,10 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                             st.setMiniprogramPage(linkByMiniApp);
 
+                            break;
+                        //语音
+                        case "7":
+                            createVoiceUrl(st, companyUserId, qwSop);
                             break;
                         //app
                         case "9":
@@ -948,6 +965,21 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return R.ok();
     }
 
+    private void createVoiceUrl(QwSopCourseFinishTempSetting.Setting st, String companyUserId, QwSop qwSop) {
+        QwSopTempVoice qwSopTempVoice = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(Long.valueOf(companyUserId), st.getValue());
+        if (qwSopTempVoice != null && qwSopTempVoice.getVoiceUrl() != null && qwSopTempVoice.getRecordType() == 1) {
+            st.setVoiceUrl(qwSopTempVoice.getVoiceUrl());
+            st.setVoiceDuration(String.valueOf(qwSopTempVoice.getDuration()));
+        } else if (qwSopTempVoice == null) {
+            qwSopTempVoice = new QwSopTempVoice();
+            qwSopTempVoice.setCompanyUserId(Long.valueOf(companyUserId));
+            qwSopTempVoice.setVoiceTxt(st.getValue());
+            qwSopTempVoice.setRecordType(0);
+            sopTempVoiceService.insertQwSopTempVoice(qwSopTempVoice);
+        }
+    }
+
+
     @Override
     public R sendUserLogsInfoMsgType(SendUserLogsInfoMsgParam param) {
         //营期一键群发
@@ -1304,6 +1336,10 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                     st.setMiniprogramPage(linkByMiniApp);
                     break;
+                case "7":
+                    QwSop qwSop = qwSopMapper.selectQwSopById(param.getSopId());
+                    createVoiceUrl(st, companyUserId, qwSop);
+                    break;
 
                 //自定义小程序
                 case "10":

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

@@ -93,3 +93,6 @@ wx_miniapp_temp:
 
 # 0 代表关闭 1代表开启(润天老商户号的扣款限制)
 enableRedPackAccount: 0
+video:
+  videoUploadDir:
+  frameOutputDir:

+ 94 - 0
fs-service/src/main/resources/application-config-druid-ddgy.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: wx41   #
+        secret: 58910ae743005c396012b029c7def579
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+      - appid: wxe
+        secret: 928d2961c81610d8f64b019597212fcd
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wwb2a10
+    appConfigs:
+      - agentId: 100005
+        secret: ec7okROXJqkN
+        token: PPKOdAloMO
+        aesKey: PKvaxtpSvNGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wx73f85f8d6119 #微信公众号或者小程序等的appid
+    mchId: 1611045 #微信支付商户号
+    mchKey: 8cab128997a3547c10898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://usepp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx571ea3a25f27fcdd # 第一个公众号的appid
+        secret: 600edcaee6b3e267f0218deac1558f3b # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+  # 开放平台app微信授权配置
+  open:
+    app-id: wx9746858bdb5e0643
+    secret: 32dfaa2b2dcad9229935ff089c65d372
+aifabu:  #爱链接
+  appKey: 7b471be905ab17ef358c610dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+#  account: tcloud
+#  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://172.16.0.65:8010
+  h5CommonApi: http://172.16.0.65:8010
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: ddgy-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: hat
+cloud_host:
+  company_name: 叮当国医
+  projectCode: DDGY
+#看课授权时显示的头像
+headerImg:
+  imgUrl: https://ddgy-1323137866.cos.ap-chongqing.myqcloud.com/fs/20251010/ddgy.jpg
+ipad:
+  ipadUrl: http://ipad.****.cn
+  aiApi: http://62:3000/api
+  voiceApi:
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+

+ 94 - 0
fs-service/src/main/resources/application-config-druid-hat.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: wx41   #
+        secret: 58910ae743005c396012b029c7def579
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+      - appid: wxe
+        secret: 928d2961c81610d8f64b019597212fcd
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wwb2a10
+    appConfigs:
+      - agentId: 100005
+        secret: ec7okROXJqkN
+        token: PPKOdAloMO
+        aesKey: PKvaxtpSvNGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wx73f85f8d6119 #微信公众号或者小程序等的appid
+    mchId: 1611045 #微信支付商户号
+    mchKey: 8cab128997a3547c10898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://usepp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wxe4bb68ede29c94b2 # 第一个公众号的appid
+        secret: 5a0c530a497c855559ee3958f8f9443d # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+  # 开放平台app微信授权配置
+  open:
+    app-id: wx9746858bdb5e0643
+    secret: 32dfaa2b2dcad9229935ff089c65d372
+aifabu:  #爱链接
+  appKey: 7b471be905ab17ef358c610dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+#  account: tcloud
+#  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://172.16.0.45:8010
+  h5CommonApi: http://172.16.0.45:8010
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: hat-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: hat
+cloud_host:
+  company_name: 恒安图
+  projectCode: HAT
+#看课授权时显示的头像
+headerImg:
+  imgUrl: https://hat-1323137866.cos.ap-chongqing.myqcloud.com/fs/20250928/hatlogo.png
+ipad:
+  ipadUrl: http://ipad.****.cn
+  aiApi: http://62:3000/api
+  voiceApi:
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+

+ 176 - 0
fs-service/src/main/resources/application-druid-ddgy.yml

@@ -0,0 +1,176 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-ddgy,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 172.16.0.35
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: YlrztekDDGY251010!3@.
+        # 连接超时时间
+        timeout: 20s
+        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://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.16.0.69:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek251010ddgy!3@.
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: false
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.69:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek251010ddgy!3@.
+                read:
+                    url: jdbc:mysql://172.16.0.69:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek251010ddgy!3@.
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: false
+                    # 设置白名单,不填则允许所有访问
+                    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
+custom:
+    token: "1o62d3YxvdHd4LEU7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
+# token配置
+token:
+    # 令牌自定义标识
+    header: Authorization
+    # 令牌密钥
+    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌有效期(默认30分钟)
+    expireTime: 180
+openIM:
+    secret: open
+    userID: imAd
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false
+
+enableRedPackAccount: 1

+ 176 - 0
fs-service/src/main/resources/application-druid-hat.yml

@@ -0,0 +1,176 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-hat,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 172.16.0.82
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: YlrztekHat250218!3@.
+        # 连接超时时间
+        timeout: 20s
+        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://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.16.0.74:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250928hat!3@.
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: false
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.74:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250928hat!3@.
+                read:
+                    url: jdbc:mysql://172.16.0.74:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250928hat!3@.
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: false
+                    # 设置白名单,不填则允许所有访问
+                    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
+custom:
+    token: "1o62d3YxvdHd4LEU7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
+# token配置
+token:
+    # 令牌自定义标识
+    header: Authorization
+    # 令牌密钥
+    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌有效期(默认30分钟)
+    expireTime: 180
+openIM:
+    secret: open
+    userID: imAd
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false
+
+enableRedPackAccount: 1

+ 46 - 0
fs-service/src/main/resources/db/20250924-宣传活动.sql

@@ -0,0 +1,46 @@
+-- 宣传活动表
+drop table if exists `fs_promotional_active`;
+create table `fs_promotional_active` (
+    `id`            bigint not null auto_increment       comment '主键ID',
+    `title`         varchar(255) not null                comment '活动标题文案',
+    `theme`         varchar(1000) not null               comment '活动主题',
+    `content`       varchar(1000) not null               comment '活动内容',
+    `is_del`        tinyint(1) default 0                 comment '是否删除 0正常 1删除',
+    `create_time`   datetime                             comment '创建时间',
+    `update_time`   datetime                             comment '修改时间',
+    primary key (`id`) using btree,
+    key idx_is_del (`is_del`),
+    key idx_create (`create_time`)
+) engine = Innodb comment '宣传活动表';
+
+-- 宣传活动资源表
+drop table if exists `fs_promotional_active_resource`;
+create table `fs_promotional_active_resource` (
+    `id`            bigint not null auto_increment       comment '主键ID',
+    `active_id`     bigint not null                      comment '活动ID',
+    `resource_id`   bigint not null                      comment '资源ID',
+    `type`          tinyint not null                     comment '资源类型 1.视频 2.医生 3.产品',
+    `product_type`  tinyint                              comment '产品类型 1.疗法 2.积分商品',
+    `create_time`   datetime                             comment '创建时间',
+    primary key (`id`) using btree,
+    key idx_create (`create_time`)
+) engine = Innodb comment '宣传活动表';
+
+-- 宣传活动访问记录
+drop table if exists `fs_promotional_active_log`;
+create table `fs_promotional_active_log` (
+    `id`            bigint not null auto_increment       comment '主键ID',
+    `active_id`     bigint not null                      comment '活动ID',
+    `user_id`       bigint not null                      comment '用户ID',
+    `resource_id`   bigint                               comment '资源ID',
+    `type`          tinyint not null                     comment '模块类型 1.首页 2.视频 3.医生 4.产品',
+    `product_type`  tinyint                              comment '产品类型 1.疗法 2.积分商品',
+    `create_time`   datetime                             comment '创建时间',
+    primary key (`id`) using btree,
+    key idx_active_user (`active_id`, `user_id`),
+    key idx_type (`type`),
+    key idx_create (`create_time`)
+) engine = Innodb comment '宣传活动访问记录';
+
+alter table fs_adv
+    add column active_id bigint comment '宣传活动';

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

@@ -394,4 +394,72 @@
         </where>
         limit 1
     </select>
+
+    <select id="getChooseCourseVideoListByMap" resultType="com.fs.course.vo.FsUserCourseVideoChooseVO">
+        select
+            fucv.video_id,
+            fuc.course_name,
+            fucv.title courseVideoName,
+            fucv.file_name videoName,
+            fucv.duration
+        from fs_user_course_video fucv
+        inner join fs_user_course fuc on fuc.course_id = fucv.course_id
+        where fuc.is_private = 1 and fuc.is_del = 0 and fucv.is_del = 0
+        <if test="params.courseId != null">
+            and fucv.course_id = #{params.courseId}
+        </if>
+        <if test="params.userId != null">
+            and fuc.user_id = #{params.userId} and fucv.user_id = #{params.userId}
+        </if>
+        <if test="params.videoIds != null">
+            and fucv.video_id in
+            <foreach collection="params.videoIds" item="videoId" open="(" separator="," close=")">
+                #{videoId}
+            </foreach>
+        </if>
+        order by fuc.course_id, fucv.video_id
+    </select>
+
+    <resultMap id="FsUserCourseVideoAppletVOMap" type="com.fs.course.vo.FsUserCourseVideoAppletVO">
+        <id     property="courseId"   column="course_id"/>
+        <result property="courseName" column="course_name"/>
+        <result property="description" column="description"/>
+        <result property="imgUrl" column="img_url"/>
+        <result property="secondImg" column="second_img"/>
+        <result property="views" column="views"/>
+        <result property="videoTotal" column="video_total"/>
+        <collection property="fsUserCourseVideoList" ofType="com.fs.course.vo.FsUserCourseVideoAppletVO$FsUserCourseVideo">
+            <result property="videoId" column="video_id"/>
+            <result property="title" column="title"/>
+            <result property="videoImgUrl" column="video_img_url"/>
+            <result property="videoUrl" column="video_url"/>
+            <result property="videoDescription" column="video_description"/>
+            <result property="totalDuration" column="total_duration"/>
+            <result property="questionBankId" column="question_bank_id"/>
+        </collection>
+    </resultMap>
+
+    <select id="getFsUserCourseVideoAppletVOListByIds" resultMap="FsUserCourseVideoAppletVOMap">
+        SELECT
+            c.course_id         AS course_id,
+            c.course_name       AS course_name,
+            c.description       AS description,
+            c.img_url           AS img_url,
+            c.second_img        AS second_img,
+            c.views             AS views,
+            1                   AS video_total,
+            v.video_id          AS video_id,
+            v.title             AS title,
+            v.thumbnail         AS video_img_url,
+            v.video_url         AS video_url,
+            v.description       AS video_description,
+            SEC_TO_TIME(v.duration)  AS total_duration,
+            v.question_bank_id AS question_bank_id
+        FROM fs_user_course c
+        LEFT JOIN fs_user_course_video v ON v.course_id = c.course_id
+        WHERE v.video_id in
+        <foreach collection="videoIds" item="videoId" open="(" separator="," close=")">
+            #{videoId}
+        </foreach>
+    </select>
 </mapper>

+ 5 - 1
fs-service/src/main/resources/mapper/his/FsAdvMapper.xml

@@ -17,10 +17,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="sort"    column="sort"    />
         <result property="advType"    column="adv_type"    />
         <result property="showType"    column="show_type"    />
+        <result property="activeId"    column="active_id"    />
     </resultMap>
 
     <sql id="selectFsAdvVo">
-        select adv_id, adv_title, image_url, adv_url,app_adv_url, content, create_time, update_time, status, sort, adv_type, show_type from fs_adv
+        select adv_id, adv_title, image_url, adv_url,app_adv_url, content, create_time, update_time, status, sort, adv_type, show_type, active_id from fs_adv
     </sql>
 
     <select id="selectFsAdvList" parameterType="FsAdv" resultMap="FsAdvResult">
@@ -57,6 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="sort != null">sort,</if>
             <if test="advType != null">adv_type,</if>
             <if test="showType != null">show_type,</if>
+            <if test="activeId != null">active_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="advTitle != null and advTitle != ''">#{advTitle},</if>
@@ -70,6 +72,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="sort != null">#{sort},</if>
             <if test="advType != null">#{advType},</if>
             <if test="showType != null">#{showType},</if>
+            <if test="activeId != null">#{activeId},</if>
          </trim>
     </insert>
 
@@ -87,6 +90,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="sort != null">sort = #{sort},</if>
             <if test="advType != null">adv_type = #{advType},</if>
             <if test="showType != null">show_type = #{showType},</if>
+            <if test="activeId != null">active_id = #{activeId},</if>
         </trim>
         where adv_id = #{advId}
     </update>

+ 58 - 0
fs-service/src/main/resources/mapper/his/FsDoctorMapper.xml

@@ -124,6 +124,64 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </select>
 
+    <select id="getChooseDoctorListByMap" resultType="com.fs.his.vo.FsDoctorChooseVO">
+        select
+            fdoc.doctor_id,
+            fdoc.doctor_name,
+            fhos.hospital_name,
+            fdep.dept_name,
+            fdoc.position
+        from fs_doctor fdoc
+        inner join  fs_department fdep on fdep.dept_id = fdoc.dept_id
+        inner join fs_hospital fhos on fhos.hospital_id = fdoc.hospital_id
+        where fdoc.status = 1 and fdoc.doctor_type = 1 and fdoc.is_audit = 1 and fdoc.work_status = 1
+        <if test="params.doctorName != null and params.doctorName != ''">
+            and fdoc.doctor_name like concat('%', #{params.doctorName}, '%')
+        </if>
+        <if test="params.hospitalId != null">
+            and fdoc.hospital_id = #{params.hospitalId}
+        </if>
+        <if test="params.deptId != null">
+            and fdoc.dept_id = #{params.deptId}
+        </if>
+        <if test="params.position != null and params.position != ''">
+            and fdoc.position like concat('%',#{params.position}, '%')
+        </if>
+        <if test="params.mobile != null and params.mobile != ''">
+            and fdoc.mobile like concat('%', #{params.mobile}, '%')
+        </if>
+        <if test="params.doctorIds != null">
+            and fdoc.doctor_id in
+            <foreach collection="params.doctorIds" item="doctorId" open="(" separator="," close=")">
+                #{doctorId}
+            </foreach>
+        </if>
+    </select>
+
+    <select id="getFsDoctorListUVOListByIds" resultType="com.fs.his.vo.FsDoctorListUVO">
+        SELECT
+            d.doctor_id,
+            d.doctor_name,
+            d.avatar,
+            d.position,
+            d.work_status,
+            d.ping_star,
+            d.order_number,
+            d.speciality,
+            d.price_json,
+            h.hospital_name,
+            h.hospital_level,
+            de.dept_name
+        FROM fs_doctor d
+        LEFT JOIN fs_hospital h ON h.hospital_id = d.hospital_id
+        LEFT JOIN fs_department de ON de.dept_id = d.dept_id
+        WHERE d.is_audit = 1 AND d.status = 1 AND d.is_show = 1 AND d.doctor_type = 1
+        AND d.doctor_id in
+        <foreach collection="doctorIds" item="doctorId" open="(" separator="," close=")">
+            #{doctorId}
+        </foreach>
+    </select>
+
     <insert id="insertFsDoctor" parameterType="FsDoctor" useGeneratedKeys="true" keyProperty="doctorId">
         insert into fs_doctor
         <trim prefix="(" suffix=")" suffixOverrides=",">

+ 41 - 0
fs-service/src/main/resources/mapper/his/FsIntegralGoodsMapper.xml

@@ -129,4 +129,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ]]>
         ORDER BY RAND()
     </select>
+
+    <select id="getChooseIntegralGoodsListByMap" resultType="com.fs.his.vo.FsIntegralGoodsChooseVO">
+        select
+            fig.goods_id,
+            fig.goods_name,
+            fig.img_url goodsImg,
+            fig.integral,
+            fig.cash,
+            fig.stock,
+            sdd.dict_label goodsTypeName
+        from fs_integral_goods fig
+        left join sys_dict_data sdd on sdd.dict_type = 'sys_integral_goods_type' and sdd.dict_value = fig.goods_type
+        where fig.status = 1
+        <if test="params.goodsName != null and params.goodsName != ''">
+            and fig.goods_name like concat('%', #{params.goodsName}, '%')
+        </if>
+        <if test="params.goodsType != null">
+            and fig.goods_type = #{params.goodsType}
+        </if>
+        <if test="params.goodsIds != null">
+            and fig.goods_id in
+            <foreach collection="params.goodsIds" item="goodsId" open="(" separator="," close=")">
+                #{goodsId}
+            </foreach>
+        </if>
+    </select>
+
+    <select id="getFsGoodsVOListByIds" resultType="com.fs.his.vo.FsGoodsVO">
+        select
+            2           as type,
+            goods_id    as id,
+            goods_name  as name,
+            img_url     as imgUrl,
+            cash        as price,
+            integral    as integral
+        from fs_integral_goods
+        where goods_id in
+        <foreach collection="goodsIds" item="goodsId" open="(" separator="," close=")">
+            #{goodsId}
+        </foreach>
+    </select>
 </mapper>

+ 47 - 0
fs-service/src/main/resources/mapper/his/FsPackageMapper.xml

@@ -86,6 +86,53 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </select>
 
+    <select id="getChoosePackageListByMap" resultType="com.fs.his.vo.FsPackageChooseVO">
+        select
+            fp.package_id,
+            fp.img_url packageImg,
+            fp.package_name,
+            fp.second_name,
+            sdd.dict_label packageTypeName,
+            sdds.dict_label packageSubTypeName
+        from fs_package fp
+        left join sys_dict_data sdd on sdd.dict_type = 'sys_package_type' and sdd.dict_value = fp.package_type
+        left join sys_dict_data sdds on sdds.dict_type = 'sys_package_sub_type' and sdds.dict_value = fp.package_sub_type
+        where fp.is_del = 0 and fp.status = 1 and fp.is_show = 1
+        <if test="params.packageName != null and params.packageName != ''">
+            and fp.package_name like concat('%', #{params.packageName}, '%')
+        </if>
+        <if test="params.secondName != null and params.secondName != ''">
+            and fp.second_name like concat('%', #{params.secondName}, '%')
+        </if>
+        <if test="params.packageType != null">
+            and fp.package_type = #{params.packageType}
+        </if>
+        <if test="params.packageSubType != null">
+            and fp.package_sub_type = #{params.packageSubType}
+        </if>
+        <if test="params.packageIds != null">
+            and fp.package_id in
+            <foreach collection="params.packageIds" item="packageId" open="(" separator="," close=")">
+                #{packageId}
+            </foreach>
+        </if>
+    </select>
+
+    <select id="getFsGoodsVOListByIds" resultType="com.fs.his.vo.FsGoodsVO">
+        select
+            1               as type,
+            package_id      as id,
+            package_name    as name,
+            img_url         as imgUrl,
+            price           as price,
+            sales           as sales
+        from fs_package
+        where package_id in
+        <foreach collection="packageIds" item="packageId" open="(" separator="," close=")">
+            #{packageId}
+        </foreach>
+    </select>
+
     <insert id="insertFsPackage" parameterType="FsPackage" useGeneratedKeys="true" keyProperty="packageId">
         insert into fs_package
         <trim prefix="(" suffix=")" suffixOverrides=",">

+ 30 - 0
fs-service/src/main/resources/mapper/his/FsPromotionalActiveLogMapper.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.his.mapper.FsPromotionalActiveLogMapper">
+
+    <select id="getPromotionalActiveLogStatByMap" resultType="com.fs.his.vo.FsPromotionalActiveStatVO">
+        select
+        fpa.title,
+        SUM(IF(type = 1, 1, 0)) AS homeViews,
+        SUM(IF(type = 2, 1, 0)) AS videoClick,
+        SUM(IF(type = 3, 1, 0)) AS doctorClick,
+        SUM(IF(type = 4, 1, 0)) AS goodsClick
+        from fs_promotional_active_log fpal
+        left join fs_promotional_active fpa on fpal.active_id = fpa.id
+        where fpa.is_del = 0
+        <if test="params.name != null and params.name != ''">
+            and fpa.title like concat('%', #{params.name}, '%')
+        </if>
+        <if test="params.startTime != null">
+            and fpal.create_time >= #{params.startTime}
+        </if>
+        <if test="params.endTime != null">
+        <![CDATA[
+            and fpal.create_time < DATE_ADD(#{params.endTime}, INTERVAL 1 DAY)
+        ]]>
+        </if>
+        group by fpal.active_id
+    </select>
+</mapper>

+ 18 - 0
fs-service/src/main/resources/mapper/his/FsPromotionalActiveMapper.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.his.mapper.FsPromotionalActiveMapper">
+
+    <select id="selectPromotionalActiveVOList" resultType="com.fs.his.vo.FsPromotionalActiveVO">
+        select * from fs_promotional_active
+        where is_del = 0
+        <if test="title != null and title != ''">
+            and title like concat('%', #{title}, '%')
+        </if>
+    </select>
+    <select id="selectPromotionalActiveVOById" resultType="com.fs.his.vo.FsPromotionalActiveVO">
+        select * from fs_promotional_active
+        where is_del = 0 and id = #{activeId}
+    </select>
+</mapper>

+ 15 - 0
fs-service/src/main/resources/mapper/his/FsPromotionalActiveResourceMapper.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.his.mapper.FsPromotionalActiveResourceMapper">
+
+    <insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
+        insert into fs_promotional_active_resource
+            (active_id, resource_id, type, product_type, create_time)
+        values
+        <foreach collection="resources" item="item" separator=",">
+            (#{item.activeId}, #{item.resourceId}, #{item.type}, #{item.productType}, #{item.createTime})
+        </foreach>
+    </insert>
+</mapper>

+ 71 - 0
fs-service/src/main/resources/mapper/qw/QwPushCountMapper.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.qw.mapper.QwPushCountMapper">
+    
+    <resultMap type="QwPushCount" id="QwPushCountResult">
+        <result property="id"    column="id"    />
+        <result property="type"    column="type"    />
+        <result property="pushCount"    column="push_count"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="status"    column="status"    />
+    </resultMap>
+
+    <sql id="selectQwPushCountVo">
+        select id, type, push_count, company_id, status from qw_push_count
+    </sql>
+
+    <select id="selectQwPushCountList" parameterType="QwPushCount" resultMap="QwPushCountResult">
+        <include refid="selectQwPushCountVo"/>
+        <where>  
+            <if test="type != null "> and type = #{type}</if>
+            <if test="pushCount != null "> and push_count = #{pushCount}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="status != null "> and status = #{status}</if>
+        </where>
+    </select>
+    
+    <select id="selectQwPushCountById" parameterType="Long" resultMap="QwPushCountResult">
+        <include refid="selectQwPushCountVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertQwPushCount" parameterType="QwPushCount" useGeneratedKeys="true" keyProperty="id">
+        insert into qw_push_count
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="type != null">type,</if>
+            <if test="pushCount != null">push_count,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="status != null">status,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="type != null">#{type},</if>
+            <if test="pushCount != null">#{pushCount},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="status != null">#{status},</if>
+         </trim>
+    </insert>
+
+    <update id="updateQwPushCount" parameterType="QwPushCount">
+        update qw_push_count
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="type != null">type = #{type},</if>
+            <if test="pushCount != null">push_count = #{pushCount},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="status != null">status = #{status},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteQwPushCountById" parameterType="Long">
+        delete from qw_push_count where id = #{id}
+    </delete>
+
+    <delete id="deleteQwPushCountByIds" parameterType="String">
+        delete from qw_push_count where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 7 - 0
fs-service/src/main/resources/mapper/qw/QwRestrictionPushRecordMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.qw.mapper.QwRestrictionPushRecordMapper">
+    
+</mapper>

+ 64 - 44
fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml

@@ -1,28 +1,29 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.sop.mapper.QwSopTempVoiceMapper">
 
     <resultMap type="QwSopTempVoice" id="QwSopTempVoiceResult">
         <result property="id"    column="id"    />
-        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
         <result property="tempId"    column="temp_id"    />
         <result property="ruleId"    column="rule_id"    />
         <result property="contentId"    column="content_id"    />
         <result property="voiceTxt"    column="voice_txt"    />
         <result property="voiceUrl"    column="voice_url"    />
+        <result property="userVoiceUrl"    column="user_voice_url"    />
+        <result property="recordType"    column="record_type"    />
         <result property="createTime"    column="create_time"    />
     </resultMap>
 
     <sql id="selectQwSopTempVoiceVo">
-        select id, qw_user_id, temp_id, rule_id, content_id, voice_txt, voice_url, create_time from qw_sop_temp_voice
+        select id,company_user_id, temp_id, rule_id, content_id, voice_txt, voice_url, create_time from qw_sop_temp_voice
     </sql>
 
     <select id="selectQwSopTempVoiceList" parameterType="QwSopTempVoice" resultMap="QwSopTempVoiceResult">
         <include refid="selectQwSopTempVoiceVo"/>
         <where>
-            <if test="qwUserId != null  and qwUserId != ''"> and qw_user_id = #{qwUserId}</if>
             <if test="tempId != null  and tempId != ''"> and temp_id = #{tempId}</if>
             <if test="ruleId != null "> and rule_id = #{ruleId}</if>
             <if test="contentId != null "> and content_id = #{contentId}</if>
@@ -32,7 +33,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectQwSopTempVoiceById" parameterType="Long" resultMap="QwSopTempVoiceResult">
-        <include refid="selectQwSopTempVoiceVo"/>
+        select id,company_user_id,duration, temp_id, rule_id, content_id, voice_txt, voice_url,user_voice_url,record_type, create_time from qw_sop_temp_voice
         where id = #{id}
     </select>
     <select id="listVoice" resultType="com.fs.sop.domain.QwSopTempVoice">
@@ -42,9 +43,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="getVoice" resultType="com.fs.sop.domain.QwSopTempVoice">
         select a.* from qw_sop_temp_voice a
         where a.company_user_id = #{companyUserId}
-        and a.company_id = #{companyId}
-        and a.temp_id = #{tempId}
-        and a.rule_id = #{rulesId}
+          and a.company_id = #{companyId}
+          and a.temp_id = #{tempId}
+          and a.rule_id = #{rulesId}
     </select>
     <select id="listVoiceTemp" resultType="com.fs.sop.domain.QwSopTempVoice">
         select * from qw_sop_temp_voice a where a.temp_id = #{tempId} and a.day_id =#{dayId}
@@ -59,49 +60,98 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where a.company_user_id in <foreach collection="userIdList" open="(" separator="," close=")" item="item">#{item}</foreach>
     </select>
 
+    <select id="selectQwSopTempVoiceByIdAndText" resultType="com.fs.sop.domain.QwSopTempVoice">
+        select id,company_user_id companyUserId,voice_txt voiceTxt from qw_sop_temp_voice where id = #{id}
+                                                                                            and voice_txt = #{voiceTxt} and record_type in (0,1) limit 1
+    </select>
+    <select id="selectQwSopTempVoiceByIdAndUserVoiceUrl" resultType="com.fs.sop.domain.QwSopTempVoice">
+        select id,company_user_id companyUserId from qw_sop_temp_voice where id = #{id} and record_type in (0,1,2) limit 1
+    </select>
+    <select id="selectQwSopTempVoiceNewList" resultType="com.fs.sop.domain.QwSopTempVoice">
+        select id,voice_txt voiceTxt,company_user_id companyUserId,voice_url voiceUrl,duration,record_type recordType,
+        user_voice_url userVoiceUrl from qw_sop_temp_voice
+        <where>
+            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
+            <if test="recordType != null"> and record_type = #{recordType}</if>
+        </where>
+        order by update_time desc,id desc
+    </select>
+
+    <select id="selectQwSopTempVoiceByQwUserIds" resultMap="QwSopTempVoiceResult">
+        select * from qw_sop_temp_voice where record_type = 0 and qw_user_id in
+        <foreach item="qwUserId" collection="ids"  open="(" separator="," close=")">
+            #{qwUserId}
+        </foreach>
+    </select>
+    <select id="selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt" resultType="com.fs.sop.domain.QwSopTempVoice">
+        select qw_user_id qwUserId,voice_url voiceUrl,voice_txt voiceTxt,user_voice_url userVoiceUrl,company_user_id companyUserId,
+               record_type recordType,duration from qw_sop_temp_voice where company_user_id = #{companyUserId} and voice_txt = #{voiceTxt}
+    </select>
+    <select id="selectQwSopTempVoiceByCompanyUserIdAndQwUserId" resultType="com.fs.sop.domain.QwSopTempVoice">
+        select id,company_user_id companyUserId,voice_url voiceUrl,voice_txt voiceTxt,duration
+        from qw_sop_temp_voice
+        where company_user_id = #{companyUserId} and qw_user_id = #{qwUserId}
+          and voice_url  is not null
+            limit 1
+    </select>
+    <select id="selectQwSopTempVoiceListLimit" resultType="com.fs.sop.domain.QwSopTempVoice">
+        select id,voice_txt voiceTxt,company_user_id companyUserId,voice_url voiceUrl,duration,record_type recordType,
+               user_voice_url userVoiceUrl from qw_sop_temp_voice
+        where record_type = #{recordType} and company_user_id is not null
+        order by date_format(create_time,'%y%m%d') desc,company_user_id desc limit 10
+    </select>
+
     <insert id="insertQwSopTempVoice" parameterType="QwSopTempVoice" useGeneratedKeys="true" keyProperty="id">
         insert into qw_sop_temp_voice
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="companyUserId != null">company_user_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
             <if test="companyId != null">company_id,</if>
             <if test="tempId != null">temp_id,</if>
             <if test="ruleId != null">rule_id,</if>
             <if test="contentId != null">content_id,</if>
             <if test="voiceTxt != null">voice_txt,</if>
             <if test="voiceUrl != null">voice_url,</if>
+            <if test="userVoiceUrl != null">user_voice_url,</if>
             <if test="createTime != null">create_time,</if>
             <if test="duration != null">duration,</if>
-         </trim>
+            <if test="recordType != null">record_type,</if>
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="tempId != null">#{tempId},</if>
             <if test="ruleId != null">#{ruleId},</if>
             <if test="contentId != null">#{contentId},</if>
             <if test="voiceTxt != null">#{voiceTxt},</if>
             <if test="voiceUrl != null">#{voiceUrl},</if>
+            <if test="userVoiceUrl != null">#{userVoiceUrl},</if>
             <if test="createTime != null">#{createTime},</if>
             <if test="duration != null">#{duration},</if>
-         </trim>
+            <if test="recordType != null">#{recordType},</if>
+        </trim>
     </insert>
     <insert id="insertBatch">
-        INSERT INTO qw_sop_temp_voice (temp_id, company_user_id, day_id, company_id, content_id, rule_id, voice_txt, voice_url, duration)
+        INSERT INTO qw_sop_temp_voice (temp_id, company_user_id, day_id, company_id, content_id, rule_id, voice_txt, voice_url,record_type, duration)
         VALUES
         <foreach collection="list" item="item" separator=",">
-            (#{item.tempId}, #{item.companyUserId}, #{item.dayId}, #{item.companyId}, #{item.contentId}, #{item.ruleId}, #{item.voiceTxt}, #{item.voiceUrl}, #{item.duration})
+            (#{item.tempId}, #{item.companyUserId}, #{item.dayId}, #{item.companyId}, #{item.contentId}, #{item.ruleId}, #{item.voiceTxt}, #{item.voiceUrl}, #{item.recordType},#{item.duration})
         </foreach>
     </insert>
 
     <update id="updateQwSopTempVoice" parameterType="QwSopTempVoice">
         update qw_sop_temp_voice
         <trim prefix="SET" suffixOverrides=",">
-            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
             <if test="tempId != null">temp_id = #{tempId},</if>
             <if test="ruleId != null">rule_id = #{ruleId},</if>
             <if test="contentId != null">content_id = #{contentId},</if>
             <if test="voiceTxt != null">voice_txt = #{voiceTxt},</if>
             <if test="voiceUrl != null">voice_url = #{voiceUrl},</if>
+            <if test="userVoiceUrl != null">user_voice_url = #{userVoiceUrl},</if>
+            <if test="recordType != null">record_type = #{recordType},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="duration != null">duration = #{duration},</if>
         </trim>
         where id = #{id}
     </update>
@@ -119,34 +169,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <delete id="remove">
         delete from qw_sop_temp_voice a where a.temp_id = #{tempId} and a.day_id =#{dayId}
     </delete>
-
-    <select id="selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select qw_user_id qwUserId,voice_url voiceUrl,voice_txt voiceTxt,user_voice_url userVoiceUrl,company_user_id companyUserId,
-               record_type recordType,duration from qw_sop_temp_voice where company_user_id = #{companyUserId} and voice_txt = #{voiceTxt}
-    </select>
-    <select id="selectQwSopTempVoiceByCompanyUserIdAndQwUserId" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select id,company_user_id companyUserId,voice_url voiceUrl,voice_txt voiceTxt,duration
-        from qw_sop_temp_voice
-        where company_user_id = #{companyUserId} and qw_user_id = #{qwUserId}
-          and voice_url  is not null
-            limit 1
-    </select>
-    <select id="selectQwSopTempVoiceListLimit" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select id,voice_txt voiceTxt,company_user_id companyUserId,voice_url voiceUrl,duration,record_type recordType,
-               user_voice_url userVoiceUrl from qw_sop_temp_voice
-        where record_type = #{recordType} and company_user_id is not null
-        order by date_format(create_time,'%y%m%d') desc,company_user_id desc limit 10
-    </select>
-    <select id="selectQwSopTempVoiceByIdAndUserVoiceUrl" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select id,company_user_id companyUserId from qw_sop_temp_voice where id = #{id} and record_type in (0,1,2) limit 1
-    </select>
-    <select id="selectQwSopTempVoiceNewList" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select id,voice_txt voiceTxt,company_user_id companyUserId,voice_url voiceUrl,duration,record_type recordType,
-        user_voice_url userVoiceUrl from qw_sop_temp_voice
-        <where>
-            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
-            <if test="recordType != null"> and record_type = #{recordType}</if>
-        </where>
-        order by update_time desc,id desc
-    </select>
 </mapper>

+ 34 - 4
fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java

@@ -1,10 +1,14 @@
 package com.fs.app.controller;
 
 
+import cn.hutool.core.lang.TypeReference;
+import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.app.annotation.Login;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
+import com.fs.common.utils.CloudHostUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.his.domain.*;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.enums.PaymentMethodEnum;
@@ -23,10 +27,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 
 
 @Slf4j
@@ -88,6 +89,7 @@ public class IntegralController extends  AppBaseController {
         param.setUserId(Long.parseLong(getUserId()));
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
         List<FsIntegralOrderListUVO> list=integralOrderService.selectFsIntegralOrderListUVO(param);
+        list.forEach(vo -> vo.setItemJson(adaptItemJson(vo.getItemJson())));
         PageInfo<FsIntegralOrderListUVO> listPageInfo=new PageInfo<>(list);
         return R.ok().put("data",listPageInfo);
     }
@@ -96,9 +98,37 @@ public class IntegralController extends  AppBaseController {
     @GetMapping("/getIntegralOrderById")
     public R getIntegralOrderById(@RequestParam("orderId")Long orderId, HttpServletRequest request){
         FsIntegralOrder order=integralOrderService.selectFsIntegralOrderByOrderId(orderId);
+        order.setItemJson(adaptItemJson(order.getItemJson()));
         return R.ok().put("data",order);
     }
 
+    /**
+     * 兼容处理itemJson
+     */
+    private String adaptItemJson(String itemJson) {
+        if (StringUtils.isBlank(itemJson)) {
+            return itemJson;
+        }
+
+        if (CloudHostUtils.hasCloudHostName("弘德堂")) {
+            if (itemJson.startsWith("{") && itemJson.endsWith("}")) {
+                FsIntegralGoods integralGoods = JSONUtil.toBean(itemJson, FsIntegralGoods.class);
+                List<FsIntegralGoods> goodsItem = new ArrayList<>();
+                goodsItem.add(integralGoods);
+                return JSONUtil.toJsonStr(goodsItem);
+            }
+        } else {
+            if (itemJson.startsWith("[") && itemJson.endsWith("]")) {
+                List<FsIntegralGoods> goodsItem = JSONUtil.toBean(itemJson, new TypeReference<List<FsIntegralGoods>>(){}, true);
+                if (goodsItem.size() == 1) {
+                    return JSONUtil.toJsonStr(goodsItem.get(0));
+                }
+            }
+        }
+
+        return itemJson;
+    }
+
 
     @Login
     @ApiOperation("创建订单")

+ 63 - 0
fs-user-app/src/main/java/com/fs/app/controller/PromotionalActiveController.java

@@ -0,0 +1,63 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.app.param.PromotionalActiveClickParam;
+import com.fs.common.core.domain.R;
+import com.fs.event.ActiveClickEvent;
+import com.fs.his.service.IFsPromotionalActiveService;
+import com.fs.his.vo.FsPromotionalActiveDetailVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.Objects;
+
+@Slf4j
+@Api("宣传活动接口")
+@RestController
+@RequestMapping("/app/promotionalActive")
+public class PromotionalActiveController extends AppBaseController {
+
+    @Autowired
+    private IFsPromotionalActiveService promotionalActiveService;
+    @Autowired
+    private ApplicationEventPublisher publisher;
+
+    @ApiOperation("获取活动详情")
+    @Login
+    @GetMapping("/detail/{activeId}")
+    public R getActiveDetail(@PathVariable Long activeId) {
+        FsPromotionalActiveDetailVO activeDetail = promotionalActiveService.getActiveDetail(activeId);
+
+        // 行为记录埋点
+        if (Objects.nonNull(activeDetail)) {
+            publisher.publishEvent(new ActiveClickEvent(this, activeId, Long.parseLong(getUserId()), ActiveClickEvent.Type.HOME));
+        }
+
+        return R.ok().put("data", activeDetail);
+    }
+
+    @ApiOperation("活动点击")
+    @Login
+    @PostMapping("/click")
+    public void activeClick(@Valid @RequestBody PromotionalActiveClickParam param) {
+        log.debug("用户点击行为 activeId: {}, userId: {}, type: {}, productType: {}, resourceId: {}",
+                param.getActiveId(), getUserId(), param.getType(), param.getProductType(), param.getResourceId());
+        // 行为记录埋点
+        ActiveClickEvent.Type type = ActiveClickEvent.Type.valueOf(param.getType());
+        ActiveClickEvent.ProductType productType = ActiveClickEvent.ProductType.valueOf(param.getProductType());
+
+        if (Objects.isNull(type) || type == ActiveClickEvent.Type.HOME) {
+            log.error("模块类型错误,忽略信息 activeId: {}, userId: {}, type: {}, productType: {}, resourceId: {}",
+                    param.getActiveId(), getUserId(), param.getType(), param.getProductType(), param.getResourceId());
+            return;
+        }
+
+        // 行为记录埋点
+        publisher.publishEvent(new ActiveClickEvent(this, param.getActiveId(), Long.parseLong(getUserId()), type, productType, param.getResourceId()));
+    }
+}

+ 15 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -398,4 +398,19 @@ public class CourseQwController extends AppBaseController {
         return courseVideoService.updateVideo();
     }
 
+    @ApiOperation("查询课程")
+    @GetMapping("/getCourseByCourseId")
+    public R getCourseByCourseId(@RequestParam Long courseId)
+    {
+        List<FsUserCourseVideoAppletVO> course = courseService.selectFsUserCourseVideoAppletListByCourseId(courseId);
+        if(course != null && !course.isEmpty()){
+            for (FsUserCourseVideoAppletVO appletVO : course) {
+                List<FsUserCourseVideoAppletVO.FsUserCourseVideo> courseVideos = courseService.selectFsUserCourseVideoAppletByCourseId(appletVO.getCourseId());
+                appletVO.setFsUserCourseVideoList(courseVideos);
+                appletVO.setVideoTotal((long) courseVideos.size());
+            }
+        }
+        return R.ok().put("data",course);
+    }
+
 }

+ 16 - 0
fs-user-app/src/main/java/com/fs/app/exception/FSExceptionHandler.java

@@ -3,8 +3,11 @@ package com.fs.app.exception;
 
 
 
+import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.dao.DuplicateKeyException;
@@ -17,6 +20,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 import org.springframework.web.servlet.NoHandlerFoundException;
 
+import javax.servlet.http.HttpServletRequest;
+
 
 /**
  * 异常处理器
@@ -83,4 +88,15 @@ public class FSExceptionHandler {
 		return R.error();
 	}
 
+	/**
+	 * 业务异常
+	 */
+	@ExceptionHandler(ServiceException.class)
+	public R handleServiceException(ServiceException e)
+	{
+		logger.error(e.getMessage(), e);
+		Integer code = e.getCode();
+		return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());
+	}
+
 }

+ 24 - 0
fs-user-app/src/main/java/com/fs/app/param/PromotionalActiveClickParam.java

@@ -0,0 +1,24 @@
+package com.fs.app.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class PromotionalActiveClickParam {
+
+    @NotNull(message = "活动ID不能为空")
+    @ApiModelProperty("活动ID")
+    private Long activeId;
+
+    @NotNull(message = "模块类型不能为空")
+    @ApiModelProperty("模块类型 1.首页 2.视频 3.医生 4.产品")
+    private Integer type;
+
+    @ApiModelProperty("产品类型 1.疗法 2.积分商品")
+    private Integer productType;
+
+    @ApiModelProperty("资源ID")
+    private Long resourceId;
+}