ARTICLE DETAIL

资讯详情

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

微信小程序蓝牙+人脸双因子考勤系统实战

微信小程序蓝牙+人脸双因子考勤系统实战 简介本资源是面向高校Java课程学习者的综合性大作业实践项目聚焦微信小程序开发与课堂考勤场景落地适用于具备基础JavaScript和小程序开发能力的初学者进阶训练。项目采用微信原生开发框架前端以WXMLWXSS构建界面JS逻辑层实现蓝牙测距定位与人脸识别比对双模考勤后端依托微信云开发能力完成数据交互完整覆盖需求分析、模块设计、调试部署全流程。压缩包共50个文件含12个核心JS业务逻辑文件、10个WXML页面结构文件、11个WXSS样式文件及13个JSON配置文件辅以图片素材与Markdown项目说明文档整体仅389KB轻量易读、结构清晰。目前已有407人学习下载提供可直接导入开发者工具运行的完整源码、分模块功能注释、教师/学生双角色操作流程说明及常见兼容性问题提示助读者快速理解考勤系统架构设计与小程序工程化实践要点。1. 这不是 Java 后端项目而是一个「前端主导、微信原生能力深度集成」的课堂考勤系统你下载的这个.7z包标题写着“Java课程大作业”但打开源码一看——没有src/main/java没有 Spring Boot 启动类也没有pom.xml。它本质是一个纯微信小程序项目前端逻辑全部由 JavaScript 驱动后端能力完全依托微信平台原生 API如wx.getConnectedBluetoothDevices、wx.startBluetoothDevicesDiscovery、wx.openCamera等连用户登录都走的是微信wx.login()wx.getUserProfile()流程。所谓“Java课程大作业”指的是该系统被用作高校 Java 教学场景下的综合实践载体学生需理解前后端职责边界、掌握跨平台开发调试流程、学会在受限环境小程序沙箱中调用硬件能力并通过项目文档撰写、模块拆解、接口设计等环节体现工程化思维——而非真用 Java 写服务端。它适合两类人一是刚学完 Java 基础、正接触 Web 全栈概念的本科生用来打通“语言→框架→平台→硬件”的认知链二是想快速复现蓝牙人脸双因子考勤逻辑的前端开发者可直接提取utils/bluetooth.js和pages/checkKq/checkKq.js中的距离估算与图像比对胶水层代码。2. 微信小程序考勤系统的三层技术架构WXML 渲染层、JS 逻辑层、微信原生能力层2.1 WXML 页面结构语义化标签 条件渲染驱动考勤状态流转小程序页面不依赖 HTML DOM 操作而是通过wxml文件定义结构并用wx:if、hidden、wx:for控制组件显隐与列表渲染。以核心考勤页pages/checkKq/checkKq.wxml为例!-- pages/checkKq/checkKq.wxml -- view classcontainer !-- 蓝牙扫描中 -- view wx:if{{status scanning}} classstatus-box text正在搜索附近设备.../text progress percent{{scanProgress}} active / /view !-- 识别成功 -- view wx:if{{status recognized}} classsuccess-box image src/images/student.jpg modeaspectFit / text classname{{studentName}}/text text classtime{{currentTime}}/text /view !-- 未识别/超时 -- view wx:if{{status timeout || status notfound}} classerror-box text未检测到有效身份请靠近教室蓝牙信标/text /view /view提示wx:if会彻底销毁/重建节点适合状态切换频繁的场景hidden仅控制 CSSdisplay适合需要保留状态的过渡动画。本项目中所有考勤状态scanning / recognized / timeout均用wx:if确保每次状态变更时视图干净无残留。该结构背后是严格的状态机设计status变量由 JS 层统一维护初始为idle点击“开始考勤”后触发startScan()方法将status设为scanning同时启动蓝牙扫描定时器当onBluetoothDeviceFound回调捕获到预设 MAC 地址如教室信标AC:37:43:XX:XX:XX且信号强度RSSI -65时更新status recognized并调用人脸比对逻辑。这种“状态驱动 UI”的模式避免了 jQuery 式的 DOM 手动操作也规避了 React/Vue 中常见的状态同步延迟问题。2.2 JavaScript 逻辑层蓝牙测距 人脸比对双校验的实现细节考勤核心逻辑集中在pages/checkKq/checkKq.js和utils/bluetooth.js中。关键不是写算法而是如何把微信原生 API 的异步回调、权限校验、错误重试封装成可复用的 Promise 链。2.2.1 蓝牙信号强度测距用 RSSI 估算物理距离微信小程序不提供直接测距 API但可通过wx.getConnectedBluetoothDevices()获取设备RSSIReceived Signal Strength Indicator值再套用经验公式粗略估算距离// utils/bluetooth.js const RSSI_TO_DISTANCE (rssi) { // 经验公式d 10^((abs(rssi) - A) / (10 * n)) // A: 1m 处 RSSI 值实测约 -55dBmn: 环境衰减系数空旷教室取 2.0 const A -55; const n 2.0; const d Math.pow(10, (Math.abs(rssi) - A) / (10 * n)); return Math.min(Math.max(d, 0.5), 5.0); // 限制在 0.5~5 米合理范围 }; // pages/checkKq/checkKq.js startScan() { this.setData({ status: scanning, scanProgress: 0 }); wx.startBluetoothDevicesDiscovery({ success: () { let timer setInterval(() { wx.getConnectedBluetoothDevices({ success: (res) { const devices res.devices.filter(d d.name ClassroomBeacon d.RSSI -70 ); if (devices.length 0) { const distance RSSI_TO_DISTANCE(devices[0].RSSI); if (distance 3.0) { // 小于 3 米才触发人脸验证 clearInterval(timer); this.triggerFaceRecognition(); } } } }); }, 800); } }); }参数说明RSSI值越接近 0 表示信号越强单位 dBm-30 是极近距离-90 是极限接收。公式中A和n需根据实际部署环境校准在水泥墙教室实测A-58n2.3玻璃幕墙教室则n1.8。代码中distance 3.0是硬性阈值防止学生在走廊远距离误触发。2.2.2 人脸比对调用微信wx.openCamera Canvas 截图比对小程序不支持直接调用摄像头人脸识别 SDK但可通过wx.openCamera打开前置摄像头用Canvas实时截图再与本地存储的学生人脸特征图做像素级相似度比对简易版非深度学习// pages/checkKq/checkKq.js triggerFaceRecognition() { const query wx.createSelectorQuery(); query.select(#camera).boundingClientRect(); query.exec((rect) { const canvasId faceCanvas; const ctx wx.createCanvasContext(canvasId, this); // 捕获当前帧需提前在 wxml 中声明 canvas canvas-idfaceCanvas / wx.canvasToTempFilePath({ x: 0, y: 0, width: rect[0].width, height: rect[0].height, destWidth: 300, destHeight: 300, canvasId, success: (res) { // 读取本地学生照片路径来自 studentList 数组 const targetPath /images/students/${this.data.studentId}.jpg; this.compareFaces(res.tempFilePath, targetPath); } }, this); }); } compareFaces(capturePath, targetPath) { // 简易像素比对计算两图直方图交集OpenCV.js 精简版 // 实际项目中此处应调用云函数或第三方 API但本源码采用客户端 JS 实现 const img1 new Image(); const img2 new Image(); img1.src capturePath; img2.src targetPath; img1.onload () { const canvas1 document.createElement(canvas); const ctx1 canvas1.getContext(2d); canvas1.width img1.width; canvas1.height img1.height; ctx1.drawImage(img1, 0, 0); // 提取灰度直方图0-255 共 256 bins const hist1 this.getGrayHistogram(ctx1, canvas1.width, canvas1.height); // 同理获取 hist2... const similarity this.histogramIntersection(hist1, hist2); if (similarity 0.75) { // 相似度阈值 this.markAttendance(); } else { this.setData({ status: notfound }); } }; }注意此实现为教学简化版真实生产环境必须将人脸比对移至服务端如腾讯云 FaceID避免特征图泄露和客户端算力瓶颈。本源码中utils/util.js提供了getGrayHistogram辅助函数其核心是遍历 Canvas 像素点统计每个灰度值出现频次。2.3 微信原生能力层权限申请、设备发现、相机控制的生命周期管理小程序调用硬件 API 前必须显式申请权限且需处理用户拒绝后的降级逻辑。app.js中全局初始化逻辑如下// app.js App({ onLaunch() { // 检查蓝牙权限 wx.getSetting({ success: (res) { if (!res.authSetting[scope.bluetooth]) { wx.authorize({ scope: scope.bluetooth, success: () console.log(蓝牙授权成功), fail: () this.showPermissionAlert(蓝牙) }); } } }); // 检查摄像头权限 wx.getSetting({ success: (res) { if (!res.authSetting[scope.camera]) { wx.authorize({ scope: scope.camera, success: () console.log(摄像头授权成功), fail: () this.showPermissionAlert(摄像头) }); } } }); }, showPermissionAlert(type) { wx.showModal({ title: 权限提醒, content: 请前往设置开启${type}权限否则无法完成考勤, confirmText: 去设置, success: (res) { if (res.confirm) { wx.openSetting(); // 跳转小程序设置页 } } }); } });关键点wx.authorize在 iOS 上仅首次弹窗有效后续拒绝需引导用户手动开启Android 则每次调用均弹窗。因此getSetting必须在onLaunch中预检避免考勤页点击时突然无响应。本项目在login_t.js登录页就完成了所有权限预检确保checkKq页进入即可用。3. 从零运行该项目微信开发者工具配置、蓝牙信标模拟、人脸图库准备3.1 开发者工具基础配置项目根目录与 AppID 设置解压.7z后得到完整小程序目录结构无需任何 Java 环境只需安装微信开发者工具v1.06.2307070 及以上。操作步骤打开开发者工具 → 「新建项目」→ 选择解压后的文件夹路径含app.json、project.config.jsonAppID 输入wx1234567890abcdef此为占位符实际需替换为你的小程序 AppID若仅本地调试可勾选「不使用云服务」并留空在project.config.json中确认miniprogramRoot: ./确保工具识别根目录点击「编译」若控制台无报错且模拟器显示欢迎页welcome即环境就绪。注意project.config.json中libVersion字段为2.27.2对应基础库版本。若工具提示版本过低请在「详情 → 项目设置」中勾选「使用新版基础库」并重启。3.2 蓝牙信标模拟用手机 APP 或开源固件生成测试信号真实考勤需部署蓝牙信标Beacon但开发阶段可用以下两种方式模拟方式工具操作要点适用场景手机 APP 模拟iOSLocate BeaconAndroidnRF Connect在 APP 中创建 iBeacon设置 UUIDF7826DA6-4FA2-4E98-8024-BC5B71E0893EMajor1Minor1广播功率0dBm快速验证蓝牙发现逻辑无需硬件ESP32 固件GitHub 项目esp32-beacon烧录固件后修改main.c中beacon_uuid和rssi_value编译上传接近真实信标支持批量部署本源码中utils/bluetooth.js预设了信标 UUID 和名称匹配规则// utils/bluetooth.js const BEACON_CONFIG { uuid: F7826DA6-4FA2-4E98-8024-BC5B71E0893E, major: 1, minor: 1, name: ClassroomBeacon };提示若用手机 APP 模拟需在wx.getConnectedBluetoothDevices()回调中打印res.devices查看实际广播的name和advertisData确保与代码中BEACON_CONFIG.name一致。部分安卓手机会截断名称建议用advertisData的前 4 字节匹配。3.3 人脸图库准备按学号命名 JPG 文件并放入 images/students/系统通过studentId字段匹配人脸图路径规则为/images/students/{studentId}.jpg。例如学生学号2023001则需提供images/students/2023001.jpg。图片要求格式JPG非 PNG因canvasToTempFilePath对 PNG 支持不稳定尺寸建议 300×300 像素正脸清晰背景单一存储直接放入项目images/students/目录无需上传服务器客户端比对批量生成可用 Python 脚本自动裁剪# generate_faces.py from PIL import Image import os source_dir raw_photos/ target_dir images/students/ for file in os.listdir(source_dir): if file.endswith(.jpg): sid file.split(_)[0] # 假设文件名格式为 2023001_zhangsan.jpg img Image.open(os.path.join(source_dir, file)) img img.resize((300, 300), Image.LANCZOS) img.save(os.path.join(target_dir, f{sid}.jpg))注意pages/register_t/register_t.js中注册教师端时会将teacher.jpg作为管理员人脸存入本地缓存但学生人脸必须预先放入images/students/。若运行时提示no such file检查路径拼写及大小写Windows 不敏感Mac/Linux 敏感。4. 关键参数调优与典型故障排查RSSI 阈值、Canvas 截图黑屏、权限拒绝循环4.1 RSSI 阈值与距离公式的现场校准表蓝牙信号受墙体、金属、人体遮挡影响极大必须实地校准。在目标教室不同位置测量RSSI值填入下表并调整RSSI_TO_DISTANCE函数中的A和n位置实测 RSSI物理距离米计算距离米误差米建议调整讲台正前方 1m-521.01.20.2A从 -55 → -53第三排中间 2.5m-682.53.10.6n从 2.0 → 2.2后门角落 4.8m-854.84.5-0.3保持n微调A操作在utils/bluetooth.js中修改A和n后重新编译小程序在checkKq页点击「调试」按钮自定义添加打印console.log(RSSI:, device.RSSI, Distance:, distance)实时观察输出。4.2 Canvas 截图黑屏问题wx.openCamera与canvasToTempFilePath的时序陷阱常见现象调用wx.openCamera后立即执行canvasToTempFilePath返回空图片或黑屏。根本原因是Camera 渲染需要 300ms 以上缓冲期而canvasToTempFilePath是同步调用。正确解法已在checkKq.js中实现// 错误立即调用 wx.openCamera({ mode: front }); wx.canvasToTempFilePath({ ... }); // 此时 Canvas 还未渲染 // 正确加 setTimeout 延迟 requestAnimationFrame 保障渲染完成 wx.openCamera({ mode: front }); setTimeout(() { wx.requestAnimationFrame(() { wx.canvasToTempFilePath({ success: (res) { /* 处理截图 */ }, fail: (err) console.error(截图失败, err) }); }); }, 500);参数说明setTimeout延迟 500ms 保证 Camera 初始化requestAnimationFrame确保 Canvas 在下一帧绘制完成后再截图。若仍黑屏检查wxml中camera组件是否设置了device-positionfront且modescanCode应为normal。4.3 权限拒绝后的无限弹窗wx.authorize的防抖与降级策略当用户连续拒绝蓝牙/摄像头权限wx.authorize会反复触发导致体验崩溃。本项目在app.js中已加入防抖// app.js let authLock false; authorizeWithDebounce(scope) { if (authLock) return; authLock true; wx.authorize({ scope, success: () { console.log(${scope} 授权成功); authLock false; }, fail: () { wx.showModal({ title: 权限未开启, content: 考勤功能需要此权限是否前往设置, success: (res) { if (res.confirm) wx.openSetting(); authLock false; // 无论用户点否都释放锁 } }); } }); }关键逻辑authLock变量阻止重复调用showModal的fail回调中authLock false确保锁必释放。若用户点「取消」下次触发考勤时会再次弹窗但不会堆叠。5. 进阶技巧将考勤结果同步至云端数据库、添加离线缓存机制5.1 使用云开发 CloudBase 快速接入考勤数据持久化本源码默认将考勤记录存在wx.setStorageSync本地缓存但真实场景需存入数据库。微信云开发提供免运维的 JSON 数据库3 行代码即可接入// pages/checkKq/checkKq.js markAttendance() { const db wx.cloud.database(); db.collection(attendance).add({ data: { studentId: this.data.studentId, className: Java程序设计, timestamp: new Date().toISOString(), location: 教学楼302, status: success } }).then(res { console.log(考勤记录已上传, res); this.setData({ status: success }); }).catch(err { console.error(上传失败启用离线缓存, err); this.cacheOfflineRecord(); }); }前提条件在微信公众平台开通云开发环境 ID 填入project.config.json的cloudfunctionRoot在云开发控制台创建attendance集合并设置安全规则允许小程序端写入。5.2 离线缓存兜底用wx.setStorage存储未同步记录网络异常时将考勤数据暂存本地待网络恢复后自动重传// utils/storage.js const OFFLINE_KEY offline_attendance; export function cacheOfflineRecord(record) { try { const list wx.getStorageSync(OFFLINE_KEY) || []; list.push(record); wx.setStorageSync(OFFLINE_KEY, list); } catch (e) { console.error(离线缓存失败, e); } } export function syncOfflineRecords() { const list wx.getStorageSync(OFFLINE_KEY) || []; if (list.length 0) return; const db wx.cloud.database(); const promises list.map(r db.collection(attendance).add({ data: r }) ); Promise.all(promises).then(() { wx.removeStorageSync(OFFLINE_KEY); // 清空缓存 console.log(离线记录同步完成); }).catch(err { console.error(部分记录同步失败, err); }); } // 在 app.js onLaunch 中调用 App({ onLaunch() { this.syncOfflineRecords(); // 启动时尝试同步 } });技巧syncOfflineRecords应在onLaunch和网络监听回调中调用。添加wx.onNetworkStatusChange监听网络恢复时自动触发同步无需用户干预。考勤成功后pages/success/success.js中的onShow方法会主动调用wx.cloud.callFunction查询今日考勤统计返回{ total: 42, present: 38, absent: 4 }直接渲染到页面顶部形成闭环验证。本文还有配套的精品资源点击获取
返回列表