增加图片文字识别后端接口

master
linlnf 4 weeks ago
parent 20a0d4c214
commit 8758733bc9

4
.gitignore vendored

@ -4,7 +4,7 @@ GinSkeleton/.vscode
GinSkeleton/.idea/
.idea/
.idea
GinSkeleton/storage/
GinSkeleton/storage/
ckeditor5/node_modules/*
GinSkeleton/config/gorm_v2.yml
GinSkeleton/public/storage

@ -184,4 +184,22 @@ Authorization|Headers|string|必填|Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
},
"msg": "Success"
}
```
#### 请求图片文字识别
> <font color=#FF4500>*post*/admin/ai_recognition/pic_recognition</font>
参数字段|参数属性|类型|选项|默认值
---|---|---|---|---
pic|form-data|string|必填|""
> 返回示例:
```json
{
"code": 200,
"data": {
"words": "out[entry]=\nfor each basic block ∈N -{entry}\nout[B] =\nchange = true\nwhile (changes) {\n// Find: fix point solution\nchange = false\nfor each B∈N - {entry} {\noldout = out[B]\nin[B]= Uout[P]\nP∈pred[B]\nout[B] = GEN[B] (in[B]∩ PRSV[B])\nif(out[B] ≠ oldout) change = true;\n}\n}\n"
},
"msg": "Success"
}
```

