492 lines
15 KiB
Go
492 lines
15 KiB
Go
package coryCommon
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/net/gclient"
|
||
"github.com/gogf/gf/v2/os/gctx"
|
||
"github.com/tiger1103/gfast-cache/cache"
|
||
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
|
||
"github.com/tiger1103/gfast/v3/library/liberr"
|
||
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
|
||
"golang.org/x/net/context"
|
||
"io"
|
||
"io/ioutil"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
/**
|
||
功能:
|
||
文字识别:百度API识别身份证、银行卡内容、人脸检测、人脸对比
|
||
*/
|
||
|
||
// OcrReq 请求体参数 身份证和银行卡都是此结构体,
|
||
type OcrReq struct {
|
||
Image string `json:"image"` // 二选一:图像数据,base64编码后进行urlencode,需去掉编码头(data:image/jpeg;base64, ) 要求base64编码和urlencode后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/jpeg/png/bmp格式
|
||
Url string `json:"url"` // 二选一:图片完整URL,URL长度不超过1024字节,URL对应的图片base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/jpeg/png/bmp格式,当image字段存在时url字段失效 请注意关闭URL防盗链
|
||
IdCardSide string `json:"id_card_side"` // 身份证需要此参数(人头面填写:front,国徽面填写:back) 银行卡不需要
|
||
DetectPhoto bool `json:"detect_photo"` // 是否检测身份证进行裁剪,默认不检测。可选值:true-检测身份证并返回证照的 base64 编码及位置信息
|
||
}
|
||
|
||
/*
|
||
1、clientId 必须参数,应用的APIKey
|
||
2、clientSecret 必须参数,应用的Secret Key;
|
||
3、存在redis的数据前缀
|
||
*/
|
||
var clientId = ""
|
||
var clientSecret = ""
|
||
var cacheRedis = "zmGoBaiDuOcrAccessToken"
|
||
|
||
func init() {
|
||
one, err := g.Cfg().Get(gctx.New(), "baiDuYun.clientId")
|
||
if err != nil {
|
||
fmt.Println("百度云API未找到!")
|
||
}
|
||
two, err := g.Cfg().Get(gctx.New(), "baiDuYun.clientSecret")
|
||
if err != nil {
|
||
fmt.Println("百度云Secret未找到!")
|
||
}
|
||
clientId = one.String()
|
||
clientSecret = two.String()
|
||
}
|
||
|
||
/*
|
||
IDCardInfo 获取身份证相关数据
|
||
*/
|
||
type IDCardInfo struct {
|
||
WordsResult WordsResult `json:"words_result"`
|
||
WordsResultNum int `json:"words_result_num"`
|
||
IDCardNumberType int `json:"idcard_number_type"`
|
||
ImageStatus string `json:"image_status"`
|
||
LogID int64 `json:"log_id"`
|
||
Photo string `json:"photo"`
|
||
}
|
||
type WordsResult struct {
|
||
Name Field `json:"姓名"`
|
||
Nation Field `json:"民族"`
|
||
Address Field `json:"住址"`
|
||
CitizenIdentification Field `json:"公民身份号码"`
|
||
Birth Field `json:"出生"`
|
||
Gender Field `json:"性别"`
|
||
ExpirationDate Field `json:"失效日期"`
|
||
IssuingAuthority Field `json:"签发机关"`
|
||
IssueDate Field `json:"签发日期"`
|
||
}
|
||
type Field struct {
|
||
Location Location `json:"location"`
|
||
Words string `json:"words"`
|
||
}
|
||
type Location struct {
|
||
Top int `json:"top"`
|
||
Left int `json:"left"`
|
||
Width int `json:"width"`
|
||
Height int `json:"height"`
|
||
}
|
||
|
||
func ImgOCR(vr OcrReq) (m map[string]interface{}) {
|
||
//请求路径+token
|
||
baseUrl := "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard"
|
||
//先从缓存里面捞取token,如果没得就重新获取token
|
||
var atStr = redisCacheStr()
|
||
// 构造 URL 参数
|
||
params := url.Values{}
|
||
params.Set("access_token", atStr)
|
||
// 构造完整的请求 URL
|
||
requestURL := fmt.Sprintf("%s?%s", baseUrl, params.Encode())
|
||
|
||
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Post(gctx.New(), requestURL, vr)
|
||
if err != nil {
|
||
return
|
||
}
|
||
var dataInfo = strings.ReplaceAll(response.ReadAllString(), " ", "")
|
||
|
||
//解析数据
|
||
bodyData := []byte(dataInfo)
|
||
var idCardInfo IDCardInfo
|
||
err = json.Unmarshal(bodyData, &idCardInfo)
|
||
if err != nil {
|
||
fmt.Println("Failed to parse JSON data:", err)
|
||
return
|
||
}
|
||
m = make(map[string]interface{})
|
||
//身份证正反面颠倒了,直接返回空
|
||
if idCardInfo.ImageStatus == "reversed_side" {
|
||
return
|
||
}
|
||
|
||
result := idCardInfo.WordsResult
|
||
//if idCardInfo.Photo != "" {
|
||
// m["pacePhoto"] = idCardInfo.Photo
|
||
//}
|
||
if result.Name.Words != "" {
|
||
m["userName"] = result.Name.Words
|
||
}
|
||
if result.Nation.Words != "" {
|
||
m["sfzNation"] = result.Nation.Words
|
||
}
|
||
if result.Address.Words != "" {
|
||
m["sfzSite"] = result.Address.Words
|
||
}
|
||
if result.CitizenIdentification.Words != "" {
|
||
m["sfzNumber"] = result.CitizenIdentification.Words
|
||
}
|
||
if result.Birth.Words != "" {
|
||
str, _ := tool.New().TimeCycle(result.Birth.Words)
|
||
m["sfzBirth"] = str
|
||
}
|
||
if result.Gender.Words != "" {
|
||
var se = result.Gender.Words
|
||
if se == "男" {
|
||
se = "1"
|
||
} else if se == "女" {
|
||
se = "2"
|
||
} else {
|
||
se = "3"
|
||
}
|
||
m["sex"] = se
|
||
}
|
||
if result.ExpirationDate.Words != "" {
|
||
str, err := tool.New().TimeCycle(result.ExpirationDate.Words)
|
||
if err == nil {
|
||
m["sfzEnd"] = str
|
||
} else {
|
||
str := result.ExpirationDate.Words
|
||
m["sfzEnd"] = str
|
||
}
|
||
}
|
||
if result.IssuingAuthority.Words != "" {
|
||
m["IssuingAuthority"] = result.IssuingAuthority.Words
|
||
}
|
||
if result.IssueDate.Words != "" {
|
||
str, _ := tool.New().TimeCycle(result.IssueDate.Words)
|
||
m["sfzStart"] = str
|
||
}
|
||
return m
|
||
}
|
||
|
||
/*
|
||
BankData 获取银行卡相关数据
|
||
针对卡号、有效期、发卡行、卡片类型、持卡人5个关键字段进行结构化识别,识别准确率超过99%
|
||
*/
|
||
type BankData struct {
|
||
ValidDate string `json:"valid_date"` //有效期
|
||
BankCardNumber string `json:"bank_card_number"` //银行卡卡号
|
||
BankName string `json:"bank_name"` //银行名,不能识别时为空
|
||
BankCardType int `json:"bank_card_type"` //银行卡类型,0:不能识别; 1:借记卡; 2:贷记卡(原信用卡大部分为贷记卡); 3:准贷记卡; 4:预付费卡
|
||
HolderName string `json:"holder_name"` //持卡人姓名,不能识别时为空
|
||
}
|
||
|
||
type Result struct {
|
||
Res BankData `json:"result"` //具体数据
|
||
Direction int `json:"direction"` //图像方向。 - - 1:未定义; - 0:正向; - 1:逆时针90度; - 2:逆时针180度; - 3:逆时针270度
|
||
LogID int64 `json:"log_id"` //请求标识码,随机数,唯一。
|
||
}
|
||
|
||
func ImgYhkOCR(vr OcrReq) (m map[string]interface{}) {
|
||
m = make(map[string]interface{})
|
||
//请求路径+token
|
||
baseUrl := "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard"
|
||
//先从缓存里面捞取token
|
||
var atStr = redisCacheStr()
|
||
// 构造 URL 参数
|
||
params := url.Values{}
|
||
params.Set("access_token", atStr)
|
||
// 构造完整的请求 URL
|
||
requestURL := fmt.Sprintf("%s?%s", baseUrl, params.Encode())
|
||
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Post(gctx.New(), requestURL, vr)
|
||
if err != nil {
|
||
return
|
||
}
|
||
//解析数据
|
||
allString := response.ReadAllString()
|
||
bodyData := []byte(allString)
|
||
var result Result
|
||
err = json.Unmarshal(bodyData, &result)
|
||
if err != nil {
|
||
fmt.Println("Failed to parse JSON data:", err)
|
||
return
|
||
}
|
||
if result.Res.ValidDate != "" {
|
||
m["ValidDate"] = result.Res.ValidDate
|
||
}
|
||
if result.Res.BankCardNumber != "" {
|
||
m["yhkNumber"] = strings.ReplaceAll(result.Res.BankCardNumber, " ", "")
|
||
}
|
||
if result.Res.BankName != "" {
|
||
m["yhkOpeningBank"] = result.Res.BankName
|
||
}
|
||
if result.Res.BankCardType >= 0 {
|
||
m["BankCardType"] = result.Res.BankCardType
|
||
}
|
||
if result.Res.HolderName != "" {
|
||
m["yhkCardholder"] = result.Res.HolderName
|
||
}
|
||
return m
|
||
|
||
}
|
||
|
||
/*
|
||
HumanFaceReq 请求参数 人脸识别+人脸检测
|
||
*/
|
||
type HumanFaceReq struct {
|
||
Image string `json:"image"` //图片此处填写base64
|
||
ImageType string `json:"image_type" gf:"default:BASE64" ` //图片类型-BASE64-URL-BASE64(此封装固定用base64)
|
||
FaceField string `json:"face_field" gf:"default:face_type,quality"` //包括age,expression,face_shape,gender,glasses,landmark,landmark150, quality,eye_status,emotion,face_type,mask,spoofing信息 逗号分隔. 默认只返回face_token、人脸框、概率和旋转角度
|
||
}
|
||
|
||
/*
|
||
HumanFaceRep 返回参数 人脸识别
|
||
*/
|
||
type HumanFaceRep struct {
|
||
ErrorCode int `json:"error_code"`
|
||
ErrorMsg string `json:"error_msg"`
|
||
LogId int `json:"log_id"`
|
||
Timestamp int `json:"timestamp"`
|
||
Cached int `json:"cached"`
|
||
Result struct {
|
||
FaceNum int `json:"face_num"`
|
||
FaceList []struct {
|
||
FaceToken string `json:"face_token"`
|
||
Location struct {
|
||
Left float64 `json:"left"`
|
||
Top float64 `json:"top"`
|
||
Width int `json:"width"`
|
||
Height int `json:"height"`
|
||
Rotation int `json:"rotation"`
|
||
} `json:"location"`
|
||
FaceProbability float64 `json:"face_probability"`
|
||
Angle struct {
|
||
Yaw float64 `json:"yaw"`
|
||
Pitch float64 `json:"pitch"`
|
||
Roll float64 `json:"roll"`
|
||
} `json:"angle"`
|
||
FaceType struct {
|
||
Type string `json:"type"`
|
||
Probability float64 `json:"probability"`
|
||
} `json:"face_type"`
|
||
Quality struct {
|
||
Occlusion struct {
|
||
LeftEye float64 `json:"left_eye"`
|
||
RightEye float64 `json:"right_eye"`
|
||
Nose float64 `json:"nose"`
|
||
Mouth float64 `json:"mouth"`
|
||
LeftCheek float64 `json:"left_cheek"`
|
||
RightCheek float64 `json:"right_cheek"`
|
||
ChinContour float64 `json:"chin_contour"`
|
||
} `json:"occlusion"`
|
||
Blur float64 `json:"blur"`
|
||
Illumination float64 `json:"illumination"`
|
||
Completeness int64 `json:"completeness"`
|
||
} `json:"quality"`
|
||
} `json:"face_list"`
|
||
} `json:"result"`
|
||
}
|
||
|
||
func HumanFace(hf *HumanFaceReq) (err error) {
|
||
ctx := gctx.New()
|
||
err = g.Try(ctx, func(ctx context.Context) {
|
||
err = nil
|
||
//1、请求地址+token
|
||
url := "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=" + redisCacheStr()
|
||
marshal, err := json.Marshal(hf)
|
||
if err != nil {
|
||
liberr.ErrIsNil(ctx, err)
|
||
return
|
||
}
|
||
//3、准备请求
|
||
payload := strings.NewReader(string(marshal))
|
||
client := &http.Client{}
|
||
req, err := http.NewRequest("POST", url, payload)
|
||
if err != nil {
|
||
liberr.ErrIsNil(ctx, err)
|
||
return
|
||
}
|
||
//4、设置请求头
|
||
req.Header.Add("Content-Type", "application/json")
|
||
//5、发送请求
|
||
res, err := client.Do(req)
|
||
if err != nil {
|
||
liberr.ErrIsNil(ctx, err)
|
||
return
|
||
}
|
||
defer res.Body.Close()
|
||
//6、返回数据
|
||
body, err := io.ReadAll(res.Body)
|
||
if err != nil {
|
||
liberr.ErrIsNil(ctx, err)
|
||
return
|
||
}
|
||
var aaa = body
|
||
//解析数据
|
||
var result HumanFaceRep
|
||
err = json.Unmarshal(aaa, &result)
|
||
if err != nil {
|
||
liberr.ErrIsNil(ctx, err)
|
||
return
|
||
}
|
||
if result.ErrorCode != 0 {
|
||
if result.ErrorMsg == "pic not has face" {
|
||
err = errors.New("这张照片没有人脸")
|
||
liberr.ErrIsNil(ctx, err)
|
||
} else {
|
||
err = errors.New(result.ErrorMsg)
|
||
liberr.ErrIsNil(ctx, err)
|
||
}
|
||
return
|
||
}
|
||
|
||
//1、人脸置信度,范围【0~1】,代表这是一张人脸的概率,0最小、1最大。其中返回0或1时,数据类型为Integer
|
||
dataInfo := result.Result.FaceList[0]
|
||
reliabilityOne := dataInfo.FaceProbability
|
||
if reliabilityOne != 1.0 {
|
||
err = errors.New("识别不清晰!")
|
||
}
|
||
//2、判断是否真是人脸 human: 真实人脸 cartoon: 卡通人脸
|
||
reliabilityTwo := dataInfo.FaceType.Type
|
||
if reliabilityTwo == "cartoon" {
|
||
err = errors.New("请传入真实人脸!")
|
||
} else {
|
||
//判断可信度 置信度,范围0~1
|
||
reliabilityThree := dataInfo.FaceType.Probability
|
||
if reliabilityThree < 0.8 {
|
||
err = errors.New("请勿化妆太过夸张!")
|
||
}
|
||
}
|
||
//3、人脸模糊程度,范围[0~1],0表示清晰,1表示模糊
|
||
reliabilityFour := dataInfo.Quality.Blur
|
||
if reliabilityFour >= 0.1 {
|
||
err = errors.New("人脸过于模糊!")
|
||
}
|
||
//4、光线太暗 0~255 值越大光线越好
|
||
reliabilityFive := dataInfo.Quality.Illumination
|
||
if reliabilityFive < 80.0 {
|
||
err = errors.New("光线太暗!")
|
||
}
|
||
//5、人脸是否完整 1完整 0不完整
|
||
reliabilitySix := dataInfo.Quality.Completeness
|
||
if reliabilitySix != 1 {
|
||
err = errors.New("请确定人脸在图框内!")
|
||
}
|
||
return
|
||
})
|
||
return
|
||
}
|
||
|
||
// ComparisonRep 人脸检测返回数据
|
||
type ComparisonRep struct {
|
||
ErrorCode int `json:"error_code"`
|
||
ErrorMsg string `json:"error_msg"`
|
||
LogId int `json:"log_id"`
|
||
Timestamp int `json:"timestamp"`
|
||
Cached int `json:"cached"`
|
||
Result struct {
|
||
Score float64 `json:"score"` //人脸相似度得分,推荐阈值80分
|
||
FaceList []struct {
|
||
FaceToken string `json:"face_token"`
|
||
} `json:"face_list"`
|
||
} `json:"result"`
|
||
}
|
||
|
||
func Comparison(arrObject []*HumanFaceReq) (score float64, err error) {
|
||
//1、请求地址+token
|
||
url := "https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=" + redisCacheStr()
|
||
//2、请求参数转字符串json
|
||
marshal, err := json.Marshal(arrObject)
|
||
if err != nil {
|
||
return
|
||
}
|
||
payload := strings.NewReader(string(marshal))
|
||
//3、准备post请求
|
||
client := &http.Client{}
|
||
req, err := http.NewRequest("POST", url, payload)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
//4、设置请求头
|
||
req.Header.Add("Content-Type", "application/json")
|
||
//5、发送请求关闭连接
|
||
res, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
defer res.Body.Close()
|
||
//6、数据结果
|
||
body, err := ioutil.ReadAll(res.Body)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
//7、解析数据
|
||
var result ComparisonRep
|
||
err = json.Unmarshal(body, &result)
|
||
if err != nil {
|
||
fmt.Println("Failed to parse JSON data:", err)
|
||
return
|
||
}
|
||
score = result.Result.Score
|
||
return score, err
|
||
}
|
||
|
||
/*
|
||
AccessTokenResponse 获取Access_token,有效期(秒为单位,有效期30天);
|
||
*/
|
||
type AccessTokenResponse struct {
|
||
AccessToken string `json:"access_token"`
|
||
}
|
||
|
||
func AccessTokenFunc() (str string) {
|
||
url := "https://aip.baidubce.com/oauth/2.0/token?client_id=" + clientId + "&client_secret=" + clientSecret + "&grant_type=client_credentials"
|
||
payload := strings.NewReader(``)
|
||
client := &http.Client{}
|
||
req, err := http.NewRequest("POST", url, payload)
|
||
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
req.Header.Add("Content-Type", "application/json")
|
||
req.Header.Add("Accept", "application/json")
|
||
|
||
res, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
defer res.Body.Close()
|
||
|
||
body, err := ioutil.ReadAll(res.Body)
|
||
var tokenResponse AccessTokenResponse
|
||
json.Unmarshal(body, &tokenResponse)
|
||
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
return tokenResponse.AccessToken
|
||
}
|
||
|
||
// 缓存捞取token,如果没得就重新获取token
|
||
func redisCacheStr() (atStr string) {
|
||
atStr = ""
|
||
ctx := gctx.New()
|
||
prefix := g.Cfg().MustGet(ctx, "system.cache.prefix").String()
|
||
gfCache := cache.New(prefix)
|
||
at := commonService.Cache().Get(ctx, gfCache.CachePrefix+cacheRedis)
|
||
if at == nil || at.String() == "" {
|
||
atStr = AccessTokenFunc()
|
||
//存储到redis,时间为29天
|
||
commonService.Cache().Set(ctx, cacheRedis, atStr, time.Hour*24*29)
|
||
} else {
|
||
atStr = at.String()
|
||
}
|
||
//atStr = "24.c8814d3fc7961820f0e23ee9d80cf96c.2592000.1696671067.282335-38777216"
|
||
return atStr
|
||
}
|