瀏覽代碼

积分订单查询接口

yuhongqi 4 天之前
父節點
當前提交
9859b90ae2
共有 23 個文件被更改,包括 754 次插入20 次删除
  1. 2 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  2. 14 0
      fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java
  3. 88 0
      fs-company-app/src/main/java/com/fs/app/controller/LiveController.java
  4. 30 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java
  5. 1 1
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  6. 8 0
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  7. 17 0
      fs-service/src/main/java/com/fs/his/mapper/CompanyIntegralOrderMapper.java
  8. 17 0
      fs-service/src/main/java/com/fs/his/service/ICompanyIntegralOrderService.java
  9. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralOrderService.java
  10. 99 0
      fs-service/src/main/java/com/fs/his/service/impl/CompanyIntegralOrderServiceImpl.java
  11. 86 1
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  12. 6 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java
  13. 5 0
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  14. 23 0
      fs-service/src/main/java/com/fs/live/param/FsLiveListParam.java
  15. 26 0
      fs-service/src/main/java/com/fs/live/param/FsLiveSortLinkParam.java
  16. 6 0
      fs-service/src/main/java/com/fs/live/service/ILiveService.java
  17. 21 0
      fs-service/src/main/java/com/fs/live/service/ILiveSortLinkService.java
  18. 11 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  19. 158 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveSortLinkServiceImpl.java
  20. 4 0
      fs-service/src/main/java/com/fs/live/vo/LiveConfigVo.java
  21. 57 0
      fs-service/src/main/resources/mapper/his/CompanyIntegralOrderMapper.xml
  22. 13 0
      fs-service/src/main/resources/mapper/live/LiveMapper.xml
  23. 55 16
      fs-user-app/src/main/java/com/fs/app/facade/impl/LiveFacadeServiceImpl.java

+ 2 - 0
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -245,6 +245,8 @@ public class FsIntegralOrderController extends BaseController
             }
 
         }
+
+        fsIntegralOrderService.fillExportWatchAttribution(newFsIntegralOrderListVOS);
         ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
         return util.exportExcel(new ArrayList<>(newFsIntegralOrderListVOS), "积分商品订单数据");
     }

+ 14 - 0
fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java

@@ -40,5 +40,19 @@ public class LiveKeysConstant {
     //记录用户观看直播间信息 直播间id、用户id、外部联系人id、qwUserId
     public static final String LIVE_USER_WATCH_LOG_CACHE = "live:user:watch:log:%s:%s:%s:%s";
 
+    public static final String CACHE_LIVE_INFO = "live:cache:info:%s";
+    public static final String CACHE_LIVE_GOODS_LIST = "live:cache:goods:list:%s:%s:%s";
+    public static final String CACHE_LIVE_MSG_LIST = "live:cache:msg:list:%s";
+    public static final String CACHE_LIVE_VIDEO = "live:cache:video:%s:%s";
+    public static final String CACHE_LIVE_SHOW_GOODS = "live:cache:goods:show:%s";
+    public static final String CACHE_LIVE_STORE = "live:cache:goods:store:%s:%s";
+    public static final String CACHE_USER_ORDER_LIST = "live:cache:order:list:%s:%s";
+    public static final String CACHE_LIVE_VIEW_DATA = "live:cache:view:data:%s";
+    public static final String CACHE_LIVE_RECENT_VIEWERS = "live:cache:recent:viewers:%s";
+    public static final int TTL_LIVE_INFO = 60;
+    public static final int TTL_LIVE_GOODS = 30;
+    public static final int TTL_USER_ORDER = 15;
+    public static final int TTL_LIVE_MSG = 30;
+    public static final int TTL_LIVE_DATA = 30;
 
 }

+ 88 - 0
fs-company-app/src/main/java/com/fs/app/controller/LiveController.java

