ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

不用互关也能发私信?Spring Boot实现私信权限策略与幂等设计

不用互关也能发私信?Spring Boot实现私信权限策略与幂等设计 刚开始做社交产品私信功能时很容易把“能不能发消息”当做一个简单的布尔值判断。产品提了一个需求“不用互关也可以发消息”不少后端第一时间会想那直接把关注关系校验去掉不就行了实际上从真实业务看什么都不做直接放开很快会被广告、骚扰和恶意注册打爆。真正要做的是把私信权限从“固定代码”改造成“可配置策略”同时在发送链路上补齐黑名单、频控、幂等和内容安全。这篇文章以“不用互关也可以发消息”为场景从一个最小可运行的 Spring Boot 示例开始讲清楚私信权限判断的顺序、关系链查询方式、幂等设计、频控策略以及接入生产环境前还需要补齐哪些能力。1. 先理解“不用互关也可以发消息”背后的私信权限模型1.1 为什么会出现“不用互关也可以发消息”的需求社交产品里私信天然有骚扰风险。最早的方案是把“关注关系”当作门槛只有互相关注或对方关注了你才能发私信。这样能挡住大量陌生人消息但代价是沟通成本变高。比如一个用户想向另一个用户咨询问题却因为对方没有关注自己而发不出消息只能去公开评论区留言转化链路更长。“不用互关也可以发消息”的价值在于降低连接成本客服通知、创作者联系粉丝、兴趣社群破冰、后台回复都依赖这种能力。但它不是简单把关注校验删掉而是把“关系链判断”换成一套组合权限接收者允许谁发、发送者是否在拉黑名单、单位时间内能发多少条、消息内容是否合规。只有这些条件都满足消息才会进入发送流程。1.2 一次私信发送背后要检查什么从一次发送请求出发私信系统至少需要按顺序处理以下检查点发送者是否已登录账号是否被禁用。接收者是否存在账号是否注销。请求是否重复提交需要幂等处理。发送者和接收者之间是否存在拉黑关系注意要双向判断。接收者配置的私信策略是否允许当前发送者发送。发送频率是否超过阈值包括全局频率、接收者维度频率和内容重复频率。消息内容是否通过合规校验。这七个检查点不是每次都要严格按同一顺序执行。顺序会影响成本和结果。比如幂等检查放在业务校验之前可以提前拦截重复请求避免多次查询关系链黑名单检查放在隐私策略之前是因为被拉黑的人没有资格触发后续判断频控检查放在最后可以避免因为关系链判断失败而白白消耗用户发送额度。1.3 私信权限模型从“固定判断”到“可配置策略”如果把“能否发送”写死在业务代码里比如只有一个if (isFollowed()) { send(); }产品每次调整策略都要改代码、发版后面会很难维护。更好的方式是把权限抽象成策略枚举配置在用户维度。策略枚举允许谁发私信是否需要互关ALL所有人不需要FOLLOWERS关注接收者的用户不需要互关但需要是接收者粉丝MUTUAL互相关注的用户需要NONE关闭私信无法发送上面这个表里ALL和FOLLOWERS都属于“不用互关也可以发消息”的场景区别在于范围限制。ALL连认证关系都不看FOLLOWERS仍然要求发送者是接收者的粉丝。实际产品中很多平台默认的策略不是ALL而是FOLLOWERS或“关注者粉丝”这样既能降低陌生人骚扰又保持了沟通链路。还有人容易混淆“关注关系”和“粉丝关系”。FOLLOWERS策略判断的是“发送者是否关注了接收者”而不是“接收者是否关注了发送者”。如果判定方向写反会出现别人关注了你但你被拦下的奇怪问题。这块在设计表结构时就要用字段语义固定下来。2. 环境准备与项目结构先搭起一套最小可运行私信服务2.1 技术选型与依赖版本最小实现使用 Spring Boot 2.7、MyBatis-Plus 3.5、MySQL 8.0、Redis 6.x。Redis 用于频控和短时间关系缓存MySQL 用于持久化消息、用户隐私设置和拉黑关系。实际项目可以换成任意 Web 框架这里用 Java 生态是为了把校验逻辑写清楚。需要准备的本地环境组件版本参考作用JDK1.8 及以上运行 Spring Boot 服务Maven3.6 及以上依赖管理和构建MySQL5.7 或 8.0持久化业务数据Redis6.x频控计数、缓存curl / Postman任意接口验证pom.xml 核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency依赖不是越新越好。示例里的版本在多数 Spring Boot 2.7 项目里可以直接使用如果原始项目依赖冲突落地前要确认 MyBatis-Plus 和 Spring Boot 的兼容性。2.2 数据库表设计把关系、隐私和消息分开私信功能涉及的数据可以拆成五张表用户表、用户隐私设置表、拉黑关系表、私信消息表、幂等表。不要把隐私策略和用户基本信息放在同一张表里也行但建议单独建表方便后续扩展多个策略字段。CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, nickname varchar(64) NOT NULL, status tinyint NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE user_privacy ( user_id bigint NOT NULL, allow_strategy varchar(20) NOT NULL DEFAULT FOLLOWERS COMMENT ALL/FOLLOWERS/MUTUAL/NONE, updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE user_relation ( id bigint NOT NULL AUTO_INCREMENT, follower_id bigint NOT NULL COMMENT 关注者, followed_id bigint NOT NULL COMMENT 被关注者, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_follow (follower_id, followed_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE user_block ( id bigint NOT NULL AUTO_INCREMENT, blocker_id bigint NOT NULL COMMENT 拉黑发起方, blocked_id bigint NOT NULL COMMENT 被拉黑方, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_block (blocker_id, blocked_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE private_message ( id bigint NOT NULL AUTO_INCREMENT, sender_id bigint NOT NULL, receiver_id bigint NOT NULL, content text NOT NULL, msg_type tinyint NOT NULL DEFAULT 1 COMMENT 1文字 2图片, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_receiver_sender (receiver_id, sender_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE idempotency ( id bigint NOT NULL AUTO_INCREMENT, biz_key varchar(64) NOT NULL COMMENT 全局幂等键, biz_hash varchar(64) NOT NULL COMMENT 请求内容摘要, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_biz_key (biz_key) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;user_privacy.allow_strategy的默认值设为FOLLOWERS比ALL安全。即使某张配置表漏写数据也不会出现所有陌生人都能私信的情况。user_relation里follower_id表示关注者followed_id表示被关注者查询“发送者是否关注接收者”时用follower_id 发送者 AND followed_id 接收者。私信消息表在生产环境中通常不会只有一个 MySQL 表量大后需要分表或者迁移到消息中间件。最小实现先用单表把链路跑通生产环境再考虑冷热分离。2.3 服务端目录结构与基础配置示例项目目录可以按以下方式拆分src/main/java/com/example/im ├── ImApplication.java ├── controller │ └── MessageController.java ├── service │ └── MessageService.java ├── mapper │ ├── UserMapper.java │ ├── UserPrivacyMapper.java │ ├── RelationMapper.java │ ├── BlockMapper.java │ ├── PrivateMessageMapper.java │ └── IdempotencyMapper.java ├── entity │ ├── UserPrivacy.java │ └── PrivateMessage.java └── enums └── PrivateChatStrategy.javaapplication.yml中需要配置 Redis、MySQL 和 MyBatis-Plusserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/im_demo?useUnicodetruecharacterEncodingutf8useSSLfalse username: root password: your_password redis: host: localhost port: 6379 mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deletedmap-underscore-to-camel-case开启后数据库字段created_at可以映射成 Java 属性createdAt避免大量手工映射。私有消息表如果后续要支持撤回需要增加status字段最小示例先不做撤回逻辑。3. 把“不用互关也可以发消息”的判定逻辑实现出来3.1 用枚举统一私信策略私信策略不应该散落在 if-else 里。先定义PrivateChatStrategy枚举为每个策略提供是否要求关注关系的标记package com.example.im.enums; public enum PrivateChatStrategy { ALL(false, false), FOLLOWERS(true, false), MUTUAL(true, true), NONE(false, false); private final boolean requireFollow; private final boolean requireMutual; PrivateChatStrategy(boolean requireFollow, boolean requireMutual) { this.requireFollow requireFollow; this.requireMutual requireMutual; } public boolean isRequireFollow() { return requireFollow; } public boolean isRequireMutual() { return requireMutual; } }判断逻辑是ALL不要求任何关系所以不用互关也能发消息FOLLOWERS要求发送者关注接收者但不需要接收者回关所以仍然属于“不用互关也能发消息”MUTUAL要求双方互相关注NONE表示接收者关闭私信。这里要注意MUTUAL和NONE并不是“不用互关”场景但它们必须和开放策略放在同一套枚举里否则后续扩展配置时还要再写一组映射。3.2 发送私信接口权限校验顺序是关键Controller 层只负责接收参数和返回结果真正的判断放在 ServiceRestController RequestMapping(/api/message) public class MessageController { private final MessageService messageService; public MessageController(MessageService messageService) { this.messageService messageService; } PostMapping(/send) public String send(RequestBody SendMessageRequest request) { return messageService.send( request.getSenderId(), request.getReceiverId(), request.getContent(), request.getIdempotentKey() ); } }生产环境中senderId应该从登录态的 token 或 Session 中解析不能由客户端传入。这里为了最小示例先从请求参数读取。MessageService的核心发送逻辑Service public class MessageService { private final UserMapper userMapper; private final UserPrivacyMapper privacyMapper; private final RelationMapper relationMapper; private final BlockMapper blockMapper; private final PrivateMessageMapper messageMapper; private final IdempotencyMapper idempotencyMapper; private final StringRedisTemplate redisTemplate; // 省略构造方法 public String send(Long senderId, Long receiverId, String content, String idempotentKey) { // 1. 发送者和接收者账号检查 if (senderId null || receiverId null || senderId.equals(receiverId)) { throw new BizException(发送者或接收者不合法); } // 2. 幂等检查防止重复提交 if (!trySaveIdempotency(idempotentKey, senderId, receiverId, content)) { throw new BizException(重复请求请勿重复发送); } // 3. 账号状态和拉黑关系检查 checkBlockRelation(senderId, receiverId); // 4. 查询接收者私信策略 UserPrivacy privacy privacyMapper.selectById(receiverId); PrivateChatStrategy strategy resolveStrategy(privacy); // 5. 按策略判断关系 if (strategy.isRequireFollow()) { boolean followed relationMapper.existsFollow(senderId, receiverId); if (!followed) { throw new BizException(对方未开放陌生人私信请先关注对方); } } if (strategy.isRequireMutual()) { boolean followBack relationMapper.existsFollow(receiverId, senderId); if (!followBack) { throw new BizException(需要互相关注后才能发送私信); } } // 6. 频控检查 checkFrequency(senderId, receiverId); // 7. 保存消息 PrivateMessage message new PrivateMessage(); message.setSenderId(senderId); message.setReceiverId(receiverId); message.setContent(content); messageMapper.insert(message); // 8. 发送事件异步清理/通知 return 发送成功; } }这段代码的顺序很有讲究。幂等检查放在最前面是因为一旦重复请求通过了后续所有业务计算都会浪费而且可能落多条消息。拉黑检查放在隐私策略之前是因为被拉黑是“资格问题”不需要再看隐私策略。频控放在允许发送之后是为了不让关系判断失败的用户消耗限流额度。resolveStrategy需要考虑隐私表数据缺失的情况。如果没有查到接收者的隐私配置不要直接默认ALL应该默认走最安全的策略比如FOLLOWERS或NONE。实际项目里可以用如下方式兜底private PrivateChatStrategy resolveStrategy(UserPrivacy privacy) { if (privacy null || StringUtils.isBlank(privacy.getAllowStrategy())) { return PrivateChatStrategy.FOLLOWERS; } try { return PrivateChatStrategy.valueOf(privacy.getAllowStrategy()); } catch (IllegalArgumentException e) { return PrivateChatStrategy.FOLLOWERS; } }BizException是自定义业务异常实际项目可以配合全局异常处理器返回统一错误码。3.3 黑名单判断要双向拉黑检查不是只查接收者是否拉黑了发送者。如果发送者把接收者拉黑了同样不应该继续发送消息否则会出现“我拉黑了你你还能给我发消息”的异常体验。private void checkBlockRelation(Long senderId, Long receiverId) { boolean blockedByReceiver blockMapper.existsBlock(receiverId, senderId); boolean blockedBySender blockMapper.existsBlock(senderId, receiverId); if (blockedByReceiver || blockedBySender) { throw new BizException(由于拉黑关系无法发送私信); } }existsBlock(blockerId, blockedId)在 Mapper 里对应一条简单 SQLSELECT COUNT(*) FROM user_block WHERE blocker_id #{blockerId} AND blocked_id #{blockedId}拉黑关系表用唯一索引(blocker_id, blocked_id)防止重复数据。做双向判断时把两个方向各自查询一次不要试图用一条IN或OR拼出模糊结果。3.4 幂等键与消息去重客户端在网络抖动时可能会重试用户也可能快速连点两次发送按钮。如果没有幂等同一条消息会落库多次。幂等表只保存业务键和请求哈希第一次插入成功后才继续发送重复的biz_key会被唯一索引挡住。private boolean trySaveIdempotency(String idempotentKey, Long senderId, Long receiverId, String content) { if (StringUtils.isBlank(idempotentKey)) { idempotentKey UUID.randomUUID().toString(); } try { Idempotency record new Idempotency(); record.setBizKey(idempotentKey); record.setBizHash(Hashing.sha256() .hashString(senderId : receiverId : content, StandardCharsets.UTF_8) .toString()); idempotencyMapper.insert(record); return true; } catch (DuplicateKeyException e) { return false; } }一定要把幂等业务的生成规则定义清楚。最稳妥的方案是服务端根据“发送者 接收者 内容哈希 时间窗口”生成客户端也可以传自己的clientMsgId但服务端必须校验同一个幂等键只能对应一条不同的消息内容。如果客户端随便传一个随机串幂等就会失效。实际实现时幂等记录不能无限增长。可以给idempotency表加一个过期时间字段或者定期清理超过 7 天的记录。也可以直接使用 Redis 的SET NX EX做短时间幂等但只依赖 Redis 会出现键过期后重复请求穿透的问题MySQL 唯一索引更适合作为最终一致性保证。4. 跑通最小闭环准备数据和验证接口4.1 准备三个测试用户先插入三个用户和关系数据便于验证不同策略INSERT INTO user (id, nickname) VALUES (1001, 普通用户A), (1002, 创作者B), (1003, 新人C); INSERT INTO user_relation (follower_id, followed_id) VALUES (1001, 1002); INSERT INTO user_privacy (user_id, allow_strategy) VALUES (1002, FOLLOWERS), (1003, ALL);这里 1002 的私信策略是FOLLOWERS1001 关注了 1002所以 1001 可以给 1002 发消息1003 的策略是ALL即使没有人与它互关也可以收到消息。4.2 发送私信接口验证启动 Spring Boot 项目后用 curl 发送一条从 1001 到 1003 的消息curl -X POST http://localhost:8080/api/message/send \ -H Content-Type: application/json \ -d {senderId:1001,receiverId:1003,content:你好来自新人C的消息,idempotentKey:key-001}预期返回发送成功再验证 1001 到 1003 的重复请求curl -X POST http://localhost:8080/api/message/send \ -H Content-Type: application/json \ -d {senderId:1001,receiverId:1003,content:你好来自新人C的消息,idempotentKey:key-001}预期返回重复请求请勿重复发送这说明幂等键生效。如果需要发一条新消息要换新的idempotentKey。4.3 不同私信策略的预期结果用表格整理测试场景方便后续做接口回归测试用例接收者策略发送者与接收者关系预期结果1001 - 1002FOLLOWERS1001 关注了 1002发送成功1003 - 1002FOLLOWERS1003 未关注 1002发送失败提示先关注1001 - 1003ALL无关系发送成功不用互关1001 - 10021001被拉黑FOLLOWERS1002 拉黑 1001发送失败提示拉黑相同 idempotentKey 重复提交任意任意发送失败提示重复ALL策略对应的就是标题里“不用互关也可以发消息”的最直接实现。FOLLOWERS更进一步保留了一层关注门槛但不需要互关。4.4 用 Redis 验证频控生效如果发送接口里使用了如下 Redis 频控private void checkFrequency(Long senderId, Long receiverId) { String key im:frequency:sender: senderId; Long count redisTemplate.opsForValue() .increment(key); if (count 1L) { redisTemplate.expire(key, Duration.ofMinutes(1)); } if (count 5) { throw new BizException(发送太频繁请稍后再试); } }第一次调用后可以在 Redis 中看到键redis-cli 127.0.0.1:6379 GET im:frequency:sender:1001 1连续发送 5 次以上后接口会返回频控异常。这个极简频控只能用来演示思路生产环境需要按时间窗口、接收者维度、内容重复度组合设计否则一个用户给不同人发消息都会共享一个计数体验不好。5. 常见问题排查私信发不出去时从哪查起5.1 提示“对方未开放陌生人私信”这个异常表示接收者策略不是ALL且发送者没有满足关注条件。可以按下面顺序排查查询user_privacy表确认接收者的allow_strategy。查询user_relation确认发送者是否关注接收者。检查代码里是否把FOLLOWERS判断成了“接收者关注发送者”方向写反会导致真正关注了的人也无法发送。SELECT allow_strategy FROM user_privacy WHERE user_id 1002; SELECT COUNT(*) FROM user_relation WHERE follower_id 1003 AND followed_id 1002;第一条 SQL 得到FOLLOWERS第二条 SQL 返回 0说明 1003 没有关注 1002所以被拦截。5.2 提示“发送太频繁请稍后再试”只要 Redis 里频控键存在请求就会被拦截。常见原因是测试时把频控阈值设得过高或过低或者频控 key 的维度不够。检查 Key 的名称和过期时间redis-cli TTL im:frequency:sender:1001如果 TTL 是-1说明 key 没有设置过期时间内存里会一直保留用户永远无法发送。修复办法是在第一次计数时主动设置过期时间或者使用带有过期时间的 Lua 脚本。5.3 同一消息被发送多次核心原因基本集中在幂等幂等键没有传服务端自动生成新键结果每次都被当成第一次请求。幂等键和消息内容没有绑定同一键被不同内容复用。幂等表唯一索引缺失重复插入没有报错。检查接口日志和idempotency表SELECT biz_key, biz_hash, COUNT(*) FROM idempotency GROUP BY biz_key HAVING COUNT(*) 1;如果出现重复biz_key说明唯一索引没建或者插入逻辑没有捕获重复键异常。如果没有任何重复记录但消息重复落库说明幂等检查根本没走需要看日志确认发送流程顺序。5.4 私信问题通用排查链路问题现象优先检查项检查方式处理建议所有用户都发不出私信接收者隐私策略默认值是否安全查询user_privacy默认值确保缺少配置时默认FOLLOWERS或NONE只有部分用户发不出关系表数据是否完整用 SQL 核对关注记录补偿关系数据检查关注回调链路批量账号提示频繁频控 key 是否按账号维度设计查看 Redis key调整计数维度增加接收者维度限制重复点击后消息出现多条幂等键生成和唯一索引查询idempotency表服务端统一生成幂等键建唯一索引拉黑后仍能发送是否只判断了一个方向查询user_block双向判断发送和被发送都要拦截消息内容为空也发送成功接口参数校验缺失看 Controller 是否有Validated增加参数校验和内容长度限制当线上出现“发不出消息”时不要一开始就怀疑 Redis 或数据库。先构造最小测试用例把发送者、接收者、策略、关系四类数据列出来再按权限流程走一遍通常能很快定位到是数据问题还是逻辑问题。6. 从最小实现到生产环境反骚扰、安全与可观测清单6.1 学习环境和生产环境的差异最小实现里为了让流程简单很多环节都是直接查库、单机计数。生产环境的私信系统还要考虑数据量、安全合规和用户体验。维度学习环境/演示项目生产环境关系链查询每次直查 MySQL使用 Redis 缓存关系异步同步关注变更频控单维度 Redis 计数多维限流支持滑动窗口、多级阈值消息落库单表 MySQL分表分库或接入消息队列登录态客户端传 senderId从 token/Session 解析用户身份内容安全不处理敏感词过滤、图片审核、举报机制日志普通日志traceId 串联全链路日志回滚无消息撤回、自动清理异常账号学习过程中可以先把单表链路跑通但心里要清楚真正上线前私信不是“能发出去”就行还要能控得住、查得清、撤得回。6.2 反骚扰设计的最佳实践“不用互关也可以发消息”容易带来两类问题陌生人广告和批量骚扰。建议从以下几方面补强分层频控。不要只限制发送者单日总条数还要限制“发送者 to 单个接收者”的频次以及“接收者每日陌生人私信总数”。比如同一发送者 1 分钟内只能给同一个人发 3 条。重复内容识别。对发送内容做 MinHash 或 SimHash短时间相似内容过多时提示稍后发送。用户反馈入口。接收者可以对陌生私信选择“不喜欢”或“拉黑”连续被拉黑多次后自动触发发送者风险标记。异步审核。文本和图片先落库再进入异步审核队列审核通过后展示风险内容直接标记处理。不要用无上限的ALL策略。即使产品要“不用互关也能发消息”也建议默认使用FOLLOWERS或加一层新用户限制比如新注册 24 小时内只能给互关用户发消息。不要把策略和业务代码耦合在一起。上线后产品可能会把ALL改成FOLLOWERS或者增加“仅允许已实名用户发送”如果策略写在 if-else 里每次调整都发版风险很高。推荐把策略配置放到配置中心或数据库通过枚举和参数组合控制。6.3 发布和排错可复用清单上线前检查清单[ ] 是否所有新用户都有默认隐私策略默认策略是否偏保守。[ ] 拉黑关系是否双向判断拉黑后原有会话是否需要隐藏。[ ] 幂等键是否由服务端统一生成idempotency表是否有唯一索引。[ ] 频控是否覆盖发送者、接收者双维度Redis key 是否设置过期时间。[ ] 接口是否从登录态获取用户身份而不是信任客户端传入的 senderId。[ ] 消息内容是否有长度限制和敏感词校验。[ ] 日志中是否包含 traceId、发送者、接收者、策略明细方便事后排查。[ ] 是否具备消息撤回和举报处理流程。线上排错清单先看异常信息是业务异常还是系统异常。业务异常直接查用户隐私表、关系表、拉黑表确认数据条件。系统异常查日志和 Redis 连接、数据库连接。频控问题用 Redis key 确认计数和过期时间。消息重复问题优先查幂等表而不是先查消息表。回到“不用互关也可以发消息”它不是一个简单的开关而是一整套权限策略的组合。真正有价值的做法是把关注关系从硬编码中解耦通过用户维度的策略配置、双向拉黑、幂等、频控和内容安全实现“开放却不失控”。在这个最小示例基础上下一步可以继续扩展多端会话、已读回执、消息撤回、会话列表未读数以及把策略判断抽成独立的规则引擎。对于刚接触社交产品后端的开发者先把权限判断顺序和幂等链路跑通比直接追求高并发更值得投入时间。
返回列表