|
@@ -0,0 +1,248 @@
|
|
|
+package com.webchat.ugc.service.moment;
|
|
|
+
|
|
|
+
|
|
|
+import com.webchat.common.constants.MomentConstants;
|
|
|
+import com.webchat.common.enums.RedisKeyEnum;
|
|
|
+import com.webchat.common.enums.messagequeue.MessageQueueEnum;
|
|
|
+import com.webchat.common.exception.BusinessException;
|
|
|
+import com.webchat.common.service.RedisService;
|
|
|
+import com.webchat.common.service.messagequeue.producer.MessageQueueProducer;
|
|
|
+import com.webchat.common.util.JsonUtil;
|
|
|
+import com.webchat.domain.dto.queue.MomentPublishMessageDTO;
|
|
|
+import com.webchat.domain.vo.request.MomentSaveOrUpdateVO;
|
|
|
+import com.webchat.domain.vo.response.moment.MomentDetailVO;
|
|
|
+import com.webchat.domain.vo.response.moment.MomentLinkVO;
|
|
|
+import com.webchat.domain.vo.response.moment.MomentMediaVO;
|
|
|
+import com.webchat.domain.vo.response.moment.MomentVO;
|
|
|
+import com.webchat.ugc.repository.dao.IMomentDAO;
|
|
|
+import com.webchat.ugc.repository.entity.MomentEntity;
|
|
|
+import com.webchat.ugc.repository.entity.MomentLinkEntity;
|
|
|
+import com.webchat.ugc.repository.entity.MomentMediaEntity;
|
|
|
+import org.apache.commons.lang3.ObjectUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.util.Assert;
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class MomentService {
|
|
|
+
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IMomentDAO momentDAO;
|
|
|
+ @Autowired
|
|
|
+ private MomentMediaService momentMediaService;
|
|
|
+ @Autowired
|
|
|
+ private MomentLinkService momentLinkService;
|
|
|
+ @Autowired
|
|
|
+ private RedisService redisService;
|
|
|
+ @Autowired
|
|
|
+ private MessageQueueProducer<MomentPublishMessageDTO, Long> messageQueueProducer;
|
|
|
+ @Autowired
|
|
|
+ private RedissonClient redissonClient;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 朋友圈动态发布
|
|
|
+ *
|
|
|
+ * @param momentSaveOrUpdate
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public Long publish(MomentSaveOrUpdateVO momentSaveOrUpdate) {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 1. 持久化
|
|
|
+ * 1.1 朋友圈动态数据持久化
|
|
|
+ * 1.2 动态资源持久化
|
|
|
+ * 1.3 链接数据持久化
|
|
|
+ */
|
|
|
+ MomentEntity moment = this.convert(momentSaveOrUpdate);
|
|
|
+ moment = momentDAO.save(moment);
|
|
|
+ Long momentId = moment.getId();
|
|
|
+ // 持久化图片、视频媒体资源数据
|
|
|
+ momentMediaService.saveMomentMedia(momentId, momentSaveOrUpdate);
|
|
|
+ // 持久化朋友圈动态连接数据
|
|
|
+ momentLinkService.saveMomentLink(momentId, momentSaveOrUpdate);
|
|
|
+ /**
|
|
|
+ * 2. 动态详情缓存(redis)
|
|
|
+ */
|
|
|
+ MomentVO momentVO = this.refreshMomentCache(momentId);
|
|
|
+ /**
|
|
|
+ * 3. mq
|
|
|
+ * 3.1
|
|
|
+ * 图片、视频媒体资源信息提取
|
|
|
+ * 连接解析
|
|
|
+ * ip归属地解析
|
|
|
+ * 3.2 大模型内容审核
|
|
|
+ * 3.3 刷新/修改动态状态
|
|
|
+ * 3.4 写扩散(把当前动态写入到所有粉丝时间线DB、Redis)
|
|
|
+ */
|
|
|
+ MomentPublishMessageDTO messageDTO = new MomentPublishMessageDTO();
|
|
|
+ messageDTO.setMoment(momentVO);
|
|
|
+ messageQueueProducer.send(MessageQueueEnum.QUEUE_MOMENT_PUBLISH, messageDTO);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单条动态详情
|
|
|
+ *
|
|
|
+ * @param momentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public MomentVO getMomentFromCache(Long momentId) {
|
|
|
+
|
|
|
+ String cacheKey = this.momentBaseCacheKey(momentId);
|
|
|
+ String cache = redisService.get(cacheKey);
|
|
|
+ if (StringUtils.isNotBlank(cache)) {
|
|
|
+ return JsonUtil.fromJson(cache, MomentVO.class);
|
|
|
+ }
|
|
|
+ // 主动刷新
|
|
|
+ String lockKey = RedisKeyEnum.MOMENT_CACHE_REFRESH_LOCK.getKey(String.valueOf(momentId));
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+ try {
|
|
|
+ lock.lock();
|
|
|
+ // 双重检查
|
|
|
+ if (StringUtils.isNotBlank(cache = redisService.get(cacheKey))) {
|
|
|
+ return JsonUtil.fromJson(cache, MomentVO.class);
|
|
|
+ }
|
|
|
+ return this.refreshMomentCache(momentId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new BusinessException("数据加载失败");
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量查询动态详情
|
|
|
+ *
|
|
|
+ * @param momentIds
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public List<MomentVO> batchGetMomentFromCache(List<Long> momentIds) {
|
|
|
+
|
|
|
+ if (CollectionUtils.isEmpty(momentIds)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<MomentVO> moments = new ArrayList<>();
|
|
|
+
|
|
|
+ List<String> keys = momentIds.stream().map(this::momentBaseCacheKey).collect(Collectors.toList());
|
|
|
+ List<String> caches = redisService.mget(keys);
|
|
|
+ for (int i = 0; i < momentIds.size(); i++) {
|
|
|
+ Long momentId = momentIds.get(i);
|
|
|
+ String cache = caches.get(i);
|
|
|
+ if (StringUtils.isNotBlank(cache)) {
|
|
|
+ moments.add(JsonUtil.fromJson(cache, MomentVO.class));
|
|
|
+ } else {
|
|
|
+ // TODO 这里可以优化,不建议在循环中查库刷新缓存,可能存在性能瓶颈
|
|
|
+ // 建议:这里批量收集一批缓存失败的ID,最后统一批量刷新
|
|
|
+ moments.add(this.refreshMomentCache(momentId));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return moments;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public MomentVO refreshMomentCache(Long momentId) {
|
|
|
+
|
|
|
+ MomentEntity moment = momentDAO.findById(momentId).orElse(null);
|
|
|
+ if (moment == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ List<MomentMediaEntity> medias = null;
|
|
|
+
|
|
|
+ if (moment.isIncludeImages()) {
|
|
|
+ medias = momentMediaService.medias(momentId, MomentConstants.MediaType.IMAGE.getType());
|
|
|
+ }
|
|
|
+ if (moment.isIncludeVideo()) {
|
|
|
+ medias = momentMediaService.medias(momentId, MomentConstants.MediaType.VIDEO.getType());
|
|
|
+ }
|
|
|
+ MomentLinkEntity link = null;
|
|
|
+ if (moment.isIncludeLink()) {
|
|
|
+ link = momentLinkService.getLink(momentId);
|
|
|
+ }
|
|
|
+ return this.refreshMomentCache(moment, medias, link);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private MomentVO refreshMomentCache(MomentEntity moment,
|
|
|
+ List<MomentMediaEntity> momentMedias,
|
|
|
+ MomentLinkEntity linkEntity) {
|
|
|
+ if (moment == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ MomentVO momentVO = this.convertVo(moment, momentMedias, linkEntity);
|
|
|
+ String momentCacheKey = this.momentBaseCacheKey(moment.getId());
|
|
|
+ redisService.set(momentCacheKey, JsonUtil.toJsonString(momentVO),
|
|
|
+ RedisKeyEnum.MOMENT_BASE_CACHE.getExpireTime());
|
|
|
+ return momentVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ private MomentVO convertVo(MomentEntity moment,
|
|
|
+ List<MomentMediaEntity> momentMedias,
|
|
|
+ MomentLinkEntity linkEntity) {
|
|
|
+
|
|
|
+ MomentVO momentVO = new MomentVO();
|
|
|
+ BeanUtils.copyProperties(moment, momentVO);
|
|
|
+ momentVO.setPublishTime(moment.getCreateDate().getTime());
|
|
|
+ if (moment.isIncludeImages()) {
|
|
|
+ List<MomentMediaVO> imagesVo = momentMedias.stream().map(img -> {
|
|
|
+ MomentMediaVO image = new MomentMediaVO();
|
|
|
+ BeanUtils.copyProperties(img, image);
|
|
|
+ return image;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ momentVO.setImages(imagesVo);
|
|
|
+ }
|
|
|
+ if (moment.isIncludeVideo()) {
|
|
|
+ MomentMediaEntity video = momentMedias.get(0);
|
|
|
+ MomentMediaVO videoVo = new MomentMediaVO();
|
|
|
+ BeanUtils.copyProperties(video, videoVo);
|
|
|
+ momentVO.setVideo(videoVo);
|
|
|
+ }
|
|
|
+ if (moment.isIncludeLink()) {
|
|
|
+ MomentLinkVO linkVO = new MomentLinkVO();
|
|
|
+ BeanUtils.copyProperties(linkEntity, linkVO);
|
|
|
+ momentVO.setLink(linkVO);
|
|
|
+ }
|
|
|
+ return momentVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String momentBaseCacheKey(Long momentId) {
|
|
|
+
|
|
|
+ return RedisKeyEnum.MOMENT_BASE_CACHE.getKey(String.valueOf(momentId));
|
|
|
+ }
|
|
|
+
|
|
|
+ private MomentEntity convert(MomentSaveOrUpdateVO momentSaveOrUpdate) {
|
|
|
+ MomentEntity moment;
|
|
|
+ Long momentId = momentSaveOrUpdate.getId();
|
|
|
+ if (momentId != null) {
|
|
|
+ moment = momentDAO.findById(momentSaveOrUpdate.getId()).orElse(null);
|
|
|
+ Assert.notNull(moment, "动态不存在");
|
|
|
+ Assert.isTrue(ObjectUtils.equals(momentSaveOrUpdate.getAuthor(), moment.getAuthor()), "无操作权限");
|
|
|
+ } else {
|
|
|
+ moment = new MomentEntity();
|
|
|
+ moment.setStatus(MomentConstants.MomentStatusEnum.NEW.getStatus());
|
|
|
+ moment.setAuthor(momentSaveOrUpdate.getAuthor());
|
|
|
+ moment.setCreateDate(new Date());
|
|
|
+ }
|
|
|
+ moment.setContent(momentSaveOrUpdate.getContent());
|
|
|
+ moment.setIncludeImages(momentSaveOrUpdate.includeImage());
|
|
|
+ moment.setIncludeVideo(momentSaveOrUpdate.includeVideo());
|
|
|
+ moment.setIncludeLink(momentSaveOrUpdate.includeLink());
|
|
|
+ return moment;
|
|
|
+ }
|
|
|
+}
|