@@ -0,0 +1,88 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.ResponseResult;
+import com.fs.live.domain.Live;
+import com.fs.live.param.FsLiveListParam;
+import com.fs.live.param.FsLiveSortLinkParam;
+import com.fs.live.service.ILiveService;
+import com.fs.live.service.ILiveSortLinkService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Api("直播相关接口")
+@RestController
+@RequestMapping("/app/fs/live")
+@Slf4j
+public class LiveController extends AppBaseController {
+
+    @Autowired
+    private ILiveService liveService;
+
+    @Autowired
+    private ILiveSortLinkService liveSortLinkService;
+
+    @Login
+    @GetMapping("/pageList")
+    @ApiOperation("未开播/直播中直播间分页列表")
+    public ResponseResult<PageInfo<Live>> pageList(FsLiveListParam param) {
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        param.setCompanyId(getCompanyId());
+        List<Live> list = liveService.listAppAvailableLive(param);
+        return ResponseResult.ok(new PageInfo<>(list));
+    }
+
+    @Login
+    @PostMapping("/liveSortLink")
+    @ApiOperation("生成直播分享链接")
+    public R createLiveSortLink(@RequestBody FsLiveSortLinkParam param) {
+        param.setCompanyId(getCompanyId());
+        param.setCompanyUserId(getCompanyUserId());
+        R liveSortLink = liveSortLinkService.createLiveSortLink(param);
+        Object url = liveSortLink.get("url");
+        if (url == null) {
+            return liveSortLink;
+        }
+        String linkId = liveSortLink.get("linkId").toString();
+        Map<String, Object> map = new HashMap<>();
+        map.put("url", url.toString());
+        map.put("linkId", linkId);
+        return R.ok(map);
+    }
+
+    @Login
+    @PostMapping("/liveSortLinkTo")
+    @ApiOperation("生成直播分享短链(APP发课)")
+    public R createLiveSortLinkTo(@RequestBody FsLiveSortLinkParam param) {
+        param.setCompanyId(getCompanyId());
+        param.setCompanyUserId(getCompanyUserId());
+        R liveSortLink = liveSortLinkService.createAppLiveSortLink(param);
+        Object url = liveSortLink.get("url");
+        if (url == null) {
+            return liveSortLink;
+        }
+        String linkId = liveSortLink.get("linkId").toString();
+        Map<String, Object> map = new HashMap<>();
+        map.put("url", url.toString());
+        map.put("linkId", linkId);
+        return R.ok(map);
+    }
+
+    @Login
+    @GetMapping("/getGotoWxAppLiveLink")
+    @ApiOperation("获取跳转微信小程序直播的链接地址")
+    public ResponseResult<String> getGotoWxAppLiveLink(String linkStr, String appid) {
+        return ResponseResult.ok(liveService.getGotoWxAppLiveLink(linkStr, appid));
+    }
+
+}

+ 30 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java

@@ -19,6 +19,7 @@ import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
 import com.fs.his.param.FsIntegralOrderCreateParam;
 import com.fs.his.param.FsIntegralOrderParam;
+import com.fs.his.service.ICompanyIntegralOrderService;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsIntegralOrderService;
 import com.fs.his.vo.FsIntegralOrderListVO;
@@ -47,6 +48,8 @@ public class FsIntegralOrderController extends BaseController
     @Autowired
     private IFsIntegralOrderService fsIntegralOrderService;
     @Autowired
+    private ICompanyIntegralOrderService companyIntegralOrderService;
+    @Autowired
     private IFsExpressService expressService;
     @Autowired
     private TokenService tokenService;
