فهرست منبع

直播回放 按照系统时间 范围 多选 红包/积分/核销卷

三七 4 روز پیش
والد
کامیت
a18e798eff
42فایلهای تغییر یافته به همراه1714 افزوده شده و 143 حذف شده
  1. 91 0
      fs-admin/src/main/java/com/fs/live/controller/LiveGroupTypeController.java
  2. 103 0
      fs-admin/src/main/java/com/fs/live/controller/LiveRedPacketLogController.java
  3. 97 0
      fs-company/src/main/java/com/fs/live/LiveCouponUserController.java
  4. 104 0
      fs-company/src/main/java/com/fs/live/LiveGroupTypeController.java
  5. 111 0
      fs-company/src/main/java/com/fs/live/LiveRedPacketLogController.java
  6. 14 14
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  7. 1 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyConfigMapper.java
  8. 5 0
      fs-service/src/main/java/com/fs/live/domain/Live.java
  9. 15 2
      fs-service/src/main/java/com/fs/live/domain/LiveCouponUser.java
  10. 27 0
      fs-service/src/main/java/com/fs/live/domain/LiveGroupType.java
  11. 7 0
      fs-service/src/main/java/com/fs/live/domain/LiveRedPacketLog.java
  12. 28 2
      fs-service/src/main/java/com/fs/live/domain/LiveWatchLog.java
  13. 5 1
      fs-service/src/main/java/com/fs/live/domain/LiveWatchUser.java
  14. 7 0
      fs-service/src/main/java/com/fs/live/mapper/LiveCouponIssueMapper.java
  15. 35 2
      fs-service/src/main/java/com/fs/live/mapper/LiveCouponUserMapper.java
  16. 61 0
      fs-service/src/main/java/com/fs/live/mapper/LiveGroupTypeMapper.java
  17. 12 1
      fs-service/src/main/java/com/fs/live/param/CouponPO.java
  18. 1 1
      fs-service/src/main/java/com/fs/live/param/LiveAutoTaskImportParam.java
  19. 19 0
      fs-service/src/main/java/com/fs/live/param/LiveCouponVerifyParam.java
  20. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveCouponIssueService.java
  21. 16 0
      fs-service/src/main/java/com/fs/live/service/ILiveCouponService.java
  22. 11 0
      fs-service/src/main/java/com/fs/live/service/ILiveCouponUserService.java
  23. 61 0
      fs-service/src/main/java/com/fs/live/service/ILiveGroupTypeService.java
  24. 5 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveCouponIssueServiceImpl.java
  25. 135 3
      fs-service/src/main/java/com/fs/live/service/impl/LiveCouponServiceImpl.java
  26. 33 5
      fs-service/src/main/java/com/fs/live/service/impl/LiveCouponUserServiceImpl.java
  27. 36 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  28. 94 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveGroupTypeServiceImpl.java
  29. 190 86
      fs-service/src/main/java/com/fs/live/service/impl/LiveRedPacketLogServiceImpl.java
  30. 76 0
      fs-service/src/main/java/com/fs/live/vo/LiveCouponUserDetailVo.java
  31. 12 0
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java
  32. 7 1
      fs-service/src/main/java/com/fs/live/vo/RecordTimeRangeVO.java
  33. 3 3
      fs-service/src/main/resources/application-druid-hst.yml
  34. 33 0
      fs-service/src/main/resources/db/changelog/changes/20260613-live-user-add-is-del.sql
  35. 1 0
      fs-service/src/main/resources/db/changelog/db.changelog-master.xml
  36. 14 0
      fs-service/src/main/resources/db/changelog/table/live_group_type.sql
  37. 94 1
      fs-service/src/main/resources/mapper/live/LiveCouponUserMapper.xml
  38. 4 3
      fs-service/src/main/resources/mapper/live/LiveDataMapper.xml
  39. 64 0
      fs-service/src/main/resources/mapper/live/LiveGroupTypeMapper.xml
  40. 16 6
      fs-service/src/main/resources/mapper/live/LiveMapper.xml
  41. 17 9
      fs-service/src/main/resources/mapper/live/LiveWatchLogMapper.xml
  42. 47 2
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveCouponController.java

+ 91 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveGroupTypeController.java

