在技術派社區中,為了保證文章的質量和社區的良性發展,所有發布的文章都需要經過審核。然而,并非所有作者的文章都需要審核,我們通過白名單機制來優化這一流程。本文將詳細介紹技術派中白名單的實現方式,以及如何利用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());