@@ -68,6 +71,33 @@ public class FsIntegralOrderController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 销售端查询积分订单(按最近一次看课记录归属,数据权限同 list)
+     */
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:list')")
+    @GetMapping("/company/list")
+    public TableDataInfo companyList(FsIntegralOrderParam fsIntegralOrder)
+    {
+        applyCompanyDataScope(fsIntegralOrder);
+        startPage();
+        List<FsIntegralOrderListVO> list = companyIntegralOrderService.selectCompanyIntegralOrderList(fsIntegralOrder);
+        for (FsIntegralOrderListVO vo : list) {
+            vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 销售端导出积分订单(与 company/list 相同查询逻辑)
+     */
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
+    @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/company/export")
+    public AjaxResult companyExport(FsIntegralOrderParam fsIntegralOrder) {
+        applyCompanyDataScope(fsIntegralOrder);
+        return companyIntegralOrderService.exportCompanyIntegralOrder(fsIntegralOrder);
+    }
+
     /**
      * 导出积分商品订单列表
      */

+ 1 - 1
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -1120,7 +1120,7 @@ public class WebSocketServer {
     }
 
 
-    @Scheduled(fixedRate = 2000)// 每2秒执行一次
+    @Scheduled(fixedRate = 10000)// 每10秒执行一次
     public void broadcastUserNumMessage() {
         Set<Long> activeLiveIds = new HashSet<>();
         // 遍历每个直播间

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

@@ -121,6 +121,14 @@ public class FsIntegralOrder
     @Excel(name = "销售公司ID")
     private Long companyId;
 
+    @TableField(exist = false)
+    @Excel(name = "所属公司")
+    private String companyName;
+
+    @TableField(exist = false)
+    @Excel(name = "所属销售")
+    private String companyUserNickName;
+
     private String loginAccount;
 
     private String remark;

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

@@ -0,0 +1,17 @@
+package com.fs.his.mapper;
+
+import com.fs.his.param.FsIntegralOrderParam;
+import com.fs.his.vo.FsIntegralOrderListVO;
+
+import java.util.List;
+
+/**
+ * 销售端积分订单(按最近看课记录归属)
+ */
+public interface CompanyIntegralOrderMapper {
+
+    /**
+     * 查询积分订单列表,数据权限与归属均按最近一次看课记录
+     */
+    List<FsIntegralOrderListVO> selectCompanyIntegralOrderList(FsIntegralOrderParam param);
+}

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

@@ -0,0 +1,17 @@
+package com.fs.his.service;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.his.param.FsIntegralOrderParam;
+import com.fs.his.vo.FsIntegralOrderListVO;
+
+import java.util.List;
+
+/**
+ * 销售端积分订单(按最近看课记录归属)
+ */
+public interface ICompanyIntegralOrderService {
+
+    List<FsIntegralOrderListVO> selectCompanyIntegralOrderList(FsIntegralOrderParam param);
+
+    AjaxResult exportCompanyIntegralOrder(FsIntegralOrderParam param);
+}

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

@@ -115,4 +115,11 @@ public interface IFsIntegralOrderService
     void createErpOrder(Long orderId) throws ParseException;
 
     FsIntegralOrderImportResultVO importOrderStatusData(List<FsIntegralOrderExcelVO> list);
+
+    /**
+     * 导出时按用户最近一次看课记录填充所属公司、所属销售
+     */
+    void fillExportWatchAttribution(List<FsIntegralOrderListVO> list);
+
+    void fillOrderExportWatchAttribution(List<FsIntegralOrder> list);
 }

+ 99 - 0
fs-service/src/main/java/com/fs/his/service/impl/CompanyIntegralOrderServiceImpl.java

@@ -0,0 +1,99 @@
+package com.fs.his.service.impl;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.mapper.CompanyIntegralOrderMapper;
+import com.fs.his.param.FsIntegralOrderParam;
+import com.fs.his.service.ICompanyIntegralOrderService;
+import com.fs.his.vo.FsIntegralOrderListVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
+
+/**
+ * 销售端积分订单(按最近看课记录归属)
+ */
+@Service
+public class CompanyIntegralOrderServiceImpl implements ICompanyIntegralOrderService {
+
+    private static final Logger log = LoggerFactory.getLogger(CompanyIntegralOrderServiceImpl.class);
+
+    @Autowired
+    private CompanyIntegralOrderMapper companyIntegralOrderMapper;
+
+    @Override
+    public List<FsIntegralOrderListVO> selectCompanyIntegralOrderList(FsIntegralOrderParam param) {
+        return companyIntegralOrderMapper.selectCompanyIntegralOrderList(param);
+    }
+
+    @Override
+    public AjaxResult exportCompanyIntegralOrder(FsIntegralOrderParam param) {
+        List<FsIntegralOrderListVO> list = companyIntegralOrderMapper.selectCompanyIntegralOrderList(param);
+        List<FsIntegralOrderListVO> exportList = buildExportRows(list);
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(exportList, "积分商品订单数据");
+    }
+
+    /**
+     * 列表与导出共用:解密手机号并解析商品明细
+     */
+    public List<FsIntegralOrderListVO> buildExportRows(List<FsIntegralOrderListVO> list) {
+        List<FsIntegralOrderListVO> exportList = new ArrayList<>();
+        for (FsIntegralOrderListVO vo : list) {
+            vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+            if (StringUtils.isEmpty(vo.getItemJson())) {
+                exportList.add(vo);
+                continue;
+            }
+            try {
+                if (vo.getItemJson().startsWith("[")) {
+                    JSONArray jsonArray = JSONArray.parseArray(vo.getItemJson());
+                    if (jsonArray == null || jsonArray.isEmpty()) {
+                        exportList.add(vo);
+                        continue;
+                    }
+                    for (int i = 0; i < jsonArray.size(); i++) {
+                        FsIntegralOrderListVO row = BeanCopyUtils.copy(vo, FsIntegralOrderListVO.class);
+                        if (row == null) {
+                            continue;
+                        }
+                        fillGoodsFromJson(row, jsonArray.getJSONObject(i));
+                        exportList.add(row);
+                    }
+                } else if (vo.getItemJson().startsWith("{")) {
+                    fillGoodsFromJson(vo, JSONObject.parseObject(vo.getItemJson()));
+                    exportList.add(vo);
+                } else {
+                    exportList.add(vo);
+                }
+            } catch (Exception e) {
+                log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
+                exportList.add(vo);
+            }
+        }
+        return exportList;
+    }
+
+    private void fillGoodsFromJson(FsIntegralOrderListVO vo, JSONObject goods) {
+        if (goods == null) {
+            return;
+        }
+        if (goods.getString("goodsName") != null) {
+            vo.setGoodsName(goods.getString("goodsName"));
+            vo.setNum(goods.getString("num"));
+        }
+        if (goods.getString("barCode") != null) {
+            vo.setBarCode(goods.getString("barCode"));
+        }
+    }
+}

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

@@ -20,6 +20,8 @@ import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.*;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.cache.ICompanyCacheService;
+import com.fs.company.cache.ICompanyUserCacheService;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyDept;
 import com.fs.company.domain.CompanyUser;
@@ -27,6 +29,8 @@ import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.service.ICompanyDeptService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.erp.domain.ErpOrder;
@@ -172,6 +176,15 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
     @Autowired
     private ICompanyDeptService companyDeptService;
 
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private ICompanyCacheService companyCacheService;
+
+    @Autowired
+    private ICompanyUserCacheService companyUserCacheService;
+
     @Autowired
     private IFsExpressService expressService;
 
@@ -815,8 +828,80 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
             processGoodsInfo(order, fsIntegralOrderSet);
         }
 
+        List<FsIntegralOrder> exportList = new ArrayList<>(fsIntegralOrderSet);
+        fillOrderExportWatchAttribution(exportList);
         ExcelUtil<FsIntegralOrder> util = new ExcelUtil<>(FsIntegralOrder.class);
-        return util.exportExcel(new ArrayList<>(fsIntegralOrderSet), "积分商品订单数据");
+        return util.exportExcel(exportList, "积分商品订单数据");
+    }
+
+    @Override
+    public void fillExportWatchAttribution(List<FsIntegralOrderListVO> list) {
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+        Map<Long, WatchAttribution> cache = new HashMap<>();
+        for (FsIntegralOrderListVO vo : list) {
+            if (vo.getUserId() == null) {
+                continue;
+            }
+            WatchAttribution attribution = cache.computeIfAbsent(vo.getUserId(), this::resolveWatchAttribution);
+            vo.setCompanyName(attribution.getCompanyName());
+            vo.setCompanyUserNickName(attribution.getCompanyUserNickName());
+        }
+    }
+
+    @Override
+    public void fillOrderExportWatchAttribution(List<FsIntegralOrder> list) {
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+        Map<Long, WatchAttribution> cache = new HashMap<>();
+        for (FsIntegralOrder order : list) {
+            if (order.getUserId() == null) {
+                continue;
+            }
+            WatchAttribution attribution = cache.computeIfAbsent(order.getUserId(), this::resolveWatchAttribution);
+            order.setCompanyName(attribution.getCompanyName());
+            order.setCompanyUserNickName(attribution.getCompanyUserNickName());
+        }
+    }
+
+    private WatchAttribution resolveWatchAttribution(Long userId) {
+        FsCourseWatchLog latestLog = fsCourseWatchLogMapper.selectLatestWatchLogByUserId(userId);
+        if (latestLog == null) {
+            return WatchAttribution.empty();
+        }
+        String companyName = null;
+        String companyUserNickName = null;
+        if (latestLog.getCompanyId() != null) {
+            companyName = companyCacheService.selectCompanyNameById(latestLog.getCompanyId());
+        }
+        if (latestLog.getCompanyUserId() != null) {
+            companyUserNickName = companyUserCacheService.selectCompanyUserNameUserById(latestLog.getCompanyUserId());
+        }
+        return new WatchAttribution(companyName, companyUserNickName);
+    }
+
+    private static class WatchAttribution {
+        private final String companyName;
+        private final String companyUserNickName;
+
+        WatchAttribution(String companyName, String companyUserNickName) {
+            this.companyName = companyName;
+            this.companyUserNickName = companyUserNickName;
+        }
+
+        static WatchAttribution empty() {
+            return new WatchAttribution(null, null);
+        }
+
+        String getCompanyName() {
+            return companyName;
+        }
+
+        String getCompanyUserNickName() {
+            return companyUserNickName;
+        }
     }
 
     @Override

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

