尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于ThinkPHP与Laravel的人脸识别考勤系统设计与实现

发布时间:2026/9/26 13:14:00

资讯中心
01
ARTICLE

基于ThinkPHP与Laravel的人脸识别考勤系统设计与实现

基于ThinkPHP与Laravel的人脸识别考勤系统设计与实现
Response## 1. 项目定位考勤系统为什么必须做人脸识别以及我对技术栈的解读先聊一个最容易被忽略的问题考勤系统一旦上人脸识别整个产品形态完全不一样了。传统打卡机主要靠指纹、IC卡、密码这几种方式有共同的毛病——指纹磨损了识别率断崖式下降卡容易丢密码可以被代打。人脸识别算是目前体验和安全性平衡得最好的考勤方案不需要接触设备员工走到摄像头前看一秒就能完成打卡而且很难被代打。我这次做的基于ThinkPHP-Laravel的人脸识别考勤管理系统Vue前端本质是一次集成型项目一台人脸识别门禁终端负责抓拍和特征比对服务端负责考勤规则计算和业务数据管理Vue负责呈现给管理员和员工。这里先解释一个看起来诡异的点——标题里为什么同时出现ThinkPHP和Laravel两个框架。在实际项目里纯从零开始选型很少会把两个框架硬塞进一个仓库但集成类项目很常见的情况是老管理后台跑在ThinkPHP上里面沉淀了部门、员工、班次等基础数据不太可能一夜之间重写新的开放API和移动端接口又想用更现代的Laravel来实现。权衡之后我把系统拆成了三个模块设备接入层用ThinkPHP搭了一个轻量网关负责接收门禁终端上传的打卡事件、心跳检测和设备状态上报。业务API层用Laravel提供REST API处理员工管理、考勤规则、报表统计给Vue前端调用。前端展示层Vue 3 Element Plus做成SPA管理人员在浏览器里实时查看考勤情况。这样分的好处很明显ThinkPHP网关和硬件打交道很稳定Laravel API侧则可以利用中间件、队列、Eloquent这些成熟机制快速开发业务。两边通过内部HTTP接口通信不共享会话状态以后拆成独立服务也容易得多。什么人适合参考这套方案如果你在公司里接到类似把旧考勤系统升级成人脸识别、给现有门禁设备加上考勤管理平台这种需求又恰好团队技术栈是PHP为主这篇文章应该能帮你少走不少弯路。我会把数据库表设计、打卡链路、迟到早退计算、Vue前端实现、以及我在部署过程中踩过的框架版本坑全部展开来讲。2. 后端怎么拆ThinkPHP与Laravel的分工、数据表与人脸比对流程2.1 框架分工谁管设备接入谁管业务API可能有人会问设备终端直接调Laravel不就行了为什么还要在中间放一层ThinkPHP网关我实际测试后的结论是人脸识别门禁终端的上报协议很不标准。有的终端走HTTP JSON有的走私有TCP协议有的文档里写的字段跟实际推送的根本对不上。如果把这种不稳定的对接逻辑直接塞进Laravel业务代码里一旦终端厂商升级协议就得重新发布整个API服务。用ThinkPHP做一层独立的设备网关终端只需要认识这一个地址网关负责做协议转换和字段清洗转发给Laravel的有效载荷永远是统一格式{ device_id: A1001, event: PUNCH, employee_no: E201, punch_time: 2025-01-01 09:00:01, face_score: 96.8 }这样做还有个隐藏收益网关可以缓冲掉设备网络抖动带来的重复上报。终端的TCP重传机制经常导致同一条打卡记录到达两次网关里做一层幂等去重后面业务侧就轻松很多。模块框架职责典型接口设备网关ThinkPHP终端接入、协议转换、心跳检测、幂等去重/device/punch/device/heartbeat业务APILaravel员工、班次、考勤记录、报表、登录鉴权/api/staff/api/attendance前端Vue 3管理后台、实时看板、打卡记录查询浏览器SPA2.2 数据表设计把考勤表设计成可追溯而不是可展示考勤系统最容易踩的坑是数据库表设计得只满足页面展示后期想回溯问题就抓瞎。比如员工某天为什么迟到管理员想看到底是设备上报延迟还是人为修改过就得靠日志表。我的核心表设计如下重点都在可追溯上员工表staff字段类型说明idint主键employee_novarchar工号终端识别的Keynamevarchar姓名dept_idint部门shift_idint班次face_featuretext人脸特征向量JSON或Base64statustinyint1正常 0停用考勤记录表attendance字段类型说明idint主键employee_novarchar工号punch_timedatetime打卡时间biz_datedate业务日期按这个字段做月报statevarcharnormal/late/early/absentdevice_idvarchar来源设备face_scoredecimal人脸相似度sourcetinyint0终端上报 1手动补登 2APIcreated_atdatetime记录创建时间这里的biz_date特别重要很多新人直接用punch_time分组结果夜班员工在凌晨打卡的数据被算到前一天月报怎么都对不上。业务日期必须由排班逻辑决定而不是打卡时间。班次表shiftid, name, start_time, end_time, late_grace_minutes, early_leave_grace_minuteslate_grace_minutes是宽限分钟数比如班次9点上班允许9:05前打卡不算迟到。宽限逻辑绝不能写死在代码里不同公司要求不同做成字段最灵活。2.3 人脸比对主流程本地1:N识别还是云端API人脸识别考勤里最核心的问题是摄像头抓拍到一张脸之后怎么知道这个人是谁现在主流有两种实现路线。一种是设备端本地识别98%的脸部比对是在门禁终端里完成的终端里面内置人脸底库识别成功后才把员工工号推送给服务端另外一种是把摄像头抓拍的图片传回云端人脸识别API由云端做1:N搜索返回最相似的人。我强烈建议优先选设备端本地识别。原因很现实考勤打卡是高频场景如果每次打卡都要传图片到服务器再等API返回延迟轻松超过1秒而且服务器带宽压力很大。门禁终端自带的识别能力通常在0.3秒内就完成终端直接推送工号服务端只需要做业务处理。服务端的人脸识别能力也要保留一份主要是管理员的人脸注册场景。新员工入职时拍一张照片通过Laravel调用人脸识别API提取特征向量写入face_feature字段再推送到每个门禁终端。这个过程不需要实时性走异步队列就可以了。3. 核心打卡链路终端上报、幂等去重和迟到早退规则3.1 打卡数据流是怎么走通的完整链路我梳理成下面几条员工经过门禁终端终端本地识别成功。终端封装设备ID、员工工号、打卡时间、相似度分数通过HTTP发到ThinkPHP网关。ThinkPHP网关校验终端签名查询Redis里是否已有相同device_id employee_no punch_time的键如果有就丢弃没有就写入Redis并转发到Laravel API。Laravel收到数据后写入考勤记录表触发班次规则计算更新当日考勤状态。Vue前端通过WebSocket或定时轮询拉取最新记录刷新实时看板。这里最容易被忽略的是第3步。门禁终端在弱网环境下会重发数据网关不做幂等的话一条打卡会生成好几条考勤记录月底汇总就出大问题。我用的简单做法是$key punch:dedup: . $deviceId . : . $employeeNo . : . $punchTime; $isDuplicate Redis::setnx($key, 1, [EX 1800]); if (!$isDuplicate) { // 重复上报直接丢弃 return response()-json([code 0, msg duplicate]); }用setnx加半小时过期不浪费存储足够覆盖终端重试窗口。3.2 迟到、早退和缺卡的计算逻辑考勤状态计算不能只看打了卡没。一个班次通常要判断两次打卡上班卡和下班卡。我采用的状态模型如下状态判定条件normal按时打卡late打卡时间晚于班次开始时间宽限early非外勤原因下班打卡早于班次结束时间-宽限absent全天无任何打卡记录Laravel侧判定逻辑示例// 假设上班时间为 09:00宽限5分钟 $shiftStart strtotime(09:00); $grace 5 * 60; if ($punchTimestamp $shiftStart $grace) { $state late; }这里有个实战细节跨天班次的start_time处理。比如夜班22:00到次日06:00如果直接比较时间字符串就会算错。我统一做法是手动把班次时间转成Carbon实例后加一天处理再和punch_time比较。缺卡状态不是实时判定的而是在下班时间过后由定时任务统一扫描。每天凌晨一点跑一次脚本把当天应当有打卡记录但实际没有的员工生成缺卡记录并推送给管理员。Laravel的Task Scheduler一行计划任务就够了。3.3 补卡和异常处理别把小问题搞成大流程实际使用中员工总会遇到设备没识别、手机忘带、摄像头被人挡住等情况这时候需要补卡。补卡流程我做了权限控制普通员工可以提交补卡申请部门主管审批后才能生效。补卡本质上是一条source1手动补登的考勤记录但状态由管理员手动指定为normal同时保留原始申请信息方便日后审计。这里要说明一个我自己栽过跟头的点补卡记录如果直接改原记录的状态审计时就完全不知道这条记录是被补来的还是设备正常上报的。所以我才在表里加了source和created_at两个字段排查纠纷时直接看source1的记录一清二楚。4. Vue前端实现要点从登录鉴权到实时打卡看板4.1 工程搭建与代码结构Vue前端我选的是Vue 3 Vite Element Plus Pinia Axios这套组合现在生态最成熟社区里的踩坑案例也多遇到问题基本都能搜到答案。目录结构src/ ├── api/ # 接口封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── layout/ # 后台布局 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── views/ │ ├── dashboard/ # 实时看板 │ ├── staff/ # 员工管理 │ ├── attendance/ # 考勤记录与报表 │ └── login/ # 登录页 └── main.ts启动项目照例是npm create vitelatest attendance-web -- --template vue cd attendance-web npm install npm run dev一个提醒Vite默认端口5173Laravel API跑在8000前后端联调时一定要配代理不然跨域问题能卡掉半天。// vite.config.ts export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } })4.2 登录鉴权Token存哪、路由守卫怎么写考勤系统涉及员工隐私数据登录鉴权不能只做前端假跳转。我用的是简化的JWT方案用户登录后Laravel签发一个Token前端存到localStorage每次请求通过Axios拦截器带上。// axios拦截器 instance.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; });路由守卫决定用户能访问哪些页面。管理员有员工管理、考勤报表权限普通员工只能看到自己的打卡状态和补卡申请入口。我用了Vue Router的beforeEach。router.beforeEach((to, from, next) { const token localStorage.getItem(token); const role localStorage.getItem(role); if (!token to.path ! /login) { next(/login); } else if (to.meta.roles !to.meta.roles.includes(role)) { next(/403); } else { next(); } });不要把角色鉴权只放在前端。前端守卫只是体验优化真正的权限校验必须由Laravel中间件做接口层面不加权限控制等于把后门敞开。4.3 实时预览和实时打卡展示实时看板是这套系统的门面。门禁终端通常支持RTSP视频流但浏览器不能直接播放RTSP需要转封装成HLSm3u8格式后由前端用hls.js播放。相关热搜词里vue播放m3u8讨论很多实际实现并不复杂import Hls from hls.js; function playHls(videoEl, url) { if (Hls.isSupported()) { const hls new Hls({ liveDurationInfinity: true }); hls.loadSource(url); hls.attachMedia(videoEl); } else if (videoEl.canPlayType(application/vnd.apple.mpegurl)) { videoEl.src url; // iOS Safari 原生支持 } }打卡列表部分我没用WebSocket而是做了3秒轮询。因为考勤系统对实时性要求在秒级就够轮询实现简单后端好维护。只有大屏展示的设备状态才用了WebSocket实时反映终端离线还是在线。4.4 考勤报表ECharts做曲线和排行月报页面按员工展示迟到、早退、缺卡、正常天数并用ECharts画柱状图和折线图。接口设计是GET /api/attendance/monthly?month2025-01dept_id3返回聚合数组后前端按日期分组渲染。这里踩过一个教训ECharts在Element Plus的el-tabs里切换Tab时图表宽度经常变成0。解决办法是切换后调用chart.resize()或者用v-if延迟渲染图表容器等DOM完全显示后再初始化。5. 安全与坑点复盘框架漏洞、Session跨域与Electron打包5.1 ThinkPHP老版本安全问题CVE-2024-29291必须重视2024年披露的CVE-2024-29291涉及ThinkPHP的多语言功能低版本存在被利用的风险影响范围很广。我先把结论给出来如果还在用ThinkPHP 6.1.5以下尽快升级到6.1.5或更高版本生产环境务必关闭多语言功能或者限制lang参数来源不要用老框架直接暴露公网设备网关端口前面至少要套一层Nginx ACL或防火墙规则。这类漏洞的特点是修复版本已经发布但很多老项目根本没人跟进升级成了长期在线的定时炸弹。考勤系统连着员工的敏感人脸数据一旦被打穿后果很严重。我也同步检查了Laravel侧Laravel官方安全公告修复得很快关键是别用composer update偷懒要手动确认项目拉到的是安全版本并且把.env里的APP_DEBUG设为falseAPP_KEY必须重新生成。5.2 前后端分离下的Session、Cookie和跨域问题基于Vue的前后端分离项目Session处理是个高频坑。最初我用PHP原生的Session存登录态Laravel API和Vue前端不在同一个域名下Cookie很难维护。后来切到Token方案一开始全部用Bearer Token但很快发现一个问题管理员在管理后台长时间挂机Token过期后突然提交直接变空白页。我的方案是Laravel做双Token机制Access Token短时效2小时Refresh Token长时效7天前端Axios拦截器捕获401时自动用Refresh Token换新的Access Token并重放原请求。整个流程用户无感自定义指令也顺便做了// 自定义按钮权限指令 v-permission app.directive(permission, { mounted(el, binding) { const perms store.perms; if (!perms.includes(binding.value)) { el.parentNode?.removeChild(el); } } });关于跨域Laravel端我推荐写中间件统一设置CORS头但生产环境不要滥用*指定前端域名并设置Access-Control-Allow-Credentials: true更安全。5.3 Electron打包成桌面考勤助手这套系统除了浏览器版后来还被打包成Windows桌面端方便前台管理员开机自动运行。用的工具是electron-builder打包流程npm install electron electron-builder -D npm run build npx electron-builder --win --x64Electron打包Vue项目有几个注意点Vite的base要设成相对路径./否则打包后加载不到静态资源Electron的主进程和渲染进程之间通信要用IPC框架不能用浏览器里那套window全局变量人脸识别终端如果走RTSP取流Electron渲染进程的CSP策略可能阻止播放需要在主进程里允许对应网络域。我在这个阶段还遇到了打包出来的exe体积巨大的问题Vue Element Plus打出来接近90MB。后面通过按需引入Element Plus组件、关闭sourcemap、启用gzip压缩压到了40MB左右。虽然不算极致但已经可以接受。至于外壳加固我是用vite-plugin-electron把主进程和渲染进程打包流程统一管理的开发模式下一条命令同时起Vite和Electron调试体验比分开跑顺畅很多。5.4 一个最容易被忽略的坑时钟同步最后分享一个特别不起眼但影响巨大的问题——人脸识别终端和服务器的时钟不同步。设备录制打卡时间用的是自身系统时间如果终端时间慢了3分钟员工明明9:00到公司设备记成8:57看起来正常哪天设备时间快了5分钟9:02打卡就会被判定迟到线下纠纷能吵到人事部门。我最后的解决方案是写了一个每分钟跑一次的巡检脚本从Laravel侧获取服务器时间批量校准所有在线终端的时钟。这个脚本排查问题的频率比框架漏洞还要高强烈建议你们在上线前就做好终端时间同步机制。最后的实操体会这套考勤系统从需求梳理到上线总共花了大概三周其中一半时间耗在和终端设备联调上真正写业务逻辑的时间反而很少。这也印证了我的一个观点人脸识别考勤系统难点并不在人脸识别算法本身而在于设备对接的健壮性、考勤规则的灵活性和数据回溯的完整性。如果让我重新做一次我会在一开始的网关层就把设备协议适配做成插件化配置而不是后补成一个个兼容分支同时会在第一天就开启Laravel的队列和定时任务因为班次状态流转这种逻辑用脚本定时扫比在接口里实时算要可靠得多。对于准备动手做类似项目的朋友我最后补充一串可以直接做的清单先把终端设备和网关的幂等通讯跑通再谈人脸识别准确率先把数据库的biz_date和source字段设计好再写报表先把框架升级到安全版本再做任何外网映射。这些看起来都是细枝末节但恰恰是它们决定了考勤系统能不能在月底报表出来的那一刻经得住考验。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。