应使用 Rule::regex() 配合自定义规则校验身份证:先用正则 /^\d{17}[\dXx]$/ 检查格式,再通过加权模11算法验证第18位校验码,并补充地区码和出生日期合法性校验。

用 Rule::regex() 配合正则做18位身份证基础格式校验
身份证字段不能只靠长度判断,必须验证前17位数字 + 第18位校验码的组合逻辑。Laravel 自带的 digits:18 会漏掉字母X和校验逻辑错误,直接导致脏数据入库。
推荐用 Rule::regex() 写精准正则,覆盖:17位数字 + 末位数字或X(大小写都接受):
use Illuminate\Validation\Rule;
<p>$rules = [
'id_card' => [
'required',
'string',
Rule::regex('/^\d{17}[\dXx]$/'),
],
];- 必须加
string类型约束,否则数字开头的ID会被自动转成int,触发类型不匹配失败 - 正则末尾用
[\dXx]而不是[0-9Xx],避免某些PHP版本下字符类解析差异 - 不要用
alpha_num,它允许纯数字但不校验位数,且放行非法字母
手写校验码算法验证必须放在自定义规则里
正则只能拦住明显格式错误,真正决定合法性的,是第18位校验码是否由前17位加权模11算出。这个逻辑没法用内置规则表达,必须封装成自定义验证规则。
在 App\Rules\ValidIdCard.php 中实现:
public function passes($attribute, $value)
{
if (!preg_match('/^\d{17}[\dXx]$/', $value)) {
return false;
}
<pre class='brush:php;toolbar:false;'>$weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
$checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
$sum = 0;
for ($i = 0; $i < 17; $i++) {
$sum += (int)$value[$i] * $weights[$i];
}
$mod = $sum % 11;
return strtoupper($value[17]) === $checkCodes[$mod];}
- 务必先过正则再算校验码,避免对非18位字符串做数组访问导致
Undefined offset - 用
strtoupper()统一处理X,不然小写x会校验失败 - 别在Controller里直接写这段逻辑——下次复用时还得抄一遍,也难测试
地区码和出生日期合法性需要额外检查
通过了位数+校验码,不代表就是真实身份证。比如 999999199901011234 格式全对,但地区码999999不存在,出生年份19990101也不合法。
建议在自定义规则中追加两步校验:
- 查地区码表:用
file_get_contents('https://raw.githubusercontent.com/elastic/elasticsearch/master/libs/geoip/src/main/resources/geolite2/country-codes.csv')不现实,应本地存一份精简版resources/data/idcard_areas.php,只含6位有效区划代码 - 解析出生日期:取
substr($value, 6, 8),用DateTime::createFromFormat('Ymd', $date)判断是否为真实日期(注意1900年前、未来日期、2月30日等边界) - 别用
checkdate()直接传年月日整数——它不校验字符串合法性,且对1900年前返回false,而老身份证确实存在1899年出生的情况
API请求体字段校验失败时别暴露细节
用户提交 id_card=123,如果返回 "The id card must match the regex...",等于告诉攻击者你用了正则;返回 "Invalid checksum" 更糟,直接泄露校验逻辑。
- 统一返回
"The id_card is invalid.",前端按字段名提示即可 - 调试阶段可在日志里记录具体失败原因,但绝不返回给客户端
- 如果项目启用了
APP_DEBUG=true,确保验证规则没抛出未捕获异常——否则会直接输出堆栈,暴露路径和类名
实际用起来,最易被跳过的其实是地区码和出生日期这两层。很多人测到校验码通过就以为万事大吉,结果上线后发现一堆“新疆生产建设兵团”开头的假号进来了。


