@@ -88,6 +88,12 @@ public class FsIntegralOrderListVO {
      * **/
     private Long companyId;
 
+    @Excel(name = "所属公司")
+    private String companyName;
+
+    @Excel(name = "所属销售")
+    private String companyUserNickName;
+
     private BigDecimal payMoney;
 
     private String erpPhone;

+ 5 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -264,6 +264,11 @@ public interface LiveMapper
             " </script>"})
     List<Live> listToLiveNoEndNew(@Param("live") Live live);
 
+    /**
+     * 销售APP:查询未开播、直播中直播间
+     */
+    List<Live> listAppAvailableLive(@Param("live") Live live);
+
     @Select("SELECT COUNT(1) FROM live WHERE is_del = 0 AND training_period_id = #{periodId}")
     int countLiveByTrainingPeriodId(@Param("periodId") Long periodId);
 }

+ 23 - 0
fs-service/src/main/java/com/fs/live/param/FsLiveListParam.java

@@ -0,0 +1,23 @@
+package com.fs.live.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(description = "销售APP直播列表查询参数")
+public class FsLiveListParam {
+
+    @ApiModelProperty(value = "页码,默认为1")
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10")
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "直播间名称关键字")
+    private String keyword;
+
+    @ApiModelProperty(value = "公司id", hidden = true)
+    private Long companyId;
+
+}

