订单号验证需分三层:格式校验(正则约束长度、字符及结构)、数据库唯一索引防并发重复、业务规则(时间戳+校验位),并规避rules返回格式、safe字段、拼写错误等静默失败陷阱。

验证订单号格式需兼顾唯一性、可读性与防篡改,不能只靠正则匹配就认为安全——比如仅用'/^\d{12}$/'会漏掉数据库已存在但格式合法的重复单号。
用正则约束基础格式
在模型的 rules() 方法中添加字符串校验规则,强制长度、字符范围和开头特征:
['order_no', 'string', 'length' => [16, 32], 'message' => '订单号长度必须在16到32位之间']
['order_no', 'match', 'pattern' => '/^[A-Z]{2}\d{6}[A-Za-z0-9]{6,18}$/', 'message' => '订单号格式错误:前两位为大写字母,第3–8位为6位数字,后续为字母数字组合']
这一步不校验是否重复,只做最外层格式兜底。若跳过此步,用户可能提交纯中文或含空格的 order_no,后续 unique 验证会直接抛出 SQL 错误而非友好提示。
确保数据库层面唯一且索引有效
订单号必须走数据库唯一约束,否则并发下单时正则+PHP 层查重会失效:
在数据表中执行 ALTER TABLE `order` ADD UNIQUE KEY `uk_order_no` (`order_no`)
【必须给 order_no 字段加 B+ 树索引】 否则 unique 验证器执行 SELECT COUNT(*) FROM order WHERE order_no = :order_no 时将触发全表扫描,高并发下极易超时或死锁。
在 rules() 中启用 unique 验证器,并显式指定 targetClass:
['order_no', 'unique', 'targetClass' => '\app\models\Order', 'message' => '该订单号已被使用']
用内联验证器嵌入业务规则
方法一:校验时间戳段是否在合理范围内(防止伪造未来单号)
['order_no', function ($model, $attribute, $params) {
$tsPart = substr($model->$attribute, 2, 6);
if (!is_numeric($tsPart) || $tsPart date('ymd')) {
$model->addError($attribute, '订单号中的日期段无效,请检查是否为最近30天内生成');
}
}]
方法二:校验校验位(如 Luhn 算法变种)
['order_no', function ($model, $attribute, $params) {
$no = $model->$attribute;
$checkDigit = (int)substr($no, -1);
$body = substr($no, 0, -1);
$sum = 0;
for ($i = 0; $i $sum += (int)$body[$i] * (($i % 2) ? 1 : 2);
}
$expected = (10 - ($sum % 10)) % 10;
if ($expected !== $checkDigit) {
$model->addError($attribute, '订单号校验位错误');
}
}]
规避常见静默失败陷阱
第一步:确认 rules() 返回的是二维数组,不是一维——错误写法:return ['order_no', 'string'],正确写法:return [['order_no'], 'string'] 或 [['order_no', 'string']]。
第二步:检查字段是否被标记为 safe。若 order_no 来自表单且未在 scenarios() 中声明为 safe,则 load() 时会被过滤丢弃,后续验证永远不触发。
第三步:验证器名拼写必须精确。写成 'unqiue' 或 'uniqe' 不会报错,但规则静默失效——因为 Yii2 遇到未知验证器名会跳过整条规则,连 warning 都不抛。











