Files
servicebase/pkg/client/netease/live_person.go
2025-11-19 10:23:05 +08:00

198 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package Netease
import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"gitea.ddegame.cn/open/servicebase/pkg/log"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/tjfoc/gmsm/sm3"
)
const (
apiURL = "https://verify.dun.163.com/v1/face/liveness/h5/auth"
recheckTokenUrl = "https://verify.dun.163.com/v1/face/liveness/h5/recheck"
version = "v1"
)
type ApplyInfoReq struct {
Name string
CardNo string
RedirectUrl string
CallBackUrl string
DataId string
CallbackValidate string
EncryptType string
}
type BaseRsp[T any] struct {
Code int `json:"code"`
Msg string `json:"msg"`
Result T `json:"result"`
}
type H5ApplyLivePersonRsp struct {
AuthUrl string `json:"authUrl"`
AuthToken string `json:"authToken"`
}
type H5ReCheckLivePersonTokenRsp struct {
TaskId string `json:"taskId"`
PicType int64 `json:"picType"`
Avatar string `json:"avatar"`
Status int64 `json:"status"`
ReasonType int `json:"reasonType"`
IsPayed int64 `json:"isPayed"`
SimilarityScore float64 `json:"similarityScore"`
FaceMatched int64 `json:"faceMatched"`
FaceAttributeInfo interface{} `json:"faceAttributeInfo"`
ExtInfo ExtInfoEntity `json:"extInfo"`
}
type ExtInfoEntity struct {
SuspectedNonageFlag bool `json:"suspectedNonageFlag"`
}
// H5ApplyLivePerson 请求易盾生成h5人身验证h5 url 和 authToken
func H5ApplyLivePerson(ctx context.Context, req *ApplyInfoReq) (*H5ApplyLivePersonRsp, error) {
params := url.Values{
"name": []string{req.Name},
"cardNo": []string{req.CardNo},
"redirectUrl": []string{req.RedirectUrl},
}
rsp, err := apply(params)
if err != nil {
return nil, err
}
return rsp, nil
}
// H5ReCheckLivePersonToken 根据authToken二次验证并获取相关信息如正面拍照的活体图片
func H5ReCheckLivePersonToken(ctx context.Context, token string) (*H5ReCheckLivePersonTokenRsp, error) {
params := url.Values{
"authToken": []string{token},
}
rsp, err := checkToken(params)
if err != nil {
return nil, err
}
return rsp, nil
}
// 生成签名信息
func genSignature(params url.Values) string {
var paramStr string
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
paramStr += key + params[key][0]
}
paramStr += viper.GetString("netease.captcha.secretKey")
if params["signatureMethod"] != nil && params["signatureMethod"][0] == "SM3" {
sm3Reader := sm3.New()
sm3Reader.Write([]byte(paramStr))
return hex.EncodeToString(sm3Reader.Sum(nil))
}
md5Reader := md5.New()
md5Reader.Write([]byte(paramStr))
return hex.EncodeToString(md5Reader.Sum(nil))
}
// 请求易盾接口
func apply(params url.Values) (*H5ApplyLivePersonRsp, error) {
params["secretId"] = []string{viper.GetString("netease.captcha.secretId")}
params["businessId"] = []string{viper.GetString("netease.captcha.businessId")}
params["version"] = []string{version}
params["timestamp"] = []string{strconv.FormatInt(time.Now().UnixNano()/1000000, 10)}
params["nonce"] = []string{strconv.FormatInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(10000000000), 10)}
params["signature"] = []string{genSignature(params)}
resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", strings.NewReader(params.Encode()))
if err != nil {
return nil, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("请求网易易盾获取h5人身验证失败状态码为%d", resp.StatusCode)
}
defer resp.Body.Close()
contents, _ := io.ReadAll(resp.Body)
log.InfoF("请求网易易盾获取h5人身验证响应: %s", string(contents))
var rsp BaseRsp[*H5ApplyLivePersonRsp]
err = json.Unmarshal(contents, &rsp)
if err != nil {
return nil, errors.WithStack(err)
}
if rsp.Code != 200 {
return nil, errors.Errorf("响应体状态码不是200(%d),信息: %s", rsp.Code, rsp.Msg)
}
return rsp.Result, nil
}
func checkToken(params url.Values) (*H5ReCheckLivePersonTokenRsp, error) {
params["secretId"] = []string{viper.GetString("netease.captcha.secretId")}
params["businessId"] = []string{viper.GetString("netease.captcha.businessId")}
params["version"] = []string{version}
params["timestamp"] = []string{strconv.FormatInt(time.Now().UnixNano()/1000000, 10)}
params["nonce"] = []string{strconv.FormatInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(10000000000), 10)}
params["signature"] = []string{genSignature(params)}
resp, err := http.Post(recheckTokenUrl, "application/x-www-form-urlencoded", strings.NewReader(params.Encode()))
if err != nil {
return nil, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("请求网易易盾获取h5人身验证失败状态码为%d", resp.StatusCode)
}
defer resp.Body.Close()
contents, _ := io.ReadAll(resp.Body)
log.InfoF("请求网易云盾获取人身核验二次验证token响应: %s", string(contents))
var rsp BaseRsp[*H5ReCheckLivePersonTokenRsp]
err = json.Unmarshal(contents, &rsp)
if err != nil {
return nil, errors.WithStack(err)
}
if rsp.Code != 200 {
return nil, errors.Errorf("响应体状态码不是200(%d),信息: %s", rsp.Code, rsp.Msg)
}
return rsp.Result, nil
}
var livePersonAndIdCheckFailReason = map[int]string{
2: "活体通过,姓名身份证号一致,人脸比对非同一人",
3: "活体通过,姓名身份证号不一致",
4: "活体不通过",
5: "活体检测超时或出现异常",
6: "活体通过,查无此身份证",
7: "活体通过,库中无此身份证照片",
8: "活体通过,人脸照过大",
9: "活体通过,权威数据源出现异常",
10: "疑似攻击,建议拦截",
11: "检测对象为未成年人",
}
func GetLivePersonAndIdCheckFailReason(code int) string {
r, ok := livePersonAndIdCheckFailReason[code]
if !ok {
return fmt.Sprintf("其他原因ReasonType==%d", code)
}
return r
}