下面我将详细分析微信朋友圈"好友可见"功能的实现流程,并使用Mermaid语法绘制时序图。
核心流程分析
1. 数据模型
- 用户关系:使用Redis的Set存储每个用户的好友列表(
user:{userId}:friends
)。 - 朋友圈内容:使用Redis的Hash存储朋友圈信息,包含可见性字段(
PUBLIC
/FRIENDS
/PRIVATE
)。 - 索引结构:使用Sorted Set按时间排序存储用户的朋友圈ID(
user:{userId}:moments
)。
2. 发布流程
- 用户创建朋友圈并设置可见性
- 系统将内容存入Redis Hash
- 将朋友圈ID添加到用户的Sorted Set
- 如果是"好友可见",还会加入全局好友可见集合(
moments:friends
)
3. 查询流程
- 获取当前用户的好友列表
- 遍历好友,获取每个好友的最新朋友圈ID
- 批量获取朋友圈内容
- 检查每条朋友圈的可见性权限
- 返回符合条件的朋友圈列表
逻辑:
1. 数据模型定义
// User.java - 用户模型
public class User {
private String userId;
private String username;
// getter/setter/constructors
}
// Moment.java - 朋友圈模型
public class Moment {
private String momentId;
private String userId; // 发布者ID
private String content; // 内容
private long timestamp; // 发布时间
private Visibility visibility; // 可见性
public enum Visibility {
PUBLIC, // 公开
FRIENDS, // 仅好友可见
PRIVATE // 仅自己可见
}
// getter/setter/constructors
}
2. Redis工具类
// RedisUtil.java - Redis连接工具
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtil {
private static final JedisPool jedisPool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(10);
config.setMinIdle(5);
jedisPool = new JedisPool(config, "localhost", 6379);
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
public static void close(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}
3. 好友关系管理服务
// FriendService.java - 好友关系管理
import redis.clients.jedis.Jedis;
import java.util.Set;
public class FriendService {
// 添加好友(双向关系)
public void addFriend(String userId, String friendId) {
try (Jedis jedis = RedisUtil.getJedis()) {
jedis.sadd(getFriendsKey(userId), friendId);
jedis.sadd(getFriendsKey(friendId), userId);
}
}
// 移除好友
public void removeFriend(String userId, String friendId) {
try (Jedis jedis = RedisUtil.getJedis()) {
jedis.srem(getFriendsKey(userId), friendId);
jedis.srem(getFriendsKey(friendId), userId);
}
}
// 判断是否为好友
public boolean isFriend(String userId, String targetId) {
try (Jedis jedis = RedisUtil.getJedis()) {
return jedis.sismember(getFriendsKey(userId), targetId);
}
}
// 获取用户的好友列表
public Set<String> getFriends(String userId) {
try (Jedis jedis = RedisUtil.getJedis()) {
return jedis.smembers(getFriendsKey(userId));
}
}
private String getFriendsKey(String userId) {
return "user:" + userId + ":friends";
}
}
4. 朋友圈服务
// MomentService.java - 朋友圈核心服务
import redis.clients.jedis.Jedis;
import java.util.*;
public class MomentService {
private final FriendService friendService = new FriendService();
// 发布朋友圈
public void publishMoment(Moment moment) {
try (Jedis jedis = RedisUtil.getJedis()) {
// 存储朋友圈内容到Hash
String momentKey = getMomentKey(moment.getMomentId());
jedis.hset(momentKey, "userId", moment.getUserId());
jedis.hset(momentKey, "content", moment.getContent());
jedis.hset(momentKey, "timestamp", String.valueOf(moment.getTimestamp()));
jedis.hset(momentKey, "visibility", moment.getVisibility().name());
// 添加到用户的朋友圈Sorted Set(按时间排序)
String userMomentsKey = getUserMomentsKey(moment.getUserId());
jedis.zadd(userMomentsKey, moment.getTimestamp(), moment.getMomentId());
// 如果是好友可见,添加到全局好友可见集合
if (moment.getVisibility() == Moment.Visibility.FRIENDS) {
jedis.sadd("moments:friends", moment.getMomentId());
}
}
}
// 获取用户可见的朋友圈列表(分页)
public List<Moment> getVisibleMoments(String viewerId, int page, int pageSize) {
List<Moment> result = new ArrayList<>();
try (Jedis jedis = RedisUtil.getJedis()) {
// 获取当前用户的好友列表
Set<String> friendIds = friendService.getFriends(viewerId);
// 遍历好友,获取每个好友的最新朋友圈
for (String friendId : friendIds) {
String userMomentsKey = getUserMomentsKey(friendId);
// 获取好友最新的朋友圈ID(按时间倒序)
Set<String> momentIds = jedis.zrevrange(
userMomentsKey,
(page - 1) * pageSize,
page * pageSize - 1
);
// 批量获取朋友圈内容
for (String momentId : momentIds) {
String momentKey = getMomentKey(momentId);
Map<String, String> momentData = jedis.hgetAll(momentKey);
// 转换为Moment对象
Moment moment = mapToMoment(momentId, momentData);
// 检查可见性权限
if (canView(viewerId, moment)) {
result.add(moment);
}
}
}
// 按时间倒序排序(确保全局时间顺序)
result.sort(Comparator.comparingLong(Moment::getTimestamp).reversed());
return result;
}
}
// 判断用户是否有权限查看朋友圈
private boolean canView(String viewerId, Moment moment) {
if (moment.getVisibility() == Moment.Visibility.PUBLIC) {
return true; // 公开内容所有人可见
} else if (moment.getVisibility() == Moment.Visibility.PRIVATE) {
return viewerId.equals(moment.getUserId()); // 仅自己可见
} else { // FRIENDS
return friendService.isFriend(viewerId, moment.getUserId()); // 仅好友可见
}
}
// 将Redis Hash数据转换为Moment对象
private Moment mapToMoment(String momentId, Map<String, String> data) {
Moment moment = new Moment();
moment.setMomentId(momentId);
moment.setUserId(data.get("userId"));
moment.setContent(data.get("content"));
moment.setTimestamp(Long.parseLong(data.get("timestamp")));
moment.setVisibility(Moment.Visibility.valueOf(data.get("visibility")));
return moment;
}
private String getMomentKey(String momentId) {
return "moment:" + momentId;
}
private String getUserMomentsKey(String userId) {
return "user:" + userId + ":moments";
}
}
5. 测试代码示例
// Main.java - 功能测试示例
public class Main {
public static void main(String[] args) {
FriendService friendService = new FriendService();
MomentService momentService = new MomentService();
// 创建用户
String userA = "user1001";
String userB = "user1002";
String userC = "user1003";
// 添加好友关系
friendService.addFriend(userA, userB); // A和B是好友
friendService.addFriend(userA, userC); // A和C是好友
// 用户B发布一条朋友圈(仅好友可见)
Moment momentB = new Moment(
UUID.randomUUID().toString(),
userB,
"今天去爬山了,风景不错!",
System.currentTimeMillis(),
Moment.Visibility.FRIENDS
);
momentService.publishMoment(momentB);
// 用户C发布一条朋友圈(公开)
Moment momentC = new Moment(
UUID.randomUUID().toString(),
userC,
"新做的菜,大家看看怎么样?",
System.currentTimeMillis(),
Moment.Visibility.PUBLIC
);
momentService.publishMoment(momentC);
// 用户A获取可见的朋友圈列表
List<Moment> visibleMoments = momentService.getVisibleMoments(userA, 1, 10);
// 输出结果
System.out.println("用户A可见的朋友圈:");
for (Moment moment : visibleMoments) {
System.out.println("[" + moment.getUserId() + "] " +
moment.getContent() + " (时间: " +
new Date(moment.getTimestamp()) + ")");
}
}
}
核心流程说明
- 好友关系管理
-
- 使用Redis的Set存储每个用户的好友列表(
user:{userId}:friends
) - 添加/删除好友时维护双向关系
- 通过
SISMEMBER
快速判断两个用户是否为好友
- 使用Redis的Set存储每个用户的好友列表(
- 朋友圈发布
-
- 将朋友圈内容存储为Redis的Hash结构
- 使用Sorted Set按时间排序存储用户的朋友圈ID
- 对于"好友可见"的内容,额外添加到全局集合
moments:friends
- 可见性控制
-
- 公开内容:所有用户可见
- 私密内容:仅发布者可见
- 好友可见内容:仅发布者的好友可见
- 查询流程
-
- 获取当前用户的好友列表
- 遍历好友,获取每个好友的最新朋友圈ID
- 批量获取朋友圈内容并检查可见性权限
- 最终按时间排序返回结果
优化建议
- 批量操作
使用Redis Pipeline或MGET
命令减少网络开销:
// 批量获取多个朋友圈内容示例
List<String> momentKeys = momentIds.stream()
.map(id -> "moment:" + id)
.collect(Collectors.toList());
List<String> results = jedis.mget(momentKeys.toArray(new String[0]));
- 缓存热点数据
对于活跃用户的好友列表和朋友圈内容,可以使用本地缓存(如Guava Cache):
LoadingCache<String, Set<String>> friendCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<String, Set<String>>() {
@Override
public Set<String> load(String userId) {
return friendService.getFriends(userId);
}
});
- 异步处理
点赞、评论等非核心操作可以通过消息队列异步处理,避免阻塞主流程。
这个实现充分利用了Redis的集合和排序功能,能够高效地实现朋友圈的好友可见性控制。在实际生产环境中,还需要考虑分布式锁、限流降级、数据持久化等问题。