基于责任链模式构建可扩展的微信群发消息风控过滤器(Java实现)
1. 风控场景与设计目标
在企业级微信群发系统中,需对每条待发送消息进行多维度风险检测,包括敏感词、频率限制、内容合规性、接收人白名单等。若采用硬编码 if-else 逻辑,将导致代码耦合度高、难以维护。责任链模式(Chain of Responsibility)允许将多个风控处理器串联,每个处理器独立决策是否放行或拦截,具备高内聚、低耦合、易扩展的特性。
2. 核心接口定义
首先定义风控上下文与处理器接口:
java
package wlkankan.cn.risk;
public class MessageContext {
private String content;
private String senderId;
private String[] receiverIds;
private boolean blocked = false;
private String blockReason;
// getters and setters
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getSenderId() { return senderId; }
public void setSenderId(String senderId) { this.senderId = senderId; }
public String[] getReceiverIds() { return receiverIds; }
public void setReceiverIds(String[] receiverIds) { this.receiverIds = receiverIds; }
public boolean isBlocked() { return blocked; }
public void setBlocked(boolean blocked) { this.blocked = blocked; }
public String getBlockReason() { return blockReason; }
public void setBlockReason(String reason) { this.blockReason = reason; }
}
public abstract class RiskFilter {
protected RiskFilter next;
public void setNext(RiskFilter next) {
this.next = next;
}
public abstract void doFilter(MessageContext ctx);
}

3. 敏感词过滤器实现
使用AC自动机加载敏感词库,高效匹配:
java
package wlkankan.cn.risk.filter;
import wlkankan.cn.risk.MessageContext;
import wlkankan.cn.risk.RiskFilter;
import wlkankan.cn.util.AhoCorasickDoubleArrayTrie;
import java.util.Map;
import java.util.Set;
public class SensitiveWordFilter extends RiskFilter {
private final AhoCorasickDoubleArrayTrie<String> acdat;
public SensitiveWordFilter(Set<String> words) {
this.acdat = new AhoCorasickDoubleArrayTrie<>();
acdat.build(words);
}
@Override
public void doFilter(MessageContext ctx) {
if (ctx.isBlocked()) return;
String content = ctx.getContent();
Map<Integer, String> matches = acdat.parseText(content);
if (!matches.isEmpty()) {
ctx.setBlocked(true);
ctx.setBlockReason("包含敏感词: " + matches.values().iterator().next());
return;
}
if (next != null) {
next.doFilter(ctx);
}
}
}
4. 发送频率限流过滤器
基于令牌桶算法控制单位时间发送次数:
java
package wlkankan.cn.risk.filter;
import com.google.common.util.concurrent.RateLimiter;
import wlkankan.cn.risk.MessageContext;
import wlkankan.cn.risk.RiskFilter;
import java.util.concurrent.ConcurrentHashMap;
public class RateLimitFilter extends RiskFilter {
private final ConcurrentHashMap<String, RateLimiter> limiters = new ConcurrentHashMap<>();
private final double permitsPerSecond;
public RateLimitFilter(double permitsPerSecond) {
this.permitsPerSecond = permitsPerSecond;
}
@Override
public void doFilter(MessageContext ctx) {
if (ctx.isBlocked()) return;
String sender = ctx.getSenderId();
RateLimiter limiter = limiters.computeIfAbsent(sender,
k -> RateLimiter.create(permitsPerSecond));
if (!limiter.tryAcquire()) {
ctx.setBlocked(true);
ctx.setBlockReason("发送频率超限");
return;
}
if (next != null) {
next.doFilter(ctx);
}
}
}
5. 接收人白名单过滤器
确保仅向授权用户发送:
java
package wlkankan.cn.risk.filter;
import wlkankan.cn.risk.MessageContext;
import wlkankan.cn.risk.RiskFilter;
import java.util.Set;
public class WhitelistFilter extends RiskFilter {
private final Set<String> whitelist;
public WhitelistFilter(Set<String> whitelist) {
this.whitelist = whitelist;
}
@Override
public void doFilter(MessageContext ctx) {
if (ctx.isBlocked()) return;
for (String receiver : ctx.getReceiverIds()) {
if (!whitelist.contains(receiver)) {
ctx.setBlocked(true);
ctx.setBlockReason("接收人不在白名单: " + receiver);
return;
}
}
if (next != null) {
next.doFilter(ctx);
}
}
}
6. 链构建与调用入口
提供工厂类动态组装过滤器链:
java
package wlkankan.cn.risk;
import wlkankan.cn.risk.filter.RateLimitFilter;
import wlkankan.cn.risk.filter.SensitiveWordFilter;
import wlkankan.cn.risk.filter.WhitelistFilter;
import java.util.Set;
public class RiskFilterChainBuilder {
public static RiskFilter buildChain(Set<String> sensitiveWords,
Set<String> whitelist,
double rateLimit) {
RiskFilter wordFilter = new SensitiveWordFilter(sensitiveWords);
RiskFilter rateFilter = new RateLimitFilter(rateLimit);
RiskFilter whiteFilter = new WhitelistFilter(whitelist);
wordFilter.setNext(rateFilter);
rateFilter.setNext(whiteFilter);
return wordFilter;
}
}
业务层调用示例:
java
package wlkankan.cn.service;
import wlkankan.cn.risk.MessageContext;
import wlkankan.cn.risk.RiskFilter;
import wlkankan.cn.risk.RiskFilterChainBuilder;
public class MassMessageService {
private final RiskFilter filterChain;
public MassMessageService() {
Set<String> words = loadSensitiveWords(); // 从配置中心加载
Set<String> whitelist = loadWhitelist();
this.filterChain = RiskFilterChainBuilder.buildChain(words, whitelist, 5.0);
}
public boolean canSend(String content, String sender, String[] receivers) {
MessageContext ctx = new MessageContext();
ctx.setContent(content);
ctx.setSenderId(sender);
ctx.setReceiverIds(receivers);
filterChain.doFilter(ctx);
return !ctx.isBlocked();
}
private Set<String> loadSensitiveWords() { /* ... */ return Set.of(); }
private Set<String> loadWhitelist() { /* ... */ return Set.of(); }
}
7. 动态扩展与热插拔
新增过滤器只需继承RiskFilter并插入链中。例如添加"图片违规检测":
java
public class ImageRiskFilter extends RiskFilter {
@Override
public void doFilter(MessageContext ctx) {
// 检查 ctx 中是否包含违规图片URL
if (next != null) next.doFilter(ctx);
}
}
通过配置化方式决定链顺序:
java
// 伪代码:从JSON配置读取 filterOrder = ["word", "image", "rate", "white"]
List<RiskFilter> filters = configOrder.stream()
.map(type -> createFilterByType(type))
.collect(Collectors.toList());
for (int i = 0; i < filters.size() - 1; i++) {
filters.get(i).setNext(filters.get(i + 1));
}
8. 性能与监控
- 所有过滤器应为无状态,支持并发;
- 记录每个过滤器耗时,用于性能分析;
- 被拦截消息写入审计日志,包含
blockReason和上下文摘要。
责任链模式使风控逻辑模块化,新规则可独立开发、测试、部署,极大提升系统可维护性与合规响应速度。