应先做空值判断并截断ua至128字符,再通过中间件统一解析归类为ua_family/ua_os,结合x-app-id等自定义头交叉验证,禁用正则硬匹配,数据库存分类字段而非原始字符串,日志用md5摘要替代完整ua。

直接用 request()->header('user-agent') 就能拿到 UA 字符串,但裸拿没用——它需要解析、归类、脱敏、防伪造,否则日志里全是杂乱无章的字符串,查不出小程序还是爬虫。
UA 字符串怎么安全提取和归一化
ThinkPHP 的 request()->header('user-agent') 不区分大小写,也兼容 USER_AGENT 或 User-Agent 写法,但不能直接信任。真实场景中,UA 可能为空、被前端篡改、或来自恶意工具(如 curl -H "User-Agent: xxx")。
- 始终做空值判断:
if (!$ua = $request->header('user-agent')) { $ua = 'unknown'; } - 避免直接记录完整 UA:长度可能超 500 字符,且含设备型号、系统路径等敏感信息;建议截断到 128 字符并用
mb_substr($ua, 0, 128) - 别用正则硬匹配“MicroMessenger”来判微信——新版 iOS 微信 UA 已移除该字段,应结合
X-Wx-Appid等自定义头交叉验证 - 若需识别客户端类型,优先走中间件统一处理,而不是每个接口都重复写判断逻辑
常见 UA 类型识别逻辑(非正则硬编码)
靠字符串包含判断容易误伤(比如“Edge”在 Edge 和 QQ 浏览器里都出现),更稳的方式是按优先级顺序匹配可信标识:
- 先看是否有
X-App-ID或X-Client-Type这类业务自定义头,有则直接取值(如app-ios-v3.2.1) - 再查标准 UA:含
AlipayClient→ 支付宝小程序;含且不含 <code>Chrome→ 微信原生小程序 - 含
cURL/httpie/wget→ 标记为 CLI 工具调用,不是用户终端 - 含
Mobile但不含iPad→ 大概率是手机浏览器;含Windows NT+→ IE
- 最后 fallback 到
other-browser 或 <code>unknown,不强行归类
为什么不能在控制器里直接写 UA 日志
因为控制器只在路由匹配成功后才执行,404、401、500 请求根本进不来,而这些恰恰是扫描器、爆破脚本高频出没的地方。真正要统计全量来源,必须把 UA 提取提前到中间件阶段。
- 新建中间件
App\Middleware\LogUaSource,在handle()开头就调用$request->header('user-agent') - 把解析后的类型(如
wxapp、android-app、curl)存进$request->withAttr('ua_type', $type) - 后续所有日志、审计、限流逻辑都从
$request->attr('ua_type')读,不重复解析 - 注意:CLI 或 Swoole 长连接环境下,
$_SERVER['HTTP_USER_AGENT']可能不存在,request()->header()会返回空,得兜底
UA 统计结果怎么避免污染数据库和日志
原始 UA 字段不能直接入库或落盘,否则既浪费空间,又埋下隐私合规风险(GDPR、个保法要求对设备标识脱敏)。
- 数据库表里不要建
user_agent TEXT字段,改为ua_family VARCHAR(32)(如chrome、safari、unknown) +ua_os VARCHAR(16)(ios、android、win) - 日志文件中禁止输出完整 UA,可用哈希摘要代替可识别性:
md5(substr($ua, 0, 64)),保留区分度但不可逆 - 如果用了 Redis 做实时 UA 分布统计,key 建议设计为
ua:summary:{date}:{ua_family},过期时间设为 24 小时,避免堆积 - 特别注意:Nginx 或 CDN 如果透传了
X-Forwarded-For却没清理原始User-Agent,可能造成 UA 被上游覆盖,得检查代理配置是否禁用proxy_set_header User-Agent
最常被忽略的一点:UA 解析逻辑一旦写死在中间件里,后续新增客户端类型(比如鸿蒙快应用、车机端)就得改代码发版。更可持续的做法是把匹配规则抽成配置表或 JSON 文件,运行时加载,让运营人员也能参与维护识别策略。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











