thinkphp无内置分片机制,需手动控制表名切换、缓存路由及上传分片;动态表名必须白名单校验,模型重写gettablename()更安全;partition()仅替换表名,不加时间条件或校验;跨月查询需手动循环+合并;缓存分片须重写驱动,redis需用hash tag;大文件上传依赖内容哈希去重,合并须流式写入。

ThinkPHP 本身没有内置的 sharding(分片)机制,所有分片逻辑都得你手动控制——无论是数据库表名切换、缓存 key 路由,还是上传分片落盘,框架只提供钩子和入口,不替你做决策。
Db::name() 动态切表必须校验后缀,不能拼接用户输入
直接 Db::name('user_' . input('month')) 是高危操作。ThinkPHP 的 name() 方法对字符串不做任何过滤,攻击者传 month=202401%00; DROP TABLE 虽然 PDO 预处理拦不住表名,但可能触发异常或绕过日志校验。
- 只允许匹配
^\d{6}$(如202401)或^\d{4}_\d{2}$(如2024_05),用preg_match()强制校验 - 合法后缀建议缓存在 Redis 或静态数组里,运行时只做白名单比对,别用
str_replace('__MONTH__', $input, 'user__MONTH__')这类占位符替换 - 模型中重写
getTableName()比在控制器里反复调name()更安全,能统一拦截和补全逻辑
模型 partition() 只换表名,不自动加时间条件
partition('202405') 的作用仅仅是把 user 替成 user_202405,它不会帮你解析 create_time、生成 WHERE 范围,也不会校验该表是否存在。
- 必须手动加
where('create_time', '>=', '2024-05-01 00:00:00')->where('create_time', ' - 如果
create_time是VARCHAR类型,MySQL 无法做分区裁剪,EXPLAIN会显示扫描全部分表 - 跨月查询要自己循环调多次
partition(),再用 PHP 合并结果;union()在 ThinkPHP 中不支持跨物理表自动优化
缓存 key 分片必须重写驱动,不能靠 tag 或配置开关
ThinkPHP 的 Cache::tag() 是逻辑分组,不是物理隔离;'shard' => true 这种配置根本不存在。File 驱动单目录超 32K 文件会卡顿,Redis 热点 key 会导致延迟抖动,这些都得你在 key 层面解决。
- File 驱动分片:继承
think\cache\driver\File,在doRead()/doWrite()中用substr(md5($key), 0, 2)生成二级路径,如cache/1a/2b/xxx - Redis 分片:把哈希段嵌进 key,如
user:0a:10086(0a来自md5('10086') % 256十六进制),而非依赖SELECT切库 - 如果用了 Redis Cluster,key 必须带
{user:0a}:10086这样的 hash tag,否则集群按完整 key 哈希,分片失效
大文件分片上传必须用前端计算的指纹去重
服务端不能信 $_FILES['file']['name'] 或 size,浏览器可伪造。重复上传识别必须基于文件内容哈希,且合并逻辑要防内存爆掉。
- 前端用
spark-md5计算全量 MD5,在首片请求中传identify字段 - 后端收到首片时查 Redis 或 DB 是否已有该
identify的上传记录;有则直接返回已传分片列表(如[0,2,3]) - 合并时用
fopen($final, 'wb')+fwrite()逐片追加,别用file_get_contents()读整个分片再file_put_contents(..., FILE_APPEND)
最易被忽略的是:所有分片逻辑都依赖「提前决策」——表名、缓存路径、上传存储位置,必须在第一次 IO 前就确定好,而不是等运行时动态推导。一旦漏掉校验或错放时机,轻则数据错乱,重则被注入或 OOM。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











