package plant import ( "encoding/base64" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/url" "strings" "sundynix-go/global" "sundynix-go/model/commom/request" "sundynix-go/model/plant" "sundynix-go/model/plant/response" "sundynix-go/pkg/httpclient" "go.uber.org/zap" ) type OcrService struct{} // ClassifyPlant 植物识别 func (s *OcrService) ClassifyPlant(file multipart.File, header *multipart.FileHeader, userId string) (response.PlantRecognitionResponse, error) { reqUrl := "https://aip.baidubce.com/rest/2.0/image-classify/v1/plant?access_token=" + getAccessToken() // 3. 读取文件的全部字节 fileBytes, err := io.ReadAll(file) if err != nil { global.Logger.Error("读取文件失败!", zap.Error(err)) } // 4. 将字节流编码为 Base64 字符串(可选 URLEncoding,根据场景选择) // 去掉编码头进行urlencode base64Str := base64.StdEncoding.EncodeToString(fileBytes) escapedBase64 := url.QueryEscape(base64Str) payload := strings.NewReader("image=" + escapedBase64 + "&baike_num=1") myHttpClient := httpclient.GetClient() req, err := http.NewRequest("POST", reqUrl, payload) if err != nil { global.Logger.Error("创建请求失败!", zap.Error(err)) return response.PlantRecognitionResponse{}, err } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Accept", "application/json") resp, err := myHttpClient.Do(req) if err != nil { global.Logger.Error("请求百度接口失败!", zap.Error(err)) return response.PlantRecognitionResponse{}, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { global.Logger.Error("解析百度接口响应失败!", zap.Error(err)) return response.PlantRecognitionResponse{}, err } // 3. 解析JSON到结构体 var plantResp response.PlantRecognitionResponse if err = json.Unmarshal(body, &plantResp); err != nil { global.Logger.Error("解析识别JSON失败!", zap.Error(err)) } //4.异步写入库 go func(userId string, apiResp response.PlantRecognitionResponse) { // A. 安全防护:防止协程 Panic 导致程序崩溃 defer func() { if r := recover(); r != nil { global.Logger.Error("异步入库发生 Panic", zap.Any("recover", r)) } }() var dbResults plant.ResultsArray = make(plant.ResultsArray, 0, len(apiResp.Result)) // 2. 循环搬运数据 for _, item := range apiResp.Result { // 处理嵌套的 BaikeInfo 指针 var baikeInfo *plant.BaikeInfo if item.BaikeInfo != nil { baikeInfo = &plant.BaikeInfo{ BaikeUrl: item.BaikeInfo.BaikeUrl, ImageUrl: item.BaikeInfo.ImageUrl, Description: item.BaikeInfo.Description, } } // 添加转换后的对象 dbResults = append(dbResults, plant.ResultItem{ Score: item.Score, Name: item.Name, BaikeInfo: baikeInfo, // 赋值刚才处理好的指针 }) } record := plant.ClassifyRecord{ UserId: userId, LogId: apiResp.LogId, AllResults: dbResults, } if err := global.DB.Create(&record).Error; err != nil { global.Logger.Error("异步植物识别结果入库失败", zap.Error(err)) } }(userId, plantResp) // 立即返回结果 return plantResp, err } // MyClassifyLog 我的植物识别记录 func (s *OcrService) MyClassifyLog(req request.PageInfo, id string) (list interface{}, total int64, err error) { limit := req.PageSize offset := req.PageSize * (req.Current - 1) db := global.DB.Model(&plant.ClassifyRecord{}) var records []plant.ClassifyRecord err = db.Count(&total).Error if err != nil { return } db = db.Where("user_id = ?", id).Limit(limit).Offset(offset).Order("created_at desc").Find(&records) return records, total, err } func getAccessToken() string { rpcUrl := "https://aip.baidubce.com/oauth/2.0/token" postData := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s", global.Config.BaiduImgClassify.ApiKey, global.Config.BaiduImgClassify.SecretKey) resp, err := http.Post(rpcUrl, "application/x-www-form-urlencoded", strings.NewReader(postData)) if err != nil { fmt.Println(err) return "" } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println(err) return "" } accessTokenObj := map[string]any{} _ = json.Unmarshal([]byte(body), &accessTokenObj) return accessTokenObj["access_token"].(string) }