应使用 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]$/'),
],
];</p>
- 必须加
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 <p>}</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill7885" title="laravel-specialist"><img
src="https://img.php.cn/upload/skill/000/000/081/179172020840450.jpg" alt="laravel-specialist" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill7885" title="laravel-specialist" class="overflowclass">laravel-specialist</a>
<p class="overflowclass">精通 Laravel 10+,遵循现代 PHP 实践的 Laravel 专家。擅长优雅语法、Eloquent ORM、队列系统及企业级特性...</p>
</div>
<a rel="nofollow" href="/xiazai/skill7885" title="laravel-specialist" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 务必先过正则再算校验码,避免对非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,确保验证规则没抛出未捕获异常——否则会直接输出堆栈,暴露路径和类名
实际用起来,最易被跳过的其实是地区码和出生日期这两层。很多人测到校验码通过就以为万事大吉,结果上线后发现一堆“新疆生产建设兵团”开头的假号进来了。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










