CodeIgniter 不提供开箱即用的数据脱敏功能,需在 Controller 或 Model 层显式实现;禁止在 View 层硬编码脱敏;SQL 层脱敏应使用 selectCast() 或 select(..., false);动态脱敏须按角色封装服务类;导出、日志、缓存等所有出口均需单独脱敏。

CodeIgniter 本身不提供开箱即用的数据脱敏功能,所有脱敏逻辑必须由开发者在应用层显式实现。不能依赖框架自动处理手机号、身份证等字段——哪怕用了 esc() 或 html_escape(),那也只是防 XSS,和脱敏无关。
脱敏该放在哪一层:Controller 还是 View?
脱敏动作必须在数据离开业务逻辑前完成,也就是 Controller 层或 Model 层返回数据之前。View 层只负责渲染,若在 View 中做脱敏(比如用 str_replace() 处理 $user->phone),会导致同一份数据在不同页面重复脱敏、无法统一策略、难以测试。
- 推荐在 Model 的查询方法中直接返回脱敏后字段,例如
getUsersWithMaskedPhone() - 或在 Controller 中调用独立的脱敏服务类(如
PhoneNumberMasker::mask($user->phone)),保持逻辑可复用 - 避免在 View 模板里写
= substr($phone, 0, 3) . '****' . substr($phone, -4) ?>—— 这种硬编码会散落各处,改规则时漏掉一个就泄露
SQL 层脱敏的坑:CONCAT + LEFT 在 CI4 查询构造器里怎么写?
CI4 的查询构造器(Query Builder)不支持原生 SQL 函数嵌套作为字段别名,直接写 select('CONCAT(LEFT(phone,3),"****",RIGHT(phone,4)) as phone') 会报错或被转义。正确做法是用 selectCast() 或原生查询:
- 用
selectCast()(CI4.4+):$builder->selectCast(['CONCAT(LEFT(phone,3),"****",RIGHT(phone,4))' => 'phone']) - 降级兼容写法(所有版本):
$builder->select("CONCAT(LEFT(phone,3),'****',RIGHT(phone,4)) as phone", false),注意第二个参数false禁用自动转义 - 但要注意:这种写法会让字段失去类型提示、ORM 映射失效,且无法被单元测试覆盖;仅适合一次性报表类查询
动态脱敏:按用户角色决定脱敏强度
客服人员能看到手机号前三位+后四位,管理员则看到完整号码——这种权限感知脱敏不能靠数据库视图硬编码,得在应用层拦截结果集。CI4 最自然的切点是自定义过滤器(Filter)或基类控制器的 after() 方法:
- 在
app/Filters/PrivacyFilter.php中,对响应内容做后置处理(需先json_encode再正则替换,慎用) - 更稳妥的方式:封装一个
DataSanitizer类,在 Controller 中显式调用,传入当前用户角色和待脱敏字段映射表,例如:$sanitizer->sanitize($users, ['phone' => 'customer'], $currentUserRole) - 切忌用全局
hook或中间件对所有 JSON 响应做无差别正则匹配——容易误杀日志 ID、订单号、API key 等非敏感字段
最易被忽略的一点:脱敏不是“显示时遮一下”就完事。导出 Excel、生成 PDF、写入日志、甚至缓存序列化数据,都可能把原始敏感值漏出去。每个出口都要单独确认是否已脱敏——没有银弹,只有逐个堵漏。