@ -38,8 +38,8 @@ const (
CurdCreatFailMsg string = "新增失败"
CurdUpdateFailCode int = -400201
CurdUpdateFailMsg string = "更新失败"
CurdUpdatePassFailCode int = -400207
CurdUpdatePassFailMsg string = "更新密码失败"
CurdUpdatePassFailCode int = -400207
CurdUpdatePassFailMsg string = "更新密码失败"
CurdDeleteFailCode int = -400202
CurdDeleteFailMsg string = "删除失败"
CurdSelectFailCode int = -400203
@ -50,10 +50,10 @@ const (
CurdLoginFailMsg string = "登录失败"
CurdRefreshTokenFailCode int = -400206
CurdRefreshTokenFailMsg string = "刷新Token失败"
CurdLogoutFailCode int = -400208
CurdLogoutFailMsg string = "登出失败"
CurdPublicKeyFailCode int = -400209
CurdPublicKeyFailMsg string = "密钥获取失败"
CurdLogoutFailCode int = -400208
CurdLogoutFailMsg string = "登出失败"
CurdPublicKeyFailCode int = -400209
CurdPublicKeyFailMsg string = "密钥获取失败"
//文件上传
FilesUploadFailCode int = -400250
FilesUploadFailMsg string = "文件上传失败, 获取上传文件发生错误!"
@ -77,4 +77,8 @@ const (
CaptchaCheckOkMsg string = "验证码校验通过"
CaptchaCheckFailCode int = -400355
CaptchaCheckFailMsg string = "验证码校验失败"
// 模型功能按相关
PicRecognitionFailMsg string = "图片文字识别失败"
PicRecognitionFailCode int = -400450
)

@ -67,4 +67,9 @@ const (
ErrorCasbinCreateAdaptFail string = "casbin NewAdapterByDBUseTableName 发生错误:"
ErrorCasbinCreateEnforcerFail string = "casbin NewEnforcer 发生错误:"
ErrorCasbinNewModelFromStringFail string = "NewModelFromString 调用时出错:"
// baiduCE 访问出现的错误
ErrorBaiduCEGetTokenFail string = "访问百度智能云时获得access_token 出现问题:"
ErrorBaiduCEUseOCRFail string = "使用百度智能云OCR接口失败"
ErrorBaiduCEPostFail string = "使用百度智能云接口失败(POST通用)"
)

@ -0,0 +1,23 @@
package web
import (
"goskeleton/app/global/consts"
"goskeleton/app/service/ai_model_cli"
"goskeleton/app/utils/response"
"github.com/gin-gonic/gin"
)
type AiRecognition struct {
}
// Ai 模型识别模块与ai相关的识别功能包括图片文字识别、语音识别
//
// 图片文字识别
func (u *AiRecognition) PicRecognition(context *gin.Context) {
if r, recogWords := ai_model_cli.RequestOCR(context); r {
response.Success(context, consts.CurdStatusOkMsg, recogWords)
} else {
response.Fail(context, consts.PicRecognitionFailCode, consts.PicRecognitionFailMsg, "")
}
}

@ -5,6 +5,7 @@ import (
"goskeleton/app/global/consts"
"goskeleton/app/http/validator/common/upload_files"
"goskeleton/app/http/validator/common/websocket"
"goskeleton/app/http/validator/web/ai_recognition"
"goskeleton/app/http/validator/web/users"
)
@ -46,4 +47,8 @@ func WebRegisterValidator() {
// Websocket 连接验证器
key = consts.ValidatorPrefix + "WebsocketConnect"
containers.Set(key, websocket.Connect{})
// ai 识别功能相关
key = consts.ValidatorPrefix + "PicRecognition"
containers.Set(key, ai_recognition.PicRecognition{})
}

@ -0,0 +1,61 @@
package ai_recognition
import (
"goskeleton/app/global/consts"
"goskeleton/app/http/controller/web"
"goskeleton/app/http/validator/core/data_transfer"
"goskeleton/app/utils/response"
"github.com/gin-gonic/gin"
)
type PicRecognition struct {
Pic string `form:"pic" json:"pic" binding:"required"` // 必填、对于文本,表示它的长度>=1
}
func (p PicRecognition) CheckParams(context *gin.Context) {
if err := context.ShouldBind(&p); err != nil {
// 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可
response.ValidatorError(context, err)
return
}
// 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(p, consts.ValidatorPrefix, context)
if extraAddBindDataContext == nil {
response.ErrorSystem(context, "PicRecognition表单参数验证器json化失败", "")
return
}
// TODO:linlnf
// pic := p.Pic
// // 先进行 URL 解码
// decodedPic, err := url.QueryUnescape(pic)
// if err != nil {
// response.ErrorSystem(context, "PicRecognition表单参数验证器URL解码失败", "")
// return
// }
// // 再进行 Base64 解码
// decodedData, err := base64.StdEncoding.DecodeString(decodedPic)
// if err != nil {
// response.ErrorSystem(context, "PicRecognition表单参数验证器Base64解码失败", "")
// return
// }
// // 检查大小不超过 10M10 * 1024 * 1024 字节)
// if len(decodedData) > 10*1024*1024 {
// response.ErrorSystem(context, "PicRecognition表单参数验证器图片大小超过 10M", "")
// return
// }
// // 将字节数据转换为图像以检查格式和尺寸
// img, _, err := image.Decode(bytes.NewReader(decodedData))
// if err != nil {
// return
// }
// bounds := img.Bounds()
// minSide := math.Min(float64(bounds.Dx()), float64(bounds.Dy()))
// maxSide := math.Max(float64(bounds.Dx()), float64(bounds.Dy()))
// // 最短边至少 15px最长边最大 8192px
// if minSide < 15 || maxSide > 8192 {
// response.ErrorSystem(context, "PicRecognition表单参数验证器图片尺寸过大或过小", "")
// return
// }
(&web.AiRecognition{}).PicRecognition(extraAddBindDataContext)
}

@ -0,0 +1,76 @@
package ai_model_cli
import (
"goskeleton/app/global/my_errors"
"goskeleton/app/global/variable"
"goskeleton/app/utils/baidubce"
"goskeleton/app/utils/files"
"net/http"
"net/url"
"strings"
"github.com/gin-gonic/gin"
)
/**
* ocr(),
* @return
*/
func RequestOCR(context *gin.Context) (r bool, c interface{}) {
path := "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic?access_token=" + baidubce.GetAccessToken()
// 获得图片数据,需要 base64 和urlencode处理
// linlnf: test
content, _ := files.GetFileBase64("storage/app/test/文字识别.png")
// 组装 body 数据参数在该网址https://cloud.baidu.com/doc/OCR/s/1k3h7y3db
data := make(url.Values)
data.Set("detect_direction", "false")
data.Set("paragraph", "false")
data.Set("probability", "false")
data.Set("multidirectional_recognize", "false")
data.Set("image", content)
payload := strings.NewReader(data.Encode())
client := &http.Client{}
// 请求数据
req, err := http.NewRequest("POST", path, payload)
if err != nil {
variable.ZapLog.Error(my_errors.ErrorBaiduCEUseOCRFail + err.Error())
return false, nil
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Accept", "application/json")
res, err := client.Do(req)
if err != nil {
variable.ZapLog.Error(my_errors.ErrorBaiduCEUseOCRFail + err.Error())
return false, nil
}
defer res.Body.Close()
//从res中提取识别出的字符信息
mbody, err := baidubce.DecodeResBody(res, "ocr")
if err != nil {
variable.ZapLog.Error(my_errors.ErrorBaiduCEUseOCRFail + err.Error())
return false, nil
}
return true, gin.H{
"words": decodeBody2Str(mbody),
}
}
/*
*
* @return
*/
func decodeBody2Str(mbody map[string]interface{}) (str string) {
words, ok := mbody["words_result"]
if ok {
sliceWords, ok := words.([]interface{})
if ok {
// 遍历切片
for _, word := range sliceWords {
str += word.(map[string]interface{})["words"].(string) + "\n"
}
}
return str
}
return ""
}

@ -0,0 +1,61 @@
package baidubce
import (
"encoding/json"
"errors"
"fmt"
"goskeleton/app/global/my_errors"
"goskeleton/app/global/variable"
"io"
"net/http"
"strings"
)
/**
* 使 AKSK Access Token
* @return string Access Token
*/
func GetAccessToken() string {
url := "https://aip.baidubce.com/oauth/2.0/token"
API_KEY := variable.ConfigYml.GetString("BaiduCE.ApiKey")
SECRET_KEY := variable.ConfigYml.GetString("BaiduCE.SecretKey")
postData := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s", API_KEY, SECRET_KEY)
resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(postData))
if err != nil {
variable.ZapLog.Error(my_errors.ErrorBaiduCEGetTokenFail + err.Error())
return ""
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
variable.ZapLog.Error(my_errors.ErrorBaiduCEGetTokenFail + err.Error())
return ""
}
accessTokenObj := map[string]any{}
_ = json.Unmarshal([]byte(body), &accessTokenObj)
return accessTokenObj["access_token"].(string)
}
func DecodeResBody(res *http.Response, flag string) (mbody map[string]interface{}, err error) {
body, err := io.ReadAll(res.Body)
if err != nil {
variable.ZapLog.Error(my_errors.ErrorBaiduCEPostFail + flag + ", " + err.Error())
return nil, err
}
// json 格式的 body
var jbody interface{}
fmt.Println(string(body))
err = json.Unmarshal(body, &jbody)
if err != nil {
variable.ZapLog.Error(my_errors.ErrorBaiduCEPostFail + flag + ", " + err.Error())
return nil, err
}
mbody = jbody.(map[string]interface{})
err_str, _ := mbody["error_msg"].(string)
if err_str != "" {
variable.ZapLog.Error(my_errors.ErrorBaiduCEPostFail + flag + ", " + err_str)
err = errors.New(err_str)
return nil, err
}
return mbody, err
}

@ -1,6 +1,7 @@
package files
import (
"encoding/base64"
"goskeleton/app/global/my_errors"
"goskeleton/app/global/variable"
"mime/multipart"
@ -47,3 +48,12 @@ func GetFilesMimeByFp(fp multipart.File) string {
return http.DetectContentType(buffer)
}
// 读取本地文件进行Base64编码
func GetFileBase64(filePath string) (string, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
}

@ -144,3 +144,7 @@ Captcha:
captchaValue: "captcha_value" #验证码值提交时的键名
length: 4 # 验证码生成时的长度
BaiduCE:
ApiKey: "AR1SUIjaKSsCcDjj11QzHDOc" # 生成鉴权签名时使用的 API_KEY
SecretKey: "zvEb5CzpuGCZNdQC1TPmDh3IOWn5aWDT" # 生成鉴权签名时使用的 SECRET_KEY

@ -61,6 +61,13 @@ func InitWebRouter_Co() *gin.Engine {
// 刷新token当过期的token在允许失效的延长时间范围内用旧token换取新token
refreshToken.Use(authorization.RefreshTokenConditionCheck()).POST("refreshtoken", validatorFactory.Create(consts.ValidatorPrefix+"RefreshToken"))
}
// TODO:linlnf
// 人工智能识别相关
aiRecognition := backend.Group("ai_recognition/")
{
// 请求图片文字识别
aiRecognition.POST("pic_recognition", validatorFactory.Create(consts.ValidatorPrefix+"PicRecognition"))
}
// 【需要token】中间件验证的路由
backend.Use(authorization.CheckTokenAuth())

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Loading…
Cancel
Save