|
@@ -0,0 +1,227 @@
|
|
|
|
|
+package com.fs.live.service.impl;
|
|
|
|
|
+
|
|
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
|
|
+import com.fs.common.utils.DateUtils;
|
|
|
|
|
+import com.fs.live.constant.LiveCommentPinEndReason;
|
|
|
|
|
+import com.fs.live.domain.LiveCommentFeatureConfig;
|
|
|
|
|
+import com.fs.live.domain.LiveCommentPinActive;
|
|
|
|
|
+import com.fs.live.domain.LiveCommentPinLog;
|
|
|
|
|
+import com.fs.live.domain.LiveMsg;
|
|
|
|
|
+import com.fs.live.mapper.LiveCommentPinActiveMapper;
|
|
|
|
|
+import com.fs.live.mapper.LiveCommentPinLogMapper;
|
|
|
|
|
+import com.fs.live.mapper.LiveMsgMapper;
|
|
|
|
|
+import com.fs.live.service.ILiveCommentFeatureConfigService;
|
|
|
|
|
+import com.fs.live.service.ILiveCommentPinService;
|
|
|
|
|
+import com.fs.live.service.LiveAppWebSocketNotifyService;
|
|
|
|
|
+import com.fs.live.util.LiveCommentWsMessageBuilder;
|
|
|
|
|
+import com.fs.live.vo.LiveCommentPinExpireEvent;
|
|
|
|
|
+import com.fs.live.vo.LiveCommentPinMonitorVo;
|
|
|
|
|
+import com.fs.company.mapper.CompanyUserRoleMapper;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+
|
|
|
|
|
+@Service
|
|
|
|
|
+public class LiveCommentPinServiceImpl implements ILiveCommentPinService {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private LiveCommentPinActiveMapper pinActiveMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private LiveCommentPinLogMapper pinLogMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private LiveMsgMapper liveMsgMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ILiveCommentFeatureConfigService featureConfigService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private LiveAppWebSocketNotifyService liveAppWebSocketNotifyService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private CompanyUserRoleMapper companyUserRoleMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public R pinComment(Long liveId, Long msgId, Long operatorUserId, String operatorNickName,
|
|
|
|
|
+ String liveRoleCode, int userType, int durationMinutes, Long companyUserId) {
|
|
|
|
|
+ LiveCommentFeatureConfig cfg = featureConfigService.getEffectiveConfig();
|
|
|
|
|
+ Long checkUserId = (companyUserId != null && companyUserId > 0) ? companyUserId : operatorUserId;
|
|
|
|
|
+ if (!hasAnyRoleRelation(checkUserId)) {
|
|
|
|
|
+ return R.error("无置顶权限");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!durationAllowed(durationMinutes, cfg.getPinDurationOptions())) {
|
|
|
|
|
+ return R.error("不支持的置顶时长");
|
|
|
|
|
+ }
|
|
|
|
|
+ LiveMsg msg = liveMsgMapper.selectLiveMsgByMsgId(msgId);
|
|
|
|
|
+ if (msg == null || !Objects.equals(msg.getLiveId(), liveId)) {
|
|
|
|
|
+ return R.error("评论不存在或不属于该直播间");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (pinActiveMapper.selectByLiveIdAndMsgId(liveId, msgId) != null) {
|
|
|
|
|
+ return R.error("该评论已在置顶中");
|
|
|
|
|
+ }
|
|
|
|
|
+ int cnt = pinActiveMapper.countByLiveId(liveId);
|
|
|
|
|
+ if (cnt >= cfg.getPinMaxPerRoom()) {
|
|
|
|
|
+ return R.error("置顶数量已达上限");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Date now = DateUtils.getNowDate();
|
|
|
|
|
+ LiveCommentPinLog log = new LiveCommentPinLog();
|
|
|
|
|
+ log.setLiveId(liveId);
|
|
|
|
|
+ log.setMsgId(msgId);
|
|
|
|
|
+ log.setOperatorUserId(operatorUserId);
|
|
|
|
|
+ log.setOperatorNickName(operatorNickName);
|
|
|
|
|
+ log.setOperatorRoleCode(liveRoleCode);
|
|
|
|
|
+ log.setDurationMinutes(durationMinutes);
|
|
|
|
|
+ log.setStartTime(now);
|
|
|
|
|
+ log.setCreateTime(now);
|
|
|
|
|
+ pinLogMapper.insertLiveCommentPinLog(log);
|
|
|
|
|
+
|
|
|
|
|
+ LiveCommentPinActive active = new LiveCommentPinActive();
|
|
|
|
|
+ active.setLiveId(liveId);
|
|
|
|
|
+ active.setMsgId(msgId);
|
|
|
|
|
+ active.setPinLogId(log.getLogId());
|
|
|
|
|
+ active.setCreateTime(now);
|
|
|
|
|
+ if (durationMinutes < 0) {
|
|
|
|
|
+ active.setExpireAt(null);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
|
|
+ cal.setTime(now);
|
|
|
|
|
+ cal.add(Calendar.MINUTE, durationMinutes);
|
|
|
|
|
+ active.setExpireAt(cal.getTime());
|
|
|
|
|
+ }
|
|
|
|
|
+ pinActiveMapper.insertLiveCommentPinActive(active);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> payload = buildPinPayload(msg, log.getLogId(), active.getExpireAt(), active.getId());
|
|
|
|
|
+ String ws = LiveCommentWsMessageBuilder.build(liveId, "commentPinned", payload);
|
|
|
|
|
+ liveAppWebSocketNotifyService.broadcastToLive(liveId, ws);
|
|
|
|
|
+ return R.ok().put("pinLogId", log.getLogId()).put("activeId", active.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public R unpinComment(Long liveId, Long msgId, Long operatorUserId, String liveRoleCode, int userType, Long companyUserId) {
|
|
|
|
|
+ LiveCommentFeatureConfig cfg = featureConfigService.getEffectiveConfig();
|
|
|
|
|
+ Long checkUserId = (companyUserId != null && companyUserId > 0) ? companyUserId : operatorUserId;
|
|
|
|
|
+ if (!hasAnyRoleRelation(checkUserId)) {
|
|
|
|
|
+ return R.error("无取消置顶权限");
|
|
|
|
|
+ }
|
|
|
|
|
+ LiveCommentPinActive active = pinActiveMapper.selectByLiveIdAndMsgId(liveId, msgId);
|
|
|
|
|
+ if (active == null) {
|
|
|
|
|
+ return R.error("当前未置顶该评论");
|
|
|
|
|
+ }
|
|
|
|
|
+ Date now = DateUtils.getNowDate();
|
|
|
|
|
+ pinLogMapper.updatePinLogEnd(active.getPinLogId(), now, LiveCommentPinEndReason.APP_CANCEL);
|
|
|
|
|
+ pinActiveMapper.deleteByLiveIdAndMsgId(liveId, msgId);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> payload = new LinkedHashMap<>();
|
|
|
|
|
+ payload.put("msgId", msgId);
|
|
|
|
|
+ payload.put("pinLogId", active.getPinLogId());
|
|
|
|
|
+ payload.put("reason", "APP_CANCEL");
|
|
|
|
|
+ String ws = LiveCommentWsMessageBuilder.build(liveId, "commentUnpinned", payload);
|
|
|
|
|
+ liveAppWebSocketNotifyService.broadcastToLive(liveId, ws);
|
|
|
|
|
+ return R.ok();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public R forceUnpinByActiveId(Long activeId, String updateBy) {
|
|
|
|
|
+ LiveCommentPinActive active = pinActiveMapper.selectById(activeId);
|
|
|
|
|
+ if (active == null) {
|
|
|
|
|
+ return R.error("置顶记录不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ Date now = DateUtils.getNowDate();
|
|
|
|
|
+ pinLogMapper.updatePinLogEnd(active.getPinLogId(), now, LiveCommentPinEndReason.ADMIN_FORCE);
|
|
|
|
|
+ pinActiveMapper.deleteById(activeId);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> payload = new LinkedHashMap<>();
|
|
|
|
|
+ payload.put("msgId", active.getMsgId());
|
|
|
|
|
+ payload.put("pinLogId", active.getPinLogId());
|
|
|
|
|
+ payload.put("reason", "ADMIN_FORCE");
|
|
|
|
|
+ payload.put("updateBy", updateBy);
|
|
|
|
|
+ String ws = LiveCommentWsMessageBuilder.build(active.getLiveId(), "commentUnpinned", payload);
|
|
|
|
|
+ liveAppWebSocketNotifyService.broadcastToLive(active.getLiveId(), ws);
|
|
|
|
|
+ return R.ok();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<LiveCommentPinActive> listActiveByLiveId(Long liveId) {
|
|
|
|
|
+ return pinActiveMapper.selectByLiveId(liveId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<LiveCommentPinLog> listPinLogs(LiveCommentPinLog query) {
|
|
|
|
|
+ return pinLogMapper.selectLiveCommentPinLogList(query);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<LiveCommentPinMonitorVo> listActiveGlobalMonitor() {
|
|
|
|
|
+ List<LiveCommentPinActive> actives = pinActiveMapper.selectAllActive();
|
|
|
|
|
+ Date now = new Date();
|
|
|
|
|
+ List<LiveCommentPinMonitorVo> out = new ArrayList<>();
|
|
|
|
|
+ for (LiveCommentPinActive a : actives) {
|
|
|
|
|
+ LiveCommentPinMonitorVo v = new LiveCommentPinMonitorVo();
|
|
|
|
|
+ v.setActive(a);
|
|
|
|
|
+ LiveMsg m = liveMsgMapper.selectLiveMsgByMsgId(a.getMsgId());
|
|
|
|
|
+ if (m != null) {
|
|
|
|
|
+ v.setMsgContent(m.getMsg());
|
|
|
|
|
+ v.setMsgNickName(m.getNickName());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (a.getExpireAt() != null) {
|
|
|
|
|
+ long sec = (a.getExpireAt().getTime() - now.getTime()) / 1000L;
|
|
|
|
|
+ v.setRemainingSeconds(Math.max(sec, 0L));
|
|
|
|
|
+ }
|
|
|
|
|
+ out.add(v);
|
|
|
|
|
+ }
|
|
|
|
|
+ return out;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public List<LiveCommentPinExpireEvent> expireDuePins() {
|
|
|
|
|
+ Date now = DateUtils.getNowDate();
|
|
|
|
|
+ List<LiveCommentPinActive> due = pinActiveMapper.selectExpired(now);
|
|
|
|
|
+ List<LiveCommentPinExpireEvent> events = new ArrayList<>();
|
|
|
|
|
+ for (LiveCommentPinActive a : due) {
|
|
|
|
|
+ pinLogMapper.updatePinLogEnd(a.getPinLogId(), now, LiveCommentPinEndReason.EXPIRED);
|
|
|
|
|
+ pinActiveMapper.deleteById(a.getId());
|
|
|
|
|
+ LiveCommentPinExpireEvent e = new LiveCommentPinExpireEvent();
|
|
|
|
|
+ e.setLiveId(a.getLiveId());
|
|
|
|
|
+ e.setMsgId(a.getMsgId());
|
|
|
|
|
+ e.setPinLogId(a.getPinLogId());
|
|
|
|
|
+ events.add(e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return events;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Map<String, Object> buildPinPayload(LiveMsg msg, Long pinLogId, Date expireAt, Long activeId) {
|
|
|
|
|
+ Map<String, Object> payload = new LinkedHashMap<>();
|
|
|
|
|
+ payload.put("msgId", msg.getMsgId());
|
|
|
|
|
+ payload.put("pinLogId", pinLogId);
|
|
|
|
|
+ payload.put("activeId", activeId);
|
|
|
|
|
+ payload.put("liveMsg", msg);
|
|
|
|
|
+ payload.put("expireAt", expireAt == null ? null : expireAt.getTime());
|
|
|
|
|
+ return payload;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean hasAnyRoleRelation(Long companyUserId) {
|
|
|
|
|
+ if (companyUserId == null || companyUserId <= 0) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return companyUserRoleMapper.countByUserId(companyUserId) > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean durationAllowed(int minutes, String optionsCsv) {
|
|
|
|
|
+ if (optionsCsv == null || optionsCsv.isEmpty()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (String p : optionsCsv.split(",")) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (Integer.parseInt(p.trim()) == minutes) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (NumberFormatException ignored) {
|
|
|
|
|
+ // skip
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|