微信小程序学习实录15:微信小程序基于百度云人脸识别的刷脸打卡开发方案


微信小程序基于百度云人脸识别的刷脸打卡开发方案

一、方案概述

你需要开发的是一套基于微信小程序+百度云人脸识别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 KeySecret Key(用于调用接口时生成Access Token);
  • 创建人脸库(Group),用于存储企业/组织内的用户人脸信息。
(2)小程序配置
  • 在微信公众平台配置小程序服务器域名(uploadFilerequest域名);
  • 开启摄像头权限(小程序需在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. 部署步骤

  1. 后端服务部署至云服务器(如阿里云ECS/腾讯云CVM),配置PHP运行环境;
  2. 配置域名+HTTPS(微信小程序要求接口必须为HTTPS);
  3. 百度云API配额配置:根据打卡人数调整QPS(每秒请求数),避免接口限流;
  4. 小程序代码上传至微信公众平台,提交审核(需说明摄像头、网络等权限用途)。

2. 运维监控

  • 日志记录:记录人脸比对失败、接口调用异常等日志,便于排查问题;
  • 百度云API监控:查看接口调用量、失败率,及时调整配额;
  • 数据库备份:定期备份打卡记录,防止数据丢失。

六、常见问题及解决方案

问题 解决方案
人脸比对成功率低 1. 提高拍摄引导,要求用户正对摄像头;2. 调整百度云比对阈值(如75%);3. 支持用户重新注册人脸
图片上传失败 1. 检查小程序域名配置;2. 优化图片压缩,控制文件大小;3. 增加上传重试机制
百度云Token过期 后端缓存Token(有效期30天),过期自动重新获取
小程序摄像头调用失败 1. 引导用户开启摄像头权限;2. 检查小程序基础库版本,兼容低版本

总结

  1. 本方案核心是小程序端人脸采集+后端调用百度云API比对,需优先完成百度云人脸库创建、小程序域名配置等前置工作;
  2. 关键优化点为图片压缩 (减少传输耗时)、活体检测 (提高安全性)、友好交互(提升用户体验);
  3. 部署时需注意HTTPS配置、接口权限校验,运维阶段重点监控百度云API调用状态和打卡记录数据安全。
相关推荐
盐焗西兰花2 小时前
鸿蒙学习实战之路-PDF转换指定页面或指定区域为图片
学习·pdf·harmonyos
鄭郑2 小时前
【Playwright学习笔记 06】用户视觉定位的方法
笔记·学习
楼田莉子2 小时前
Linux系统小项目——“主从设计模式”进程池
linux·服务器·开发语言·c++·vscode·学习
云边散步2 小时前
godot2D游戏教程系列一(9)-终结
学习·游戏·游戏开发
jrlong2 小时前
DataWhale大模型基础与量化微调task4学习笔记(第 1章:参数高效微调_LoRA 方法详解)
笔记·学习
鄭郑3 小时前
【Playwright学习笔记 07】其它用户视觉定位的方法
笔记·学习
LYS_06183 小时前
寒假学习(5)(C语言5+模数电5)
c语言·学习·模数电
火云洞红孩儿3 小时前
2026年,用PyMe可视化编程重塑Python学习
开发语言·python·学习
莫桐3 小时前
微信小程序-ios环境下webview打开的h5页面replace跳转方式不生效问题
ios·微信小程序·小程序