通过技术派发文章的时候,发文章会先通过审核,只有通过审核在会在网站上进行展示。是不是所有的作者都要经过审核呢?
当然不是,在这里做了一个白名单,在白名单中的用户发文之后是不需要进入审核的,可以直接上线。
问题的产生的疑问?
为什么要进行审核?
如后台文章列表
文章参差不齐,有的值得点赞、收藏,有的可能有违规的内容,还有的文章不符合网站的主题要求等,若不经过审核直接上线,会导致网站的文章质量下滑。作为一个交流的学习型的网站社区,审核是必不可少的。
白名单如何实现的?
对作者添加白名单有很多可选方案
配置文件写死(相当于硬编码方式)
- 优点:简单
- 缺点:不灵活,每次改动都需要改代码发版,基本上不适用实际生产项目
数据库配置一个白名单
- 优点:灵活,实用性强
- 缺点:实现有点重、难
基于redis的set实现白名单
- 优点:实现简单,轻量
- 缺点:依赖于redis
本项目的白名单就是基于redis的set来实现的,下面来看一下详细实现策略。
redis实现白名单
对于使用redis来实现白名单,最容易想到的数据结构就是set。
set基本知识点
首先熟悉一下redis中的set的相关命令操作
添加
#向集合中添加多个成员
sadd key val1 val2
集合数量
scard key
判断集合是否包含元素val
sismember key val
返回1表示在里面 0表示不再里面
返回集合所有成员
smembers key
随机移除集合中的一个元素
spop key
随机返回集合中的几个元素
srandmember key count
删除集合中成员
srem key val
简单演示一下
集合除上述基本操作之外,还支持多集合之间的相互操作
差集
返回第一个集合以其他集合之间的差异
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...
演示一下
RedisTemplate操作知识点
Spring项目中,借助RedisTemplate操作set也很简单
新增
/**
新增一个 sadd
*/
public void add(String key , String value){
redisTemplate.opsForSet().add(key ,value);
}
删除
/**
* 删除集合中的值 srem
* @param key
* @param value
*/
public void remove(String key , String value){
redisTemplate.opsForSet().remove(key ,value);
}
判断是否存在
/**
* 判断是否包含 sismember
* @param key
* @param value
*/
public void contains(String key , String value){
redisTemplate.opsForSet().isMember(key ,value);
}
获取所有的value、
/**
* 获取集合中所有的值 smembers
* @param key
* @return
*/
public Set<String> values(String key){
return redisTemplate.opsForSet().members(key);
}
集合运算
/**
* 返回多个集合的并集
* @param key1
* @param key2
* @return
*/
public Set<String> union(String key1 ,String key2){
return redisTemplate.opsForSet().union(key1 ,key2);
}
/**
* 返回多个集合的交集 sinter
* @param key1
* @param key2
* @return
*/
public Set<String> intersect(String key1 ,String key2){
return redisTemplate.opsForSet().intersect(key1 ,key2);
}
/**
* 返回在key1 中而不在key2中的集合 sdiff
* @param key1
* @param key2
* @return
*/
public Set<String> difference(String key1 ,String key2){
return redisTemplate.opsForSet().difference(key1 ,key2);
}
白名单使用实例
白名单的相关业务封装在ArticleWhileListService中
/**
* 判断作者是否再文章发布的白名单中;
* 这个白名单主要是用于控制作者发文章之后是否需要进行审核
*
* @param authorId
* @return
*/
boolean authorInArticleWhiteList(Long authorId);
/**
* 获取所有的白名单用户
*
* @return
*/
List<BaseUserInfoDTO> queryAllArticleWhiteListAuthors();
/**
* 将用户添加到白名单中
*
* @param userId
*/
void addAuthor2ArticleWhitList(Long userId);
/**
* 从白名单中移除用户
*
* @param userId
*/
void removeAuthorFromArticleWhiteList(Long userId);
核心主要使用就是第一个,判断作者是否是白名单用户;剩下的三个用于管理员后台维护白名单使用,具体实现如下
/**
* 实用 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);
}
对于set的操作我们进行了统一的封装,与上面介绍到的知识点差不多,不过这里选择另外一种实现策略。
核心的封装的几个公共方法。
/**
* 判断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));
}
});
}
该方法接受两个参数:key表示Redis集合的键名,value表示要判断的值。它使用了一个泛型类型T来表示值的类型,以便可以处理不同类型的值。
方法内部使用了template.execute方法来执行一个RedisCallback匿名内部类的实例。这个回调类实现了doInRedis方法,该方法接受一个RedisConnection对象作为参数,并调用其sIsMember方法来判断值是否存在于Redis集合中。
keyBytes(key)和valBytes(value)是两个辅助方法,用于将键名和值转换为字节数组表示形式,以便与Redis进行通信。
最后,如果值存在于集合中,则返回true,否则返回false。
/**
* 获取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());
}
});
}
该方法接受两个参数:key表示Redis集合的键名,clz表示要转换的目标类型。它使用了一个泛型类型T来表示目标类型,以便可以处理不同类型的值。
方法内部使用了template.execute方法来执行一个RedisCallback匿名内部类的实例。这个回调类实现了doInRedis方法,该方法接受一个RedisConnection对象作为参数,并调用其sMembers方法来获取集合中的所有元素。
keyBytes(key)是一个辅助方法,用于将键名转换为字节数组表示形式,以便与Redis进行通信。
如果集合为空,则返回一个空的Set对象;否则,将集合中的每个元素转换为目标类型,并收集到一个新的Set对象中。最后,返回包含集合所有元素的Set对象。
/**
* 往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;
}
该方法接受两个参数:key表示Redis集合的键名,val表示要添加的值。它使用了一个泛型类型T来表示值的类型,以便可以处理不同类型的值。
方法内部使用了template.execute方法来执行一个RedisCallback匿名内部类的实例。这个回调类实现了doInRedis方法,该方法接受一个RedisConnection对象作为参数,并调用其sAdd方法将值添加到集合中。
keyBytes(key)和valBytes(val)是两个辅助方法,用于将键名和值转换为字节数组表示形式,以便与Redis进行通信。
最后,根据sAdd方法的返回值判断是否成功添加了值。如果返回值大于0,则表示成功添加,返回true;否则返回false。
/**
* 移除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;
}
});
}
下面我们再看一下具体的使用场景。
对于非白名单的用户,若操作的是上线的文章,则需要进入审核
发布文章
以上是使用白名单的场景,当然对于管理员来说,还有操作白名单的入口,统一收拢在
AuthorWhiteListController
小结
核心思考就是基于白名单场景,使用redis中的set具体使用。