

微信小程序基于百度云人脸识别的刷脸打卡开发方案
一、方案概述
你需要开发的是一套基于微信小程序+百度云人脸识别API的刷脸打卡系统,核心功能为用户通过小程序前置摄像头拍摄人脸、压缩图片后上传至后端,与百度云人脸库中的用户人脸信息比对,完成身份验证并实现打卡。本方案将从技术架构、核心流程、代码实现、部署运维等维度完整阐述开发思路。
二、技术架构设计
1. 整体架构
javascript
graph TD
A[微信小程序端] --> B[后端服务(PHP/Node.js)]
B --> C[百度云人脸识别API]
B --> D[数据库(MySQL/Redis)]
A: 人脸拍摄、图片压缩、交互展示
B: 接口转发、用户鉴权、打卡记录存储
C: 人脸注册、人脸比对、活体检测
D: 存储用户信息、人脸库ID、打卡记录
2. 技术栈选型
| 层级 | 技术/工具 | 说明 |
|---|---|---|
| 小程序端 | 微信原生框架 | 负责前端交互、摄像头调用、图片处理 |
| 后端 | PHP | 封装百度云API、处理小程序请求、数据存储 |
| 人脸识别 | 百度云人脸识别SDK | 提供人脸注册、比对、活体检测核心能力 |
| 数据库 | MySQL | 存储用户OpenID、人脸库ID、打卡记录 |
| 图片存储 | 阿里云OSS/腾讯云COS | 存储用户上传的人脸图片(可选) |
三、核心功能开发流程
1. 前置准备
(1)百度云人脸识别服务开通
- 登录百度智能云控制台,开通"人脸识别"服务;
- 创建应用,获取
API Key和Secret Key(用于调用接口时生成Access Token); - 创建人脸库(Group),用于存储企业/组织内的用户人脸信息。
(2)小程序配置
- 在微信公众平台配置小程序服务器域名(
uploadFile、request域名); - 开启摄像头权限(小程序需在
app.json中声明scope.camera权限); - 配置用户登录态(获取OpenID作为用户唯一标识)。
2. 核心功能模块开发
(1)用户人脸注册(首次使用)
- 流程:用户授权登录→拍摄人脸→上传后端→调用百度云
人脸注册API→绑定OpenID与人脸库ID→存储至数据库; - 核心接口(百度云):
/rest/2.0/face/v3/faceset/user/add。
(2)刷脸打卡(核心流程)
复用你提供的小程序代码逻辑,优化后核心流程:
javascript
graph LR
A[点击"开通刷脸功能"按钮] --> B[锁定按钮+显示加载状态]
B --> C[调用wx.chooseMedia调用前置摄像头]
C --> D[获取图片文件大小并压缩(800*800,质量30%)]
D --> E[上传压缩后的图片至后端]
E --> F[后端调用百度云人脸比对API]
F --> G{比对结果}
G -->|成功(相似度≥80%)| H[记录打卡时间+返回成功提示+跳转打卡页]
G -->|失败| I[返回失败提示+解锁按钮]
3. 关键代码实现
(1)小程序端核心代码(基于你提供的代码优化)
javascript
// pages/faceAuth/faceAuth.js
const util = require('../../utils/util.js')
Page({
data: {
imageUrl: '',
compressedImageUrl: '',
isLoading: false,
isBtnDisabled: false,
resultMsg: '',
userId: '', // 最终替换为用户OpenID
},
onLoad() {
this.showShare();
this.getUserOpenid(); // 优先获取用户OpenID
},
// 获取用户OpenID(关键:用户唯一标识)
getUserOpenid() {
const that = this;
wx.login({
success: (res) => {
wx.request({
url: util.hostDomain + 'api/user/getOpenid',
method: 'POST',
data: { code: res.code },
success: (res) => {
if (res.data.code === 200) {
that.setData({ userId: res.data.openid });
} else {
that.setData({ resultMsg: '登录失败,请重试' });
}
},
fail: () => {
that.setData({ resultMsg: '网络错误,无法获取用户信息' });
}
});
}
});
},
// 拍摄人脸并处理
chooseImage() {
const that = this;
if (!that.data.userId) {
wx.showToast({ title: '用户未登录', icon: 'none' });
return;
}
that.setData({
isBtnDisabled: true,
isLoading: true,
resultMsg: ''
});
wx.chooseMedia({
count: 1,
sizeType: ['original'],
sourceType: ['camera'],
camera: 'front',
success: (res) => {
const tempFilePath = res.tempFiles[0].tempFilePath;
that.setData({ imageUrl: tempFilePath });
// 压缩图片(优化:增加压缩质量动态调整)
that.compressImage(tempFilePath, () => {
that.submitFace();
});
},
fail: (err) => {
console.error('拍摄失败:', err);
that.setData({
resultMsg: '拍摄取消或失败,请重试',
isLoading: false,
isBtnDisabled: false
});
}
});
},
// 图片压缩(优化:动态调整压缩质量)
compressImage(tempFilePath, callback) {
const that = this;
wx.compressImage({
src: tempFilePath,
quality: 30, // 可根据原图大小动态调整
width: 800,
height: 800,
success: (res) => {
that.setData({ compressedImageUrl: res.tempFilePath });
callback();
},
fail: (err) => {
console.error('压缩失败:', err);
that.setData({ compressedImageUrl: tempFilePath });
callback();
}
});
},
// 提交人脸比对
submitFace() {
const that = this;
const uploadFilePath = this.data.compressedImageUrl || this.data.imageUrl;
wx.uploadFile({
url: util.hostDomain + 'api/face/compare', // 后端人脸比对接口
filePath: uploadFilePath,
name: 'files',
formData: { userId: this.data.userId },
success: (res) => {
let result = JSON.parse(res.data);
if (result.code === 1) {
that.setData({ resultMsg: '打卡成功!' });
wx.showToast({
title: "打卡成功",
icon: "success",
success: () => {
setTimeout(() => {
wx.redirectTo({ url: '../faceClock/index' });
}, 1000);
}
});
} else {
that.setData({ resultMsg: '打卡失败:' + (result.msg || '人脸比对不通过') });
wx.showToast({ title: result.msg, icon: 'none' });
}
},
fail: (err) => {
that.setData({ resultMsg: '网络错误,请重试' });
},
complete: () => {
that.setData({ isLoading: false, isBtnDisabled: false });
}
});
},
// 其他方法(showShare等)保持不变
});
(2)后端核心代码(PHP示例)
php
<?php
// api/face/compare.php
header("Content-Type: application/json;charset=utf-8");
header("Access-Control-Allow-Origin: *"); // 小程序端需配置合法域名
// 1. 百度云API配置
$API_KEY = "你的百度云API Key";
$SECRET_KEY = "你的百度云Secret Key";
$GROUP_ID = "你的人脸库Group ID";
// 2. 获取小程序上传的图片和用户ID
$userId = $_POST['userId'];
$file = $_FILES['files'];
// 3. 获取百度云Access Token
function getAccessToken($apiKey, $secretKey) {
$url = "https://aip.baidubce.com/oauth/2.0/token";
$postData = [
'grant_type' => 'client_credentials',
'client_id' => $apiKey,
'client_secret' => $secretKey
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ch);
curl_close($ch);
$res = json_decode($res, true);
return $res['access_token'] ?? '';
}
// 4. 人脸比对(搜索人脸库中匹配的用户)
function faceCompare($accessToken, $filePath, $groupId, $userId) {
$url = "https://aip.baidubce.com/rest/2.0/face/v3/search";
// 将图片转为base64
$image = file_get_contents($filePath);
$imageBase64 = base64_encode($image);
$postData = [
'image' => $imageBase64,
'image_type' => 'BASE64',
'group_id_list' => $groupId,
'user_id' => $userId, // 可选:指定用户比对,提高效率
'match_threshold' => 80 // 相似度阈值,≥80视为通过
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . "?access_token=" . $accessToken);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ch);
curl_close($ch);
return json_decode($res, true);
}
// 主逻辑
$accessToken = getAccessToken($API_KEY, $SECRET_KEY);
if (!$accessToken) {
echo json_encode(['code' => -1, 'msg' => '获取百度云Token失败']);
exit;
}
// 保存上传的图片(临时)
$tempPath = './temp/' . uniqid() . '.jpg';
move_uploaded_file($file['tmp_name'], $tempPath);
// 调用人脸比对
$faceRes = faceCompare($accessToken, $tempPath, $GROUP_ID, $userId);
unlink($tempPath); // 删除临时文件
if ($faceRes['error_code'] !== 0) {
echo json_encode(['code' => -1, 'msg' => $faceRes['error_msg']]);
exit;
}
// 检查相似度
$score = $faceRes['result']['user_list'][0]['score'] ?? 0;
if ($score >= 80) {
// 比对成功,记录打卡记录(示例:写入数据库)
// $db->exec("INSERT INTO clock_record (user_id, clock_time) VALUES ('$userId', NOW())");
echo json_encode(['code' => 1, 'msg' => '人脸比对通过']);
} else {
echo json_encode(['code' => -1, 'msg' => '人脸相似度不足('.$score.'%),打卡失败']);
}
?>
(3)小程序WXML/ WXSS优化
复用你提供的WXML结构,重点优化:
- 增加人脸引导框(帮助用户对齐拍摄);
- 优化按钮状态(加载中禁用、失败后解锁);
- 补充活体检测提示(可选,提高安全性)。
四、关键优化点
1. 图片处理优化
- 你已实现图片压缩(800*800,质量30%),可补充动态压缩策略:原图<500KB时不压缩,>500KB时按比例压缩;
- 上传前校验图片格式(仅允许jpg/png),避免无效上传。
2. 安全性优化
- 增加活体检测 :调用百度云
活体检测API(/rest/2.0/face/v3/faceverify),防止照片/视频作弊; - 后端校验用户OpenID:确保请求来自合法用户,防止接口滥用;
- 接口加签:小程序端请求后端时携带签名(如timestamp+nonce+appSecret),防止接口伪造。
3. 体验优化
- 拍摄时增加人脸引导框(你提供的CSS已包含,需关联到WXML);
- 加载状态可视化:按钮显示loading动画,提示用户等待;
- 错误提示友好化:区分"网络错误""人脸比对失败""权限不足"等不同错误类型。
五、部署与运维
1. 部署步骤
- 后端服务部署至云服务器(如阿里云ECS/腾讯云CVM),配置PHP运行环境;
- 配置域名+HTTPS(微信小程序要求接口必须为HTTPS);
- 百度云API配额配置:根据打卡人数调整QPS(每秒请求数),避免接口限流;
- 小程序代码上传至微信公众平台,提交审核(需说明摄像头、网络等权限用途)。
2. 运维监控
- 日志记录:记录人脸比对失败、接口调用异常等日志,便于排查问题;
- 百度云API监控:查看接口调用量、失败率,及时调整配额;
- 数据库备份:定期备份打卡记录,防止数据丢失。
六、常见问题及解决方案
| 问题 | 解决方案 |
|---|---|
| 人脸比对成功率低 | 1. 提高拍摄引导,要求用户正对摄像头;2. 调整百度云比对阈值(如75%);3. 支持用户重新注册人脸 |
| 图片上传失败 | 1. 检查小程序域名配置;2. 优化图片压缩,控制文件大小;3. 增加上传重试机制 |
| 百度云Token过期 | 后端缓存Token(有效期30天),过期自动重新获取 |
| 小程序摄像头调用失败 | 1. 引导用户开启摄像头权限;2. 检查小程序基础库版本,兼容低版本 |
总结
- 本方案核心是小程序端人脸采集+后端调用百度云API比对,需优先完成百度云人脸库创建、小程序域名配置等前置工作;
- 关键优化点为图片压缩 (减少传输耗时)、活体检测 (提高安全性)、友好交互(提升用户体验);
- 部署时需注意HTTPS配置、接口权限校验,运维阶段重点监控百度云API调用状态和打卡记录数据安全。