Compare commits
3 Commits
b70d9073d8
...
5e4f9b1203
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e4f9b1203 | |||
| aca7c657fa | |||
| 1e350a4af5 |
@@ -0,0 +1,37 @@
|
|||||||
|
package com.hanserwei.hannote.user.relation.biz.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ThreadPoolConfig {
|
||||||
|
|
||||||
|
@Bean(name = "relationTaskExecutor")
|
||||||
|
public ThreadPoolTaskExecutor taskExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
// 核心线程数
|
||||||
|
executor.setCorePoolSize(10);
|
||||||
|
// 最大线程数
|
||||||
|
executor.setMaxPoolSize(50);
|
||||||
|
// 队列容量
|
||||||
|
executor.setQueueCapacity(200);
|
||||||
|
// 线程活跃时间(秒)
|
||||||
|
executor.setKeepAliveSeconds(30);
|
||||||
|
// 线程名前缀
|
||||||
|
executor.setThreadNamePrefix("UserExecutor-");
|
||||||
|
|
||||||
|
// 拒绝策略:由调用线程处理(一般为主线程)
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
|
||||||
|
// 等待所有任务结束后再关闭线程池
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
// 设置等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是被没有完成的任务阻塞
|
||||||
|
executor.setAwaitTerminationSeconds(60);
|
||||||
|
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import jakarta.annotation.Resource;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.rocketmq.common.message.Message;
|
import org.apache.rocketmq.common.message.Message;
|
||||||
|
import org.apache.rocketmq.spring.annotation.ConsumeMode;
|
||||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
@@ -32,7 +33,8 @@ import java.util.Objects;
|
|||||||
@Component
|
@Component
|
||||||
@RocketMQMessageListener(
|
@RocketMQMessageListener(
|
||||||
consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW,
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW,
|
||||||
topic = MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW
|
topic = MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW,
|
||||||
|
consumeMode = ConsumeMode.ORDERLY
|
||||||
)
|
)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -165,6 +167,7 @@ public class FollowUnfollowConsumer implements RocketMQListener<Message> {
|
|||||||
// Lua脚本
|
// Lua脚本
|
||||||
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
||||||
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/follow_check_and_update_fans_zset.lua")));
|
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/follow_check_and_update_fans_zset.lua")));
|
||||||
|
script.setResultType(Long.class);
|
||||||
|
|
||||||
// 时间戳
|
// 时间戳
|
||||||
long timestamp = DateUtils.localDateTime2Timestamp(createTime);
|
long timestamp = DateUtils.localDateTime2Timestamp(createTime);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.hanserwei.hannote.user.relation.biz.controller;
|
package com.hanserwei.hannote.user.relation.biz.controller;
|
||||||
|
|
||||||
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.user.dto.req.FindFollowingListReqVO;
|
||||||
|
import com.hanserwei.hannote.user.dto.resp.FindFollowingUserRspVO;
|
||||||
import com.hanserwei.hannote.user.relation.biz.model.vo.FollowUserReqVO;
|
import com.hanserwei.hannote.user.relation.biz.model.vo.FollowUserReqVO;
|
||||||
import com.hanserwei.hannote.user.relation.biz.model.vo.UnfollowUserReqVO;
|
import com.hanserwei.hannote.user.relation.biz.model.vo.UnfollowUserReqVO;
|
||||||
import com.hanserwei.hannote.user.relation.biz.service.RelationService;
|
import com.hanserwei.hannote.user.relation.biz.service.RelationService;
|
||||||
@@ -32,4 +35,10 @@ public class RelationController {
|
|||||||
public Response<?> unfollow(@Validated @RequestBody UnfollowUserReqVO unfollowUserReqVO) {
|
public Response<?> unfollow(@Validated @RequestBody UnfollowUserReqVO unfollowUserReqVO) {
|
||||||
return relationService.unfollow(unfollowUserReqVO);
|
return relationService.unfollow(unfollowUserReqVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/following/list")
|
||||||
|
@ApiOperationLog(description = "查询用户关注列表")
|
||||||
|
public PageResponse<FindFollowingUserRspVO> findFollowingList(@Validated @RequestBody FindFollowingListReqVO findFollowingListReqVO) {
|
||||||
|
return relationService.findFollowingList(findFollowingListReqVO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package com.hanserwei.hannote.user.relation.biz.rpc;
|
package com.hanserwei.hannote.user.relation.biz.rpc;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
import com.hanserwei.hannote.user.api.UserFeignApi;
|
import com.hanserwei.hannote.user.api.UserFeignApi;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
|
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
|
||||||
|
import com.hanserwei.hannote.user.dto.req.FindUsersByIdsReqDTO;
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -34,4 +37,22 @@ public class UserRpcService {
|
|||||||
return response.getData();
|
return response.getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询用户信息
|
||||||
|
*
|
||||||
|
* @param userIds 用户 ID集合
|
||||||
|
* @return 用户信息集合
|
||||||
|
*/
|
||||||
|
public List<FindUserByIdRspDTO> findByIds(List<Long> userIds) {
|
||||||
|
FindUsersByIdsReqDTO findUsersByIdsReqDTO = new FindUsersByIdsReqDTO();
|
||||||
|
findUsersByIdsReqDTO.setIds(userIds);
|
||||||
|
|
||||||
|
Response<List<FindUserByIdRspDTO>> response = userFeignApi.findByIds(findUsersByIdsReqDTO);
|
||||||
|
|
||||||
|
if (!response.isSuccess() || Objects.isNull(response.getData()) || CollUtil.isEmpty(response.getData())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.hanserwei.hannote.user.relation.biz.service;
|
package com.hanserwei.hannote.user.relation.biz.service;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.user.dto.req.FindFollowingListReqVO;
|
||||||
|
import com.hanserwei.hannote.user.dto.resp.FindFollowingUserRspVO;
|
||||||
import com.hanserwei.hannote.user.relation.biz.model.vo.FollowUserReqVO;
|
import com.hanserwei.hannote.user.relation.biz.model.vo.FollowUserReqVO;
|
||||||
import com.hanserwei.hannote.user.relation.biz.model.vo.UnfollowUserReqVO;
|
import com.hanserwei.hannote.user.relation.biz.model.vo.UnfollowUserReqVO;
|
||||||
|
|
||||||
@@ -20,4 +23,11 @@ public interface RelationService {
|
|||||||
* @return 响应
|
* @return 响应
|
||||||
*/
|
*/
|
||||||
Response<?> unfollow(UnfollowUserReqVO unfollowUserReqVO);
|
Response<?> unfollow(UnfollowUserReqVO unfollowUserReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询关注列表
|
||||||
|
* @param findFollowingListReqVO 查询关注列表请求
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
PageResponse<FindFollowingUserRspVO> findFollowingList(FindFollowingListReqVO findFollowingListReqVO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ package com.hanserwei.hannote.user.relation.biz.service.impl;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
||||||
import com.hanserwei.framework.common.exception.ApiException;
|
import com.hanserwei.framework.common.exception.ApiException;
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
|
import com.hanserwei.hannote.user.dto.req.FindFollowingListReqVO;
|
||||||
|
import com.hanserwei.hannote.user.dto.resp.FindFollowingUserRspVO;
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
import com.hanserwei.hannote.user.relation.biz.constant.MQConstants;
|
import com.hanserwei.hannote.user.relation.biz.constant.MQConstants;
|
||||||
import com.hanserwei.hannote.user.relation.biz.constant.RedisKeyConstants;
|
import com.hanserwei.hannote.user.relation.biz.constant.RedisKeyConstants;
|
||||||
@@ -31,6 +35,7 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.support.MessageBuilder;
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.scripting.support.ResourceScriptSource;
|
import org.springframework.scripting.support.ResourceScriptSource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -38,6 +43,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -51,6 +57,8 @@ public class RelationServiceImpl implements RelationService {
|
|||||||
private FollowingDOService followingDOService;
|
private FollowingDOService followingDOService;
|
||||||
@Resource
|
@Resource
|
||||||
private RocketMQTemplate rocketMQTemplate;
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
@Resource(name = "relationTaskExecutor")
|
||||||
|
private ThreadPoolTaskExecutor taskExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<?> follow(FollowUserReqVO followUserReqVO) {
|
public Response<?> follow(FollowUserReqVO followUserReqVO) {
|
||||||
@@ -264,6 +272,162 @@ public class RelationServiceImpl implements RelationService {
|
|||||||
return Response.success();
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResponse<FindFollowingUserRspVO> findFollowingList(FindFollowingListReqVO findFollowingListReqVO) {
|
||||||
|
// 要查询的用户ID
|
||||||
|
Long userId = findFollowingListReqVO.getUserId();
|
||||||
|
// 页码
|
||||||
|
Integer pageNo = findFollowingListReqVO.getPageNo();
|
||||||
|
// 先从Redis中查询
|
||||||
|
String followingRedisKey = RedisKeyConstants.buildUserFollowingKey(userId);
|
||||||
|
// 查询目标用户的关注列表ZSet的总大小
|
||||||
|
Long total = redisTemplate.opsForZSet().zCard(followingRedisKey);
|
||||||
|
log.info("==> 查询目标用户的关注列表ZSet的总大小{}", total);
|
||||||
|
|
||||||
|
// 构建回参
|
||||||
|
List<FindFollowingUserRspVO> findFollowingUserRspVOS = null;
|
||||||
|
|
||||||
|
//每页展示10条数据
|
||||||
|
long limit = 10L;
|
||||||
|
if (total != null && total > 0) {
|
||||||
|
// 缓存有数据
|
||||||
|
// 计算一共多少页
|
||||||
|
long totalPage = PageResponse.getTotalPage(total, limit);
|
||||||
|
|
||||||
|
// 请求页码超过总页数
|
||||||
|
if (pageNo > totalPage) {
|
||||||
|
log.info("==> 请求页码超过总页数,返回空数据");
|
||||||
|
return PageResponse.success(null, pageNo, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备从ZSet中查询分页数据
|
||||||
|
// 每页展示10条数据,计算偏移量
|
||||||
|
long offset = PageResponse.getOffset(pageNo, limit);
|
||||||
|
|
||||||
|
// 使用 ZREVRANGEBYSCORE 命令按 score 降序获取元素,同时使用 LIMIT 子句实现分页
|
||||||
|
// 注意:这里使用了 Double.POSITIVE_INFINITY 和 Double.NEGATIVE_INFINITY 作为分数范围
|
||||||
|
// 因为关注列表最多有 1000 个元素,这样可以确保获取到所有的元素
|
||||||
|
Set<Object> followingUserIdsSet = redisTemplate.opsForZSet()
|
||||||
|
.reverseRangeByScore(followingRedisKey,
|
||||||
|
Double.NEGATIVE_INFINITY,
|
||||||
|
Double.POSITIVE_INFINITY,
|
||||||
|
offset,
|
||||||
|
limit);
|
||||||
|
if (CollUtil.isNotEmpty(followingUserIdsSet)) {
|
||||||
|
//提取所有ID
|
||||||
|
List<Long> userIds = followingUserIdsSet.stream()
|
||||||
|
.map(object -> Long.parseLong(object.toString())).toList();
|
||||||
|
|
||||||
|
log.info("==> 批量查询用户信息,用户ID: {}", userIds);
|
||||||
|
|
||||||
|
// RPC: 批量查询用户信息
|
||||||
|
//noinspection ConstantValue
|
||||||
|
findFollowingUserRspVOS = rpcUserServiceAndDTO2VO(userIds, findFollowingUserRspVOS);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 若 Redis 中没有数据,则从数据库查询
|
||||||
|
// 先查询记录总量
|
||||||
|
long count = followingDOService.count(new LambdaQueryWrapper<>(FollowingDO.class)
|
||||||
|
.eq(FollowingDO::getUserId, userId));
|
||||||
|
// 计算一共多少页
|
||||||
|
|
||||||
|
long totalPage = PageResponse.getTotalPage(count, limit);
|
||||||
|
|
||||||
|
// 请求页码超过总页数
|
||||||
|
if (pageNo > totalPage) {
|
||||||
|
log.info("==> 批量查询用户信息,返回空数据");
|
||||||
|
//noinspection DataFlowIssue
|
||||||
|
return PageResponse.success(null, pageNo, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 偏移量
|
||||||
|
long offset = PageResponse.getOffset(pageNo, limit);
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
// 从数据库分页查询
|
||||||
|
Page<FollowingDO> page = followingDOService.page(new Page<>(offset / limit + 1, limit),
|
||||||
|
new LambdaQueryWrapper<FollowingDO>()
|
||||||
|
.eq(FollowingDO::getUserId, userId)
|
||||||
|
.orderByDesc(FollowingDO::getCreateTime));
|
||||||
|
List<FollowingDO> followingDOS = page.getRecords();
|
||||||
|
// 赋值真实地记录总数
|
||||||
|
total = count;
|
||||||
|
// 若记录不为空
|
||||||
|
if (CollUtil.isNotEmpty(followingDOS)) {
|
||||||
|
// 提取所有关注用户 ID 到集合中
|
||||||
|
List<Long> userIds = followingDOS.stream().map(FollowingDO::getFollowingUserId).toList();
|
||||||
|
|
||||||
|
// RPC: 调用用户服务,并将 DTO 转换为 VO
|
||||||
|
//noinspection ConstantValue
|
||||||
|
findFollowingUserRspVOS = rpcUserServiceAndDTO2VO(userIds, findFollowingUserRspVOS);
|
||||||
|
|
||||||
|
// 异步将关注列表全量同步到 Redis
|
||||||
|
taskExecutor.submit(() -> syncFollowingList2Redis(userId));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PageResponse.success(findFollowingUserRspVOS,
|
||||||
|
pageNo,
|
||||||
|
total);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全量同步关注列表到 Redis
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
private void syncFollowingList2Redis(Long userId) {
|
||||||
|
Page<FollowingDO> page = followingDOService.page(new Page<>(1, 1000),
|
||||||
|
new LambdaQueryWrapper<>(FollowingDO.class)
|
||||||
|
.select(FollowingDO::getFollowingUserId, FollowingDO::getCreateTime)
|
||||||
|
.eq(FollowingDO::getUserId, userId));
|
||||||
|
List<FollowingDO> followingDOS = page.getRecords();
|
||||||
|
log.info("==> 全量同步用户关注列表{}", JsonUtils.toJsonString(followingDOS));
|
||||||
|
if (CollUtil.isNotEmpty(followingDOS)) {
|
||||||
|
// 用户关注列表 Redis Key
|
||||||
|
String followingListRedisKey = RedisKeyConstants.buildUserFollowingKey(userId);
|
||||||
|
// 随机过期时间
|
||||||
|
// 保底1天+随机秒数
|
||||||
|
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
|
||||||
|
// 构建 Lua 参数
|
||||||
|
Object[] luaArgs = buildLuaArgs(followingDOS, expireSeconds);
|
||||||
|
|
||||||
|
// 执行 Lua 脚本,批量同步关注关系数据到 Redis 中
|
||||||
|
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
||||||
|
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/follow_batch_add_and_expire.lua")));
|
||||||
|
script.setResultType(Long.class);
|
||||||
|
redisTemplate.execute(script, Collections.singletonList(followingListRedisKey), luaArgs);
|
||||||
|
log.info("==> 全量同步用户关注列表到 Redis,用户ID: {}", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC: 调用用户服务,并将 DTO 转换为 VO
|
||||||
|
*
|
||||||
|
* @param userIds 用户 ID 列表
|
||||||
|
* @param findFollowingUserRspVOS 跟随用户列表
|
||||||
|
* @return 跟随用户列表
|
||||||
|
*/
|
||||||
|
private List<FindFollowingUserRspVO> rpcUserServiceAndDTO2VO(List<Long> userIds, List<FindFollowingUserRspVO> findFollowingUserRspVOS) {
|
||||||
|
// RPC: 批量查询用户信息
|
||||||
|
List<FindUserByIdRspDTO> findUserByIdRspDTOS = userRpcService.findByIds(userIds);
|
||||||
|
|
||||||
|
// 若不为空,DTO 转 VO
|
||||||
|
if (CollUtil.isNotEmpty(findUserByIdRspDTOS)) {
|
||||||
|
findFollowingUserRspVOS = findUserByIdRspDTOS.stream()
|
||||||
|
.map(dto -> FindFollowingUserRspVO.builder()
|
||||||
|
.userId(dto.getId())
|
||||||
|
.avatar(dto.getAvatar())
|
||||||
|
.nickname(dto.getNickName())
|
||||||
|
.introduction(dto.getIntroduction())
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
return findFollowingUserRspVOS;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验 Lua 脚本结果,根据状态码抛出对应的业务异常
|
* 校验 Lua 脚本结果,根据状态码抛出对应的业务异常
|
||||||
* @param result Lua 脚本返回结果
|
* @param result Lua 脚本返回结果
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.hanserwei.hannote.user.relation.biz;
|
|||||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||||
import com.hanserwei.hannote.user.relation.biz.constant.MQConstants;
|
import com.hanserwei.hannote.user.relation.biz.constant.MQConstants;
|
||||||
import com.hanserwei.hannote.user.relation.biz.model.dto.FollowUserMqDTO;
|
import com.hanserwei.hannote.user.relation.biz.model.dto.FollowUserMqDTO;
|
||||||
|
import com.hanserwei.hannote.user.relation.biz.model.dto.UnfollowUserMqDTO;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.rocketmq.client.producer.SendCallback;
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
@@ -59,4 +60,69 @@ class MQTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:发送对同一个用户关注、取关 MQ
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testSendFollowUnfollowMQ() {
|
||||||
|
// 操作者用户ID
|
||||||
|
Long userId = 100L;
|
||||||
|
// 目标用户ID
|
||||||
|
Long targetUserId = 2100L;
|
||||||
|
|
||||||
|
for (long i = 0; i < 100; i++) {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
// 偶数发送关注 MQ
|
||||||
|
log.info("{} 是偶数", i);
|
||||||
|
|
||||||
|
// 发送 MQ
|
||||||
|
// 构建消息体 DTO
|
||||||
|
FollowUserMqDTO followUserMqDTO = FollowUserMqDTO.builder()
|
||||||
|
.userId(userId)
|
||||||
|
.followUserId(targetUserId)
|
||||||
|
.createTime(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(followUserMqDTO))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 通过冒号连接, 可让 MQ 发送给主题 Topic 时,携带上标签 Tag
|
||||||
|
String destination = MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW + ":" + MQConstants.TAG_FOLLOW;
|
||||||
|
|
||||||
|
String hashKey = String.valueOf(userId);
|
||||||
|
|
||||||
|
// 发送 MQ 消息
|
||||||
|
SendResult sendResult = rocketMQTemplate.syncSendOrderly(destination, message, hashKey);
|
||||||
|
|
||||||
|
|
||||||
|
log.info("==> MQ 发送结果,SendResult: {}", sendResult);
|
||||||
|
} else { // 取关发送取关 MQ
|
||||||
|
log.info("{} 是奇数", i);
|
||||||
|
|
||||||
|
// 发送 MQ
|
||||||
|
// 构建消息体 DTO
|
||||||
|
UnfollowUserMqDTO unfollowUserMqDTO = UnfollowUserMqDTO.builder()
|
||||||
|
.userId(userId)
|
||||||
|
.unfollowUserId(targetUserId)
|
||||||
|
.createTime(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(unfollowUserMqDTO))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 通过冒号连接, 可让 MQ 发送给主题 Topic 时,携带上标签 Tag
|
||||||
|
String destination = MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW + ":" + MQConstants.TAG_UNFOLLOW;
|
||||||
|
|
||||||
|
String hashKey = String.valueOf(userId);
|
||||||
|
|
||||||
|
// 发送 MQ 消息
|
||||||
|
SendResult sendResult = rocketMQTemplate.syncSendOrderly(destination, message, hashKey);
|
||||||
|
|
||||||
|
log.info("==> MQ 发送结果,SendResult: {}", sendResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,16 +2,15 @@ package com.hanserwei.hannote.user.api;
|
|||||||
|
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
import com.hanserwei.hannote.user.constant.ApiConstants;
|
import com.hanserwei.hannote.user.constant.ApiConstants;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByEmailReqDTO;
|
import com.hanserwei.hannote.user.dto.req.*;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.RegisterUserReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.UpdateUserPasswordReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@FeignClient(name = ApiConstants.SERVICE_NAME)
|
@FeignClient(name = ApiConstants.SERVICE_NAME)
|
||||||
public interface UserFeignApi {
|
public interface UserFeignApi {
|
||||||
|
|
||||||
@@ -52,4 +51,13 @@ public interface UserFeignApi {
|
|||||||
*/
|
*/
|
||||||
@PostMapping(value = PREFIX + "/findById")
|
@PostMapping(value = PREFIX + "/findById")
|
||||||
Response<FindUserByIdRspDTO> findById(@RequestBody FindUserByIdReqDTO findUserByIdReqDTO);
|
Response<FindUserByIdRspDTO> findById(@RequestBody FindUserByIdReqDTO findUserByIdReqDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询用户信息
|
||||||
|
*
|
||||||
|
* @param findUsersByIdsReqDTO 批量查询信息请求
|
||||||
|
* @return 响应
|
||||||
|
*/
|
||||||
|
@PostMapping(value = PREFIX + "/findByIds")
|
||||||
|
Response<List<FindUserByIdRspDTO>> findByIds(@RequestBody FindUsersByIdsReqDTO findUsersByIdsReqDTO);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.hanserwei.hannote.user.dto.req;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class FindFollowingListReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "查询用户 ID 不能为空")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@NotNull(message = "页码不能为空")
|
||||||
|
private Integer pageNo = 1; // 默认值为第一页
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.hanserwei.hannote.user.dto.req;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class FindUsersByIdsReqDTO {
|
||||||
|
|
||||||
|
@NotNull(message = "用户 ID 集合不能为空")
|
||||||
|
@Size(min = 1, max = 10, message = "用户 ID 集合大小必须大于等于 1, 小于等于 10")
|
||||||
|
private List<Long> ids;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.hanserwei.hannote.user.dto.resp;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class FindFollowingUserRspVO {
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
private String introduction;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,4 +25,9 @@ public class FindUserByIdRspDTO {
|
|||||||
* 头像
|
* 头像
|
||||||
*/
|
*/
|
||||||
private String avatar;
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简介
|
||||||
|
*/
|
||||||
|
private String introduction;
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,7 @@ import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
|||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
import com.hanserwei.hannote.user.biz.model.vo.UpdateUserInfoReqVO;
|
import com.hanserwei.hannote.user.biz.model.vo.UpdateUserInfoReqVO;
|
||||||
import com.hanserwei.hannote.user.biz.service.UserService;
|
import com.hanserwei.hannote.user.biz.service.UserService;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByEmailReqDTO;
|
import com.hanserwei.hannote.user.dto.req.*;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.RegisterUserReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.UpdateUserPasswordReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -19,6 +16,8 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/user")
|
@RequestMapping("/user")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -63,4 +62,10 @@ public class UserController {
|
|||||||
return userService.findById(findUserByIdReqDTO);
|
return userService.findById(findUserByIdReqDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/findByIds")
|
||||||
|
@ApiOperationLog(description = "批量查询用户信息")
|
||||||
|
public Response<List<FindUserByIdRspDTO>> findByIds(@Validated @RequestBody FindUsersByIdsReqDTO findUsersByIdsReqDTO) {
|
||||||
|
return userService.findByIds(findUsersByIdsReqDTO);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,12 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
|||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
import com.hanserwei.hannote.user.biz.domain.dataobject.UserDO;
|
import com.hanserwei.hannote.user.biz.domain.dataobject.UserDO;
|
||||||
import com.hanserwei.hannote.user.biz.model.vo.UpdateUserInfoReqVO;
|
import com.hanserwei.hannote.user.biz.model.vo.UpdateUserInfoReqVO;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByEmailReqDTO;
|
import com.hanserwei.hannote.user.dto.req.*;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.RegisterUserReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.UpdateUserPasswordReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface UserService extends IService<UserDO> {
|
public interface UserService extends IService<UserDO> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,4 +51,12 @@ public interface UserService extends IService<UserDO> {
|
|||||||
* @return 响应结果
|
* @return 响应结果
|
||||||
*/
|
*/
|
||||||
Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO);
|
Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量根据用户 ID 查询用户信息
|
||||||
|
*
|
||||||
|
* @param findUsersByIdsReqDTO 批量查询用户信息请求参数
|
||||||
|
* @return 响应结果
|
||||||
|
*/
|
||||||
|
Response<List<FindUserByIdRspDTO>> findByIds(FindUsersByIdsReqDTO findUsersByIdsReqDTO);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.hanserwei.hannote.user.biz.service.impl;
|
package com.hanserwei.hannote.user.biz.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
||||||
import com.hanserwei.framework.common.enums.DeletedEnum;
|
import com.hanserwei.framework.common.enums.DeletedEnum;
|
||||||
import com.hanserwei.framework.common.enums.StatusEnum;
|
import com.hanserwei.framework.common.enums.StatusEnum;
|
||||||
@@ -27,16 +30,17 @@ import com.hanserwei.hannote.user.biz.model.vo.UpdateUserInfoReqVO;
|
|||||||
import com.hanserwei.hannote.user.biz.rpc.DistributedIdGeneratorRpcService;
|
import com.hanserwei.hannote.user.biz.rpc.DistributedIdGeneratorRpcService;
|
||||||
import com.hanserwei.hannote.user.biz.rpc.OssRpcService;
|
import com.hanserwei.hannote.user.biz.rpc.OssRpcService;
|
||||||
import com.hanserwei.hannote.user.biz.service.UserService;
|
import com.hanserwei.hannote.user.biz.service.UserService;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByEmailReqDTO;
|
import com.hanserwei.hannote.user.dto.req.*;
|
||||||
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.RegisterUserReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.req.UpdateUserPasswordReqDTO;
|
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.data.redis.core.RedisOperations;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.SessionCallback;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -46,8 +50,10 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -296,6 +302,7 @@ public class UserServiceImpl extends ServiceImpl<UserDOMapper, UserDO> implement
|
|||||||
.id(userDO.getId())
|
.id(userDO.getId())
|
||||||
.nickName(userDO.getNickname())
|
.nickName(userDO.getNickname())
|
||||||
.avatar(userDO.getAvatar())
|
.avatar(userDO.getAvatar())
|
||||||
|
.introduction(userDO.getIntroduction())
|
||||||
.build();
|
.build();
|
||||||
threadPoolTaskExecutor.submit(() -> {
|
threadPoolTaskExecutor.submit(() -> {
|
||||||
// 过期时间保底1天+随机秒数,避免缓存雪崩
|
// 过期时间保底1天+随机秒数,避免缓存雪崩
|
||||||
@@ -304,5 +311,119 @@ public class UserServiceImpl extends ServiceImpl<UserDOMapper, UserDO> implement
|
|||||||
});
|
});
|
||||||
return Response.success(findUserByIdRspDTO);
|
return Response.success(findUserByIdRspDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<FindUserByIdRspDTO>> findByIds(FindUsersByIdsReqDTO findUsersByIdsReqDTO) {
|
||||||
|
List<Long> userIds = findUsersByIdsReqDTO.getIds();
|
||||||
|
|
||||||
|
// 构建Redis的Key集合
|
||||||
|
List<String> userInfoKeys = userIds.stream()
|
||||||
|
.map(RedisKeyConstants::buildUserInfoKey)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 先从Redis中获取
|
||||||
|
List<Object> redisValues = redisTemplate.opsForValue().multiGet(userInfoKeys);
|
||||||
|
|
||||||
|
// 如果缓存中不为空,过滤掉空值
|
||||||
|
if (CollUtil.isNotEmpty(redisValues)){
|
||||||
|
redisValues = redisValues.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返参
|
||||||
|
List<FindUserByIdRspDTO> findUserByIdRspDTOS = Lists.newArrayList();
|
||||||
|
|
||||||
|
// 将过滤后的缓存集合,转换为 DTO 返参实体类
|
||||||
|
if (CollUtil.isNotEmpty(redisValues)) {
|
||||||
|
findUserByIdRspDTOS = redisValues.stream()
|
||||||
|
.map(value -> JsonUtils.parseObject(String.valueOf(value), FindUserByIdRspDTO.class))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果被查询的用户信息,都在 Redis 缓存中, 则直接返回
|
||||||
|
if (CollUtil.size(userIds) == CollUtil.size(findUserByIdRspDTOS)) {
|
||||||
|
return Response.success(findUserByIdRspDTOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 还有另外两种情况:一种是缓存里没有用户信息数据,还有一种是缓存里数据不全,需要从数据库中补充
|
||||||
|
// 筛选出缓存里没有的用户数据,去查数据库
|
||||||
|
List<Long> userIdsNeedQuery = null;
|
||||||
|
if (CollUtil.isNotEmpty(findUserByIdRspDTOS)) {
|
||||||
|
// 将 findUserInfoByIdRspDTOS 集合转 Map
|
||||||
|
Map<Long, FindUserByIdRspDTO> map = findUserByIdRspDTOS.stream()
|
||||||
|
.collect(Collectors.toMap(FindUserByIdRspDTO::getId, p -> p));
|
||||||
|
|
||||||
|
// 筛选出需要查 DB 的用户 ID
|
||||||
|
userIdsNeedQuery = userIds.stream()
|
||||||
|
.filter(id -> Objects.isNull(map.get(id)))
|
||||||
|
.toList();
|
||||||
|
} else { // 缓存中一条用户信息都没查到,则提交的用户 ID 集合都需要查数据库
|
||||||
|
userIdsNeedQuery = userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库中批量查询用户信息
|
||||||
|
List<UserDO> userDOs = this.list(new LambdaQueryWrapper<>(UserDO.class)
|
||||||
|
.eq(UserDO::getStatus, 0)
|
||||||
|
.eq(UserDO::getIsDeleted, 0)
|
||||||
|
.in(UserDO::getId, userIdsNeedQuery));
|
||||||
|
List<FindUserByIdRspDTO> findUserByIdRspDTOS2 = null;
|
||||||
|
// 若数据不为空,则转为 DTO 返回
|
||||||
|
if (CollUtil.isNotEmpty(userDOs)){
|
||||||
|
findUserByIdRspDTOS2 = userDOs.stream()
|
||||||
|
.map(userDO -> FindUserByIdRspDTO.builder()
|
||||||
|
.id(userDO.getId())
|
||||||
|
.nickName(userDO.getNickname())
|
||||||
|
.introduction(userDO.getIntroduction())
|
||||||
|
.avatar(userDO.getAvatar())
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
// 批量更新 Redis
|
||||||
|
List<FindUserByIdRspDTO> finalFindUserByIdRspDTOS = findUserByIdRspDTOS2;
|
||||||
|
threadPoolTaskExecutor.submit(()->{
|
||||||
|
// DTO集合转Map
|
||||||
|
Map<Long, FindUserByIdRspDTO> map = finalFindUserByIdRspDTOS.stream()
|
||||||
|
.collect(Collectors.toMap(FindUserByIdRspDTO::getId, p -> p));
|
||||||
|
|
||||||
|
// 执行pipeline操作
|
||||||
|
//noinspection NullableProblems
|
||||||
|
redisTemplate.executePipelined(new SessionCallback<>() {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Object execute(RedisOperations operations) throws DataAccessException {
|
||||||
|
|
||||||
|
// 你可以直接获取 ValueOperations
|
||||||
|
ValueOperations<String, Object> valueOperations = operations.opsForValue();
|
||||||
|
|
||||||
|
for (UserDO userDO : userDOs) {
|
||||||
|
Long userId = userDO.getId();
|
||||||
|
|
||||||
|
// 用户缓存Key (K = String)
|
||||||
|
String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
|
||||||
|
|
||||||
|
// DTO转JSON
|
||||||
|
FindUserByIdRspDTO findUserByIdRspDTO = map.get(userId);
|
||||||
|
|
||||||
|
// 的值类型是 Object,所以它可以接受 String。
|
||||||
|
String value = JsonUtils.toJsonString(findUserByIdRspDTO);
|
||||||
|
|
||||||
|
// 过期时间
|
||||||
|
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
|
||||||
|
|
||||||
|
valueOperations.set(userInfoRedisKey, value, expireSeconds, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 合并数据
|
||||||
|
if (CollUtil.isNotEmpty(findUserByIdRspDTOS2)) {
|
||||||
|
findUserByIdRspDTOS.addAll(findUserByIdRspDTOS2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.success(findUserByIdRspDTOS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.hanserwei.framework.common.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页响应(未使用任何分页插件)
|
||||||
|
*
|
||||||
|
* @author hanserwei
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class PageResponse<T> extends Response<List<T>> {
|
||||||
|
private long pageNo; // 当前页码
|
||||||
|
private long totalCount; // 总数据量
|
||||||
|
private long pageSize; // 每页展示的数据量
|
||||||
|
private long totalPage; // 总页数
|
||||||
|
|
||||||
|
public static <T> PageResponse<T> success(List<T> data, long pageNo, long totalCount) {
|
||||||
|
PageResponse<T> pageResponse = new PageResponse<>();
|
||||||
|
pageResponse.setSuccess(true);
|
||||||
|
pageResponse.setData(data);
|
||||||
|
pageResponse.setPageNo(pageNo);
|
||||||
|
pageResponse.setTotalCount(totalCount);
|
||||||
|
// 每页展示的数据量
|
||||||
|
long pageSize = 10L;
|
||||||
|
pageResponse.setPageSize(pageSize);
|
||||||
|
// 计算总页数
|
||||||
|
long totalPage = (totalCount + pageSize - 1) / pageSize;
|
||||||
|
pageResponse.setTotalPage(totalPage);
|
||||||
|
return pageResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> PageResponse<T> success(List<T> data, long pageNo, long totalCount, long pageSize) {
|
||||||
|
PageResponse<T> pageResponse = new PageResponse<>();
|
||||||
|
pageResponse.setSuccess(true);
|
||||||
|
pageResponse.setData(data);
|
||||||
|
pageResponse.setPageNo(pageNo);
|
||||||
|
pageResponse.setTotalCount(totalCount);
|
||||||
|
pageResponse.setPageSize(pageSize);
|
||||||
|
// 计算总页数
|
||||||
|
long totalPage = pageSize == 0 ? 0 : (totalCount + pageSize - 1) / pageSize;
|
||||||
|
pageResponse.setTotalPage(totalPage);
|
||||||
|
return pageResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取总页数
|
||||||
|
* @return 总页数
|
||||||
|
*/
|
||||||
|
public static long getTotalPage(long totalCount, long pageSize) {
|
||||||
|
return pageSize == 0 ? 0 : (totalCount + pageSize - 1) / pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算分页查询的 offset
|
||||||
|
* @param pageNo 页码
|
||||||
|
* @param pageSize 每页展示的数据量
|
||||||
|
* @return offset
|
||||||
|
*/
|
||||||
|
public static long getOffset(long pageNo, long pageSize) {
|
||||||
|
// 如果页码小于 1,默认返回第一页的 offset
|
||||||
|
if (pageNo < 1) {
|
||||||
|
pageNo = 1;
|
||||||
|
}
|
||||||
|
return (pageNo - 1) * pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,3 +155,22 @@ Authorization: Bearer {{token}}
|
|||||||
{
|
{
|
||||||
"unfollowUserId": 2100
|
"unfollowUserId": 2100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### 批量查询用户信息
|
||||||
|
POST http://localhost:8000/user/user/findByIds
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"ids": [100,2100,4100]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询用户关注列表
|
||||||
|
POST http://localhost:8000/relation/relation/following/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"userId": 100,
|
||||||
|
"pageNo": 1
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user