ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PHP防伪码查询系统源码:高并发防刷与防伪码生成实战

PHP防伪码查询系统源码:高并发防刷与防伪码生成实战 简介这是一套基于PHP与MySQL开发的产品商品防伪码查询系统源码面向中小型企业、电商卖家及建站开发者用于为商品生成唯一防伪码并提供在线真伪验证服务。系统支持防伪码自动生成可自定义长度、前缀与组合形式也能批量导入xls、txt、csv文件并导出为txt文档管理端保留两个备用字段可记录型号或补充说明同时统计每个防伪码的被查询次数自定义搜索结果内容并留存查询关键字、时间与IP历史记录管理员可登录完成增删改操作通过install.php即可在线安装。压缩包共68个文件约3.16MB包含11个php核心程序、7个css与7个js前端资源、13张jpg与11张png界面图片以及csv、xls、txt格式的导入模板和字体图标文件结构完整。目前已有1898人学习下载适合需要快速搭建防伪验证平台、研究查询逻辑与后台管理的开发者参考使用。1. PHP产品商品防伪码查询系统源码从零搭建一套能扛住真实流量的防伪查询后端你手里有一批货出厂时贴了防伪码消费者刮开涂层后扫码或输入一串数字系统要在几百毫秒内告诉他这瓶酒、这盒药、这台配件是不是正品以及这个码被查过几次。这就是 PHP 产品商品防伪码查询系统源码要解决的核心问题。它不是一个简单的「输入码、返回真伪」的接口而是一整套包含码生成、码入库、查询鉴权、查询次数风控、防批量扫描的闭环。适合谁适合手里有 PHP 环境、有 MySQL、想自己掌控数据、不想把防伪查询托管给第三方的小型品牌方或系统集成商。热搜里「PHP 防伪码查询系统」「源码」反复出现说明大量从业者卡在「知道要做但不知道码怎么生成、查询怎么防刷、并发怎么扛」这三步上。这篇就按我实际落过的方案把每一步拆开讲清楚。2. 防伪码的生成逻辑与批量入库别让随机数埋下重复的雷2.1 防伪码到底该长什么样长度、字符集与校验位防伪码不是随便rand()一串数字就完事。我见过最典型的翻车现场用mt_rand(100000, 999999)生成六位码十万个商品里必然出现重复因为六位数字空间只有九十万生日悖论下几万个就开始碰撞。常见做法是采用「前缀 随机主体 校验位」的结构。前缀标识批次或产品线主体用足够大的字符空间校验位用来在查询前先做一次本地合法性判断把明显乱输的请求挡在数据库之外。字符集选择上要避开容易混淆的字符。0 和 O、1 和 I 和 L在刮刮卡或打印模糊时是投诉重灾区。我一般用23456789ABCDEFGHJKLMNPQRSTUVWXYZ这 32 个字符去掉 0、1、O、I做 Base32 风格的编码。长度上16 位是常见平衡点32 的 16 次方是 2 的 80 次方空间足够大碰撞概率可以忽略同时消费者手动输入也不至于崩溃。校验位用简单的模运算即可不需要加密强度。比如对前 15 位按位置加权求和后取模 32映射到字符集里的一个字符作为第 16 位。这样前端或接口层可以先校验格式不对直接返回「码无效」不查库。?php // 防伪码生成核心字符集、随机主体、校验位 function generateAntiFakeCode(string $prefix P): string { // 去掉易混淆字符的 32 字符集 $charset 23456789ABCDEFGHJKLMNPQRSTUVWXYZ; $body ; // 生成 15 位随机主体使用 random_int 保证密码学安全 for ($i 0; $i 15; $i) { $body . $charset[random_int(0, 31)]; } // 计算校验位按位置加权求和取模 $sum 0; for ($i 0; $i 15; $i) { $pos strpos($charset, $body[$i]); $sum $pos * ($i 1); // 位置权重从 1 开始 } $check $charset[$sum % 32]; return $prefix . $body . $check; } // 批量生成并去重入库前先做一次内存去重 function batchGenerate(int $count, string $prefix P): array { $codes []; while (count($codes) $count) { $code generateAntiFakeCode($prefix); $codes[$code] true; // 用键去重 } return array_keys($codes); }逻辑说明random_int比rand和mt_rand更适合安全场景虽然防伪码不要求密码学强度但避免可预测性能省掉很多麻烦。校验位算法里位置权重让相同字符在不同位置产生不同贡献降低简单替换攻击的成功率。batchGenerate用数组键去重是因为即使空间很大批量生成时仍建议做一次内存级去重成本极低。参数说明$prefix建议按产品线或批次传入比如「A」代表白酒、「B」代表保健品方便后续按前缀分表或分库。$count单次批量建议不超过 50 万再大就分批避免 PHP 内存超限。字符集长度必须是 32否则取模映射会出错。2.2 入库表结构设计与批量插入的性能取舍码生成之后要入库。表结构我一般这样设计主键用自增id防伪码字段加唯一索引另外记录批次号、生成时间、绑定状态、查询次数、首次查询时间、首次查询 IP 的哈希。唯一索引是底线它能在数据库层面兜住任何生成逻辑的漏洞哪怕代码写错了重复插入也会被拦下来。批量插入时单条INSERT循环是新手最容易踩的性能坑。一万个码循环插入在普通机械盘上可能要几十秒。常见做法是用INSERT INTO ... VALUES (...), (...), ...的多值语法每批 500 到 1000 条。再大受max_allowed_packet限制反而容易失败。?php // 批量入库多值 INSERT每批 500 条 function batchInsertCodes(PDO $pdo, array $codes, string $batchNo): int { $batchSize 500; $inserted 0; $chunks array_chunk($codes, $batchSize); $sql INSERT INTO anti_fake_codes (code, batch_no, status, query_count, created_at) VALUES ; foreach ($chunks as $chunk) { $placeholders []; $values []; foreach ($chunk as $code) { $placeholders[] (?, ?, 0, 0, NOW()); $values[] $code; $values[] $batchNo; } $stmt $pdo-prepare($sql . implode(,, $placeholders)); $stmt-execute($values); $inserted count($chunk); } return $inserted; }逻辑说明array_chunk把大数组切成小批避免单条 SQL 过长。占位符动态拼接时每个码对应两个参数code 和 batch_no顺序不能错。status默认 0 表示未绑定后续可以扩展为已售出、已激活等状态。参数说明$batchSize设为 500 是经验值MySQL 的max_allowed_packet默认 4MB 或 16MB500 条 16 位码加批次号远低于限制。如果服务器配置较低可以降到 200。$batchNo建议用日期加序列比如20250101-001方便追溯。提示入库前务必确认code字段有唯一索引。我见过因为漏加唯一索引批量插入时重复码混进去后来查询时同一个码返回多条记录排查了半天。3. 查询接口的实现与防刷让每个码的查询次数成为风控信号3.1 查询接口的最小闭环从输入到返回真伪查询接口是整个系统的门面。消费者输入码接口要做的事按顺序是格式校验、查库、判断状态、更新查询次数、返回结果。顺序不能乱格式校验放最前面把无效请求挡在数据库之外这是最便宜的防护。返回结果要克制。不要返回「该码是正品生产于 XX 厂批次 XX」这种信息那等于给造假者送情报。我一般只返回三类结果正品且首次查询、正品但已被查询过附带查询次数、码不存在或已失效。首次查询和多次查询的区分很重要它是消费者判断真伪的核心依据。?php // 查询接口核心逻辑 function queryCode(PDO $pdo, string $inputCode, string $clientIp): array { $charset 23456789ABCDEFGHJKLMNPQRSTUVWXYZ; $inputCode strtoupper(trim($inputCode)); // 1. 格式校验长度和字符集 if (strlen($inputCode) ! 16 || strspn($inputCode, $charset) ! 16) { return [status invalid, msg 防伪码格式不正确]; } // 2. 校验位验证 $body substr($inputCode, 1, 15); $check $inputCode[15]; $sum 0; for ($i 0; $i 15; $i) { $sum strpos($charset, $body[$i]) * ($i 1); } if ($charset[$sum % 32] ! $check) { return [status invalid, msg 防伪码校验失败]; } // 3. 查库并加行锁防止并发下查询次数更新丢失 $pdo-beginTransaction(); $stmt $pdo-prepare(SELECT id, status, query_count, first_query_ip FROM anti_fake_codes WHERE code ? FOR UPDATE); $stmt-execute([$inputCode]); $row $stmt-fetch(PDO::FETCH_ASSOC); if (!$row) { $pdo-rollBack(); return [status not_found, msg 该防伪码不存在]; } if ($row[status] 2) { $pdo-rollBack(); return [status disabled, msg 该防伪码已被禁用]; } // 4. 更新查询次数和首次查询信息 $ipHash hash(sha256, $clientIp); if ($row[query_count] 0) { $update $pdo-prepare(UPDATE anti_fake_codes SET query_count 1, first_query_time NOW(), first_query_ip ? WHERE id ?); $update-execute([$ipHash, $row[id]]); $pdo-commit(); return [status first, msg 正品首次查询]; } else { $update $pdo-prepare(UPDATE anti_fake_codes SET query_count query_count 1 WHERE id ?); $update-execute([$row[id]]); $pdo-commit(); return [status repeat, msg 该码已被查询过, count $row[query_count] 1]; } }逻辑说明FOR UPDATE行锁是关键。没有它两个请求同时查到query_count 0都认为是首次查询都更新为 1消费者会看到两次「首次查询」直接引发信任危机。事务包裹查和更新保证原子性。IP 存哈希而不是明文兼顾风控和隐私。参数说明$inputCode统一转大写并去空格因为消费者输入时经常带空格或小写。$clientIp从$_SERVER[REMOTE_ADDR]获取如果前面有反向代理要确保拿到的是真实 IP否则风控数据全错。3.2 防批量扫描查询频率限制与异常模式识别防伪系统最怕的不是单个消费者查而是有人拿脚本批量扫。扫的目的有两个一是找出哪些码还没被查询过然后造假二是探测码的生成规律。防刷要做两层频率限制和模式识别。频率限制按 IP 和按时间窗口做。同一个 IP 一分钟内查询超过 20 次就触发限制返回「查询过于频繁」。这个阈值不能太低因为有些消费者在信号不好时会反复点也不能太高给脚本留空间。我一般用 Redis 做计数器键是query:ip:{ip_hash}:{分钟时间戳}过期时间 120 秒。?php // 基于 Redis 的频率限制 function checkRateLimit(Redis $redis, string $ipHash): bool { $minute floor(time() / 60); $key query:ip:{$ipHash}:{$minute}; $count $redis-incr($key); if ($count 1) { $redis-expire($key, 120); // 两分钟过期覆盖跨分钟边界 } return $count 20; // 每分钟最多 20 次 }逻辑说明incr是原子操作并发下不会计数错误。第一次设置过期时间后续递增不重置过期保证窗口滑动。120 秒过期是为了覆盖分钟边界避免刚设置就过期导致计数丢失。参数说明阈值 20 是经验值可以根据业务调整。如果是高客单价商品可以降到 10如果是快消品可以放宽到 30。Redis 连接建议用长连接或连接池避免每次查询都建连。模式识别更进阶如果同一个 IP 在短时间内查询了大量不同前缀的码或者查询的码集中在某个批次这比单纯高频更可疑。我一般会记录查询日志异步分析发现异常批次时人工介入必要时批量禁用该批次的码。注意频率限制的返回信息不要暴露具体阈值只说「查询过于频繁请稍后再试」避免给攻击者调参依据。4. 避坑与排查那些让防伪系统翻车的细节4.1 查询次数更新丢失并发下的经典翻车现象同一个防伪码两个消费者几乎同时查询都返回「首次查询」。原因查询和更新之间没有锁两个请求都读到query_count 0都执行了「设为 1」的更新。解决用FOR UPDATE行锁或原子更新UPDATE ... SET query_count query_count 1 WHERE code ? AND query_count 0根据影响行数判断是否首次。我一般用行锁逻辑更直观。4.2 码生成重复随机数空间不足或去重缺失现象批量生成十万个码入库时唯一索引报错。原因用了mt_rand生成短码空间不够或者生成时没做内存去重。解决换用 32 字符集 16 位长度生成时用数组键去重入库时保留唯一索引作为最后防线。如果已经出现重复用INSERT IGNORE跳过重复再补生成差额。4.3 查询接口被刷爆没有频率限制或限制太松现象数据库 CPU 飙升查询响应从几十毫秒变成几秒。原因接口没有频率限制或者限制只按 IP 但攻击者用代理池。解决加 Redis 频率限制同时按 IP 和按码查询次数做双重限制。如果攻击者用代理池可以叠加行为分析比如同一时间段内大量不同 IP 查询同一批次触发批次级限流。4.4 首次查询 IP 记录错误反向代理下拿到内网 IP现象所有查询的首次 IP 都是127.0.0.1或10.x.x.x。原因服务器前面有 Nginx 或负载均衡REMOTE_ADDR拿到的是代理 IP。解决在 Nginx 配置里设置proxy_set_header X-Real-IP $remote_addr;PHP 里优先读HTTP_X_REAL_IP并确保只有可信代理才能设置这个头否则可以被伪造。4.5 码状态管理混乱禁用和删除分不清现象某个批次的码出问题运营直接删库结果消费者查询返回「不存在」引发恐慌。原因没有区分「禁用」和「删除」。解决用status字段标记2 表示禁用查询时返回「该码已被禁用」而不是「不存在」。删除只用于测试数据生产数据永远不物理删除。5. 进阶技巧用查询日志反哺防伪策略与验证码识别5.1 查询日志表设计与异步写入查询日志是防伪系统的黑匣子。我一般单独建一张query_logs表记录码、IP 哈希、查询时间、返回状态。写入用异步方式避免拖慢查询接口。简单做法是用fastcgi_finish_request()在返回响应后继续写日志或者丢到 Redis 队列里由后台进程消费。?php // 在返回响应后异步写日志 function logQueryAsync(PDO $pdo, string $code, string $ipHash, string $status): void { // 先输出响应给客户端 if (function_exists(fastcgi_finish_request)) { fastcgi_finish_request(); } $stmt $pdo-prepare(INSERT INTO query_logs (code, ip_hash, status, created_at) VALUES (?, ?, ?, NOW())); $stmt-execute([$code, $ipHash, $status]); }逻辑说明fastcgi_finish_request在 PHP-FPM 环境下会先把响应发给客户端然后继续执行后面的代码。这样日志写入的耗时不会算在接口响应时间里。如果没有这个函数比如 CLI 环境就退化为同步写入。参数说明日志表建议按月分表或按批次分表避免单表过大。ip_hash用 SHA-256不可逆符合隐私要求。status记录返回状态方便后续分析哪些码被频繁查询、哪些 IP 在批量扫。5.2 从日志里发现异常模式三个可落地的查询日志积累一段时间后用几条 SQL 就能发现异常。第一条查同一 IP 在短时间内的查询次数分布找出高频 IP。第二条查同一码被查询的次数分布找出被反复查询的码。第三条查某个批次码的查询率如果某批次查询率异常低可能还没上市就被扫了。-- 找出过去 24 小时内查询超过 100 次的 IP SELECT ip_hash, COUNT(*) AS cnt FROM query_logs WHERE created_at DATE_SUB(NOW(), INTERVAL 24 HOUR) GROUP BY ip_hash HAVING cnt 100 ORDER BY cnt DESC; -- 找出被查询次数最多的码 SELECT code, COUNT(*) AS cnt FROM query_logs WHERE created_at DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY code HAVING cnt 50 ORDER BY cnt DESC LIMIT 20;逻辑说明第一条 SQL 定位高频 IP可以加入黑名单或人工审核。第二条 SQL 找出被反复查询的码可能是消费者投诉或攻击者在试探。第三条需要结合批次表计算每个批次的查询率异常低的批次要警惕。参数说明阈值 100 和 50 是示例根据业务量调整。查询日志表要有created_at索引否则全表扫描会很慢。如果日志量很大建议用分区表或归档到冷存储。5.3 验证码识别在防伪查询里的边界热搜里出现了「php ocr 识别验证码」「php 验证码如何识别」这其实是防刷的另一面有些防伪查询入口会加图形验证码防止脚本自动提交。但验证码本身也可能被 OCR 识别。我的经验是防伪查询场景下图形验证码的收益有限因为消费者体验会变差而攻击者用 OCR 或打码平台都能绕过。更有效的组合是频率限制 行为分析 码本身的空间足够大。如果一定要加验证码用滑块或点选式比纯字符验证码的 OCR 成本高得多。我自己的习惯是防伪系统上线前先跑一轮压力测试用脚本模拟 1000 个并发查询看数据库连接数、Redis 命中率、接口响应时间。压测中暴露的问题比上线后被消费者投诉再排查要便宜得多。这套 PHP 防伪码查询系统源码的核心不在代码多复杂而在每个环节都留了后悔药唯一索引兜底、行锁防并发、频率限制防刷、日志留痕可追溯。希望帮到你。本文还有配套的精品资源点击获取
返回列表