@@ -0,0 +1,91 @@
+package com.fs.live.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.live.domain.LiveGroupType;
+import com.fs.live.service.ILiveGroupTypeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 直播间分组分类Controller
+ *
+ * @author fs
+ * @date 2026-06-16
+ */
+@RestController
+@RequestMapping("/live/liveGroupType")
+public class LiveGroupTypeController extends BaseController
+{
+    @Autowired
+    private ILiveGroupTypeService liveGroupTypeService;
+
+    /**
+     * 查询直播间分组分类列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(LiveGroupType liveGroupType)
+    {
+        startPage();
+        List<LiveGroupType> list = liveGroupTypeService.selectLiveGroupTypeList(liveGroupType);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出直播间分组分类列表
+     */
+    @Log(title = "直播间分组分类", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(LiveGroupType liveGroupType)
+    {
+        List<LiveGroupType> list = liveGroupTypeService.selectLiveGroupTypeList(liveGroupType);
+        ExcelUtil<LiveGroupType> util = new ExcelUtil<LiveGroupType>(LiveGroupType.class);
+        return util.exportExcel(list, "直播间分组分类数据");
+    }
+
+    /**
+     * 获取直播间分组分类详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(liveGroupTypeService.selectLiveGroupTypeById(id));
+    }
+
+    /**
+     * 新增直播间分组分类
+     */
+    @Log(title = "直播间分组分类", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody LiveGroupType liveGroupType)
+    {
+        return toAjax(liveGroupTypeService.insertLiveGroupType(liveGroupType));
+    }
+
+    /**
+     * 修改直播间分组分类
+     */
+    @Log(title = "直播间分组分类", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody LiveGroupType liveGroupType)
+    {
+        return toAjax(liveGroupTypeService.updateLiveGroupType(liveGroupType));
+    }
+
+    /**
+     * 删除直播间分组分类
+     */
+    @Log(title = "直播间分组分类", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(liveGroupTypeService.deleteLiveGroupTypeByIds(ids));
+    }
+}

+ 103 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveRedPacketLogController.java

@@ -0,0 +1,103 @@
+package com.fs.live.controller;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.live.domain.LiveRedPacketLog;
+import com.fs.live.service.ILiveRedPacketLogService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 直播红包 记录Controller
+ *
+ * @author fs
+ * @date 2026-06-15
+ */
+@RestController
+@RequestMapping("/live/liveRedPacketLog")
+public class LiveRedPacketLogController extends BaseController
+{
+    @Autowired
+    private ILiveRedPacketLogService liveRedPacketLogService;
+
+    /**
+     * 查询直播红包 记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(LiveRedPacketLog liveRedPacketLog)
+    {
+        startPage();
+        List<LiveRedPacketLog> list = liveRedPacketLogService.selectLiveRedPacketLogList(liveRedPacketLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出直播红包 记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:export')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(LiveRedPacketLog liveRedPacketLog)
+    {
+        List<LiveRedPacketLog> list = liveRedPacketLogService.selectLiveRedPacketLogList(liveRedPacketLog);
+        ExcelUtil<LiveRedPacketLog> util = new ExcelUtil<LiveRedPacketLog>(LiveRedPacketLog.class);
+        return util.exportExcel(list, "直播红包 记录数据");
+    }
+
+    /**
+     * 获取直播红包 记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(liveRedPacketLogService.selectLiveRedPacketLogByLogId(logId));
+    }
+
+    /**
+     * 新增直播红包 记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:add')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody LiveRedPacketLog liveRedPacketLog)
+    {
+        return toAjax(liveRedPacketLogService.insertLiveRedPacketLog(liveRedPacketLog));
+    }
+
+    /**
+     * 修改直播红包 记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:edit')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody LiveRedPacketLog liveRedPacketLog)
+    {
+        return toAjax(liveRedPacketLogService.updateLiveRedPacketLog(liveRedPacketLog));
+    }
+
+    /**
+     * 删除直播红包 记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:remove')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(liveRedPacketLogService.deleteLiveRedPacketLogByLogIds(logIds));
+    }
+}

+ 97 - 0
fs-company/src/main/java/com/fs/live/LiveCouponUserController.java

@@ -0,0 +1,97 @@
+package com.fs.live;
+
+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.live.domain.LiveCouponUser;
+import com.fs.live.service.ILiveCouponUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 优惠券发放记录Controller
+ *
+ * @author fs
+ * @date 2025-09-30
+ */
+@RestController
+@RequestMapping("/live/coupon/user")
+public class LiveCouponUserController extends BaseController
+{
+    @Autowired
+    private ILiveCouponUserService liveCouponUserService;
+
+    /**
+     * 查询优惠券发放记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:issue:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(LiveCouponUser liveCouponUser)
+    {
+        startPage();
+        List<LiveCouponUser> list = liveCouponUserService.selectLiveCouponUserList(liveCouponUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出优惠券发放记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:issue:export')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(LiveCouponUser liveCouponUser)
+    {
+        List<LiveCouponUser> list = liveCouponUserService.selectLiveCouponUserList(liveCouponUser);
+        ExcelUtil<LiveCouponUser> util = new ExcelUtil<LiveCouponUser>(LiveCouponUser.class);
+        return util.exportExcel(list, "user");
+    }
+
+    /**
+     * 获取优惠券发放记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('live:issue:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(liveCouponUserService.selectLiveCouponUserById(id));
+    }
+
+    /**
+     * 新增优惠券发放记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:issue:add')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody LiveCouponUser liveCouponUser)
+    {
+        return toAjax(liveCouponUserService.insertLiveCouponUser(liveCouponUser));
+    }
+
+    /**
+     * 修改优惠券发放记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:issue:edit')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody LiveCouponUser liveCouponUser)
+    {
+        return toAjax(liveCouponUserService.updateLiveCouponUser(liveCouponUser));
+    }
+
+    /**
+     * 删除优惠券发放记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:issue:remove')")
+    @Log(title = "优惠券发放记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(liveCouponUserService.deleteLiveCouponUserByIds(ids));
+    }
+}

+ 104 - 0
fs-company/src/main/java/com/fs/live/LiveGroupTypeController.java

@@ -0,0 +1,104 @@
+package com.fs.live;
+
+import java.util.List;
+
+import com.fs.live.domain.LiveGroupType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.live.service.ILiveGroupTypeService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 直播间分组分类Controller
+ *
+ * @author fs
+ * @date 2026-06-16
+ */
+@RestController
+@RequestMapping("/live/liveGroupType")
+public class LiveGroupTypeController extends BaseController
+{
+    @Autowired
+    private ILiveGroupTypeService liveGroupTypeService;
+
+    /**
+     * 查询直播间分组分类列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveGroupType:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(LiveGroupType liveGroupType)
+    {
+        startPage();
+        List<LiveGroupType> list = liveGroupTypeService.selectLiveGroupTypeList(liveGroupType);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出直播间分组分类列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveGroupType:export')")
+    @Log(title = "直播间分组分类", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(LiveGroupType liveGroupType)
+    {
+        List<LiveGroupType> list = liveGroupTypeService.selectLiveGroupTypeList(liveGroupType);
+        ExcelUtil<LiveGroupType> util = new ExcelUtil<LiveGroupType>(LiveGroupType.class);
+        return util.exportExcel(list, "直播间分组分类数据");
+    }
+
+    /**
+     * 获取直播间分组分类详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveGroupType:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(liveGroupTypeService.selectLiveGroupTypeById(id));
+    }
+
+    /**
+     * 新增直播间分组分类
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveGroupType:add')")
+    @Log(title = "直播间分组分类", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody LiveGroupType liveGroupType)
+    {
+        return toAjax(liveGroupTypeService.insertLiveGroupType(liveGroupType));
+    }
+
+    /**
+     * 修改直播间分组分类
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveGroupType:edit')")
+    @Log(title = "直播间分组分类", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody LiveGroupType liveGroupType)
+    {
+        return toAjax(liveGroupTypeService.updateLiveGroupType(liveGroupType));
+    }
+
+    /**
+     * 删除直播间分组分类
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveGroupType:remove')")
+    @Log(title = "直播间分组分类", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(liveGroupTypeService.deleteLiveGroupTypeByIds(ids));
+    }
+}

+ 111 - 0
fs-company/src/main/java/com/fs/live/LiveRedPacketLogController.java

@@ -0,0 +1,111 @@
+package com.fs.live;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.live.domain.LiveRedPacketLog;
+import com.fs.live.service.ILiveRedPacketLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 直播红包 记录Controller
+ *
+ * @author fs
+ * @date 2026-06-15
+ */
+@RestController
+@RequestMapping("/live/liveRedPacketLog")
+public class LiveRedPacketLogController extends BaseController
+{
+    @Autowired
+    private ILiveRedPacketLogService liveRedPacketLogService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询直播红包 记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(LiveRedPacketLog liveRedPacketLog)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        liveRedPacketLog.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        startPage();
+        List<LiveRedPacketLog> list = liveRedPacketLogService.selectLiveRedPacketLogList(liveRedPacketLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出直播红包 记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:export')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(LiveRedPacketLog liveRedPacketLog)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        liveRedPacketLog.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        List<LiveRedPacketLog> list = liveRedPacketLogService.selectLiveRedPacketLogList(liveRedPacketLog);
+        ExcelUtil<LiveRedPacketLog> util = new ExcelUtil<LiveRedPacketLog>(LiveRedPacketLog.class);
+        return util.exportExcel(list, "直播红包 记录数据");
+    }
+
+    /**
+     * 获取直播红包 记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(liveRedPacketLogService.selectLiveRedPacketLogByLogId(logId));
+    }
+
+    /**
+     * 新增直播红包 记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:add')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody LiveRedPacketLog liveRedPacketLog)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        liveRedPacketLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        return toAjax(liveRedPacketLogService.insertLiveRedPacketLog(liveRedPacketLog));
+    }
+
+    /**
+     * 修改直播红包 记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:edit')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody LiveRedPacketLog liveRedPacketLog)
+    {
+        return toAjax(liveRedPacketLogService.updateLiveRedPacketLog(liveRedPacketLog));
+    }
+
+    /**
+     * 删除直播红包 记录
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveRedPacketLog:remove')")
+    @Log(title = "直播红包 记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(liveRedPacketLogService.deleteLiveRedPacketLogByLogIds(logIds));
+    }
+}

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

@@ -30,6 +30,8 @@ import com.fs.his.service.IFsIntegralCountService;
 import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.utils.qrcode.QRCodeUtils;
+import com.fs.live.param.LiveRedPacketParam;
+import com.fs.live.service.ILiveRedPacketLogService;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwContactWayMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
@@ -206,22 +208,20 @@ public class CommonController {
     @Autowired
     private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
 
-    @GetMapping("/test")
-    public R test(){
-
-        Long userId = 10568613L;
-        String AppId = "";
-
-        if (AppId == null || AppId.isEmpty()) {
-            return goodsService.getCourseIntegralGoods(userId);
-        }
 
-        String integralGoods = fsCoursePlaySourceConfigService.selectCoursePlaySourceConfigByAppIdGoods(AppId);
-        if (!StringUtil.strIsNullOrEmpty(integralGoods)) {
-            return goodsService.getCourseIntegralGoodsByMiniApp(AppId, userId, integralGoods);
-        }
+    @Autowired
+    private ILiveRedPacketLogService redPacketLogService;
 
-        return goodsService.getCourseIntegralGoods(userId);
+    @GetMapping("/test")
+    public R test(){
+        LiveRedPacketParam param=new LiveRedPacketParam();
+        param.setUserId(12858L);
+        param.setLiveId(91L);
+        param.setCompanyUserId(9605L);
+        param.setCompanyId(321L);
+        param.setAppId("ww890da2ce046e357c");
+
+        return  redPacketLogService.sendLiveReward(param);
     }
 
     /**

+ 1 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyConfigMapper.java

@@ -101,7 +101,7 @@ public interface CompanyConfigMapper
             "fs_course_play_source_config\n" +
             "where \n" +
             "is_del = 0 \n" +
-            "and status = 0" +
+            "and status = 0 \n" +
             "and name like '%app%' ")
     List<CompanyMiniAppVO> getCompanyMiniAppAllList();
 

+ 5 - 0
fs-service/src/main/java/com/fs/live/domain/Live.java

@@ -149,4 +149,9 @@ public class   Live extends BaseEntity {
     /** 查询用:为 true 时仅查训练营直播间(training_period_id 非空),总后台审核列表等 */
     @TableField(exist = false)
     private Boolean onlyTrainingCampLive;
+
+    /**
+    * 直播间分类
+    */
+    private Integer liveGroupType;
 }

+ 15 - 2
fs-service/src/main/java/com/fs/live/domain/LiveCouponUser.java

@@ -57,10 +57,23 @@ public class LiveCouponUser extends BaseEntity
     @Excel(name = "获取方式")
     private String type;
 
-    /** 状态(0:未使用,1:已使用, 2:已过期) */
-    @Excel(name = "状态", readConverterExp = "0=:未使用,1:已使用,,2=:已过期")
+    /** 状态(0:未核销,1:已核销, 2:已过期) */
+    @Excel(name = "状态", readConverterExp = "0=:未核销,1:已核销,,2=:已过期")
     private Integer status;
 
+    /** 核销码(用于扫码核销) */
+    @Excel(name = "核销码")
+    private String verifyCode;
+
+    /** 核销销售用户ID */
+    @Excel(name = "核销销售用户ID")
+    private Long verifyUserId;
+
+    /** 核销时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "核销时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date verifyTime;
+
     /** 是否有效 */
     @Excel(name = "是否有效")
     private Integer isFail;

+ 27 - 0
fs-service/src/main/java/com/fs/live/domain/LiveGroupType.java

@@ -0,0 +1,27 @@
+package com.fs.live.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 直播间分组分类对象 live_group_type
+ *
+ * @author fs
+ * @date 2026-06-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class LiveGroupType extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 直播间分类 */
+    @Excel(name = "直播间分类")
+    private String liveGroupType;
+
+
+}

+ 7 - 0
fs-service/src/main/java/com/fs/live/domain/LiveRedPacketLog.java

@@ -69,9 +69,16 @@ public class LiveRedPacketLog extends BaseEntity{
     @Excel(name = "商户ID")
     private String mchId;
 
+
     /** 直播方式 1 app 2浏览器(可能有) */
     @Excel(name = "直播方式 1 app 2浏览器(可能有)")
     private Long wathcType;
 
+    /**
+    * 备注
+    */
+    @Excel(name = "备注")
+    private String remark;
+
 
 }

+ 28 - 2
fs-service/src/main/java/com/fs/live/domain/LiveWatchLog.java

@@ -3,9 +3,11 @@ package com.fs.live.domain;
 import java.util.Date;
 import java.util.Objects;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
+import liquibase.pro.packaged.S;
 import lombok.Data;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.EqualsAndHashCode;
@@ -23,14 +25,23 @@ public class LiveWatchLog extends BaseEntity{
     /** 日志id */
     private Long logId;
 
-    /** 用户userId */
-    @Excel(name = "用户userId")
+    /** 会员userId */
+    @Excel(name = "会员userId")
     private Long userId;
 
+    @Excel(name = "会员名称")
+    @TableField(exist = false)
+    private String userIdName;
+
     /** 直播间id */
     @Excel(name = "直播间id")
     private Long liveId;
 
+    /** 直播间名称 */
+    @TableField(exist = false)
+    @Excel(name = "直播间名称")
+    private String liveName;
+
     /** 记录类型 1看课中 2完课 3待看课 4看课中断 */
     @Excel(name = "记录类型 1看课中 2完课 3待看课 4看课中断")
     private Integer logType;
@@ -39,10 +50,20 @@ public class LiveWatchLog extends BaseEntity{
     @Excel(name = "外部联系人id")
     private Long externalContactId;
 
+    /** 外部联系人名称*/
+    @Excel(name = "外部联系人名称")
+    @TableField(exist = false)
+    private String externalContactName;
+
     /** 销售id */
     @Excel(name = "销售id")
     private Long companyUserId;
 
+    /** 销售名称 */
+    @Excel(name = "销售名称")
+    @TableField(exist = false)
+    private String companyUserName;
+
     /** 公司id */
     @Excel(name = "公司id")
     private Long companyId;
@@ -68,6 +89,11 @@ public class LiveWatchLog extends BaseEntity{
     /** 分享人企微id */
     @Excel(name = "分享人企微id")
     private String qwUserId;
+
+    /** 分享人企微名称 */
+    @Excel(name = "分享人企微名称 */")
+    @TableField(exist = false)
+    private String qwUserName;
     /**
      * 查看直播类型:1、直播,2、回放
      */

+ 5 - 1
fs-service/src/main/java/com/fs/live/domain/LiveWatchUser.java

@@ -51,6 +51,10 @@ public class LiveWatchUser extends BaseEntity {
 
     private String nickName;
     private String tabName;
+    /**
+    * 备注
+    */
+    private String remark;
 
     /** 直播进入标记:0-否 1-是 */
     @Excel(name = "直播进入标记")
@@ -73,7 +77,7 @@ public class LiveWatchUser extends BaseEntity {
     */
     private Integer sendType;
     /**
-    * 奖励类型 1红包 2积分 3 设置奖励
+    * 奖励类型 1红包 2积分 3 红包 + 积分 (1|2) 4核销卷 5 红包 + 核销卷 (1|4) 6积分 + 核销卷 (2|4) 7 全部 (1|2|4) 99 未设置奖励
     */
     private Integer rewardType;
 

+ 7 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveCouponIssueMapper.java

@@ -1,6 +1,9 @@
 package com.fs.live.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveCouponIssue;
 import com.fs.live.domain.LiveCouponIssueRelation;
 import com.fs.live.param.CouponPO;
@@ -78,4 +81,8 @@ public interface LiveCouponIssueMapper
 
     @Select("select * from live_coupon_issue where coupon_id= #{couponId}")
     LiveCouponIssue selectLiveCouponIssueByCouponId(@Param("couponId") Long couponId);
+
+    @DataSource(DataSourceType.SLAVE)
+    @Select("select * from live_coupon_issue where coupon_id= #{couponId} and status=1  and is_del=0 ")
+    LiveCouponIssue selectLiveCouponIssueByCouponIdByStatus(@Param("couponId") Long couponId);
 }

+ 35 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveCouponUserMapper.java

@@ -6,6 +6,7 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveCouponUser;
 import com.fs.live.param.CouponPO;
+import com.fs.live.vo.LiveCouponUserDetailVo;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -34,6 +35,9 @@ public interface LiveCouponUserMapper
      */
     public List<LiveCouponUser> selectLiveCouponUserList(LiveCouponUser liveCouponUser);
 
+    @DataSource(DataSourceType.SLAVE)
+    public List<LiveCouponUser> selectLiveCouponUserListVO(LiveCouponUser liveCouponUser);
+
     /**
      * 新增优惠券发放记录
      *
@@ -66,15 +70,44 @@ public interface LiveCouponUserMapper
      */
     public int deleteLiveCouponUserByIds(Long[] ids);
 
-    @Select("select lcu.* from live_coupon_user lcu  where lcu.user_id = #{coupon.userId}")
+//    @Select("select lcu.* from live_coupon_user lcu  where lcu.user_id = #{coupon.userId}")
+//    List<LiveCouponUser> selectLiveCouponUserByCouponPO(@Param("coupon") CouponPO coupon);
+
+    @Select("<script>" +
+            "select lcu.* from live_coupon_user lcu where lcu.user_id = #{coupon.userId} and lcu.is_del = 0" +
+            " <if test='coupon.status != null'> and lcu.status = #{coupon.status}</if>" +
+            " order by lcu.create_time desc" +
+            "</script>")
     List<LiveCouponUser> selectLiveCouponUserByCouponPO(@Param("coupon") CouponPO coupon);
 
+
+//    @Select("<script>" +
+//            "select lcu.* from live_coupon_user lcu where lcu.user_id= #{coupon.userId} " +
+//            " <if test='coupon.goodsId != null'>" +
+//            " and (lcu.goods_id= #{coupon.goodsId} or lcu.goods_id is null or lcu.goods_id = 0)" +
+//            " </if>" +
+//            "</script>")
+//    @DataSource(DataSourceType.SLAVE)
+//    List<LiveCouponUser> curCoupon(@Param("coupon") CouponPO coupon);
+
     @Select("<script>" +
-            "select lcu.* from live_coupon_user lcu where lcu.user_id= #{coupon.userId} " +
+            "select lcu.* from live_coupon_user lcu where lcu.user_id= #{coupon.userId} and lcu.is_del = 0" +
+            " <if test='coupon.status != null'> and lcu.status = #{coupon.status}</if>" +
             " <if test='coupon.goodsId != null'>" +
             " and (lcu.goods_id= #{coupon.goodsId} or lcu.goods_id is null or lcu.goods_id = 0)" +
             " </if>" +
+            " order by lcu.create_time desc" +
             "</script>")
     @DataSource(DataSourceType.SLAVE)
     List<LiveCouponUser> curCoupon(@Param("coupon") CouponPO coupon);
+
+    @Select("select lcu.* from live_coupon_user lcu where lcu.verify_code = #{verifyCode} and lcu.is_del = 0 limit 1")
+    LiveCouponUser selectLiveCouponUserByVerifyCode(@Param("verifyCode") String verifyCode);
+
+    List<LiveCouponUserDetailVo> selectLiveCouponUserDetailList(CouponPO coupon);
+
+    LiveCouponUserDetailVo selectLiveCouponUserDetailById(@Param("id") Long id);
+
+    LiveCouponUserDetailVo selectLiveCouponUserDetailByVerifyCode(@Param("verifyCode") String verifyCode);
+
 }

+ 61 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveGroupTypeMapper.java

@@ -0,0 +1,61 @@
+package com.fs.live.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.live.domain.LiveGroupType;
+
+/**
+ * 直播间分组分类Mapper接口
+ * 
+ * @author fs
+ * @date 2026-06-16
+ */
+public interface LiveGroupTypeMapper extends BaseMapper<LiveGroupType>{
+    /**
+     * 查询直播间分组分类
+     * 
+     * @param id 直播间分组分类主键
+     * @return 直播间分组分类
+     */
+    LiveGroupType selectLiveGroupTypeById(Long id);
+
+    /**
+     * 查询直播间分组分类列表
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 直播间分组分类集合
+     */
+    List<LiveGroupType> selectLiveGroupTypeList(LiveGroupType liveGroupType);
+
+    /**
+     * 新增直播间分组分类
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 结果
+     */
+    int insertLiveGroupType(LiveGroupType liveGroupType);
+
+    /**
+     * 修改直播间分组分类
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 结果
+     */
+    int updateLiveGroupType(LiveGroupType liveGroupType);
+
+    /**
+     * 删除直播间分组分类
+     * 
+     * @param id 直播间分组分类主键
+     * @return 结果
+     */
+    int deleteLiveGroupTypeById(Long id);
+
+    /**
+     * 批量删除直播间分组分类
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteLiveGroupTypeByIds(Long[] ids);
+}

+ 12 - 1
fs-service/src/main/java/com/fs/live/param/CouponPO.java

@@ -1,9 +1,12 @@
 package com.fs.live.param;
 
+import com.fs.common.param.BaseQueryParam;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 
 @Data
-public class CouponPO {
+@EqualsAndHashCode(callSuper = true)
+public class CouponPO extends BaseQueryParam {
 
     /**
      * 直播间id
@@ -22,5 +25,13 @@ public class CouponPO {
     private Long userId;
     private Long goodsId;
 
+    /** 优惠券发放记录id */
+    private Long couponUserId;
+
+    /** 核销码 */
+    private String verifyCode;
+
+    /** 状态筛选(0:未核销,1:已核销,2:已过期) */
+    private Integer status;
 
 }

+ 1 - 1
fs-service/src/main/java/com/fs/live/param/LiveAutoTaskImportParam.java

@@ -10,7 +10,7 @@ import java.io.Serializable;
 public class LiveAutoTaskImportParam implements Serializable {
 
     @JsonFormat(pattern = "HH:mm:ss")
-    @Excel(name = "时间(格式为 00:00:00)", width = 20, dateFormat = "HH:mm:ss")
+    @Excel(name = "时间(格式为 00:00:00)", width = 20)
     private String registerDate;
 
     @Excel(name = "昵称")

+ 19 - 0
fs-service/src/main/java/com/fs/live/param/LiveCouponVerifyParam.java

@@ -0,0 +1,19 @@
+package com.fs.live.param;
+
+import lombok.Data;
+
+/**
+ * 直播优惠券核销参数
+ */
+@Data
+public class LiveCouponVerifyParam {
+
+    /** 核销码(扫码获取) */
+    private String verifyCode;
+
+    /** 优惠券发放记录id(可选,与核销码二选一) */
+    private Long couponUserId;
+
+    /** 销售用户ID(服务端填充) */
+    private Long verifyUserId;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/ILiveCouponIssueService.java

@@ -65,4 +65,6 @@ public interface ILiveCouponIssueService
     List<LiveCouponIssue> curCoupon(CouponPO coupon);
 
     LiveCouponIssue selectLiveCouponIssueByCouponId(Long id);
+
+    LiveCouponIssue selectLiveCouponIssueByCouponIdByStatus(Long couponId);
 }

+ 16 - 0
fs-service/src/main/java/com/fs/live/service/ILiveCouponService.java

@@ -6,6 +6,7 @@ import java.util.Map;
 import com.fs.common.core.domain.R;
 import com.fs.live.domain.LiveCoupon;
 import com.fs.live.param.CouponPO;
+import com.fs.live.param.LiveCouponVerifyParam;
 import com.fs.live.vo.LiveCouponListVo;
 
 
@@ -95,4 +96,19 @@ public interface ILiveCouponService
      * @return 结果
      */
     R useNoThresholdCoupon(CouponPO coupon);
+
+    /**
+     * 用户优惠券详情列表(含核销码、状态等)
+     */
+    R userCouponDetailList(CouponPO coupon);
+
+    /**
+     * 优惠券详情(用户查看 / 销售扫码预览)
+     */
+    R couponDetail(CouponPO coupon);
+
+    /**
+     * 销售扫码核销优惠券
+     */
+    R verifyCoupon(LiveCouponVerifyParam param);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/live/service/ILiveCouponUserService.java

@@ -3,6 +3,7 @@ package com.fs.live.service;
 import java.util.List;
 import com.fs.live.domain.LiveCouponUser;
 import com.fs.live.param.CouponPO;
+import com.fs.live.vo.LiveCouponUserDetailVo;
 
 /**
  * 优惠券发放记录Service接口
@@ -27,6 +28,7 @@ public interface ILiveCouponUserService
      * @return 优惠券发放记录集合
      */
     public List<LiveCouponUser> selectLiveCouponUserList(LiveCouponUser liveCouponUser);
+    public List<LiveCouponUser> selectLiveCouponUserListVO(LiveCouponUser liveCouponUser);
 
     /**
      * 新增优惠券发放记录
@@ -63,4 +65,13 @@ public interface ILiveCouponUserService
     List<LiveCouponUser> selectLiveCouponUserByCouponPO(CouponPO coupon);
 
     List<LiveCouponUser> curCoupon(CouponPO coupon);
+
+
+    List<LiveCouponUserDetailVo> selectLiveCouponUserDetailList(CouponPO coupon);
+
+    LiveCouponUserDetailVo selectLiveCouponUserDetailById(Long id);
+
+    LiveCouponUserDetailVo selectLiveCouponUserDetailByVerifyCode(String verifyCode);
+
+    LiveCouponUser selectLiveCouponUserByVerifyCode(String verifyCode);
 }

+ 61 - 0
fs-service/src/main/java/com/fs/live/service/ILiveGroupTypeService.java

@@ -0,0 +1,61 @@
+package com.fs.live.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.live.domain.LiveGroupType;
+
+/**
+ * 直播间分组分类Service接口
+ * 
+ * @author fs
+ * @date 2026-06-16
+ */
+public interface ILiveGroupTypeService extends IService<LiveGroupType>{
+    /**
+     * 查询直播间分组分类
+     * 
+     * @param id 直播间分组分类主键
+     * @return 直播间分组分类
+     */
+    LiveGroupType selectLiveGroupTypeById(Long id);
+
+    /**
+     * 查询直播间分组分类列表
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 直播间分组分类集合
+     */
+    List<LiveGroupType> selectLiveGroupTypeList(LiveGroupType liveGroupType);
+
+    /**
+     * 新增直播间分组分类
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 结果
+     */
+    int insertLiveGroupType(LiveGroupType liveGroupType);
+
+    /**
+     * 修改直播间分组分类
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 结果
+     */
+    int updateLiveGroupType(LiveGroupType liveGroupType);
+
+    /**
+     * 批量删除直播间分组分类
+     * 
+     * @param ids 需要删除的直播间分组分类主键集合
+     * @return 结果
+     */
+    int deleteLiveGroupTypeByIds(Long[] ids);
+
+    /**
+     * 删除直播间分组分类信息
+     * 
+     * @param id 直播间分组分类主键
+     * @return 结果
+     */
+    int deleteLiveGroupTypeById(Long id);
+}

+ 5 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveCouponIssueServiceImpl.java

@@ -162,4 +162,9 @@ public class LiveCouponIssueServiceImpl implements ILiveCouponIssueService
     public LiveCouponIssue selectLiveCouponIssueByCouponId(Long couponId) {
         return liveCouponIssueMapper.selectLiveCouponIssueByCouponId(couponId);
     }
+
+    @Override
+    public LiveCouponIssue selectLiveCouponIssueByCouponIdByStatus(Long couponId) {
+        return liveCouponIssueMapper.selectLiveCouponIssueByCouponIdByStatus(couponId);
+    }
 }

+ 135 - 3
fs-service/src/main/java/com/fs/live/service/impl/LiveCouponServiceImpl.java

@@ -15,9 +15,13 @@ import com.fs.live.mapper.LiveCouponIssueMapper;
 import com.fs.live.mapper.LiveCouponIssueUserMapper;
 import com.fs.live.mapper.LiveMapper;
 import com.fs.live.param.CouponPO;
+import com.fs.live.param.LiveCouponVerifyParam;
 import com.fs.live.service.*;
 import com.fs.live.vo.LiveCouponListVo;
 
+import com.fs.live.vo.LiveCouponUserDetailVo;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.checkerframework.checker.units.qual.A;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -33,6 +37,10 @@ import org.springframework.transaction.annotation.Transactional;
 @Service
 public class LiveCouponServiceImpl implements ILiveCouponService
 {
+
+    /** 获取方式:1-直播优惠卷 */
+    private static final String COMPLETION_COUPON_TYPE = "1";
+
     @Autowired
     private LiveCouponMapper liveCouponMapper;
 
@@ -349,7 +357,8 @@ public class LiveCouponServiceImpl implements ILiveCouponService
         userRecord.setIsDel(0);
         userRecord.setIsDel(0);
         userRecord.setGoodsId(coupon.getGoodsId());
-        userRecord.setType("live-"+coupon.getLiveId());
+        userRecord.setType(COMPLETION_COUPON_TYPE+"-live-"+coupon.getLiveId());
+        userRecord.setVerifyCode(generateVerifyCode());
         //库存 remain_count
         issue.setRemainCount(issue.getRemainCount()-1);
         liveCouponIssueService.updateLiveCouponIssue(issue);
@@ -367,8 +376,9 @@ public class LiveCouponServiceImpl implements ILiveCouponService
 
     @Override
     public R userCouponList(CouponPO coupon) {
-        List<LiveCouponUser> list = liveCouponUserService.selectLiveCouponUserByCouponPO(coupon);
-        return R.ok().put("data", list);
+//        List<LiveCouponUser> list = liveCouponUserService.selectLiveCouponUserByCouponPO(coupon);
+//        return R.ok().put("data", list);
+        return userCouponDetailList(coupon);
     }
 
     @Override
@@ -377,6 +387,128 @@ public class LiveCouponServiceImpl implements ILiveCouponService
         return R.ok().put("data", list);
     }
 
+
+    @Override
+    public R userCouponDetailList(CouponPO coupon) {
+        int pageNum = coupon.getPageNum() != null ? coupon.getPageNum()
+                : (coupon.getPage() != null ? coupon.getPage() : 1);
+        int pageSize = coupon.getPageSize() != null ? coupon.getPageSize() : 10;
+        PageHelper.startPage(pageNum, pageSize);
+        List<LiveCouponUserDetailVo> list = liveCouponUserService.selectLiveCouponUserDetailList(coupon);
+        list.forEach(this::fillCouponStatusName);
+        PageInfo<LiveCouponUserDetailVo> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
+    @Override
+    public R couponDetail(CouponPO coupon) {
+        LiveCouponUserDetailVo detail;
+        if (StringUtils.isNotEmpty(coupon.getVerifyCode())) {
+            detail = liveCouponUserService.selectLiveCouponUserDetailByVerifyCode(coupon.getVerifyCode());
+        } else if (coupon.getCouponUserId() != null) {
+            detail = liveCouponUserService.selectLiveCouponUserDetailById(coupon.getCouponUserId());
+        } else {
+            return R.error("优惠券参数不能为空");
+        }
+        if (detail == null) {
+            return R.error("优惠券不存在");
+        }
+        if (coupon.getUserId() != null && detail.getUserId() != null
+                && !coupon.getUserId().equals(Long.valueOf(detail.getUserId()))) {
+            return R.error("无权查看该优惠券");
+        }
+        fillCouponStatusName(detail);
+        ensureVerifyCode(detail);
+        return R.ok().put("data", detail);
+    }
+
+    @Override
+    @Transactional
+    public R verifyCoupon(LiveCouponVerifyParam param) {
+        if (param.getVerifyUserId() == null) {
+            return R.error("销售未登录");
+        }
+        if (StringUtils.isEmpty(param.getVerifyCode()) && param.getCouponUserId() == null) {
+            return R.error("核销码不能为空");
+        }
+
+        LiveCouponUser couponUser;
+        if (StringUtils.isNotEmpty(param.getVerifyCode())) {
+            couponUser = liveCouponUserService.selectLiveCouponUserByVerifyCode(param.getVerifyCode());
+        } else {
+            couponUser = liveCouponUserService.selectLiveCouponUserById(param.getCouponUserId());
+        }
+        if (couponUser == null || Objects.equals(couponUser.getIsDel(), 1)) {
+            return R.error("优惠券不存在");
+        }
+
+        Date now = DateUtils.getNowDate();
+        if (couponUser.getStatus() != null && couponUser.getStatus() == 1) {
+            return R.error("优惠券已核销");
+        }
+        if (couponUser.getStatus() != null && couponUser.getStatus() == 2) {
+            return R.error("优惠券已过期");
+        }
+        if (couponUser.getLimitTime() != null && couponUser.getLimitTime().before(now)) {
+            LiveCouponUser expired = new LiveCouponUser();
+            expired.setId(couponUser.getId());
+            expired.setStatus(2);
+            expired.setIsFail(0);
+            expired.setUpdateTime(now);
+            liveCouponUserService.updateLiveCouponUser(expired);
+            return R.error("优惠券已过期");
+        }
+
+        LiveCouponUser update = new LiveCouponUser();
+        update.setId(couponUser.getId());
+        update.setStatus(1);
+        update.setUseTime(now);
+        update.setVerifyTime(now);
+        update.setVerifyUserId(param.getVerifyUserId());
+        update.setUpdateTime(now);
+        if (StringUtils.isEmpty(couponUser.getVerifyCode())) {
+            update.setVerifyCode(generateVerifyCode());
+        }
+        liveCouponUserService.updateLiveCouponUser(update);
+
+        LiveCouponUserDetailVo detail = liveCouponUserService.selectLiveCouponUserDetailById(couponUser.getId());
+        fillCouponStatusName(detail);
+        return R.ok("核销成功").put("data", detail);
+    }
+
+    private void fillCouponStatusName(LiveCouponUserDetailVo detail) {
+        if (detail == null || detail.getStatus() == null) {
+            return;
+        }
+        switch (detail.getStatus()) {
+            case 1:
+                detail.setStatusName("已核销");
+                break;
+            case 2:
+                detail.setStatusName("已过期");
+                break;
+            default:
+                detail.setStatusName("未核销");
+                break;
+        }
+    }
+
+    private void ensureVerifyCode(LiveCouponUserDetailVo detail) {
+        if (detail == null || StringUtils.isNotEmpty(detail.getVerifyCode())) {
+            return;
+        }
+        LiveCouponUser update = new LiveCouponUser();
+        update.setId(detail.getId());
+        update.setVerifyCode(generateVerifyCode());
+        update.setUpdateTime(DateUtils.getNowDate());
+        liveCouponUserService.updateLiveCouponUser(update);
+        detail.setVerifyCode(update.getVerifyCode());
+    }
+
+    private String generateVerifyCode() {
+        return "LC" + System.currentTimeMillis() + (int) (Math.random() * 1000);
+    }
+
     @Override
     public List<LiveCoupon> listOn(Long liveId) {
         return liveCouponMapper.listOn(liveId);

+ 33 - 5
fs-service/src/main/java/com/fs/live/service/impl/LiveCouponUserServiceImpl.java

@@ -1,14 +1,17 @@
 package com.fs.live.service.impl;
 
-import java.util.Collections;
-import java.util.List;
+
 import com.fs.common.utils.DateUtils;
+import com.fs.live.domain.LiveCouponUser;
+import com.fs.live.mapper.LiveCouponUserMapper;
 import com.fs.live.param.CouponPO;
+import com.fs.live.service.ILiveCouponUserService;
+import com.fs.live.vo.LiveCouponUserDetailVo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.fs.live.mapper.LiveCouponUserMapper;
-import com.fs.live.domain.LiveCouponUser;
-import com.fs.live.service.ILiveCouponUserService;
+
+import java.util.Collections;
+import java.util.List;
 
 /**
  * 优惠券发放记录Service业务层处理
@@ -46,6 +49,11 @@ public class LiveCouponUserServiceImpl implements ILiveCouponUserService
         return liveCouponUserMapper.selectLiveCouponUserList(liveCouponUser);
     }
 
+    @Override
+    public List<LiveCouponUser> selectLiveCouponUserListVO(LiveCouponUser liveCouponUser) {
+        return liveCouponUserMapper.selectLiveCouponUserListVO(liveCouponUser);
+    }
+
     /**
      * 新增优惠券发放记录
      *
@@ -105,4 +113,24 @@ public class LiveCouponUserServiceImpl implements ILiveCouponUserService
     public List<LiveCouponUser> curCoupon(CouponPO coupon) {
         return liveCouponUserMapper.curCoupon(coupon);
     }
+
+    @Override
+    public List<LiveCouponUserDetailVo> selectLiveCouponUserDetailList(CouponPO coupon) {
+        return liveCouponUserMapper.selectLiveCouponUserDetailList(coupon);
+    }
+
+    @Override
+    public LiveCouponUserDetailVo selectLiveCouponUserDetailById(Long id) {
+        return liveCouponUserMapper.selectLiveCouponUserDetailById(id);
+    }
+
+    @Override
+    public LiveCouponUserDetailVo selectLiveCouponUserDetailByVerifyCode(String verifyCode) {
+        return liveCouponUserMapper.selectLiveCouponUserDetailByVerifyCode(verifyCode);
+    }
+
+    @Override
+    public LiveCouponUser selectLiveCouponUserByVerifyCode(String verifyCode) {
+        return liveCouponUserMapper.selectLiveCouponUserByVerifyCode(verifyCode);
+    }
 }

+ 36 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.live.service.impl;
 
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.ServiceException;
@@ -109,6 +110,9 @@ public class LiveDataServiceImpl implements ILiveDataService {
     @Autowired
     private FsStoreOrderItemScrmMapper fsStoreOrderItemScrmMapper;
 
+    @Autowired
+    private LiveVideoMapper liveVideoMapper;
+
     /* 直播大屏展示 数据接口 */
     @Override
     public R dashboardData(Long liveId) {
@@ -1334,6 +1338,38 @@ public class LiveDataServiceImpl implements ILiveDataService {
     @Override
     public R getLiveUserDetailListBySql(Long liveId, Long companyId, Long companyUserId ) {
         List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId, companyId, companyUserId);
+
+        Live live = liveMapper.selectLiveByLiveId(liveId);
+        LiveWatchConfig config = JSON.parseObject(live.getConfigJson(), LiveWatchConfig.class);
+
+        if (config.getEnabled() && 3 == config.getParticipateCondition()) {
+
+            Long completionRate = config.getCompletionRate();
+
+            //根据直播间id 查询 直播视频时长,来判断是否 完课
+            List<LiveVideo> videos = liveVideoMapper.selectByLiveId(liveId);
+            //视频时长
+            Long videoDuration = videos.stream()
+                    .filter(v -> v.getVideoType() != null && (v.getVideoType() == 1 || v.getVideoType() == 2))
+                    .mapToLong(v -> v.getDuration() != null ? v.getDuration() : 0L)
+                    .sum();
+
+            // 视频时长 * 100 作为完课阈值
+            long threshold = videoDuration * completionRate;
+
+            for (LiveUserDetailVo userDetail : userDetailList) {
+                long liveWatch = userDetail.getLiveWatchDuration() != null ? userDetail.getLiveWatchDuration() : 0L;
+                long playbackWatch = userDetail.getPlaybackWatchDuration() != null ? userDetail.getPlaybackWatchDuration() : 0L;
+
+                boolean liveCompleted = liveWatch * 100 >= threshold ;
+                boolean playbackCompleted = playbackWatch * 100 >= threshold;
+
+                userDetail.setIsCompleted(liveCompleted || playbackCompleted ? 1 : 0);
+            }
+
+        }
+
+
         // 使用 PageInfo 获取分页信息
         PageInfo<LiveUserDetailVo> pageInfo = new PageInfo<>(userDetailList);
         R data = R.ok().put("data", userDetailList);

+ 94 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveGroupTypeServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.live.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.live.mapper.LiveGroupTypeMapper;
+import com.fs.live.domain.LiveGroupType;
+import com.fs.live.service.ILiveGroupTypeService;
+
+/**
+ * 直播间分组分类Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-06-16
+ */
+@Service
+public class LiveGroupTypeServiceImpl extends ServiceImpl<LiveGroupTypeMapper, LiveGroupType> implements ILiveGroupTypeService {
+
+    /**
+     * 查询直播间分组分类
+     * 
+     * @param id 直播间分组分类主键
+     * @return 直播间分组分类
+     */
+    @Override
+    public LiveGroupType selectLiveGroupTypeById(Long id)
+    {
+        return baseMapper.selectLiveGroupTypeById(id);
+    }
+
+    /**
+     * 查询直播间分组分类列表
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 直播间分组分类
+     */
+    @Override
+    public List<LiveGroupType> selectLiveGroupTypeList(LiveGroupType liveGroupType)
+    {
+        return baseMapper.selectLiveGroupTypeList(liveGroupType);
+    }
+
+    /**
+     * 新增直播间分组分类
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 结果
+     */
+    @Override
+    public int insertLiveGroupType(LiveGroupType liveGroupType)
+    {
+        liveGroupType.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertLiveGroupType(liveGroupType);
+    }
+
+    /**
+     * 修改直播间分组分类
+     * 
+     * @param liveGroupType 直播间分组分类
+     * @return 结果
+     */
+    @Override
+    public int updateLiveGroupType(LiveGroupType liveGroupType)
+    {
+        liveGroupType.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateLiveGroupType(liveGroupType);
+    }
+
+    /**
+     * 批量删除直播间分组分类
+     * 
+     * @param ids 需要删除的直播间分组分类主键
+     * @return 结果
+     */
+    @Override
+    public int deleteLiveGroupTypeByIds(Long[] ids)
+    {
+        return baseMapper.deleteLiveGroupTypeByIds(ids);
+    }
+
+    /**
+     * 删除直播间分组分类信息
+     * 
+     * @param id 直播间分组分类主键
+     * @return 结果
+     */
+    @Override
+    public int deleteLiveGroupTypeById(Long id)
+    {
+        return baseMapper.deleteLiveGroupTypeById(id);
+    }
+}

+ 190 - 86
fs-service/src/main/java/com/fs/live/service/impl/LiveRedPacketLogServiceImpl.java

@@ -2,10 +2,7 @@ package com.fs.live.service.impl;
 
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 import cn.hutool.json.JSONUtil;
@@ -14,6 +11,7 @@ import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.constant.FsConstants;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -35,15 +33,12 @@ import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
-import com.fs.live.domain.Live;
-import com.fs.live.domain.LiveWatchConfig;
-import com.fs.live.domain.LiveWatchUser;
+import com.fs.live.domain.*;
 import com.fs.live.mapper.LiveWatchUserMapper;
+import com.fs.live.param.CouponPO;
 import com.fs.live.param.LiveRedPacketParam;
 import com.fs.live.mapper.LiveMapper;
-import com.fs.live.service.ILiveMsgService;
-import com.fs.live.service.ILiveService;
-import com.fs.live.service.ILiveWatchUserService;
+import com.fs.live.service.*;
 import com.fs.live.vo.RecordTimeRangeVO;
 import com.fs.voice.utils.StringUtil;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
@@ -57,8 +52,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import com.fs.live.mapper.LiveRedPacketLogMapper;
-import com.fs.live.domain.LiveRedPacketLog;
-import com.fs.live.service.ILiveRedPacketLogService;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
@@ -75,6 +68,9 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
 
     private static final Logger logger = LoggerFactory.getLogger(LiveRedPacketLogServiceImpl.class);
 
+    /** 获取方式:4-完课奖励 */
+    private static final String COMPLETION_COUPON_TYPE = "4";
+
     @Autowired
     private RedissonClient redissonClient;
 
@@ -116,6 +112,16 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
     @Lazy
     private ILiveWatchUserService liveWatchUserService;
 
+    @Autowired
+    private ILiveCouponService liveCouponService;
+
+    @Autowired
+    private ILiveCouponIssueService liveCouponIssueService;
+
+    @Autowired
+    private ILiveCouponUserService liveCouponUserService;
+
+
     /**
      * 查询直播红包 记录
      *
@@ -311,40 +317,21 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
                                 && replayUser.getOnlineSeconds() * 100 >= duration * completionRate) {
 
                             // 如果是 回放完课 发积分 //新增 时间范围 范围内可以领取红包,范围外领取积分或者不领
-                            if (config.getRecordRedPacketEnabled() && !StringUtil.strIsNullOrEmpty(config.getRecordTimeRangeStr())){
+                            if (config.getRecordRedPacketEnabled()!=null
+                                    && config.getRecordRedPacketEnabled()
+                                    && !StringUtil.strIsNullOrEmpty(config.getRecordTimeRangeStr())){
 
                                 List<RecordTimeRangeVO> ranges = parseRecordRewards(config.getRecordTimeRangeStr());
                                 RecordTimeRangeVO matched = matchCurrentTime(ranges);
 
                                 if (matched != null) {
-                                    // 根据 rewardType 发放对应奖励 1=红包 2=积分
-                                    if ("1".equals(matched.getRewardType())) {
-
-                                        WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
-
-                                        if (StringUtil.strIsNullOrEmpty(user.getAppOpenId())){
-                                            return R.error("请重新登录app");
-                                        }
-
-                                        // 用app的 appOpenId
-                                        String openId = user.getAppOpenId();
-                                        packetParam.setOpenId(openId);
-                                        BeanUtils.copyProperties(param, packetParam);
-
-                                        //重置红包数
-                                        config.setRedPacketAmount(matched.getRedPacketAmount());
-                                        return sendAppLiveRedPacketAuto(packetParam, watchUser, config, param);
-                                    }else if ("2".equals(matched.getRewardType())) {
-                                        //重置发放的积分
-                                        config.setScoreAmount(matched.getScoreAmount());
-                                        return sendLiveIntegralReward(param, user, watchUser, config);
-                                    }
+                                    return dispatchRecordRewards(matched, user, param, watchUser, config);
                                 }else {
                                     return R.error("当前时间段,不满足领取奖励条件");
                                 }
                             }else {
-                                // 更新直播观看记录的奖励类型
-                                watchUser.setRewardType(3);
+                                // 更新直播观看记录的奖励类型,99=未设置奖励
+                                watchUser.setRewardType(99);
                                 watchUser.setSendType(1);
 
                                 watchUserMapper.updateLiveWatchUser(watchUser);
@@ -382,40 +369,21 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
 
                     //新增 时间范围 范围内可以领取红包,范围外领取积分或者不领
                     // 如果是 回放完课 发积分 //新增 时间范围 范围内可以领取红包,范围外领取积分或者不领
-                    if (config.getRecordRedPacketEnabled() && !StringUtil.strIsNullOrEmpty(config.getRecordTimeRangeStr())){
+                    if (config.getRecordRedPacketEnabled()!=null
+                            && config.getRecordRedPacketEnabled()
+                            && !StringUtil.strIsNullOrEmpty(config.getRecordTimeRangeStr())){
 
                         List<RecordTimeRangeVO> ranges = parseRecordRewards(config.getRecordTimeRangeStr());
                         RecordTimeRangeVO matched = matchCurrentTime(ranges);
 
                         if (matched != null) {
-                            // 根据 rewardType 发放对应奖励 1=红包 2=积分
-                            if ("1".equals(matched.getRewardType())) {
-
-                                WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
-
-                                if (StringUtil.strIsNullOrEmpty(user.getAppOpenId())){
-                                    return R.error("请重新登录app");
-                                }
-
-                                // 用app的 appOpenId
-                                String openId = user.getAppOpenId();
-                                packetParam.setOpenId(openId);
-                                BeanUtils.copyProperties(param, packetParam);
-
-                                //重置红包数
-                                config.setRedPacketAmount(matched.getRedPacketAmount());
-                                return sendAppLiveRedPacketAuto(packetParam, watchUser, config, param);
-                            }else if ("2".equals(matched.getRewardType())) {
-                                //重置发放的积分
-                                config.setScoreAmount(matched.getScoreAmount());
-                                return sendLiveIntegralReward(param, user, watchUser, config);
-                            }
+                            return dispatchRecordRewards(matched, user, param, watchUser, config);
                         }else {
                             return R.error("当前时间段,不满足领取奖励条件");
                         }
                     }else {
-                        // 更新直播观看记录的奖励类型
-                        watchUser.setRewardType(3);
+                        // 更新直播观看记录的奖励类型,99=未设置奖励
+                        watchUser.setRewardType(99);
                         watchUser.setSendType(1);
 
                         watchUserMapper.updateLiveWatchUser(watchUser);
@@ -424,12 +392,12 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
                     }
 
                     // 更新直播观看记录的奖励类型
-                    watchUser.setRewardType(3);
-                    watchUser.setSendType(1);
-
-                    watchUserMapper.updateLiveWatchUser(watchUser);
-                    log.error("回放未满足-奖励设置外:{},{}",live.getLiveId(),config);
-                    return R.ok("未设置回放奖励");
+//                    watchUser.setRewardType(3);
+//                    watchUser.setSendType(1);
+//
+//                    watchUserMapper.updateLiveWatchUser(watchUser);
+//                    log.error("回放未满足-奖励设置外:{},{}",live.getLiveId(),config);
+//                    return R.ok("未设置回放奖励");
 //                    // 如果是 回放完课 发积分
 //                    return sendLiveIntegralReward(param, user, watchUser, config);
                 }else {
@@ -456,26 +424,87 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
         }
         String[] records = recordTimeRangeStr.split(",");
         for (String record : records) {
-            String[] parts = record.split("-", 5);
+            if (record.isEmpty()) continue;
+            String[] parts = record.split("-", 6);
             if (parts.length < 3) continue;
 
             RecordTimeRangeVO range = new RecordTimeRangeVO();
             range.setStartTime(parts[0]);
             range.setEndTime(parts[1]);
-            range.setRewardType(parts[2]);
 
-            if ("1".equals(parts[2]) && parts.length > 3 && !parts[3].isEmpty()) {
+            // rewardTypes: 用|分隔的多个类型,如 "1|2|3"
+            if (parts[2] != null && !parts[2].isEmpty()) {
+                range.setRewardTypes(Arrays.asList(parts[2].split("\\|")));
+            }
+
+            // 红包金额
+            if (parts.length > 3 && !parts[3].isEmpty()) {
                 range.setRedPacketAmount(BigDecimal.valueOf(Double.parseDouble(parts[3])));
             }
-            if ("2".equals(parts[2]) && parts.length > 4 && !parts[4].isEmpty()) {
+            // 积分
+            if (parts.length > 4 && !parts[4].isEmpty()) {
                 range.setScoreAmount(Long.valueOf(parts[4]));
             }
+            // 核销卷ID
+            if (parts.length > 5 && !parts[5].isEmpty()) {
+                range.setCouponId(Long.valueOf(parts[5]));
+            }
 
             result.add(range);
         }
         return result;
     }
 
+    /**
+     * 根据匹配到的时间段,组合发放奖励(红包/积分/核销卷)
+     */
+    private R dispatchRecordRewards(RecordTimeRangeVO matched, FsUser user, LiveRedPacketParam param,
+                                     LiveWatchUser watchUser, LiveWatchConfig config) {
+        boolean hasReward = false;
+        R lastResult = R.ok();
+        // 组合 rewardType: 1=红包, 2=积分, 4=核销卷, 组合值相加
+        int combinedRewardType = 0;
+
+        // 红包
+        if (matched.getRewardTypes() != null && matched.getRewardTypes().contains("1")) {
+            WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
+            if (StringUtil.strIsNullOrEmpty(user.getAppOpenId())) {
+                return R.error("请重新登录app");
+            }
+            String openId = user.getAppOpenId();
+            packetParam.setOpenId(openId);
+            BeanUtils.copyProperties(param, packetParam);
+            config.setRedPacketAmount(matched.getRedPacketAmount());
+            lastResult = sendAppLiveRedPacketAuto(packetParam, watchUser, config, param);
+//            lastResult = R.ok();
+            hasReward = true;
+            combinedRewardType |= 1;
+        }
+        // 积分
+        if (matched.getRewardTypes() != null && matched.getRewardTypes().contains("2")) {
+            config.setScoreAmount(matched.getScoreAmount());
+            lastResult = sendLiveIntegralReward(param, user, watchUser, config);
+            hasReward = true;
+            combinedRewardType |= 2;
+        }
+        // 核销卷
+        if (matched.getRewardTypes() != null && matched.getRewardTypes().contains("3")) {
+            lastResult=sendIssueCoupon(param.getLiveId(),user.getUserId(),matched.getCouponId(),watchUser);
+            hasReward = true;
+            combinedRewardType |= 4;
+        }
+
+        if (!hasReward) {
+            return R.error("当前时间段,不满足领取奖励条件");
+        }
+
+        // 覆盖为组合后的 rewardType,标记实际发放了哪些奖励
+        watchUser.setRewardType(combinedRewardType);
+        watchUser.setSendType(1);
+        watchUserMapper.updateLiveWatchUser(watchUser);
+        return lastResult;
+    }
+
     private static int toMinutes(String timeStr) {
         String[] parts = timeStr.split(":");
         return Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]);
@@ -498,6 +527,81 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
         return null;
     }
 
+    /**
+    * 发放优惠卷
+    */
+    public R sendIssueCoupon(Long liveId, Long userId, Long couponId,LiveWatchUser watchUser) {
+
+
+
+        LiveCoupon coupon = liveCouponService.selectLiveCouponById(couponId);
+        if (coupon == null) {
+            watchUser.setRemark("核销卷不存在-发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+        //限制领取次数
+        Integer limitReceiveCount = coupon.getLimitReceiveCount();
+
+        //查寻领取次数
+        LiveCouponUser couponUser = new LiveCouponUser();
+        couponUser.setCouponId(couponId);
+        couponUser.setUserId(userId.intValue());
+
+
+        List<LiveCouponUser> liveCouponUsers = liveCouponUserService.selectLiveCouponUserListVO(couponUser);
+        if (liveCouponUsers != null && liveCouponUsers.size() >= limitReceiveCount) {
+            watchUser.setRemark("该核销卷领取达到上限,发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+
+        LiveCouponIssue couponIssue = liveCouponIssueService.selectLiveCouponIssueByCouponIdByStatus(couponId);
+        if (couponIssue == null || couponIssue.getStatus() == null || couponIssue.getStatus() != 1) {
+            watchUser.setRemark("核销卷未发布或已删除-发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+        if (couponIssue.getRemainCount() <= 0) {
+            watchUser.setRemark("核销卷剩余数量不足-发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+
+        Date now = new Date();
+
+        couponUser.setCouponTitle(coupon.getTitle());
+        couponUser.setCouponPrice(coupon.getCouponPrice());
+        couponUser.setUseMinPrice(coupon.getUseMinPrice());
+        if (couponIssue.getLimitTime() != null) {
+            couponUser.setLimitTime(couponIssue.getLimitTime());
+        } else if (coupon.getCouponTime() != null) {
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(now);
+            cal.add(Calendar.DATE, coupon.getCouponTime().intValue());
+            couponUser.setLimitTime(cal.getTime());
+        }
+
+        couponUser.setType(COMPLETION_COUPON_TYPE+"-live-"+liveId);
+        couponUser.setStatus(0);
+        couponUser.setIsFail(0);
+        couponUser.setIsDel(0);
+        couponUser.setCreateTime(now);
+
+        //库存 remain_count
+        couponIssue.setRemainCount(couponIssue.getRemainCount()-1);
+        liveCouponIssueService.updateLiveCouponIssue(couponIssue);
+
+        //录入直播核销卷发放记录
+        liveCouponUserService.insertLiveCouponUser(couponUser);
+
+        return R.ok();
+    }
+
     @Override
     public R syncLiveRedPacket(String outBatchNo, String batchId) {
         LiveRedPacketLog log = redPacketLogMapper.selectLiveRedPacketLogByBatchNo(outBatchNo);
@@ -611,9 +715,9 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
                 if (sendRedPacket.get("code").equals(200)) {
 
                     // 更新观看记录的奖励类型
-                    watchUser.setRewardType(1);
-                    watchUser.setSendType(1);
-                    watchUserMapper.updateLiveWatchUser(watchUser);
+//                    watchUser.setRewardType(1);
+//                    watchUser.setSendType(1);
+//                    watchUserMapper.updateLiveWatchUser(watchUser);
 
                     LiveRedPacketLog liveRedPacketLog = new LiveRedPacketLog();
                     TransferBillsResult transferBillsResult;
@@ -667,9 +771,9 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
                     if (sendRedPacket.get("code").equals(200)) {
 
                         // 更新观看记录的奖励类型
-                        watchUser.setRewardType(1);
-                        watchUser.setSendType(1);
-                        watchUserMapper.updateLiveWatchUser(watchUser);
+//                        watchUser.setRewardType(1);
+//                        watchUser.setSendType(1);
+//                        watchUserMapper.updateLiveWatchUser(watchUser);
 
                         LiveRedPacketLog liveRedPacketLog = new LiveRedPacketLog();
                         TransferBillsResult transferBillsResult;
@@ -706,10 +810,10 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
             }
         } else {
 
-            // 更新直播观看记录的奖励类型
-            watchUser.setRewardType(1);
-            watchUser.setSendType(1);
-            watchUserMapper.updateLiveWatchUser(watchUser);
+//            // 更新直播观看记录的奖励类型
+//            watchUser.setRewardType(1);
+//            watchUser.setSendType(1);
+//            watchUserMapper.updateLiveWatchUser(watchUser);
 
             LiveRedPacketLog liveRedPacketLog = new LiveRedPacketLog();
             // 添加红包记录
@@ -806,10 +910,10 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
         integralLogs.setCreateTime(new Date());
         fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
 
-        //更新看课记录的奖励类型
-        watchUser.setRewardType(2);
-        watchUser.setSendType(1);
-        watchUserMapper.updateLiveWatchUser(watchUser);
+//        //更新看课记录的奖励类型
+//        watchUser.setRewardType(2);
+//        watchUser.setSendType(1);
+//        watchUserMapper.updateLiveWatchUser(watchUser);
         logger.info("发放奖励====================》直播看课记录,{}", watchUser);
 
         //积分转换红包

+ 76 - 0
fs-service/src/main/java/com/fs/live/vo/LiveCouponUserDetailVo.java

@@ -0,0 +1,76 @@
+package com.fs.live.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
+/**
+ * 用户优惠券详情(App端列表/详情展示)
+ */
+@Data
+public class LiveCouponUserDetailVo {
+
+    /** 优惠券发放记录id */
+    private Long id;
+
+    /** 优惠券模板id */
+    private Long couponId;
+
+    /** 优惠券所属用户 */
+    private Integer userId;
+
+    /** 优惠券名称 */
+    private String couponTitle;
+
+
+    /** 优惠券的面值 */
+    private BigDecimal couponPrice;
+
+    /** 最低消费多少金额可用优惠券 */
+    private BigDecimal useMinPrice;
+
+    /** 优惠券结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date limitTime;
+
+    /** 使用时间/核销时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date useTime;
+
+    /** 获取方式 */
+    private String type;
+
+    /** 状态(0:未核销,1:已核销,2:已过期) */
+    private Integer status;
+
+    /** 状态描述 */
+    private String statusName;
+
+    /** 是否有效 */
+    private Integer isFail;
+
+    /** 商品ID */
+    private Long goodsId;
+
+    /** 核销码(用于生成二维码) */
+    private String verifyCode;
+
+    /** 核销销售用户ID */
+    private Long verifyUserId;
+
+    /** 核销销售姓名 */
+    private String verifyUserName;
+
+    /** 领取时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 用户昵称 */
+    private String nickname;
+
+    /** 用户手机号 */
+    private String phone;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java

@@ -1,5 +1,6 @@
 package com.fs.live.vo;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -35,6 +36,17 @@ public class LiveUserDetailVo {
 
     /** 分公司的销售是谁 */
     private String salesName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String updateTime;
+
+    /**
+    * 是否领取奖励 1红包 2积分 3没设置
+    */
+    private Integer rewardType;
+
+    /** 是否完课 1是 0否 */
+    private Integer isCompleted;
 }
 
 

+ 7 - 1
fs-service/src/main/java/com/fs/live/vo/RecordTimeRangeVO.java

@@ -3,13 +3,14 @@ package com.fs.live.vo;
 import lombok.Data;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 @Data
 public class RecordTimeRangeVO {
 
     private String startTime;   // "17:44"
     private String endTime;     // "18:00"
-    private String rewardType;  // 1=红包 2=积分
+    private List<String> rewardTypes;  // 1=红包 2=积分 3=核销卷,多个用|分隔
     /**
     * 红包金额
     */
@@ -20,4 +21,9 @@ public class RecordTimeRangeVO {
     */
     private Long scoreAmount;
 
+    /**
+    * 核销卷ID
+    */
+    private Long couponId;
+
 }

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

@@ -44,14 +44,14 @@ spring:
       druid:
         # 主库数据源
         master:
-          url: jdbc:mysql://172.16.0.56:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+          url: jdbc:mysql://172.16.0.56:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
           username: root
           password: ylrz17452131..!@YY
         # 从库数据源
         slave:
           # 从数据源开关/默认关闭
           enabled: false
-          url: jdbc:mysql://172.16.0.56:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+          url: jdbc:mysql://172.16.0.56:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
           username: root
           password: ylrz17452131..!@YY
         # 初始连接数
@@ -100,7 +100,7 @@ spring:
         # 主库数据源
         master:
 #          url: jdbc:mysql://172.17.0.3:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-          url: jdbc:mysql://172.16.0.56:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+          url: jdbc:mysql://172.16.0.56:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
           username: root
           password: ylrz17452131..!@YY
         # 初始连接数

+ 33 - 0
fs-service/src/main/resources/db/changelog/changes/20260613-live-user-add-is-del.sql

@@ -15,3 +15,36 @@ ALTER TABLE live_user
 ALTER TABLE fs_course_play_source_config
     ADD COLUMN integral_goods VARCHAR(2000) NULL DEFAULT NULL COMMENT '积分商品配置';
 --rollback ALTER TABLE fs_course_play_source_config DROP COLUMN integral_goods;
+
+--changeset sgw:20260616-live-live_group_type
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'live'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'live' AND column_name = 'live_group_type'
+ALTER TABLE live
+    ADD COLUMN live_group_type integer DEFAULT NULL COMMENT '直播间分组分类';
+--rollback ALTER TABLE live DROP COLUMN live_group_type;
+
+
+--changeset sgw:20260616-live_coupon_user_verify_code
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user' AND column_name = 'verify_code'
+ALTER TABLE live_coupon_user
+    ADD COLUMN verify_code VARCHAR(64) NULL DEFAULT NULL COMMENT '核销码';
+--rollback ALTER TABLE live_coupon_user DROP COLUMN verify_code;
+
+--changeset sgw:20260616-live_coupon_user_verify_user_id
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user' AND column_name = 'verify_user_id'
+ALTER TABLE live_coupon_user
+    ADD COLUMN verify_user_id BigInteger DEFAULT NULL COMMENT '核销销售用户ID';
+--rollback ALTER TABLE live_coupon_user DROP COLUMN verify_user_id;
+
+--changeset sgw:20260616-live_coupon_user_verify_time
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user' AND column_name = 'verify_time'
+ALTER TABLE live_coupon_user
+    ADD COLUMN verify_time DATETIME DEFAULT NULL COMMENT '核销时间';
+--rollback ALTER TABLE live_coupon_user DROP COLUMN verify_time;

+ 1 - 0
fs-service/src/main/resources/db/changelog/db.changelog-master.xml

@@ -10,5 +10,6 @@
 
     <include file="baseline/baseline.sql" relativeToChangelogFile="true"/>
     <include file="changes/20260613-live-user-add-is-del.sql" relativeToChangelogFile="true"/>
+    <include file="table/live_group_type.sql" relativeToChangelogFile="true"/>
 
 </databaseChangeLog>

+ 14 - 0
fs-service/src/main/resources/db/changelog/table/live_group_type.sql

@@ -0,0 +1,14 @@
+--liquibase formatted sql
+
+
+
+--changeset sgw:20260616-live_group_type
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'live_group_type'
+CREATE TABLE `live_group_type` (
+                                   `id` int NOT NULL AUTO_INCREMENT,
+                                   `live_group_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '直播间分类',
+                                   `create_time` datetime DEFAULT NULL,
+                                   `update_time` datetime DEFAULT NULL,
+                                   PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

+ 94 - 1
fs-service/src/main/resources/mapper/live/LiveCouponUserMapper.xml

@@ -20,10 +20,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="isFail"    column="is_fail"    />
         <result property="isDel"    column="is_del"    />
         <result property="goodsId"    column="goods_id"    />
+        <result property="verifyCode"    column="verify_code"    />
+        <result property="verifyUserId"    column="verify_user_id"    />
+        <result property="verifyTime"    column="verify_time"    />
+    </resultMap>
+
+    <resultMap type="com.fs.live.vo.LiveCouponUserDetailVo" id="LiveCouponUserDetailVoResult">
+        <result property="id" column="id"/>
+        <result property="couponId" column="coupon_id"/>
+        <result property="userId" column="user_id"/>
+        <result property="couponTitle" column="coupon_title"/>
+        <result property="couponPrice" column="coupon_price"/>
+        <result property="useMinPrice" column="use_min_price"/>
+        <result property="limitTime" column="limit_time"/>
+        <result property="useTime" column="use_time"/>
+        <result property="type" column="type"/>
+        <result property="status" column="status"/>
+        <result property="isFail" column="is_fail"/>
+        <result property="goodsId" column="goods_id"/>
+        <result property="verifyCode" column="verify_code"/>
+        <result property="verifyUserId" column="verify_user_id"/>
+        <result property="createTime" column="create_time"/>
+        <result property="nickname" column="nickname"/>
+        <result property="phone" column="phone"/>
+        <result property="verifyUserName" column="verify_user_name"/>
     </resultMap>
 
     <sql id="selectLiveCouponUserVo">
-        select id, coupon_id, user_id, coupon_title, coupon_price, use_min_price, create_time, update_time, limit_time, use_time, type, status, is_fail, is_del,goods_id from live_coupon_user
+        select id, coupon_id, user_id, coupon_title, coupon_price, use_min_price, create_time, update_time, limit_time, use_time, type, status, is_fail, is_del, goods_id, verify_code, verify_user_id, verify_time from live_coupon_user
+    </sql>
+
+    <sql id="selectLiveCouponUserDetailSql">
+        select lcu.id, lcu.coupon_id, lcu.user_id, lcu.coupon_title, lcu.coupon_price, lcu.use_min_price,
+               lcu.create_time, lcu.limit_time, lcu.use_time, lcu.type, lcu.status, lcu.is_fail, lcu.goods_id,
+               lcu.verify_code, lcu.verify_user_id, u.nick_name as nickname, u.phone as phone,
+               cu.nick_name as verify_user_name
+        from live_coupon_user lcu
+                 left join fs_user u on lcu.user_id = u.user_id
+                 left join company_user cu on lcu.verify_user_id = cu.user_id
     </sql>
 
     <select id="selectLiveCouponUserList" parameterType="LiveCouponUser" resultMap="LiveCouponUserResult">
@@ -34,6 +68,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         from live_coupon_user as  cou
          left join fs_user u on cou.user_id=u.user_id
 
+        <where>
+            <if test="couponId != null "> and cou.coupon_id = #{couponId}</if>
+            <if test="userId != null "> and cou.user_id = #{userId}</if>
+            <if test="couponTitle != null  and couponTitle != ''"> and cou.coupon_title = #{couponTitle}</if>
+            <if test="couponPrice != null "> and cou.coupon_price = #{couponPrice}</if>
+            <if test="useMinPrice != null "> and cou.use_min_price = #{useMinPrice}</if>
+            <if test="limitTime != null "> and cou.limit_time = #{limitTime}</if>
+            <if test="useTime != null "> and cou.use_time = #{useTime}</if>
+            <if test="type != null  and type != ''"> and cou.type = #{type}</if>
+            <if test="status != null "> and cou.status = #{status}</if>
+            <if test="isFail != null "> and cou.is_fail = #{isFail}</if>
+            <if test="isDel != null "> and cou.is_del = #{isDel}</if>
+            <if test="goodsId != null "> and cou.goods_id = #{goodsId}</if>
+            <if test="verifyCode != null and verifyCode != ''"> and cou.verify_code = #{verifyCode}</if>
+            <if test="verifyUserId != null "> and cou.verify_user_id = #{verifyUserId}</if>
+        </where>
+    </select>
+
+    <select id="selectLiveCouponUserListVO" parameterType="LiveCouponUser" resultMap="LiveCouponUserResult">
+        select id, coupon_id, user_id, coupon_title, coupon_price, use_min_price, create_time, update_time,
+               limit_time, use_time, type, status, is_fail, is_del, goods_id, verify_code, verify_user_id,
+               verify_time from live_coupon_user
         <where>
             <if test="couponId != null "> and coupon_id = #{couponId}</if>
             <if test="userId != null "> and user_id = #{userId}</if>
@@ -47,7 +103,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isFail != null "> and is_fail = #{isFail}</if>
             <if test="isDel != null "> and is_del = #{isDel}</if>
             <if test="goodsId != null "> and goods_id = #{goodsId}</if>
+            <if test="verifyCode != null and verifyCode != ''"> and verify_code = #{verifyCode}</if>
+            <if test="verifyUserId != null "> and verify_user_id = #{verifyUserId}</if>
+        </where>
+
+    </select>
+
+    <select id="selectLiveCouponUserDetailList" parameterType="com.fs.live.param.CouponPO" resultMap="LiveCouponUserDetailVoResult">
+        <include refid="selectLiveCouponUserDetailSql"/>
+        <where>
+            lcu.is_del = 0
+            <if test="userId != null"> and lcu.user_id = #{userId}</if>
+            <if test="status != null"> and lcu.status = #{status}</if>
+            <if test="goodsId != null">
+                and (lcu.goods_id = #{goodsId} or lcu.goods_id is null or lcu.goods_id = 0)
+            </if>
         </where>
+        order by lcu.create_time desc
+    </select>
+
+    <select id="selectLiveCouponUserDetailById" parameterType="Long" resultMap="LiveCouponUserDetailVoResult">
+        <include refid="selectLiveCouponUserDetailSql"/>
+        where lcu.id = #{id} and lcu.is_del = 0
+    </select>
+
+    <select id="selectLiveCouponUserDetailByVerifyCode" parameterType="String" resultMap="LiveCouponUserDetailVoResult">
+        <include refid="selectLiveCouponUserDetailSql"/>
+        where lcu.verify_code = #{verifyCode} and lcu.is_del = 0
+        limit 1
     </select>
 
     <select id="selectLiveCouponUserById" parameterType="Long" resultMap="LiveCouponUserResult">
@@ -55,6 +138,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where id = #{id}
     </select>
 
+
     <insert id="insertLiveCouponUser" parameterType="LiveCouponUser" useGeneratedKeys="true" keyProperty="id">
         insert into live_coupon_user
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -72,6 +156,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isFail != null">is_fail,</if>
             <if test="isDel != null">is_del,</if>
             <if test="goodsId != null">goods_id,</if>
+            <if test="verifyCode != null and verifyCode != ''">verify_code,</if>
+            <if test="verifyUserId != null">verify_user_id,</if>
+            <if test="verifyTime != null">verify_time,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="couponId != null">#{couponId},</if>
@@ -88,6 +175,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isFail != null">#{isFail},</if>
             <if test="isDel != null">#{isDel},</if>
             <if test="goodsId != null">#{goodsId},</if>
+            <if test="verifyCode != null and verifyCode != ''">#{verifyCode},</if>
+            <if test="verifyUserId != null">#{verifyUserId},</if>
+            <if test="verifyTime != null">#{verifyTime},</if>
          </trim>
     </insert>
 
@@ -108,6 +198,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isFail != null">is_fail = #{isFail},</if>
             <if test="isDel != null">is_del = #{isDel},</if>
             <if test="goodsId != null">goods_id = #{goodsId},</if>
+            <if test="verifyCode != null and verifyCode != ''">verify_code = #{verifyCode},</if>
+            <if test="verifyUserId != null">verify_user_id = #{verifyUserId},</if>
+            <if test="verifyTime != null">verify_time = #{verifyTime},</if>
         </trim>
         where id = #{id}
     </update>

+ 4 - 3
fs-service/src/main/resources/mapper/live/LiveDataMapper.xml

@@ -1037,7 +1037,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(order_info.orderCount, 0) AS orderCount,
             COALESCE(order_info.orderAmount, 0) AS orderAmount,
             COALESCE(c.company_name, '') AS companyName,
-            COALESCE(cu.user_name, '') AS salesName
+            COALESCE(cu.user_name, '') AS salesName,
+            MAX(lwu.reward_type) as rewardType
         FROM live_watch_user lwu
         LEFT JOIN fs_user u ON lwu.user_id = u.user_id
         LEFT JOIN (
@@ -1046,7 +1047,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 COUNT(DISTINCT id) AS orderCount,
                 SUM(CASE WHEN (paid = 1 OR paid = '1') THEN pay_price ELSE 0 END) AS orderAmount
             FROM fs_store_order_scrm
-            WHERE order_type = 2 AND remark = #{liveId} AND user_id IS NOT NULL
+            WHERE order_type = 2 AND live_id = #{liveId} AND user_id IS NOT NULL
             GROUP BY user_id
         ) order_info ON lwu.user_id = order_info.user_id
         left join live_user_first_entry lufe on lwu.live_id = lufe.live_id and lwu.user_id = lufe.user_id
@@ -1277,7 +1278,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(COUNT(DISTINCT cu.user_id), 0) AS employeeCount
         FROM company_user cu
             LEFT JOIN company c ON cu.company_id = c.company_id
-        WHERE 
+        WHERE
         cu.status = 0
         AND cu.del_flag = 0
         <if test="companyIds != null and companyIds.size() > 0">

+ 64 - 0
fs-service/src/main/resources/mapper/live/LiveGroupTypeMapper.xml

@@ -0,0 +1,64 @@
+<?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.live.mapper.LiveGroupTypeMapper">
+
+    <resultMap type="LiveGroupType" id="LiveGroupTypeResult">
+        <result property="id"    column="id"    />
+        <result property="liveGroupType"    column="live_group_type"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectLiveGroupTypeVo">
+        select id, live_group_type, create_time, update_time from live_group_type
+    </sql>
+
+    <select id="selectLiveGroupTypeList" parameterType="LiveGroupType" resultMap="LiveGroupTypeResult">
+        <include refid="selectLiveGroupTypeVo"/>
+        <where>
+            <if test="liveGroupType != null  and liveGroupType != ''"> and live_group_type = #{liveGroupType}</if>
+        </where>
+    </select>
+
+    <select id="selectLiveGroupTypeById" parameterType="Long" resultMap="LiveGroupTypeResult">
+        <include refid="selectLiveGroupTypeVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertLiveGroupType" parameterType="LiveGroupType" useGeneratedKeys="true" keyProperty="id">
+        insert into live_group_type
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="liveGroupType != null">live_group_type,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="liveGroupType != null">#{liveGroupType},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateLiveGroupType" parameterType="LiveGroupType">
+        update live_group_type
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="liveGroupType != null">live_group_type = #{liveGroupType},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteLiveGroupTypeById" parameterType="Long">
+        delete from live_group_type where id = #{id}
+    </delete>
+
+    <delete id="deleteLiveGroupTypeByIds" parameterType="String">
+        delete from live_group_type where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 16 - 6
fs-service/src/main/resources/mapper/live/LiveMapper.xml

@@ -34,18 +34,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="idCardUrl"    column="id_card_url"    />
         <result property="liveCodeUrl"    column="live_code_url"    />
         <result property="globalVisible"    column="global_visible"    />
+        <result property="liveGroupType"    column="live_group_type"    />
     </resultMap>
 
     <sql id="selectLiveVo">
         select live_id, company_id, company_user_id, training_period_id, talent_id, live_name, is_audit, live_desc, show_type, status, anchor_id, live_type, start_time, finish_time,
                live_img_url, live_config, id_card_url, is_show, is_del, qw_qr_code, rtmp_url, flv_hls_url,
-               create_time, create_by, update_by, update_time, remark,config_json,global_visible from live
+               create_time, create_by, update_by, update_time, remark,config_json,global_visible,live_group_type from live
     </sql>
 
     <select id="liveList" parameterType="Live" resultMap="LiveResult">
         select a.live_id, a.company_id, a.company_user_id,talent_id, a.live_name, a.is_audit, a.live_desc, a.show_type, a.status, a.anchor_id,
         a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,
-        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,b.file_size,a.global_visible
+        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,b.file_size,
+        a.global_visible,a.live_group_type
         from live
         a
         left join live_video b on a.live_id = b.live_id
@@ -57,7 +59,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectReadyStartLiveList" parameterType="Live" resultMap="LiveResult">
         select a.live_id, a.company_id, a.company_user_id,talent_id, a.live_name, a.is_audit, a.live_desc, a.show_type, a.status, a.anchor_id,
         a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,
-        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,b.file_size,a.global_visible
+        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,b.file_size,
+        a.global_visible,a.live_group_type
         from live
         a
         left join live_video b on a.live_id = b.live_id
@@ -71,7 +74,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectLiveList" parameterType="com.fs.live.domain.Live" resultMap="LiveResult">
         select a.live_id, a.company_id, a.company_user_id, a.training_period_id, a.talent_id, a.live_name, a.is_audit, a.live_desc, a.show_type, a.status, a.anchor_id,
                a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,
-               a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,a.global_visible
+               a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,a.global_visible,a.live_group_type
                 ,c.live_code_url,IFNULL(d.company_name, '总台') AS company_name,b.file_size
 
         from live a
@@ -111,6 +114,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="isAudit != null "> and is_audit = #{isAudit}</if>
         <if test="beginTime != null and beginTime != ''"> and a.create_time &gt;= STR_TO_DATE(#{beginTime}, '%Y-%m-%d %H:%i:%s')</if>
         <if test="endTime != null and endTime != ''"> and a.create_time &lt;= STR_TO_DATE(#{endTime}, '%Y-%m-%d %H:%i:%s')</if>
+        <if test="liveGroupType != null  and liveGroupType != ''"> and live_group_type = #{liveGroupType}</if>
         order by create_time desc
     </select>
 
@@ -192,6 +196,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isAudit != null">is_audit,</if>
             <if test="idCardUrl != null">id_card_url,</if>
             <if test="globalVisible != null">global_visible,</if>
+            <if test="liveGroupType != null">live_group_type,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyId != null">#{companyId},</if>
@@ -222,6 +227,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isAudit != null">#{isAudit},</if>
             <if test="idCardUrl != null">#{idCardUrl},</if>
             <if test="globalVisible != null">#{globalVisible},</if>
+            <if test="liveGroupType != null">#{liveGroupType},</if>
          </trim>
     </insert>
 
@@ -255,6 +261,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isAudit != null">is_audit = #{isAudit},</if>
             <if test="idCardUrl != null">id_card_url = #{idCardUrl},</if>
             <if test="globalVisible != null">global_visible = #{globalVisible},</if>
+            <if test="liveGroupType != null">live_group_type = #{liveGroupType},</if>
         </trim>
         where live_id = #{liveId}
     </update>
@@ -337,6 +344,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 <if test="live.liveConfig != null and live.liveConfig != ''">live_config = #{live.liveConfig},</if>
                 <if test="live.remark != null and live.remark != ''">remark = #{live.remark},</if>
                 <if test="live.globalVisible != null and live.globalVisible != ''">global_visible = #{live.globalVisible},</if>
+                <if test="live.liveGroupType != null and live.liveGroupType != ''">live_group_type = #{live.liveGroupType},</if>
                 update_time = NOW()
             </set>
             WHERE live_id = #{live.liveId}
@@ -356,7 +364,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="liveShowList"  resultMap="LiveResult">
         select a.live_id, a.company_id, a.company_user_id,talent_id, a.live_name, a.is_audit, a.live_desc, a.show_type, a.status, a.anchor_id,
         a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,
-        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,b.file_size
+        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,
+        b.file_size,a.live_group_type
         from live
         a
         left join live_video b on a.live_id = b.live_id
@@ -377,7 +386,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectLiveShowReadyStartLiveList"  resultMap="LiveResult">
         select a.live_id, a.company_id, a.company_user_id,talent_id, a.live_name, a.is_audit, a.live_desc, a.show_type, a.status, a.anchor_id,
         a.live_type, a.start_time, a.finish_time, a.live_img_url, a.live_config, a.id_card_url, a.is_show, a.is_del, a.qw_qr_code, a.rtmp_url,
-        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,b.file_size
+        a.flv_hls_url, a.create_time, a.create_by, a.update_by, a.update_time, a.remark,config_json, b.video_url,c.company_name,
+        b.file_size,a.live_group_type
         from live
         a
         left join live_video b on a.live_id = b.live_id

+ 17 - 9
fs-service/src/main/resources/mapper/live/LiveWatchLogMapper.xml

@@ -216,33 +216,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectLiveWatchLogListInfo"   parameterType="LiveWatchLog" resultType="com.fs.live.vo.LiveWatchLogListVO">
-        select
+        SELECT
         t2.nick_name as userName,
         t2.avatar as userAvatar,
+        t2.user_id,
         t3.live_name,
         t4.name as qwExternalName,
+        t4.id as externalContactId,
         t4.avatar as qwExternalAvatar,
         t5.nick_name as companyUserName,
         t6.qw_user_name,
+        t3.live_id,
         t1.*
-        from live_watch_log t1
-        left join fs_user t2 on t1.user_id = t2.user_id
-        left join live t3 on t3.live_id = t1.live_id
-        left join qw_external_contact t4 on t4.id = t1.external_contact_id
-        left join company_user t5 on t5.user_id = t1.company_user_id
-        left join qw_user t6 on t6.id = t1.qw_user_id
+        FROM live_watch_log t1
+        LEFT JOIN qw_external_contact t4 ON t4.id = t1.external_contact_id
+        LEFT JOIN fs_user t2 ON t4.fs_user_id = t2.user_id
+        LEFT JOIN live t3 ON t3.live_id = t1.live_id
+        LEFT JOIN company_user t5 ON t5.user_id = t1.company_user_id
+        LEFT JOIN qw_user t6 ON t6.id = t1.qw_user_id
         <where>
-            <if test="userId != null "> and t1.user_id = #{userId}</if>
+            <if test="userId != null "> and t2.user_id = #{userId}</if>
+            <if test="userIdName != null "> and t2.nick_name like concat( #{userIdName}, '%')</if>
             <if test="liveId != null "> and t1.live_id = #{liveId}</if>
+            <if test="liveName != null "> and t3.live_name  like concat( #{liveName}, '%')</if>
             <if test="logType != null "> and t1.log_type = #{logType}</if>
             <if test="externalContactId != null "> and t1.external_contact_id = #{externalContactId}</if>
+            <if test="externalContactName != null "> and t4.name like concat( #{externalContactName}, '%')</if>
             <if test="companyId != null "> and t1.company_id = #{companyId}</if>
             <if test="companyUserId != null "> and t1.company_user_id = #{companyUserId}</if>
+            <if test="companyUserName != null "> and t5.nick_name like concat( #{companyUserName}, '%')</if>
             <if test="finishTime != null "> and t1.finish_time = #{finishTime}</if>
             <if test="sopCreateTime != null "> and t1.sop_create_time = #{sopCreateTime}</if>
             <if test="sendAppId != null  and sendAppId != ''"> and t1.send_app_id = #{sendAppId}</if>
             <if test="logSource != null "> and t1.log_source = #{logSource}</if>
             <if test="qwUserId != null  and qwUserId != ''"> and t1.qw_user_id = #{qwUserId}</if>
+            <if test="qwUserName != null  and qwUserName != ''"> and t6.qw_user_name like concat( #{qwUserName}, '%')</if>
             <if test="watchType != null">and t1.watch_type = #{watchType} </if>
             <if test="corpId != null">and t1.corp_id = #{corpId} </if>
             <if test="liveBuy != null">and t1.live_buy = #{liveBuy} </if>
@@ -340,4 +348,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{item.logId}
         </foreach>
     </update>
-</mapper>
+</mapper>

+ 47 - 2
fs-user-app/src/main/java/com/fs/app/controller/live/LiveCouponController.java

@@ -4,7 +4,10 @@ import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
 import com.fs.app.facade.LiveFacadeService;
 import com.fs.common.core.domain.R;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.live.param.CouponPO;
+import com.fs.live.param.LiveCouponVerifyParam;
 import com.fs.live.service.ILiveCouponService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -20,6 +23,8 @@ public class LiveCouponController extends AppBaseController {
     private ILiveCouponService liveCouponService;
     @Autowired
     private LiveFacadeService liveFacadeService;
+    @Autowired
+    private ICompanyUserService companyUserService;
 
     /**
      * 领取优惠券
@@ -31,14 +36,54 @@ public class LiveCouponController extends AppBaseController {
         return liveFacadeService.couponClaim(coupon);
     }
 
+//    /**
+//     * 用户优惠券列表
+//     */
+//    @Login
+//    @PostMapping("/list")
+//    public R list(@RequestBody CouponPO coupon) {
+//        coupon.setUserId(Long.parseLong(getUserId()));
+//        return liveCouponService.userCouponList(coupon);
+//    }
+
     /**
-     * 用户优惠券列表
+     * 用户优惠券详情列表(含核销码、状态)
      */
     @Login
     @PostMapping("/list")
     public R list(@RequestBody CouponPO coupon) {
         coupon.setUserId(Long.parseLong(getUserId()));
-        return liveCouponService.userCouponList(coupon);
+        return liveCouponService.userCouponDetailList(coupon);
+    }
+
+    /**
+     * 优惠券详情(用户查看 / 销售扫码预览)
+     */
+    @Login
+    @PostMapping("/detail")
+    public R detail(@RequestBody CouponPO coupon) {
+        if (coupon.getCouponUserId() == null && coupon.getVerifyCode() == null) {
+            return R.error("参数不能为空");
+        }
+        if (coupon.getCouponUserId() != null) {
+            coupon.setUserId(Long.parseLong(getUserId()));
+        }
+        return liveCouponService.couponDetail(coupon);
+    }
+
+    /**
+     * 销售扫码核销优惠券
+     */
+    @Login
+    @PostMapping("/verify")
+    public R verify(@RequestBody LiveCouponVerifyParam param) {
+        Long companyUserId = getCompanyUserId();
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(companyUserId);
+        if (companyUser == null) {
+            return R.error("销售不存在");
+        }
+        param.setVerifyUserId(companyUserId);
+        return liveCouponService.verifyCoupon(param);
     }
 
     /**