在技术派社区中,为了保证文章的质量和社区的良性发展,所有发布的文章都需要经过审核。然而,并非所有作者的文章都需要审核,我们通过白名单机制来优化这一流程。本文将详细介绍技术派中白名单的实现方式,以及如何利用Redis的Set数据结构来管理白名单。
1 为什么要审核?
虽然大部分作者发布的文章都是高质量的,但也有一些作者只是为了体验发文流程而发布测试内容。如果不经过审核直接上线,可能会导致社区文章质量的下降。因此,审核机制是保证社区文章质量的重要手段。
2 白名单的实现方案
在技术派中,我们为部分作者设置了白名单,这些作者发布的文章无需审核即可直接上线。白名单的实现有多种方案,我们选择了基于Redis的Set数据结构来实现。以下是几种可选的方案及其优缺点:
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
配置文件写死(硬编码方式) | 简单 | 不灵活,每次改动需改代码发版,不适用于实际生产项目 | 对配置变动需求极低的简单测试场景 |
数据库配置白名单表 | 灵活,适用性强 | 实现有点重 | 生产环境中对配置灵活性要求高,且能接受一定实现复杂度的场景 |
基于redis的set实现白名单 | 实现简单,轻量 | 依赖redis | 对性能要求较高、且项目中已使用redis,对配置管理有一定灵活性需求的场景 |
3 技术派中的白名单实现策略
技术派中的白名单就是基于Redis的Set来实现的。以下是详细的实现策略:
3.1 Redis Set的基本操作
以下是Redis Set的一些基本操作命令:
- 添加成员:
SADD key val1 val2
- 获取集合成员数量:
SCARD key
- 判断成员是否存在:
SISMEMBER key val
,返回1表示存在,0表示不存在 - 获取所有成员:
SMEMBERS key
- 随机移除成员:
SPOP key
- 随机返回成员:
SRANDMEMBER key count
- 删除成员:
SREM key val
此外,Set还支持多个集合之间的操作,如求差集、交集、并集等。
- 返回第一个集合与其他集合之间的差异:
sdiff key1 key2 key3...
- 返回所有给定集合的差值,并存储在destination:
sdiffstore destination key1 key2 key3...
- 返回给定集合的交集:
sinter key1 key2
- 返回给定集合的交集,并存储在destination集合中 :
sinterstore destination key1 key2...
- 返回所有给定集合的并集:
sunion key1 key2...
- 返回所有给定集合的并集,并存储在destination集合中:
sunionstore destination key1 key2...
3.2 Spring项目中使用RedisTemplate操作Set
在Spring项目中,我们可以使用RedisTemplate
来操作Redis的Set。以下是一些常用的操作示例:
- 新增成员:
public void add(String key, String value) {redisTemplate.opsForSet().add(key, value);
}
- 删除成员:
public void remove(String key, String value) {redisTemplate.opsForSet().remove(key, value);
}
- 判断成员是否存在:
public void contains(String key, String value) {redisTemplate.opsForSet().isMember(key, value);
}
- 获取所有成员:
public Set<String> values(String key) {return redisTemplate.opsForSet().members(key);
}
- 集合运算:
public Set<String> union(String key1, String key2) {return redisTemplate.opsForSet().union(key1, key2);
}public Set<String> intersect(String key1, String key2) {return redisTemplate.opsForSet().intersect(key1, key2);
}public Set<String> diff(String key1, String key2) {return redisTemplate.opsForSet().difference(key1, key2);
}
3.3 白名单的使用实例
在技术派中,白名单的相关业务逻辑封装在com.github.paicoding.forum.service.user.service.AuthorWhiteListService
中。以下是一些核心方法:
-
判断作者是否在白名单中:
boolean authorInArticleWhiteList(Long authorId);
-
获取所有白名单用户:
List<BaseUserInfoDTO> queryAllArticleWhiteListAuthors();
-
添加用户到白名单:
void addAuthor2ArticleWhitList(Long userId);
-
从白名单中移除用户:
void removeAuthorFromArticelWhiteList(Long userId);
实现代码如下:
@Service
public class AuthorWhiteListServiceImpl implements AuthorWhiteListService {/*** 实用 redis - set 来存储允许直接发文章的白名单*/private static final String ARTICLE_WHITE_LIST = "auth_article_white_list";@Autowiredprivate UserService userService;@Overridepublic boolean authorInArticleWhiteList(Long authorId) {return RedisClient.sIsMember(ARTICLE_WHITE_LIST, authorId);}/*** 获取所有的白名单用户** @return*/@Overridepublic List<BaseUserInfoDTO> queryAllArticleWhiteListAuthors() {Set<Long> users = RedisClient.sGetAll(ARTICLE_WHITE_LIST, Long.class);if (CollectionUtils.isEmpty(users)) {return Collections.emptyList();}List<BaseUserInfoDTO> userInfos = userService.batchQueryBasicUserInfo(users);return userInfos;}@Overridepublic void addAuthor2ArticleWhitList(Long userId) {RedisClient.sPut(ARTICLE_WHITE_LIST, userId);}@Overridepublic void removeAuthorFromArticleWhiteList(Long userId) {RedisClient.sDel(ARTICLE_WHITE_LIST, userId);}
}
核心封装的几个公共方法,位于com.github.paicoding.forum.core.cache.RedisClient#sIsMember
处
/*** 判断value是否再set中** @param key* @param value* @return*/
public static <T> Boolean sIsMember(String key, T value) {return template.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {return connection.sIsMember(keyBytes(key), valBytes(value));}});
}/*** 获取set中的所有内容** @param key* @param clz* @param <T>* @return*/
public static <T> Set<T> sGetAll(String key, Class<T> clz) {return template.execute(new RedisCallback<Set<T>>() {@Overridepublic Set<T> doInRedis(RedisConnection connection) throws DataAccessException {Set<byte[]> set = connection.sMembers(keyBytes(key));if (CollectionUtils.isEmpty(set)) {return Collections.emptySet();}return set.stream().map(s -> toObj(s, clz)).collect(Collectors.toSet());}});
}/*** 往set中添加内容** @param key* @param val* @param <T>* @return*/
public static <T> boolean sPut(String key, T val) {return template.execute(new RedisCallback<Long>() {@Overridepublic Long doInRedis(RedisConnection connection) throws DataAccessException {return connection.sAdd(keyBytes(key), valBytes(val));}}) > 0;
}/*** 移除set中的内容** @param key* @param val* @param <T>*/
public static <T> void sDel(String key, T val) {template.execute(new RedisCallback<Void>() {@Overridepublic Void doInRedis(RedisConnection connection) throws DataAccessException {connection.sRem(keyBytes(key), valBytes(val));return null;}});
}
3.4 白名单的应用场景
在文章发布的核心服务中,我们通过白名单机制来决定文章是否需要审核。代码位于com.github.paicoding.forum.service.article.service.impl.ArticleWriteServiceImpl
,以下是一些关键代码片段:
- 判断是否需要审核:
private boolean needToReview(ArticleDO article) {BaseUserInfoDTO user = ReqInfoContext.getReqInfo().getUser();if (user.getRole() != null && user.getRole().equalsIgnoreCase(UserRole.ADMIN.name())) {return false;}return article.getStatus() == PushStatusEnum.ONLINE.getCode() && !articleWhiteListService.authorInArticleWhiteList(article.getUserId());
}
- 发布文章:
private Long insertArticle(ArticleDO article, String content, Set<Long> tags) {if (needToReview(article)) {article.setStatus(PushStatusEnum.REVIEW.getCode());}// 保存文章、内容和标签// ...
}
- 更新文章:
private Long updateArticle(ArticleDO article, String content, Set<Long> tags) {boolean review = article.getStatus().equals(PushStatusEnum.REVIEW.getCode());if (needToReview(article)) {article.setStatus(PushStatusEnum.REVIEW.getCode());}// 更新文章、内容和标签// ...
}
3.5 管理员操作白名单
管理员可以通过com.github.paicoding.forum.web.admin.rest.AuthorWhiteListController
来管理白名单用户:
@RestController
@Api(value = "发布文章作者白名单管理控制器", tags = "作者白名单")
@Permission(role = UserRole.ADMIN)
@RequestMapping(path = {"api/admin/author/whitelist"})
public class AuthorWhiteListController {@Autowiredprivate AuthorWhiteListService articleWhiteListService;@GetMapping(path = "get")@ApiOperation(value = "白名单列表", notes = "返回作者白名单列表")public ResVo<List<BaseUserInfoDTO>> whiteList() {return ResVo.ok(articleWhiteListService.queryAllArticleWhiteListAuthors());}@GetMapping(path = "add")@ApiOperation(value = "添加白名单", notes = "将指定作者加入作者白名单列表")@ApiImplicitParam(name = "authorId", value = "传入需要添加白名单的作者UserId", required = true, allowEmptyValue = false, example = "1")public ResVo<Boolean> addAuthor(@RequestParam("authorId") Long authorId) {articleWhiteListService.addAuthor2ArticleWhitList(authorId);return ResVo.ok(true);}@GetMapping(path = "remove")@ApiOperation(value = "删除白名单", notes = "将作者从白名单列表")@ApiImplicitParam(name = "authorId", value = "传入需要删除白名单的作者UserId", required = true, allowEmptyValue = false, example = "1")public ResVo<Boolean> rmAuthor(@RequestParam("authorId") Long authorId) {articleWhiteListService.removeAuthorFromArticleWhiteList(authorId);return ResVo.ok(true);}
}
4 总结
本文介绍了技术派中白名单机制的实现,重点讲解了如何利用Redis的Set数据结构来管理白名单用户。通过白名单机制,我们能够有效减少不必要的审核流程,提升用户体验。同时,本文也展示了如何在Spring项目中使用RedisTemplate
来操作Redis的Set,希望对大家有所帮助。
Redis的五种基本数据结构(String、List、Set、ZSet、Hash)是每个开发者都应该掌握的知识点。然而,仅仅了解这些数据结构是不够的,更重要的是能够结合实际场景来选择合适的数据结构,这样才能真正发挥Redis的优势。
5 思维导图
6 参考链接
- 技术派Redis实现作者白名单
- 项目仓库(GitHub)
- 项目仓库(码云)
7 附录:Redis 五种数据结构的应用场景
Redis 提供了五种基本数据结构:字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。每种数据结构都有其独特的特性和适用场景。以下是每种数据结构的应用场景详解:
7.1 字符串(String)
应用场景:
- 缓存数据:将经常访问的数据缓存在 Redis 中,以加快访问速度。例如,缓存用户信息、商品信息等。
- 计数器:记录某个事件发生的次数,例如网站的访问次数、文章的点赞次数等。可以使用
INCR
和DECR
命令来实现。 - 分布式锁:使用字符串的
SETNX
命令来实现分布式锁。SETNX
命令在键不存在时设置键值,可以用来实现互斥锁。
示例代码:
// 缓存数据
redisTemplate.opsForValue().set("user:1", "John Doe");
String user = redisTemplate.opsForValue().get("user:1");// 计数器
redisTemplate.opsForValue().increment("page:view:count");
Long viewCount = redisTemplate.opsForValue().get("page:view:count");// 分布式锁
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent("lock:key", "lockValue");
if (lockAcquired) {try {// 执行业务逻辑} finally {redisTemplate.delete("lock:key");}
}
7.2 哈希(Hash)
应用场景:
- 存储对象属性:将对象的属性存储在哈希中,方便对属性进行读写操作。例如,存储用户信息、商品信息等。
- 缓存对象:将对象序列化后存储在哈希中,以便快速获取和更新对象。
- 记录用户信息:存储用户的详细信息,如用户名、年龄、性别等。
示例代码:
// 存储对象属性
redisTemplate.opsForHash().put("user:1", "name", "John Doe");
redisTemplate.opsForHash().put("user:1", "age", 30);
String name = (String) redisTemplate.opsForHash().get("user:1", "name");
Integer age = (Integer) redisTemplate.opsForHash().get("user:1", "age");// 缓存对象
User user = new User("John Doe", 30);
redisTemplate.opsForHash().putAll("user:1", new ObjectMapper().convertValue(user, Map.class));
User cachedUser = new ObjectMapper().convertValue(redisTemplate.opsForHash().entries("user:1"), User.class);
7.3 列表(List)
应用场景:
- 消息队列:用于实现消息队列,将生产者产生的消息存储在列表中,消费者从列表中获取消息进行处理。
- 最新动态:存储用户的最新动态或消息,如微博的用户动态、新闻网站的最新消息等。
- 实时排行榜:用于存储用户的分数或权重,并根据分数进行排序,实现实时排行榜功能。
示例代码:
// 消息队列
redisTemplate.opsForList().rightPush("message:queue", "message1");
String message = redisTemplate.opsForList().leftPop("message:queue");// 最新动态
redisTemplate.opsForList().rightPush("user:1:timeline", "动态1");
List<String> timeline = redisTemplate.opsForList().range("user:1:timeline", 0, -1);// 实时排行榜
redisTemplate.opsForList().rightPush("leaderboard", "user:1");
redisTemplate.opsForList().rightPush("leaderboard", "user:2");
List<String> leaderboard = redisTemplate.opsForList().range("leaderboard", 0, -1);
7.4 集合(Set)
应用场景:
- 好友关系:存储用户的好友关系,利用集合的交集、并集、差集等操作来实现好友关系的管理。
- 标签管理:将对象关联的标签存储在集合中,方便进行标签的添加、删除和检索。
- 唯一值集合:用于存储唯一值,如去重、统计等场景。
示例代码:
// 好友关系
redisTemplate.opsForSet().add("user:1:friends", "user:2", "user:3");
Set<String> friends = redisTemplate.opsForSet().members("user:1:friends");// 标签管理
redisTemplate.opsForSet().add("article:1:tags", "技术", "Redis");
Set<String> tags = redisTemplate.opsForSet().members("article:1:tags");// 唯一值集合
redisTemplate.opsForSet().add("unique:values", "value1", "value2");
Set<String> uniqueValues = redisTemplate.opsForSet().members("unique:values");
7.5 有序集合(Sorted Set)
应用场景:
- 排行榜:存储用户的分数,并根据分数进行排序,实现排行榜功能。例如,游戏中的积分排行榜、电商网站的销量排行榜等。
- 实时热门数据:存储数据的热度值,并根据热度值进行排序,用于实时热门数据的展示。例如,新闻网站的热门新闻、社交媒体的热门话题等。
- 计划任务:存储定时任务的执行时间,并根据时间戳进行排序,用于实现计划任务的调度。
示例代码:
// 排行榜
redisTemplate.opsForZSet().add("leaderboard", "user:1", 100);
redisTemplate.opsForZSet().add("leaderboard", "user:2", 200);
Set<String> topUsers = redisTemplate.opsForZSet().range("leaderboard", 0, 9);// 实时热门数据
redisTemplate.opsForZSet().add("hot:news", "新闻1", 10);
redisTemplate.opsForZSet().add("hot:news", "新闻2", 20);
Set<String> hotNews = redisTemplate.opsForZSet().range("hot:news", 0, -1);// 计划任务
redisTemplate.opsForZSet().add("schedule:tasks", "task1", System.currentTimeMillis() + 60000);
Set<String> tasks = redisTemplate.opsForZSet().rangeByScore("schedule:tasks", 0, System.currentTimeMillis());