package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
// APIConfig API配置
type APIConfig struct {
Key string // API Key
OpenID string // OpenID,用于生成加密密钥
}
// VerifyRequest 人脸对比请求参数
type VerifyRequest struct {
IDCard string // 身份证号码
RealName string // 真实姓名
Image string // 人脸图片base64编码(不含data:image/jpg;base64,前缀)
Thousand string // 可选,1:返回千分制分数
}
// VerifyResult 返回结果
type VerifyResult struct {
Message string `json:"message"` // 识别说明
Res int `json:"res"` // 比对结果
Score string `json:"score"` // 比对分值(res为1时存在)
Code string `json:"code"` // res为2时存在
Desc string `json:"desc"` // res为2时存在
}
// APIResponse API响应
type APIResponse struct {
ErrorCode int `json:"error_code"` // 返回码,0计费一次
Reason string `json:"reason"` // 返回说明
Result VerifyResult `json:"result"` // 返回数据
}
// getAESKey 根据OpenID生成AES加密密钥
// 规则:OpenId经过Md5后取前16位,并转小写
func getAESKey(openID string) string {
hash := md5.Sum([]byte(openID))
hashStr := fmt.Sprintf("%x", hash) // 转换为小写16进制字符串
return hashStr[:16] // 取前16位
}
// aesEncrypt AES/ECB/PKCS5Padding 加密
func aesEncrypt(plaintext, key string) (string, error) {
block, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
// PKCS5Padding
plainBytes := []byte(plaintext)
padding := aes.BlockSize - len(plainBytes)%aes.BlockSize
padtext := append(plainBytes, strings.Repeat(string(byte(padding)), padding)...)
// ECB 模式加密
encrypted := make([]byte, len(padtext))
blockMode := NewECBEncrypter(block)
blockMode.CryptBlocks(encrypted, padtext)
// Base64编码
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// ECB 模式实现
type ecb struct {
b cipher.Block
blockSize int
}
func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
return &ecb{b: b, blockSize: b.BlockSize()}
}
func (x *ecb) BlockSize() int { return x.blockSize }
func (x *ecb) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
// VerifyFace 调用人脸对比加密版API
func VerifyFace(config APIConfig, req VerifyRequest) (*APIResponse, error) {
apiURL := "http://apis.juhe.cn/verifyface/verifyEncry"
// 生成AES密钥
aesKey := getAESKey(config.OpenID)
// 加密身份证号码
encryptedIDCard, err := aesEncrypt(req.IDCard, aesKey)
if err != nil {
return nil, fmt.Errorf("加密身份证号码失败: %v", err)
}
// 加密姓名
encryptedRealName, err := aesEncrypt(req.RealName, aesKey)
if err != nil {
return nil, fmt.Errorf("加密姓名失败: %v", err)
}
// 加密图片base64
encryptedImage, err := aesEncrypt(req.Image, aesKey)
if err != nil {
return nil, fmt.Errorf("加密图片失败: %v", err)
}
// 构建POST表单数据
formData := url.Values{}
formData.Set("key", config.Key)
formData.Set("idcard", encryptedIDCard)
formData.Set("realname", encryptedRealName)
formData.Set("image", encryptedImage)
if req.Thousand != "" {
formData.Set("thousand", req.Thousand)
}
// 发送POST请求
resp, err := http.Post(
apiURL,
"application/x-www-form-urlencoded",
strings.NewReader(formData.Encode()),
)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %v", err)
}
// 解析JSON响应
var apiResp APIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
return &apiResp, nil
}
// imageToBase64 读取本地图片文件并转换为base64编码
func imageToBase64(imagePath string) (string, error) {
// 读取图片文件
imageData, err := os.ReadFile(imagePath)
if err != nil {
return "", fmt.Errorf("读取图片文件失败: %v", err)
}
// 转换为base64编码
base64Str := base64.StdEncoding.EncodeToString(imageData)
return base64Str, nil
}
// 解释比对结果
func explainResult(result VerifyResult) string {
switch result.Res {
case 1:
return fmt.Sprintf("✓ 身份证号与姓名匹配,且比对成功。相似度分数: %s (推荐阈值≥60)", result.Score)
case 2:
return fmt.Sprintf("✗ 身份证核查成功,但人脸比对失败。错误码: %s, 说明: %s", result.Code, result.Desc)
case 3:
return "✗ 身份证号与姓名不匹配"
default:
return "未知结果"
}
}
func main() {
// 示例用法
config := APIConfig{
Key: "7a312b221d9a8cce31xxxxxxxxxx", // 请替换为你的API Key
OpenID: "JH285250c81e065e255dxxxxxxxxxxxxx", // 请替换为你的OpenID
}
// 读取本地图片并转换为base64
imagePath := "/Users/java0904/test264-go/face.jpg" // 请将图片放在项目根目录,或使用完整路径
fmt.Printf("正在读取图片: %s\n", imagePath)
imageBase64, err := imageToBase64(imagePath)
if err != nil {
fmt.Printf("读取图片失败: %v\n", err)
return
}
fmt.Printf("图片读取成功,大小: %.2f KB\n", float64(len(imageBase64))/1024*3/4) // base64解码后的大小
request := VerifyRequest{
IDCard: "320382xxxxxxxxxxxx19", // 请替换为实际身份证号码
RealName: "魏xxxxxxx", // 请替换为实际姓名
Image: imageBase64, // 使用读取的图片base64编码
Thousand: "", // 可选,传"1"返回千分制分数
}
fmt.Println("正在调用人脸对比API...")
result, err := VerifyFace(config, request)
if err != nil {
fmt.Printf("调用失败: %v\n", err)
return
}
// 输出结果
fmt.Printf("\n=== API响应 ===\n")
fmt.Printf("返回码: %d\n", result.ErrorCode)
fmt.Printf("返回说明: %s\n", result.Reason)
fmt.Printf("识别说明: %s\n", result.Result.Message)
fmt.Printf("比对结果: %s\n", explainResult(result.Result))
// 详细结果(JSON格式)
jsonResult, _ := json.MarshalIndent(result, "", " ")
fmt.Printf("\n=== 完整响应(JSON) ===\n%s\n", string(jsonResult))
}
登录