diff --git a/app/global/errcode/animal.go b/app/global/errcode/animal.go index ee52bf9..41eb8c5 100644 --- a/app/global/errcode/animal.go +++ b/app/global/errcode/animal.go @@ -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] = "猫脸识别失败,请重新尝试~ 😿" } diff --git a/app/http/controller/web/animal_controller.go b/app/http/controller/web/animal_controller.go index 804834a..f4f6cb6 100644 --- a/app/http/controller/web/animal_controller.go +++ b/app/http/controller/web/animal_controller.go @@ -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) { diff --git a/app/http/validator/web/animal/catface_guess.go b/app/http/validator/web/animal/catface_guess.go index d353471..9762d14 100644 --- a/app/http/validator/web/animal/catface_guess.go +++ b/app/http/validator/web/animal/catface_guess.go @@ -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) { diff --git a/app/model/animal_com.go b/app/model/animal_com.go index 873d6c5..1f2fda3 100644 --- a/app/model/animal_com.go +++ b/app/model/animal_com.go @@ -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 +} diff --git a/app/model/base_model.go b/app/model/base_model.go index 3528502..aca5abc 100644 --- a/app/model/base_model.go +++ b/app/model/base_model.go @@ -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 { diff --git a/app/model_res/catface.go b/app/model_res/catface.go new file mode 100644 index 0000000..56abca8 --- /dev/null +++ b/app/model_res/catface.go @@ -0,0 +1,8 @@ +package model_res + +import "catface/app/model" + +type CatfaceCat struct { + Animal model.Animal `json:"animal"` + Conf float64 `json:"conf"` +} diff --git a/app/service/catface/catface.go b/app/service/catface/catface.go index 03c5ad4..8814c4d 100644 --- a/app/service/catface/catface.go +++ b/app/service/catface/catface.go @@ -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 } diff --git a/config/config.yml b/config/config.yml index c8fd995..9d50d89 100644 --- a/config/config.yml +++ b/config/config.yml @@ -79,6 +79,7 @@ FileUploadSetting: AvatarWidth: 200 DocsRootPath: "docs" # TODO 或许 upload 模块可以写一下自动区分大致的文件类型所在的位置。 + CatFaceTempRootPath: "catfaceTemp" # casbin 权限控制api接口 Casbin: diff --git a/test/explain_test.go b/test/explain_test.go index c7e897a..678b35b 100644 --- a/test/explain_test.go +++ b/test/explain_test.go @@ -42,5 +42,5 @@ func TestExplain(t *testing.T) { } // 调用 StructToString 函数 - t.Logf("结构体内容:", StructToString(encounter)) + t.Logf("结构体内容:%v", StructToString(encounter)) } diff --git a/test/model_2_test.go b/test/model_2_test.go index b40c07f..1ed9b2e 100644 --- a/test/model_2_test.go +++ b/test/model_2_test.go @@ -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) +} diff --git a/test/sse/sse_test.go b/test/sse/sse_test.go index 47f57a7..fe599ae 100644 --- a/test/sse/sse_test.go +++ b/test/sse/sse_test.go @@ -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") +// }