+ 26 - 0
fs-service/src/main/java/com/fs/live/param/FsLiveSortLinkParam.java

@@ -0,0 +1,26 @@
+package com.fs.live.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(description = "生成直播分享短链入参")
+public class FsLiveSortLinkParam {
+
+    @ApiModelProperty(value = "直播间id", required = true)
+    private Long liveId;
+
+    @ApiModelProperty(value = "公司id", hidden = true)
+    private Long companyId;
+
+    @ApiModelProperty(value = "销售id", hidden = true)
+    private Long companyUserId;
+
+    @ApiModelProperty(value = "企微主体id")
+    private String corpId;
+
+    @ApiModelProperty(value = "链接有效时长(分钟)")
+    private Integer effectiveDuration;
+
+}

+ 6 - 0
fs-service/src/main/java/com/fs/live/service/ILiveService.java

@@ -4,6 +4,7 @@ package com.fs.live.service;
 import com.fs.common.core.page.PageRequest;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.vo.CompanyVO;
+import com.fs.live.param.FsLiveListParam;
 import com.fs.live.param.LiveNotifyParam;
 import com.fs.live.vo.LiveVo;
 import com.fs.common.core.domain.R;
@@ -234,6 +235,11 @@ public interface ILiveService
 
     List<Live> listToLiveNoEndNew(Live live);
 
+    /**
+     * 销售APP:查询未开播、直播中直播间
+     */
+    List<Live> listAppAvailableLive(FsLiveListParam param);
+
     /**
      * 训练营直播间审核列表(含营期/训练营/企业名称)
      */

+ 21 - 0
fs-service/src/main/java/com/fs/live/service/ILiveSortLinkService.java

@@ -0,0 +1,21 @@
+package com.fs.live.service;
+
+import com.fs.common.core.domain.R;
+import com.fs.live.param.FsLiveSortLinkParam;
+
+/**
+ * 直播短链 Service
+ */
+public interface ILiveSortLinkService {
+
+    /**
+     * 生成直播分享链接(domain + 固定真实路径)
+     */
+    R createLiveSortLink(FsLiveSortLinkParam param);
+
+    /**
+     * 销售APP:生成直播分享短链(参照 createAppCourseSortLink)
+     */
+    R createAppLiveSortLink(FsLiveSortLinkParam param);
+
+}

+ 11 - 2
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -38,6 +38,7 @@ import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import com.fs.live.dto.TemplateMessageSendRequestDTO;
 import com.fs.live.enums.MiniAppNotifyTaskStatusEnum;
+import com.fs.live.param.FsLiveListParam;
 import com.fs.live.param.LiveNotifyParam;
 import com.fs.live.vo.LiveVo;
 import com.fs.common.constant.LiveKeysConstant;
@@ -395,6 +396,13 @@ public class LiveServiceImpl implements ILiveService
         return baseMapper.listToLiveNoEndNew(live);
     }
 
