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 }