Long 3 недель назад
Родитель
Сommit
819c6b57d3
54 измененных файлов с 1693 добавлено и 28 удалено
  1. 21 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  2. 26 4
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java
  3. 20 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java
  4. 24 8
      fs-admin/src/main/java/com/fs/his/controller/FsPackageController.java
  5. 71 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveController.java
  6. 41 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveLogController.java
  7. 10 4
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  8. 6 4
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  9. 9 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  10. 23 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoChooseVO.java
  11. 60 0
      fs-service/src/main/java/com/fs/event/ActiveClickEvent.java
  12. 27 0
      fs-service/src/main/java/com/fs/event/ActiveClickListener.java
  13. 2 0
      fs-service/src/main/java/com/fs/his/domain/FsAdv.java
  14. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActive.java
  15. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveLog.java
  16. 39 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveResource.java
  17. 38 0
      fs-service/src/main/java/com/fs/his/dto/FsPromotionalActiveDTO.java
  18. 11 0
      fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java
  19. 14 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralGoodsMapper.java
  20. 11 0
      fs-service/src/main/java/com/fs/his/mapper/FsPackageMapper.java
  21. 17 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveLogMapper.java
  22. 29 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveMapper.java
  23. 15 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveResourceMapper.java
  24. 6 0
      fs-service/src/main/java/com/fs/his/service/IFsDoctorService.java
  25. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralGoodsService.java
  26. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsPackageService.java
  27. 21 0
      fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveLogService.java
  28. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveResourceService.java
  29. 48 0
      fs-service/src/main/java/com/fs/his/service/IFsPromotionalActiveService.java
  30. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsDoctorServiceImpl.java
  31. 10 3
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  32. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPackageServiceImpl.java
  33. 41 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveLogServiceImpl.java
  34. 12 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveResourceServiceImpl.java
  35. 276 0
      fs-service/src/main/java/com/fs/his/service/impl/FsPromotionalActiveServiceImpl.java
  36. 23 0
      fs-service/src/main/java/com/fs/his/vo/FsDoctorChooseVO.java
  37. 31 0
      fs-service/src/main/java/com/fs/his/vo/FsGoodsVO.java
  38. 31 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralGoodsChooseVO.java
  39. 26 0
      fs-service/src/main/java/com/fs/his/vo/FsPackageChooseVO.java
  40. 38 0
      fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveDetailVO.java
  41. 23 0
      fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveStatVO.java
  42. 41 0
      fs-service/src/main/java/com/fs/his/vo/FsPromotionalActiveVO.java
  43. 46 0
      fs-service/src/main/resources/db/20250924-宣传活动.sql
  44. 68 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  45. 5 1
      fs-service/src/main/resources/mapper/his/FsAdvMapper.xml
  46. 58 0
      fs-service/src/main/resources/mapper/his/FsDoctorMapper.xml
  47. 41 0
      fs-service/src/main/resources/mapper/his/FsIntegralGoodsMapper.xml
  48. 47 0
      fs-service/src/main/resources/mapper/his/FsPackageMapper.xml
  49. 30 0
      fs-service/src/main/resources/mapper/his/FsPromotionalActiveLogMapper.xml
  50. 18 0
      fs-service/src/main/resources/mapper/his/FsPromotionalActiveMapper.xml
  51. 15 0
      fs-service/src/main/resources/mapper/his/FsPromotionalActiveResourceMapper.xml
  52. 63 0
      fs-user-app/src/main/java/com/fs/app/controller/PromotionalActiveController.java
  53. 16 0
      fs-user-app/src/main/java/com/fs/app/exception/FSExceptionHandler.java
  54. 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));
+    }
 }

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

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

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

+ 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;
@@ -2855,4 +2852,12 @@ 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();
+    }
+}

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

+ 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

@@ -390,4 +390,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>

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

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