+    @Override
+    public List<Live> listAppAvailableLive(FsLiveListParam param) {
+        Live live = new Live();
+        live.setCompanyId(param.getCompanyId());
+        live.setLiveName(param.getKeyword());
+        return baseMapper.listAppAvailableLive(live);
+    }
 
     /**
      * 查询直播
@@ -533,8 +541,9 @@ public class LiveServiceImpl implements ILiveService
     public LiveConfigVo asyncToCacheLiveConfig(Long liveId) {
         LiveConfigVo liveConfigVo = currentActivities(liveId);
         ThreadUtil.execute(()->{
-            redisCache.deleteObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG, liveId,liveId));
-            redisCache.setCacheObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, liveId), liveConfigVo,LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_EXPIRE, TimeUnit.SECONDS);
+            String configKey = String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG, liveId, liveId);
+            redisCache.deleteObject(configKey);
+            redisCache.setCacheObject(configKey, liveConfigVo, LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_EXPIRE, TimeUnit.SECONDS);
         });
         return liveConfigVo;
     }

+ 158 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveSortLinkServiceImpl.java

@@ -0,0 +1,158 @@
+package com.fs.live.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.CloudHostUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.course.config.CourseConfig;
+import com.fs.course.domain.FsCourseLink;
+import com.fs.course.mapper.FsCourseLinkMapper;
+import com.fs.course.service.impl.FsUserCourseServiceImpl;
+import com.fs.live.domain.Live;
+import com.fs.live.param.FsLiveSortLinkParam;
+import com.fs.live.service.ILiveService;
+import com.fs.live.service.ILiveSortLinkService;
+import com.fs.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.fs.voice.utils.StringUtil;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.UUID;
+
+import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;
+
+/**
+ * 直播短链 Service 实现
+ */
+@Service
+@Slf4j
+public class LiveSortLinkServiceImpl implements ILiveSortLinkService {
+
+    private static final String APP_LIVE_REAL_LINK =
+            "/pages_live/living?liveId=%s&companyId=%s&companyUserId=%s";
+    private static final String APP_LIVE_SHORT_LINK = "/courseH5/pages_live/living?s=";
+
+    @Autowired
+    private ISysConfigService configService;
+    @Autowired
+    private FsCourseLinkMapper fsCourseLinkMapper;
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+    @Autowired
+    private ILiveService liveService;
+
+    @Override
+    public R createLiveSortLink(FsLiveSortLinkParam param) {
+        if (param.getLiveId() == null) {
+            return R.error("直播间id不能为空");
+        }
+        Live live = liveService.selectLiveByLiveId(param.getLiveId());
+        if (live == null) {
+            return R.error("直播间不存在");
+        }
+
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(param.getCompanyId());
+        link.setCompanyUserId(param.getCompanyUserId());
+        link.setLiveId(param.getLiveId());
+        link.setCorpId(param.getCorpId());
+        link.setLinkType(0);
+        link.setIsRoom(0);
+        String random = generateRandomStringWithLock();
+        if (StringUtil.strIsNullOrEmpty(random)) {
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        } else {
+            link.setLink(random);
+        }
+        link.setCreateTime(new Date());
+
+        String realLinkPath = String.format(APP_LIVE_REAL_LINK,
+                param.getLiveId(), param.getCompanyId(), param.getCompanyUserId());
+        link.setRealLink(realLinkPath);
+        link.setUpdateTime(getExpireTime(param.getEffectiveDuration(), config, link.getCreateTime()).getTime());
+
+        int i = fsCourseLinkMapper.insertFsCourseLink(link);
+        if (i <= 0) {
+            return R.error("生成链接失败!");
+        }
+        String domainName = getDomainName(param.getCompanyUserId(), config);
+        String sortLink = domainName + realLinkPath;
+        return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
+    }
+
+    @Override
+    public R createAppLiveSortLink(FsLiveSortLinkParam param) {
+        if (param.getLiveId() == null) {
+            return R.error("直播间id不能为空");
+        }
+        Live live = liveService.selectLiveByLiveId(param.getLiveId());
+        if (live == null) {
+            return R.error("直播间不存在");
+        }
+
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        String random = FsUserCourseServiceImpl.generateRandomString();
+
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(param.getCompanyId());
+        link.setCompanyUserId(param.getCompanyUserId());
+        link.setLiveId(param.getLiveId());
+        link.setCorpId(param.getCorpId());
+        link.setLinkType(0);
+        link.setIsRoom(0);
+        link.setLink(random);
+
+        String realLinkPath = String.format(APP_LIVE_REAL_LINK,
+                param.getLiveId(), param.getCompanyId(), param.getCompanyUserId());
+        link.setRealLink(realLinkPath);
+        link.setCreateTime(new Date());
+        link.setUpdateTime(getExpireTime(param.getEffectiveDuration(), config, link.getCreateTime()).getTime());
+
+        int i = fsCourseLinkMapper.insertFsCourseLink(link);
+        if (i > 0) {
+            String domainName = getDomainName(param.getCompanyUserId(), config);
+            if (CloudHostUtils.hasCloudHostName("中康", "蒙牛", "鸿森堂", "北京卓美")) {
+                String sortLink = domainName + realLinkPath;
+                return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
+            }
+            String sortLink = domainName + APP_LIVE_SHORT_LINK + link.getLink();
+            return R.ok().put("url", sortLink).put("link", random).put("linkId", link.getLinkId());
+        }
+        return R.error("生成链接失败!");
+    }
+
+    private String getDomainName(Long companyUserId, CourseConfig config) {
+        String domainName = companyUserMapper.selectDomainByUserId(companyUserId);
+        if (StringUtils.isEmpty(domainName)) {
+            domainName = config.getRealLinkDomainName();
+        }
+        return domainName;
+    }
+
+    private Calendar getExpireTime(Integer effectiveDuration, CourseConfig config, Date createTime) {
+        Calendar calendar = Calendar.getInstance();
+        if (effectiveDuration == null || effectiveDuration == 0) {
+            Integer expireDays = config.getVideoLinkExpireDate() != null ? config.getVideoLinkExpireDate() : 1;
+            calendar.setTime(createTime);
+            calendar.add(Calendar.DAY_OF_MONTH, expireDays);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+        } else {
+            calendar.setTime(createTime);
+            calendar.add(Calendar.MINUTE, effectiveDuration);
+        }
+        return calendar;
+    }
+
+}

