feat(catface): 优化猫脸识别功能
- 新增猫脸识别失败的错误码和错误信息 - 重构猫脸识别结果处理逻辑,增加对结果为空的处理 - 优化猫脸识别结果展示,包括品种翻译和动物信息展示 - 新增测试用例,验证猫脸识别功能正常工作
This commit is contained in:
parent
d04987302f
commit
609e02dfd2
@ -3,13 +3,19 @@ package errcode
|
||||
const (
|
||||
ErrAnimalSqlFind = iota + ErrAnimal
|
||||
AnimalNoFind
|
||||
|
||||
// TAG
|
||||
CatFaceFail
|
||||
// CatFaceNoFind // INFO 交给前端判断更合适
|
||||
)
|
||||
|
||||
func AnimalMsgInit(m msg) {
|
||||
m[ErrAnimalSqlFind] = "Animals 表单查询失败"
|
||||
m[AnimalNoFind] = "Animals 没有查询到符合条件的目标"
|
||||
m[CatFaceFail] = "猫脸识别失败"
|
||||
}
|
||||
|
||||
func AnimalMsgUserInit(m msg) {
|
||||
m[AnimalNoFind] = "没有更多符合此条件的毛茸茸啦,试着更换查询条件或者新增吧~"
|
||||
m[CatFaceFail] = "猫脸识别失败,请重新尝试~ 😿"
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"catface/app/model"
|
||||
"catface/app/model_es"
|
||||
"catface/app/model_redis"
|
||||
"catface/app/model_res"
|
||||
"catface/app/service/animals/curd"
|
||||
"catface/app/service/catface"
|
||||
"catface/app/service/upload_file"
|
||||
@ -26,32 +27,52 @@ type Animals struct { // INFO 起到一个标记的作用,这样 web.xxx 的
|
||||
func (a *Animals) Guess(context *gin.Context) {
|
||||
// 1. Get Params
|
||||
filePath := context.GetString(consts.ValidatorPrefix + "file_path")
|
||||
// 2. Get Result
|
||||
catRes := catface.GetCatfaceResult(filePath)
|
||||
// 3. Response
|
||||
filePath = filepath.Join(variable.ConfigYml.GetString("FileUploadSetting.UploadFileSavePath"), variable.ConfigYml.GetString("FileUploadSetting.CatFaceTempRootPath"), filePath)
|
||||
|
||||
type subT struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status uint8 `json:"status"`
|
||||
Department uint8 `json:"department"`
|
||||
// STAGE - 2 Get Result From Python API
|
||||
catRes, err := catface.GetCatfaceResult(filePath)
|
||||
if err != nil {
|
||||
response.Fail(context, errcode.CatFaceFail, errcode.ErrMsg[errcode.CatFaceFail], errcode.ErrMsgForUser[errcode.CatFaceFail])
|
||||
return
|
||||
}
|
||||
|
||||
type t struct {
|
||||
List []subT `json:"list"`
|
||||
}
|
||||
// STAGE - 3
|
||||
|
||||
var resList t
|
||||
for _, v := range catRes.Cats {
|
||||
resList.List = append(resList.List, subT{
|
||||
Id: v.Id,
|
||||
Name: model.CreateAnimalFactory("").ShowByID(v.Id).Name,
|
||||
Status: model.CreateAnimalFactory("").ShowByID(v.Id).Status,
|
||||
Department: model.CreateAnimalFactory("").ShowByID(v.Id).Department,
|
||||
// FaceBreed: En -> Zh
|
||||
faceBreedZh := model.CreateAnmBreedFactory("").GetNameZhByEn(catRes.FaceBreed)
|
||||
|
||||
// CatFace 返回为空,直接返回。
|
||||
if len(catRes.Cats) == 0 {
|
||||
response.Success(context, consts.CurdStatusOkMsg, gin.H{
|
||||
"face_breed": faceBreedZh,
|
||||
"animals": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
// fill other information
|
||||
var ids []int64
|
||||
for _, v := range catRes.Cats {
|
||||
ids = append(ids, v.Id)
|
||||
}
|
||||
|
||||
response.Success(context, consts.CurdStatusOkMsg, resList)
|
||||
animals := model.CreateAnimalFactory("").ShowByIDs(ids, "id", "name", "status", "department", "nick_names", "description", "avatar")
|
||||
|
||||
var res []model_res.CatfaceCat
|
||||
for _, animal := range animals {
|
||||
for _, cat := range catRes.Cats {
|
||||
if cat.Id == animal.Id {
|
||||
res = append(res, model_res.CatfaceCat{
|
||||
Animal: animal,
|
||||
Conf: cat.Conf,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(context, consts.CurdStatusOkMsg, gin.H{
|
||||
"face_breed": faceBreedZh,
|
||||
"animals": res,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Animals) List(context *gin.Context) {
|
||||
|
@ -5,10 +5,12 @@ import (
|
||||
"catface/app/http/controller/web"
|
||||
"catface/app/http/validator/core/data_transfer"
|
||||
"catface/app/utils/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CatfaceGuess struct {
|
||||
FilePath string `form:"file_path" json:"file_path" binding:"required"`
|
||||
}
|
||||
|
||||
func (c CatfaceGuess) CheckParams(context *gin.Context) {
|
||||
|
@ -1,11 +1,6 @@
|
||||
package model
|
||||
|
||||
// INFO 一些基础表单的整合
|
||||
|
||||
type AnmBreed struct {
|
||||
BriefModel
|
||||
}
|
||||
|
||||
// TAG 一些基础表单的整合
|
||||
type AnmSterilzation struct { // TEST How to use BriefModel, the dif between Common
|
||||
Id int64 `json:"id"`
|
||||
NameZh string `json:"name_zh"`
|
||||
@ -28,3 +23,24 @@ type AnmVaccination struct {
|
||||
type AnmDeworming struct {
|
||||
BriefModel
|
||||
}
|
||||
|
||||
// TAG 带了点函数处理
|
||||
func CreateAnmBreedFactory(sqlType string) *AnmBreed {
|
||||
return &AnmBreed{BriefModel: BriefModel{DB: UseDbConn(sqlType)}}
|
||||
}
|
||||
|
||||
type AnmBreed struct {
|
||||
BriefModel
|
||||
}
|
||||
|
||||
func (a *AnmBreed) TableName() string {
|
||||
return "anm_breeds"
|
||||
}
|
||||
|
||||
func (a *AnmBreed) GetNameZhByEn(name_en string) string {
|
||||
var temp AnmBreed
|
||||
if result := a.DB.Where("name_en = ?", name_en).First(&temp); result.Error != nil {
|
||||
return "" // 如果查询失败,返回空字符串
|
||||
}
|
||||
return temp.NameZh
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ type BaseModel struct {
|
||||
}
|
||||
|
||||
type BriefModel struct {
|
||||
Id int64 `json:"id"`
|
||||
NameZh string `json:"name_zh"`
|
||||
NameEn string `json:"name_en"`
|
||||
*gorm.DB `gorm:"-" json:"-"`
|
||||
Id int64 `json:"id"`
|
||||
NameZh string `json:"name_zh"`
|
||||
NameEn string `json:"name_en"`
|
||||
}
|
||||
|
||||
type Color struct {
|
||||
|
8
app/model_res/catface.go
Normal file
8
app/model_res/catface.go
Normal file
@ -0,0 +1,8 @@
|
||||
package model_res
|
||||
|
||||
import "catface/app/model"
|
||||
|
||||
type CatfaceCat struct {
|
||||
Animal model.Animal `json:"animal"`
|
||||
Conf float64 `json:"conf"`
|
||||
}
|
@ -4,29 +4,42 @@ import (
|
||||
"catface/app/global/variable"
|
||||
"catface/app/utils/micro_service"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/carlmjohnson/requests"
|
||||
)
|
||||
|
||||
type FaceRes struct {
|
||||
FaceBreed int `json:"face_breed"`
|
||||
type FaceData struct {
|
||||
FaceBreed string `json:"face_breed"`
|
||||
Cats []struct {
|
||||
Id int64 `json:"id"`
|
||||
Prob float64 `json:"prob"`
|
||||
Conf float64 `json:"conf"`
|
||||
} `json:"cats"`
|
||||
}
|
||||
|
||||
func GetCatfaceResult(filePath string) FaceRes {
|
||||
type CatFaceRes struct {
|
||||
Status int `json:"status"`
|
||||
Data FaceData `json:"data"`
|
||||
}
|
||||
|
||||
func GetCatfaceResult(filePath string) (*FaceData, error) {
|
||||
body := map[string]interface{}{
|
||||
"file_path": filePath,
|
||||
}
|
||||
var res FaceRes
|
||||
var res CatFaceRes
|
||||
err := requests.URL(micro_service.FetchPythonServiceUrl("cnn/detect_cat")).
|
||||
BodyJSON(&body).
|
||||
ToJSON(&res).
|
||||
Fetch(context.Background())
|
||||
|
||||
if err != nil {
|
||||
variable.ZapLog.Error("获取cat face结果集失败: " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res
|
||||
if res.Status != 200 {
|
||||
return nil, fmt.Errorf("请求状态码错误: %d", res.Status)
|
||||
}
|
||||
|
||||
return &res.Data, nil
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ FileUploadSetting:
|
||||
|
||||
AvatarWidth: 200
|
||||
DocsRootPath: "docs" # TODO 或许 upload 模块可以写一下自动区分大致的文件类型所在的位置。
|
||||
CatFaceTempRootPath: "catfaceTemp"
|
||||
|
||||
# casbin 权限控制api接口
|
||||
Casbin:
|
||||
|
@ -42,5 +42,5 @@ func TestExplain(t *testing.T) {
|
||||
}
|
||||
|
||||
// 调用 StructToString 函数
|
||||
t.Logf("结构体内容:", StructToString(encounter))
|
||||
t.Logf("结构体内容:%v", StructToString(encounter))
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package test
|
||||
|
||||
import (
|
||||
"catface/app/model"
|
||||
_ "catface/bootstrap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -14,3 +15,8 @@ func TestDocModel(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnmBreed(t *testing.T) {
|
||||
res := model.CreateAnmBreedFactory("").GetNameZhByEn("li")
|
||||
t.Log(res)
|
||||
}
|
||||
|
@ -1,97 +1,98 @@
|
||||
//后端代码
|
||||
// //后端代码
|
||||
|
||||
//注意 **我注释的代码,是不使用gin框架封装的Stream方法,也就是C.Stream(func())和C.ssevent(),只是C.Stream要改成for循环持续的从通道里面进行读,直到通道关闭,结束for循环**
|
||||
// //注意 **我注释的代码,是不使用gin框架封装的Stream方法,也就是C.Stream(func())和C.ssevent(),只是C.Stream要改成for循环持续的从通道里面进行读,直到通道关闭,结束for循环**
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"catface/app/service/nlp/glm"
|
||||
_ "catface/bootstrap"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
// "time"
|
||||
// import (
|
||||
// "catface/app/service/nlp/glm"
|
||||
// _ "catface/bootstrap"
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "testing"
|
||||
// // "time"
|
||||
//
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
// "github.com/gin-gonic/gin"
|
||||
|
||||
)
|
||||
// )
|
||||
|
||||
func SSE(c *gin.Context) {
|
||||
// 设置响应头,告诉前端适用event-stream事件流交互
|
||||
//c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
//c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
//c.Writer.Header().Set("Connection", "keep-alive")
|
||||
// func SSE(c *gin.Context) {
|
||||
// // 设置响应头,告诉前端适用event-stream事件流交互
|
||||
// //c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
// //c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
// //c.Writer.Header().Set("Connection", "keep-alive")
|
||||
|
||||
// 判断是否支持sse
|
||||
//w := c.Writer
|
||||
//flusher, _ := w.(http.Flusher)
|
||||
query := c.Query("query")
|
||||
// // 判断是否支持sse
|
||||
// //w := c.Writer
|
||||
// //flusher, _ := w.(http.Flusher)
|
||||
// query := c.Query("query")
|
||||
|
||||
// 接收前端页面关闭连接通知
|
||||
closeNotify := c.Request.Context().Done()
|
||||
// // 接收前端页面关闭连接通知
|
||||
// closeNotify := c.Request.Context().Done()
|
||||
|
||||
// 开启协程监听前端页面是否关闭了连接,关闭连接会触发此方法
|
||||
go func() {
|
||||
<-closeNotify
|
||||
fmt.Println("SSE关闭了")
|
||||
return
|
||||
}()
|
||||
// // 开启协程监听前端页面是否关闭了连接,关闭连接会触发此方法
|
||||
// go func() {
|
||||
// <-closeNotify
|
||||
// fmt.Println("SSE关闭了")
|
||||
// return
|
||||
// }()
|
||||
|
||||
//新建一个通道,用于数据接收和响应
|
||||
Chan := make(chan string)
|
||||
// //新建一个通道,用于数据接收和响应
|
||||
// Chan := make(chan string)
|
||||
|
||||
// 异步接收GPT响应,然后把响应的数据发送到通道Chan
|
||||
go func() {
|
||||
err := glm.ChatStream(query, Chan)
|
||||
if err != nil {
|
||||
fmt.Println("Error", err)
|
||||
}
|
||||
// // 异步接收GPT响应,然后把响应的数据发送到通道Chan
|
||||
// go func() {
|
||||
// err := glm.ChatStream(query, Chan)
|
||||
// if err != nil {
|
||||
// fmt.Println("Error", err)
|
||||
// }
|
||||
|
||||
close(Chan)
|
||||
}()
|
||||
// close(Chan)
|
||||
// }()
|
||||
|
||||
// gin框架封装的stream,会持续的调用这个func方法,记得返回true;返回false代表结束调用func方法
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
select {
|
||||
case i, ok := <-Chan:
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
c.SSEvent("chat", i) // c.SSEvent会自动修改响应头为事件流,并发送”test“事件流给前端监听”test“的回调方法
|
||||
//flusher.Flush() // 确保立即发送
|
||||
return true
|
||||
case <-closeNotify:
|
||||
fmt.Println("SSE关闭了")
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
// // gin框架封装的stream,会持续的调用这个func方法,记得返回true;返回false代表结束调用func方法
|
||||
// c.Stream(func(w io.Writer) bool {
|
||||
// select {
|
||||
// case i, ok := <-Chan:
|
||||
// if !ok {
|
||||
// return false
|
||||
// }
|
||||
// c.SSEvent("chat", i) // c.SSEvent会自动修改响应头为事件流,并发送”test“事件流给前端监听”test“的回调方法
|
||||
// //flusher.Flush() // 确保立即发送
|
||||
// return true
|
||||
// case <-closeNotify:
|
||||
// fmt.Println("SSE关闭了")
|
||||
// return false
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
func TestSSE(t *testing.T) {
|
||||
engine := gin.Default()
|
||||
// 设置跨域中间件
|
||||
engine.Use(func(context *gin.Context) {
|
||||
origin := context.GetHeader("Origin")
|
||||
// 允许 Origin 字段中的域发送请求
|
||||
context.Writer.Header().Add("Access-Control-Allow-Origin", origin) // 这边我的前端页面在63342,会涉及跨域,这个根据自己情况设置,或者直接设置为”*“,放行所有的
|
||||
// 设置预验请求有效期为 86400 秒
|
||||
context.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||
// 设置允许请求的方法
|
||||
context.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, U`PDATE, PATCH")
|
||||
// 设置允许请求的 Header
|
||||
context.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Apitoken")
|
||||
// 设置拿到除基本字段外的其他字段,如上面的Apitoken, 这里通过引用Access-Control-Expose-Headers,进行配置,效果是一样的。
|
||||
context.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Headers")
|
||||
// 配置是否可以带认证信息
|
||||
context.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
// OPTIONS请求返回200
|
||||
if context.Request.Method == "OPTIONS" {
|
||||
fmt.Println(context.Request.Header)
|
||||
context.AbortWithStatus(200)
|
||||
} else {
|
||||
context.Next()
|
||||
}
|
||||
})
|
||||
engine.GET("/admin/rag/default_talk", SSE) // TIP 记得适用get请求,我用post前端报404,资料说是SSE只支持get请求
|
||||
engine.Run(":20201")
|
||||
}
|
||||
// func TestSSE(t *testing.T) {
|
||||
// engine := gin.Default()
|
||||
// // 设置跨域中间件
|
||||
// engine.Use(func(context *gin.Context) {
|
||||
// origin := context.GetHeader("Origin")
|
||||
// // 允许 Origin 字段中的域发送请求
|
||||
// context.Writer.Header().Add("Access-Control-Allow-Origin", origin) // 这边我的前端页面在63342,会涉及跨域,这个根据自己情况设置,或者直接设置为”*“,放行所有的
|
||||
// // 设置预验请求有效期为 86400 秒
|
||||
// context.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||
// // 设置允许请求的方法
|
||||
// context.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, U`PDATE, PATCH")
|
||||
// // 设置允许请求的 Header
|
||||
// context.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Apitoken")
|
||||
// // 设置拿到除基本字段外的其他字段,如上面的Apitoken, 这里通过引用Access-Control-Expose-Headers,进行配置,效果是一样的。
|
||||
// context.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Headers")
|
||||
// // 配置是否可以带认证信息
|
||||
// context.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
// // OPTIONS请求返回200
|
||||
// if context.Request.Method == "OPTIONS" {
|
||||
// fmt.Println(context.Request.Header)
|
||||
// context.AbortWithStatus(200)
|
||||
// } else {
|
||||
// context.Next()
|
||||
// }
|
||||
// })
|
||||
// engine.GET("/admin/rag/default_talk", SSE) // TIP 记得适用get请求,我用post前端报404,资料说是SSE只支持get请求
|
||||
// engine.Run(":20201")
|
||||
// }
|
||||
|
Loading…
x
Reference in New Issue
Block a user