今天聊聊 PBlack——一个开源的手机号黑名单/白名单检测平台。我会从架构设计的角度,拆解它是如何解决这些痛点的,以及为什么选择 Vue + Django + Go 这样的技术组合。

为什么需要手机号过滤?
先说说背景。
在金融风控、电商防刷、社交平台注册这些场景里,手机号是最基础的身份标识。但问题来了:
- 骚扰电话营销:用户被各种推广电话轰炸,体验极差
- 羊毛党批量注册:用虚拟号、接码平台批量薅羊毛
- 欺诈风险:黑名单号码反复作案,平台损失巨大
传统的解决方案要么简单粗暴(直接拒接所有陌生号),要么成本太高(每次都要调用第三方接口)。PBlack 的设计目标就是:既要快,又要准,还要省钱。
整体架构:三层分离
PBlack 采用前后端分离的微服务架构,核心分为三层:
┌─────────────────────────────────────────────────────────┐
│ 前端层 (pfront) │
│ Vue 3 + TypeScript + Vite │
│ 管理界面、API 密钥管理、消费统计看板 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ API 服务层 (papi) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ phone-filter │ │ teddy-api │ │ pfront-api │ │
│ │ -api (Go) │ │ -proxy (Go) │ │ (Go) │ │
│ │ 黑名单检测 │ │ 上游代理 │ │ 认证/消息 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 数据与管理后台层 │
│ Django + PostgreSQL + Redis │
│ 用户管理、号码库、访问记录、缓存加速 │
└─────────────────────────────────────────────────────────┘这个分层不是拍脑袋决定的。每一层都有自己的职责,而且可以独立部署、独立扩展。
技术选型:为什么这样组合?
前端:Vue 3 + Vite
选择 Vue 3 而不是 React,主要是考虑开发效率和团队熟悉度。Vite 的冷启动速度比 Webpack 快一个数量级,本地开发体验很好。
TypeScript 是必须的——手机号过滤涉及大量的 API 接口对接,类型安全能避免很多低级错误。
管理后台:Django
Django 的 admin 界面是开箱即用的。用户管理、API 密钥管理、黑白名单维护这些功能,用 Django 的 ModelAdmin 几行代码就能搞定。
如果用 Go 写后台,光 CRUD 接口就要写一堆。Django 的 ORM 和 admin 在这种场景下是降维打击。
核心检测服务:Go
这是整个系统的性能瓶颈所在。手机号检测是高频操作,QPS 可能达到几千甚至上万。
Go 的 goroutine + channel 模型非常适合这种高并发、低延迟的场景。实测下来,单机可以轻松支撑 10k+ QPS。
// 核心检测逻辑:Redis Set 的 O(1) 查询
func (s *BlacklistService) CheckSingleNumber(phone string) (bool, error) {
isBlacklisted, err := database.IsPhoneInBlacklistSet(phone)
if err != nil {
return false, err
}
s.updateStats(isBlacklisted)
return isBlacklisted, nil
}数据流转:一次检测请求的全流程
来看看一个黑名单检测请求是怎么处理的:
客户端 → phone-filter-api → 签名验证 → 用户鉴权 → Redis 查询
↓
客户端 ← 返回结果 ← 组装响应 ← 命中判定 ← 黑名单 Set关键设计点:
- Redis Set 存储:黑名单和白名单分别用 Redis 的 Set 数据结构存储,
SISMEMBER命令是 O(1) 复杂度,百万级号码也能毫秒级响应 多级检测策略:
level=1:只查黑名单,命中即拦截level=2:只查白名单,用于"只允许特定号码"的场景level=3:黑名单优先,未命中时调用上游 API 二次确认
- 批处理优化:访问记录和消费记录不实时写入数据库,而是先放入内存队列,每 5 秒批量 flush 一次,大幅降低数据库压力
// 后台批处理器,降低主链路延迟
type BatchProcessor struct {
accessRecords []*models.AccessRecord
consumptionRecords []*models.ConsumptionRecord
mutex sync.Mutex
batchSize int // 默认 100 条批量写入
}
func (bp *BatchProcessor) startBackgroundProcessing() {
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
bp.flushRecords() // 定时批量刷盘
}
}安全设计:不只是黑名单
PBlack 的安全机制是多层防护:
1. 签名验证
每个请求必须携带 appId、timestamp、sign 三个参数。签名算法如下:
sign = MD5(appId + timestamp + appSecret)timestamp 必须在 5 分钟内,防止重放攻击。
2. IP 白名单
每个 API 密钥可以绑定允许的 IP 列表,即使密钥泄露,攻击者也无法从其他 IP 调用。
3. 余额与配额控制
每个用户有独立的余额和单价设置,每次检测按单价扣费。余额不足时自动拒绝服务。

部署架构:从开发到生产
本地开发时,用 Docker Compose 一键启动所有依赖:
# postgres/docker-compose.yaml
services:
postgres:
image: postgres:15-alpine
ports:
- "5432:5432"
environment:
POSTGRES_DB: phonedb
POSTGRES_PASSWORD: uWNZugjBqbcf8dxC生产环境建议:
- Nginx 反向代理:SSL 终止、静态资源缓存、限流
- systemd 进程守护:API 服务和 Worker 分别托管
- PostgreSQL 主从:读写分离,查询走从库
- Redis Cluster:支持横向扩展
性能数据
在 4C8G 的云服务器上实测:
| 指标 | 数值 |
|---|---|
| 单机 QPS | 12,000+ |
| 平均响应时间 | 3-5ms |
| Redis 命中率 | 99.8% |
| 批处理延迟 | < 5s |
总结
PBlack 的架构设计遵循几个核心原则:
- 职责分离:Django 做管理、Go 做性能、Vue 做交互,各取所长
- 缓存优先:Redis Set 是核心,数据库只是持久化备份
- 批处理降载:非关键路径异步化,主链路保持轻量
- 安全第一:签名 + IP 白名单 + 余额控制,多层防护
这套架构已经在实际业务中跑了半年多,处理了几千万次检测请求。如果你也在做类似的风控系统,希望这些经验对你有帮助。
下篇预告:《30 分钟跑起来:PBlack 本地开发环境搭建实战》
评论 (0)