+ 4 - 0
fs-service/src/main/java/com/fs/live/vo/LiveConfigVo.java

@@ -1,14 +1,18 @@
 package com.fs.live.vo;
 
 import com.fs.live.domain.LiveRedConf;
+import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 import java.io.Serializable;
 import java.util.List;
 
 @Data
 @Builder
+@NoArgsConstructor
+@AllArgsConstructor
 public class LiveConfigVo implements Serializable {
     // 商品信息
     private LiveGoodsVo liveGoodsVo;

+ 57 - 0
fs-service/src/main/resources/mapper/his/CompanyIntegralOrderMapper.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.his.mapper.CompanyIntegralOrderMapper">
+
+    <select id="selectCompanyIntegralOrderList" parameterType="com.fs.his.param.FsIntegralOrderParam"
+            resultType="com.fs.his.vo.FsIntegralOrderListVO">
+        SELECT
+            fio.order_id AS orderId,
+            fio.order_code AS orderCode,
+            fio.user_id AS userId,
+            fio.user_name AS userName,
+            fio.user_phone AS userPhone,
+            fio.user_address AS userAddress,
+            fio.item_json AS itemJson,
+            fio.integral AS integral,
+            fio.pay_money AS payMoney,
+            fio.status AS status,
+            fio.delivery_code AS deliveryCode,
+            fio.delivery_name AS deliveryName,
+            fio.delivery_sn AS deliverySn,
+            fio.delivery_time AS deliveryTime,
+            fio.create_time AS createTime,
+            wl.company_id AS companyId,
+            wl.company_user_id AS companyUserId,
+            wl.qw_user_id AS qwUserId,
+            c.company_name AS companyName,
+            CONCAT(cu.nick_name, '_', cu.user_name) AS companyUserNickName
+        FROM fs_integral_order fio
+        INNER JOIN fs_course_watch_log wl ON wl.log_id = (
+            SELECT w2.log_id
+            FROM fs_course_watch_log w2
+            WHERE w2.user_id = fio.user_id
+              AND w2.duration > 0
+              AND (w2.send_type = 1 OR w2.send_type = 2)
+            ORDER BY w2.update_time DESC, w2.log_id DESC
+            LIMIT 1
+        )
+        LEFT JOIN company c ON wl.company_id = c.company_id
+        LEFT JOIN company_user cu ON wl.company_user_id = cu.user_id
+        <where>
+            <if test="companyId != null">AND wl.company_id = #{companyId}</if>
+            <if test="companyUserId != null">AND wl.company_user_id = #{companyUserId}</if>
+            <if test="qwUserId != null">AND wl.qw_user_id = #{qwUserId}</if>
+            <if test="orderCode != null and orderCode != ''">AND fio.order_code = #{orderCode}</if>
+            <if test="userName != null and userName != ''">AND fio.user_name LIKE CONCAT('%', #{userName}, '%')</if>
+            <if test="userPhone != null and userPhone != ''">AND fio.user_phone = #{userPhone}</if>
+            <if test="integral != null and integral != ''">AND fio.integral = #{integral}</if>
+            <if test="status != null and status != ''">AND fio.status = #{status}</if>
+            <if test="deliverySn != null and deliverySn != ''">AND fio.delivery_sn LIKE CONCAT('%', #{deliverySn}, '%')</if>
+            <if test="sTime != null">AND DATE(fio.create_time) &gt;= DATE(#{sTime})</if>
+            <if test="eTime != null">AND DATE(fio.create_time) &lt;= DATE(#{eTime})</if>
+        </where>
+        ORDER BY fio.order_id DESC
+    </select>
+</mapper>

+ 13 - 0
fs-service/src/main/resources/mapper/live/LiveMapper.xml

@@ -417,6 +417,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     </select>
 
+    <select id="listAppAvailableLive" resultMap="LiveResult">
+        <include refid="selectLiveVo"/>
+        where is_audit = 1 and is_del = 0 and is_show = 1
+          and status in (1, 2)
+          and live_type in (2, 3)
+          and training_period_id is null
+          and (company_id = #{live.companyId} or company_id is null)
+        <if test="live.liveName != null and live.liveName != ''">
+            and live_name like concat('%', #{live.liveName}, '%')
+        </if>
+        order by create_time desc
+    </select>
+
     <select id="selectLiveIdsByCompanyParam" resultType="java.lang.Long">
         SELECT l.live_id
         FROM live l

+ 55 - 16
fs-user-app/src/main/java/com/fs/app/facade/impl/LiveFacadeServiceImpl.java

@@ -199,29 +199,20 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
 
     @Override
     public R currentActivities(Long liveId, String userId) {
-        // 直播间配置信息
-        Object oLiveConfigVo = redisCache.getCacheObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG, liveId, liveId));
-        LiveConfigVo liveConfigVo;
-        if (ObjectUtil.isNotEmpty(oLiveConfigVo)) {
-            liveConfigVo = JSON.parseObject(oLiveConfigVo.toString(), LiveConfigVo.class);
-        } else {
-            liveConfigVo = liveService.asyncToCacheLiveConfig(liveId);
+        LiveConfigVo liveConfigVo = loadLiveConfigFromCache(liveId);
+        if (liveConfigVo == null) {
+            return R.error("未找到直播活动配置");
         }
-        List<LiveRedConf> redConfs = liveConfigVo.getLiveRedConfs()
+
+        List<LiveRedConf> redConfs = CollUtil.emptyIfNull(liveConfigVo.getLiveRedConfs())
                 .stream()
                 .filter(item -> ObjectUtil.isEmpty(redisCache.hashGet(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, liveId, item.getRedId()), userId)))
                 .collect(Collectors.toList());
-        List<LiveLotteryConfVo> lotteryConfs = liveConfigVo.getLiveLotteryConfs()
+        List<LiveLotteryConfVo> lotteryConfs = CollUtil.emptyIfNull(liveConfigVo.getLiveLotteryConfs())
                 .stream()
                 .filter(item -> ObjectUtil.isEmpty(redisCache.hashGet(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, liveId, item.getLotteryId()), userId)))
                 .collect(Collectors.toList());
-        Object cacheObject = redisCache.getCacheObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG, liveId, LiveKeysConstant.TOP_MSG));
-        LiveMsgVo liveMsg;
-        if (ObjectUtil.isNotEmpty(cacheObject)) {
-            liveMsg = JSON.parseObject(cacheObject.toString(), LiveMsgVo.class);
-        } else {
-            liveMsg = null;
-        }
+        LiveMsgVo liveMsg = loadTopMsgFromCache(liveId);
 
         return R.ok()
                 .put("red", redConfs)
@@ -230,6 +221,54 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
                 .put("topMsg", liveMsg);
     }
 
+    private LiveConfigVo loadLiveConfigFromCache(Long liveId) {
+        String configKey = String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG, liveId, liveId);
+        try {
+            Object cached = redisCache.getCacheObject(configKey);
+            if (cached instanceof LiveConfigVo) {
+                return (LiveConfigVo) cached;
+            }
+            if (cached instanceof String) {
+                String json = (String) cached;
+                if (json.trim().startsWith("{")) {
+                    return JSON.parseObject(json, LiveConfigVo.class);
+                }
+            }
+            if (cached != null) {
+                log.warn("LiveConfigVo 缓存类型/格式异常,清除脏数据 key={}, type={}", configKey, cached.getClass().getName());
+                redisCache.deleteObject(configKey);
+            }
+        } catch (Exception e) {
+            log.warn("LiveConfigVo 缓存反序列化失败,清除脏数据 key={}", configKey, e);
+            redisCache.deleteObject(configKey);
+        }
+        return liveService.asyncToCacheLiveConfig(liveId);
+    }
+
+    private LiveMsgVo loadTopMsgFromCache(Long liveId) {
+        String key = String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG, liveId, LiveKeysConstant.TOP_MSG);
+        try {
+            Object cached = redisCache.getCacheObject(key);
+            if (cached instanceof LiveMsgVo) {
+                return (LiveMsgVo) cached;
+            }
+            if (cached instanceof String) {
+                String json = (String) cached;
+                if (json.trim().startsWith("{")) {
+                    return JSON.parseObject(json, LiveMsgVo.class);
+                }
+            }
+            if (cached != null) {
+                log.warn("LiveMsgVo 缓存类型/格式异常,清除脏数据 key={}, type={}", key, cached.getClass().getName());
+                redisCache.deleteObject(key);
+            }
+        } catch (Exception e) {
+            log.warn("LiveMsgVo 缓存反序列化失败 key={}", key, e);
+            redisCache.deleteObject(key);
+        }
+        return null;
+    }
+
     @Override
     @DistributeLock(keyExpression = "#red.redId +'_'+#red.userId", scene = "red_claim", waitTime = 1000, errorMsg = "红包领取失败")
     public R redClaim(RedPO red) {