前言
Yam TV 作为一个多源视频聚合平台,需要对成人内容做双重过滤——系统级管理员配置的全局过滤 + 用户级个人偏好过滤。两个开关只要有一个开启,就应该触发过滤。本文记录这个功能从设计到实现、从复杂到简化的演进过程。
需求定义
- 用户可以在设置中开启「成人内容过滤」开关
- 过滤逻辑与系统级配置的过滤一致——同样的关键词、同样的源拦截规则
- 用户级和系统级只要有一个开启就生效(OR 逻辑)
- 每个用户/设备的偏好独立存储
方案一:Flutter 端 Dio 拦截器(失败)
第一个想法:在 Dio 拦截器中拦截所有请求,自动添加 ?adult=false 参数。
class AdultFilterInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (enabled && _targetPaths.any(...)) {
options.queryParameters['adult'] = 'false';
}
handler.next(options);
}
}
这个方案有三个问题:
- 竞态条件:拦截器在 App 启动时注册,但用户偏好从 SharedPreferences 异步加载,存在时间窗口内拦截器读不到正确值。
- 路径覆盖不全:需要维护一个
_targetPaths列表,漏掉一个路径过滤就不生效。 - 参数传递冗余:每个请求都要携带
?adult=false,增加带宽和日志噪音。
方案二:后端存储 + Flutter 同步 API(最终方案)
更好的方案:把用户偏好存到后端,后端在处理 API 请求时自动读取并过滤。Flutter 不需要在每个请求上传参。
// Flutter:开关变化时同步到后端
onChanged: (v) async {
final dio = ref.read(dioProvider);
await dio.put('/sync/adult-filter', data: {'enabled': v});
}
// 后端:存储到 KV
WatchService.setAdultFilter(ns, enabled);
// 后端:处理请求时读取
AdultFilterService.getEnabled(user, clientId);
// 在 sourceFeed/search 中调用 filterAdultContent()
后端过滤逻辑
新增 isAdultContent() 函数,不依赖 content_filter_enabled:
function isAdultContent(item, cfg) {
// 1. 源被标记为 adult → 过滤
if (site.adult === true) return true;
// 2. type_id 在黑名单 → 过滤
if (blockedTypeIds.includes(typeId)) return true;
// 3. 标题/名称匹配成人关键词 → 过滤
if (blockedKeywords.some(kw => name.includes(kw))) return true;
return false;
}
用户级和系统级的统一门逻辑(在所有接入点保持一致):
const shouldFilter = adultFilterEnabled || cfg.content_filter_enabled;
if (shouldFilter) {
items = filterAdultContent(items, cfg);
}
架构图
用户开关 → PUT /sync/adult-filter
↓
KV 存储 (adult-filter:{clientId})
↓
处理请求时 → AdultFilterService.getEnabled()
↓
OR 逻辑:系统级 || 用户级 → 过滤
接入点
- 搜索(/search)
- 流式搜索(/search/stream)
- 源站列表(/search/source-feed)
- 源列表(/search/sources)
- 分类列表(/search/source-categories)
所有接入点使用完全相同的 || 门和完全相同的 filterAdultContent() 函数,保证过滤结果一致。
总结
最初打算在 Flutter 端用 Dio 拦截器在每个请求上加参数,但这引入了竞态条件和维护成本。最终方案将偏好存储在后端,后端自动读取并过滤——更简洁、更可靠、更易维护。这也符合「功能归属后端」的设计原则,避免前端与逻辑耦合。
讨论
还没有留言,来留下第一条评论吧!