【技术派后端篇】技术派中的白名单机制:基于Redis的Set实现

在技术派社区中,为了保证文章的质量和社区的良性发展,所有发布的文章都需要经过审核。然而,并非所有作者的文章都需要审核,我们通过白名单机制来优化这一流程。本文将详细介绍技术派中白名单的实现方式,以及如何利用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...
  • 返回所有给定集合的差值,并存储在destinationsdiffstore 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。以下是一些常用的操作示例:

  • 新增成员
java 复制代码
public void add(String key, String value) {
    redisTemplate.opsForSet().add(key, value);
}
  • 删除成员
java 复制代码
public void remove(String key, String value) {
    redisTemplate.opsForSet().remove(key, value);
}
  • 判断成员是否存在
java 复制代码
public void contains(String key, String value) {
    redisTemplate.opsForSet().isMember(key, value);
}
  • 获取所有成员
java 复制代码
public Set<String> values(String key) {
    return redisTemplate.opsForSet().members(key);
}
  • 集合运算
java 复制代码
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);

实现代码如下:

java 复制代码
@Service
public class AuthorWhiteListServiceImpl implements AuthorWhiteListService {
    /**
     * 实用 redis - set 来存储允许直接发文章的白名单
     */
    private static final String ARTICLE_WHITE_LIST = "auth_article_white_list";

    @Autowired
    private UserService userService;

    @Override
    public boolean authorInArticleWhiteList(Long authorId) {
        return RedisClient.sIsMember(ARTICLE_WHITE_LIST, authorId);
    }

    /**
     * 获取所有的白名单用户
     *
     * @return
     */
    @Override
    public 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;
    }

    @Override
    public void addAuthor2ArticleWhitList(Long userId) {
        RedisClient.sPut(ARTICLE_WHITE_LIST, userId);
    }

    @Override
    public void removeAuthorFromArticleWhiteList(Long userId) {
        RedisClient.sDel(ARTICLE_WHITE_LIST, userId);
    }
}

核心封装的几个公共方法,位于com.github.paicoding.forum.core.cache.RedisClient#sIsMember

java 复制代码
/**
 * 判断value是否再set中
 *
 * @param key
 * @param value
 * @return
 */
public static <T> Boolean sIsMember(String key, T value) {
    return template.execute(new RedisCallback<Boolean>() {
        @Override
        public 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>>() {
        @Override
        public 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>() {
        @Override
        public 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>() {
        @Override
        public 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,以下是一些关键代码片段:

  • 判断是否需要审核
java 复制代码
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());
}
  • 发布文章
java 复制代码
private Long insertArticle(ArticleDO article, String content, Set<Long> tags) {
    if (needToReview(article)) {
        article.setStatus(PushStatusEnum.REVIEW.getCode());
    }
    // 保存文章、内容和标签
    // ...
}
  • 更新文章
java 复制代码
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来管理白名单用户:

java 复制代码
@RestController
@Api(value = "发布文章作者白名单管理控制器", tags = "作者白名单")
@Permission(role = UserRole.ADMIN)
@RequestMapping(path = {"api/admin/author/whitelist"})
public class AuthorWhiteListController {
    @Autowired
    private 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 参考链接

  1. 技术派Redis实现作者白名单
  2. 项目仓库(GitHub)
  3. 项目仓库(码云)

7 附录:Redis 五种数据结构的应用场景

Redis 提供了五种基本数据结构:字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。每种数据结构都有其独特的特性和适用场景。以下是每种数据结构的应用场景详解:

7.1 字符串(String)

应用场景:

  • 缓存数据:将经常访问的数据缓存在 Redis 中,以加快访问速度。例如,缓存用户信息、商品信息等。
  • 计数器 :记录某个事件发生的次数,例如网站的访问次数、文章的点赞次数等。可以使用 INCRDECR 命令来实现。
  • 分布式锁 :使用字符串的 SETNX 命令来实现分布式锁。SETNX 命令在键不存在时设置键值,可以用来实现互斥锁。

示例代码:

java 复制代码
// 缓存数据
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)

应用场景:

  • 存储对象属性:将对象的属性存储在哈希中,方便对属性进行读写操作。例如,存储用户信息、商品信息等。
  • 缓存对象:将对象序列化后存储在哈希中,以便快速获取和更新对象。
  • 记录用户信息:存储用户的详细信息,如用户名、年龄、性别等。

示例代码:

java 复制代码
// 存储对象属性
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)

应用场景:

  • 消息队列:用于实现消息队列,将生产者产生的消息存储在列表中,消费者从列表中获取消息进行处理。
  • 最新动态:存储用户的最新动态或消息,如微博的用户动态、新闻网站的最新消息等。
  • 实时排行榜:用于存储用户的分数或权重,并根据分数进行排序,实现实时排行榜功能。

示例代码:

java 复制代码
// 消息队列
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)

应用场景:

  • 好友关系:存储用户的好友关系,利用集合的交集、并集、差集等操作来实现好友关系的管理。
  • 标签管理:将对象关联的标签存储在集合中,方便进行标签的添加、删除和检索。
  • 唯一值集合:用于存储唯一值,如去重、统计等场景。

示例代码:

java 复制代码
// 好友关系
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)

应用场景:

  • 排行榜:存储用户的分数,并根据分数进行排序,实现排行榜功能。例如,游戏中的积分排行榜、电商网站的销量排行榜等。
  • 实时热门数据:存储数据的热度值,并根据热度值进行排序,用于实时热门数据的展示。例如,新闻网站的热门新闻、社交媒体的热门话题等。
  • 计划任务:存储定时任务的执行时间,并根据时间戳进行排序,用于实现计划任务的调度。

示例代码:

java 复制代码
// 排行榜
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());
相关推荐
Kx…………20 分钟前
Day2—3:前端项目uniapp壁纸实战
前端·css·学习·uni-app·html
培根芝士2 小时前
Electron打包支持多语言
前端·javascript·electron
mr_cmx2 小时前
Nodejs数据库单一连接模式和连接池模式的概述及写法
前端·数据库·node.js
东部欧安时3 小时前
研一自救指南 - 07. CSS面向面试学习
前端·css
涵信3 小时前
第十二节:原理深挖-React Fiber架构核心思想
前端·react.js·架构
ohMyGod_1233 小时前
React-useRef
前端·javascript·react.js
每一天,每一步3 小时前
AI语音助手 React 组件使用js-audio-recorder实现,将获取到的语音转成base64发送给后端,后端接口返回文本内容
前端·javascript·react.js
上趣工作室3 小时前
vue3专题1------父组件中更改子组件的属性
前端·javascript·vue.js
冯诺一没有曼3 小时前
无线网络入侵检测系统实战 | 基于React+Python的可视化安全平台开发详解
前端·安全·react.js
getapi3 小时前
flutter app实现分辨率自适应的图片资源加载
前端·javascript·flutter