Hai Lin преди 1 седмица
родител
ревизия
0aad5175df

+ 15 - 0
mirage-service/src/main/java/com/mirage/mirageservice/MirageServiceApplication.java

@@ -8,17 +8,20 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
+import org.springframework.core.task.AsyncTaskExecutor;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 import org.springframework.http.converter.StringHttpMessageConverter;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 import org.springframework.web.client.RestTemplate;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 import javax.annotation.Resource;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ThreadPoolExecutor;
 
 @EnableAsync
 @Import({GsonConfig.class})
@@ -48,6 +51,18 @@ public class MirageServiceApplication implements WebMvcConfigurer {
         return template;
     }
 
+    @Bean("asyncPublishEventExecutor")
+    public AsyncTaskExecutor asyncTaskExecutor() {
+        ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
+        asyncTaskExecutor.setMaxPoolSize(50);
+        asyncTaskExecutor.setCorePoolSize(20);
+        asyncTaskExecutor.setThreadNamePrefix("async-publish-event-thread-pool-");
+        asyncTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
+        asyncTaskExecutor.setQueueCapacity(50000);
+        asyncTaskExecutor.initialize();
+        return asyncTaskExecutor;
+    }
+
 
     public static void main(String[] args) {
         SpringApplication.run(MirageServiceApplication.class, args);

+ 11 - 4
mirage-service/src/main/java/com/mirage/mirageservice/controller/FamilyController.java

@@ -10,6 +10,7 @@ import com.mirage.mirageservice.enums.FamilyConfigEnum;
 import com.mirage.mirageservice.enums.FamilyRelationEnum;
 import com.mirage.mirageservice.enums.MomentsVisibleTypeEnum;
 import com.mirage.mirageservice.meta.*;
+import com.mirage.mirageservice.service.AsyncService;
 import com.mirage.mirageservice.service.FamilyService;
 import com.mirage.mirageservice.service.WechatService;
 import lombok.extern.slf4j.Slf4j;
@@ -35,6 +36,8 @@ public class FamilyController {
     private FamilyService familyService;
     @Resource
     private WechatService wechatService;
+    @Resource
+    private AsyncService asyncService;
 
     /**
      * 获取自己的亲属关系卡片信息.
@@ -286,16 +289,20 @@ public class FamilyController {
     }
 
     /**
-     * 新家族圈消息提示.
-     * @return true / false
+     * 新家族圈消息提示(新消息、新回复、新点赞).
+     * @return Map结构,key:newsMoments(true/false) newCommentOrLike (NewsRedDodInfo type 1:点赞 2:评论 momentId:家族圈信息id)
      */
-    // TODO 新回复、新点赞
     @Auth(value = AuthType.COOKIES)
     @RequestMapping(value = "/member/newsRedDot", method = RequestMethod.GET)
     public Object newsRedDot(){
-        return familyService.newsRedDot(AppContext.getUid());
+        Map<String, Object> newsRedDot = Maps.newHashMap();
+        newsRedDot.put("newsMoments", familyService.newsRedDot(AppContext.getUid()));
+        newsRedDot.put("newCommentOrLike", asyncService.batchGetAndRemoveRedDotData(AppContext.getUid()));
+        return newsRedDot;
     }
 
+
+
     /**
      * 家族圈点赞或取消.
      *

+ 4 - 0
mirage-service/src/main/java/com/mirage/mirageservice/mapper/mysql/FamilyMemberInfoMapper.java

@@ -1,6 +1,7 @@
 package com.mirage.mirageservice.mapper.mysql;
 
 import com.mirage.mirageservice.domain.FamilyMemberInfo;
+import com.mirage.mirageservice.meta.UserAndMemberIdInfo;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.Collection;
@@ -27,4 +28,7 @@ public interface FamilyMemberInfoMapper {
     FamilyMemberInfo selectByUid(@Param("uid") Long uid);
 
     List<FamilyMemberInfo> selectByIdIn(@Param("idCollection") Collection<Long> idCollection);
+
+    List<UserAndMemberIdInfo> selectUidUserInnerJoin(@Param("likeBranchFamilyHall")String likeBranchFamilyHall, @Param("likeBirthPlace")String likeBirthPlace, @Param("sex")Integer sex, @Param("likeClusterPlace")String likeClusterPlace);
+
 }

+ 33 - 0
mirage-service/src/main/java/com/mirage/mirageservice/meta/NewsRedDodInfo.java

@@ -0,0 +1,33 @@
+package com.mirage.mirageservice.meta;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * Created by hzlinhai on 2025/10/27.
+ */
+@Data
+public class NewsRedDodInfo {
+    /**
+     * 1:点赞 2:评论
+     */
+    private Integer type;
+
+    /**
+     * 来源用户id
+     */
+    private Long uid;
+
+    private String name;
+
+    private String headImgUrl;
+
+    private Long momentId;
+
+    private Long commentId;
+    /**
+     * 事件时间
+     */
+    private Date eventTime;
+}

+ 14 - 0
mirage-service/src/main/java/com/mirage/mirageservice/meta/UserAndMemberIdInfo.java

@@ -0,0 +1,14 @@
+package com.mirage.mirageservice.meta;
+
+import lombok.Data;
+
+/**
+ * Created by hzlinhai on 2025/10/27.
+ */
+@Data
+public class UserAndMemberIdInfo {
+
+    private Long uid;
+
+    private Long mid;
+}

+ 129 - 0
mirage-service/src/main/java/com/mirage/mirageservice/service/AsyncService.java

@@ -0,0 +1,129 @@
+package com.mirage.mirageservice.service;
+
+import com.google.common.collect.Lists;
+import com.mirage.core.utils.GsonUtil;
+import com.mirage.mirageservice.domain.FamilyMemberInfo;
+import com.mirage.mirageservice.domain.FamilyMoments;
+import com.mirage.mirageservice.enums.MomentsVisibleTypeEnum;
+import com.mirage.mirageservice.mapper.mysql.FamilyMemberInfoMapper;
+import com.mirage.mirageservice.meta.NewsRedDodInfo;
+import com.mirage.mirageservice.meta.UserAndMemberIdInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Created by hzlinhai on 2025/10/27.
+ */
+@Service
+@Slf4j
+public class AsyncService {
+
+    private static final String REDIS_MOMENTS_NEWS_RED_DOT = "redis_moments_news_red_dot_";
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+    @Resource
+    private FamilyMemberInfoMapper familyMemberInfoMapper;
+
+    @Async("asyncPublishEventExecutor")
+    public void commentAndLikePublishEvent(Long uid, Long commentId, FamilyMoments familyMoments){
+        if(null == uid
+                || null == familyMoments){
+            return;
+        }
+//        REDIS_MOMENTS_NEWS_RED_DOT
+        List<UserAndMemberIdInfo> userAndMemberIdInfos = Lists.newArrayList();
+        // 可见范围:0:全员 1:支系堂派 2:聚落 3:家族内男性 4:出生地
+        if(Objects.equals(familyMoments.getVisibleType(), MomentsVisibleTypeEnum.ALL_MEMBER.getType())){
+            userAndMemberIdInfos = familyMemberInfoMapper.selectUidUserInnerJoin(null, null, null, null);
+        } else if(Objects.equals(familyMoments.getVisibleType(), MomentsVisibleTypeEnum.SPECIAL_BRANCH_HALL.getType())){
+            userAndMemberIdInfos = familyMemberInfoMapper.selectUidUserInnerJoin(familyMoments.getVisibleValue(), null, null, null);
+        } else if(Objects.equals(familyMoments.getVisibleType(), MomentsVisibleTypeEnum.SPECIAL_CLUSTER_PLACE.getType())){
+            userAndMemberIdInfos = familyMemberInfoMapper.selectUidUserInnerJoin(null, null, null, familyMoments.getVisibleValue());
+        } else if (Objects.equals(familyMoments.getVisibleType(), MomentsVisibleTypeEnum.SPECIAL_SEX.getType())) {
+            userAndMemberIdInfos = familyMemberInfoMapper.selectUidUserInnerJoin(null, null, Integer.parseInt(familyMoments.getVisibleValue()), null);
+        } else if (Objects.equals(familyMoments.getVisibleType(), MomentsVisibleTypeEnum.SPECIAL_BIRTH_PLACE.getType())) {
+            userAndMemberIdInfos = familyMemberInfoMapper.selectUidUserInnerJoin(null, familyMoments.getVisibleValue(), null, null);
+        }
+        List<Long> notfiyUidList = Lists.newArrayList();
+        if(null == userAndMemberIdInfos || userAndMemberIdInfos.isEmpty()){
+            return;
+        }
+        FamilyMemberInfo memberInfo = familyMemberInfoMapper.selectByUid(uid);
+        if(null == memberInfo){
+            return;
+        }
+        notfiyUidList = userAndMemberIdInfos.stream().map(UserAndMemberIdInfo::getUid).collect(Collectors.toList());
+        for(Long notifyUid : notfiyUidList){
+            if(Objects.equals(notifyUid, uid)){
+                continue;
+            }
+            NewsRedDodInfo newsRedDodInfo = new NewsRedDodInfo();
+            newsRedDodInfo.setType(1);
+            newsRedDodInfo.setEventTime(new Date());
+            newsRedDodInfo.setUid(uid);
+            newsRedDodInfo.setCommentId(commentId);
+            newsRedDodInfo.setName(memberInfo.getName());
+            newsRedDodInfo.setMomentId(familyMoments.getId());
+            newsRedDodInfo.setHeadImgUrl(memberInfo.getHeadImgUrl());
+            // 从队列右侧入队(rpush:FIFO队列,若用lpush则为栈结构)
+            stringRedisTemplate.opsForList().rightPush(REDIS_MOMENTS_NEWS_RED_DOT + notifyUid, GsonUtil.toJson(newsRedDodInfo));
+        }
+    }
+
+    public List<NewsRedDodInfo> batchGetAndRemoveRedDotData(Long uid){
+        List<NewsRedDodInfo> result = Lists.newArrayList();
+        Long count = this.getRedDotQueueSize(uid);
+        if(null == count || count <= 0){
+            return result;
+        }
+        List<String> queueValueList = this.batchGetAndRemoveRedDotData(uid, count);
+        if(null == queueValueList || queueValueList.isEmpty()){
+            return result;
+        }
+        for(String cacheValue : queueValueList){
+            result.add(GsonUtil.fromJson(cacheValue, NewsRedDodInfo.class));
+        }
+        return result;
+    }
+
+    /**
+     * 从指定用户的红点队列中取出一条数据(出队,同时删除)
+     * @param uid 用户ID
+     * @return 取出的数据(无数据时返回null)
+     */
+    private String getAndRemoveRedDotData(Long uid) {
+        String key = REDIS_MOMENTS_NEWS_RED_DOT + uid;
+        // 从队列左侧出队(lpop:对应rightPush的FIFO顺序,出队后数据自动删除)
+        return stringRedisTemplate.opsForList().leftPop(key);
+    }
+
+    /**
+     * 批量取出指定用户的红点队列数据(出队,同时删除)
+     * @param uid 用户ID
+     * @param count 要取出的数量
+     * @return 取出的数据列表(无数据时返回空列表)
+     */
+    private List<String> batchGetAndRemoveRedDotData(Long uid, long count) {
+        String key = REDIS_MOMENTS_NEWS_RED_DOT + uid;
+        // 批量从左侧出队(最多返回count条,出队后数据自动删除)
+        return stringRedisTemplate.opsForList().leftPop(key, count);
+    }
+
+    /**
+     * 获取队列当前长度
+     * @param uid 用户ID
+     * @return 队列长度
+     */
+    private Long getRedDotQueueSize(Long uid) {
+        String key = REDIS_MOMENTS_NEWS_RED_DOT + uid;
+        return stringRedisTemplate.opsForList().size(key);
+    }
+}

+ 10 - 4
mirage-service/src/main/java/com/mirage/mirageservice/service/FamilyService.java

@@ -32,6 +32,8 @@ import java.util.stream.Collectors;
 public class FamilyService {
 
     private static final String REDIS_MOMENTS_LAST_ID = "redis_moments_last_id_";
+    private static final String REDIS_MOMENTS_OBSERVE = "redis_moments_observe_";
+    private static final String REDIS_MOMENTS_NEWS_RED_DOT = "redis_moments_news_red_dot_";
     @Resource
     private StringRedisTemplate stringRedisTemplate;
     @Resource
@@ -46,6 +48,8 @@ public class FamilyService {
     private FamilyMomentsLikeMapper familyMomentsLikeMapper;
     @Resource
     private FamilyMomentsCommentMapper familyMomentsCommentMapper;
+    @Resource
+    private AsyncService asyncService;
 
     public SelfFamilyCardResponse getSelfFamilyMember(Long uid){
         SelfFamilyCardResponse familyCardResponse = new SelfFamilyCardResponse();
@@ -470,7 +474,7 @@ public class FamilyService {
         }
          // 最后一个id记录,用于红点提示
         long alreadyReadId = result.get(result.size() - 1).getFamilyMoments().getId();
-        stringRedisTemplate.boundValueOps(REDIS_MOMENTS_LAST_ID).set(String.valueOf(alreadyReadId));
+        stringRedisTemplate.boundValueOps(REDIS_MOMENTS_LAST_ID+uid).set(String.valueOf(alreadyReadId));
         return result;
     }
 
@@ -484,7 +488,7 @@ public class FamilyService {
         if(null == uid){
             return false;
         }
-        String cacheValue = stringRedisTemplate.boundValueOps(REDIS_MOMENTS_LAST_ID).get();
+        String cacheValue = stringRedisTemplate.boundValueOps(REDIS_MOMENTS_LAST_ID+uid).get();
         Long lastId = StringUtils.isBlank(cacheValue) ? null : Long.parseLong(cacheValue);
         List<FamilyMoments> familyMoments = familyMomentsMapper.selectAllMoments(0, 0, null, lastId, 1000);
         if(null == familyMoments || familyMoments.isEmpty()){
@@ -557,7 +561,8 @@ public class FamilyService {
             familyMomentsLike.setGmtModified(new Date());
             familyMomentsLike.setGmtCreate(new Date());
             familyMomentsLikeMapper.insertSelective(familyMomentsLike);
-            // TODO 点赞信息 同步通知该家族圈发布者,已点赞者,已评论者
+            // 点赞信息 同步通知该家族圈发布者,已点赞者,已评论者
+            asyncService.commentAndLikePublishEvent(uid, null, familyMoments);
             return true;
         }
         return false;
@@ -593,7 +598,8 @@ public class FamilyService {
         familyMomentsComment.setGmtCreate(new Date());
         familyMomentsComment.setGmtModified(new Date());
         familyMomentsCommentMapper.insertSelective(familyMomentsComment);
-        // TODO 评论信息 同步通知该家族圈发布者,已点赞者,已评论者
+        // 评论信息 同步通知该家族圈发布者,已点赞者,已评论者
+        asyncService.commentAndLikePublishEvent(uid, familyMomentsComment.getId(), familyMoments);
         return true;
     }
 

+ 20 - 0
mirage-service/src/main/resources/com/mirage/mirageservice/mapper/mysql/FamilyMemberInfoMapper.xml

@@ -440,4 +440,24 @@
     </foreach>
   </select>
 
+  <select id="selectUidUserInnerJoin" resultType="com.mirage.mirageservice.meta.UserAndMemberIdInfo">
+    select
+      wu.id as uid,
+      mif.id as mid
+    from cs_min_wechat_user wu,family_member_info mif
+    where wu.id = mif.uid and mif.uid != -1
+    <if test="likeBranchFamilyHall != null">
+      and branch_family_hall like concat('%',#{likeBranchFamilyHall,jdbcType=VARCHAR},'%')
+    </if>
+    <if test="likeBirthPlace != null">
+      or birth_place like concat('%',#{likeBirthPlace,jdbcType=VARCHAR},'%')
+    </if>
+    <if test="sex != null">
+      or sex=#{sex,jdbcType=INTEGER}
+    </if>
+    <if test="likeClusterPlace != null">
+      or cluster_place like concat('%',#{likeClusterPlace,jdbcType=VARCHAR},'%')
+    </if>
+  </select>
+
 </mapper>