|
|
@@ -0,0 +1,259 @@
|
|
|
+package handlers
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "context"
|
|
|
+ "encoding/base64"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "github.com/nfnt/resize"
|
|
|
+ "image"
|
|
|
+ "image/jpeg"
|
|
|
+ "image/png"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ cameraMachineUrl = "http://127.0.0.1:7074"
|
|
|
+ // GetPhotoRecordUrl 获取照片记录
|
|
|
+ GetPhotoRecordUrl = "/get_photo_records"
|
|
|
+ // RemoveBackground 移除背景
|
|
|
+ RemoveBackground = "/remove_background"
|
|
|
+)
|
|
|
+
|
|
|
+type CameraMachineHandler struct {
|
|
|
+ ctx context.Context
|
|
|
+ handlerRequest *HandlerRequests
|
|
|
+ token string
|
|
|
+}
|
|
|
+
|
|
|
+func NewCameraMachineHandler(ctx context.Context, token string) *CameraMachineHandler {
|
|
|
+ return &CameraMachineHandler{
|
|
|
+ token: token,
|
|
|
+ ctx: ctx,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// PhotoRecord 表示单个照片记录
|
|
|
+type PhotoRecord struct {
|
|
|
+ ID int `json:"id"`
|
|
|
+ ImagePath string `json:"image_path"`
|
|
|
+ ImageDealMode int `json:"image_deal_mode"`
|
|
|
+ UpdateTime string `json:"update_time"`
|
|
|
+ DeleteTime *string `json:"delete_time"`
|
|
|
+ ActionID int `json:"action_id"`
|
|
|
+ GoodsArtNo string `json:"goods_art_no"`
|
|
|
+ ImageIndex int `json:"image_index"`
|
|
|
+ PhotoCreateTime string `json:"photo_create_time"`
|
|
|
+ CreateTime string `json:"create_time"`
|
|
|
+}
|
|
|
+
|
|
|
+// PhotoRecordItem 表示包含照片记录和动作名称的项目
|
|
|
+type PhotoRecordItem struct {
|
|
|
+ PhotoRecord PhotoRecord `json:"PhotoRecord"`
|
|
|
+ ActionName string `json:"action_name"`
|
|
|
+}
|
|
|
+
|
|
|
+// PhotoRecordList 表示单个货号的照片记录列表
|
|
|
+type PhotoRecordList struct {
|
|
|
+ GoodsArtNo string `json:"goods_art_no"`
|
|
|
+ ActionTime string `json:"action_time"`
|
|
|
+ Items []string `json:"items"`
|
|
|
+}
|
|
|
+
|
|
|
+// PhotoRecordResponse 表示完整的API响应
|
|
|
+type PhotoRecordResponse struct {
|
|
|
+ List []PhotoRecordList `json:"list"`
|
|
|
+ CurrentPage int `json:"current_page"`
|
|
|
+ Size int `json:"size"`
|
|
|
+ TotalCount int `json:"total_count"`
|
|
|
+ TotalPages int `json:"total_pages"`
|
|
|
+ HasPrev bool `json:"has_prev"`
|
|
|
+ HasNext bool `json:"has_next"`
|
|
|
+}
|
|
|
+
|
|
|
+// convertImageToBase64 将图片文件转换为 base64 编码
|
|
|
+// compressAndEncodeImage 压缩图片并转换为 base64 编码
|
|
|
+func (t *CameraMachineHandler) compressAndEncodeImage(imagePath string, maxWidth uint) (string, error) {
|
|
|
+ // 检查文件是否存在
|
|
|
+ if _, err := os.Stat(imagePath); os.IsNotExist(err) {
|
|
|
+ return "", fmt.Errorf("图片文件不存在: %s", imagePath)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取图片文件
|
|
|
+ file, err := os.Open(imagePath)
|
|
|
+ if err != nil {
|
|
|
+ return "", fmt.Errorf("打开图片文件失败: %v", err)
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ // 根据文件扩展名确定图片类型
|
|
|
+ ext := strings.ToLower(filepath.Ext(imagePath))
|
|
|
+
|
|
|
+ var img image.Image
|
|
|
+ switch ext {
|
|
|
+ case ".jpg", ".jpeg":
|
|
|
+ img, err = jpeg.Decode(file)
|
|
|
+ case ".png":
|
|
|
+ img, err = png.Decode(file)
|
|
|
+ default:
|
|
|
+ return "", fmt.Errorf("不支持的图片格式: %s", ext)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return "", fmt.Errorf("解码图片失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取原始图片尺寸
|
|
|
+ originalBounds := img.Bounds()
|
|
|
+ originalWidth := uint(originalBounds.Dx())
|
|
|
+ originalHeight := uint(originalBounds.Dy())
|
|
|
+
|
|
|
+ // 如果原始宽度小于等于最大宽度,则不需要压缩
|
|
|
+ var resizedImg image.Image
|
|
|
+ if originalWidth <= maxWidth {
|
|
|
+ resizedImg = img
|
|
|
+ } else {
|
|
|
+ // 按比例计算新高度
|
|
|
+ newHeight := uint(float64(originalHeight) * float64(maxWidth) / float64(originalWidth))
|
|
|
+ // 调整图片大小
|
|
|
+ resizedImg = resize.Resize(maxWidth, newHeight, img, resize.Lanczos3)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将压缩后的图片编码为 JPEG 格式
|
|
|
+ var buf bytes.Buffer
|
|
|
+ err = jpeg.Encode(&buf, resizedImg, &jpeg.Options{Quality: 80})
|
|
|
+ if err != nil {
|
|
|
+ return "", fmt.Errorf("编码压缩后的图片失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将图片数据转换为 base64 编码
|
|
|
+ base64Data := base64.StdEncoding.EncodeToString(buf.Bytes())
|
|
|
+
|
|
|
+ return base64Data, nil
|
|
|
+}
|
|
|
+
|
|
|
+// GetPhotoList 获取拍照记录列表
|
|
|
+func (t *CameraMachineHandler) GetPhotoList(page string) (*PhotoRecordResponse, error) {
|
|
|
+ t.handlerRequest = NewHandlerRequests(t.ctx, t.token, cameraMachineUrl)
|
|
|
+ request, err := t.handlerRequest.MakeGetRequest(GetPhotoRecordUrl + "?page=" + page)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将返回结果转换为 map 以提取 data 字段
|
|
|
+ resultMap, ok := request.(map[string]interface{})
|
|
|
+ if !ok {
|
|
|
+ return nil, fmt.Errorf("无法将响应转换为 map")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取 data 字段
|
|
|
+ dataValue, exists := resultMap["data"]
|
|
|
+ if !exists {
|
|
|
+ return nil, fmt.Errorf("响应中不存在 data 字段")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将 data 字段序列化为 JSON
|
|
|
+ dataJson, err := json.Marshal(dataValue)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("序列化 data 字段失败: %v", err)
|
|
|
+ }
|
|
|
+ // 先解析为原始响应结构
|
|
|
+ var originalResponse struct {
|
|
|
+ List []struct {
|
|
|
+ GoodsArtNo string `json:"goods_art_no"`
|
|
|
+ ActionTime string `json:"action_time"`
|
|
|
+ Items []PhotoRecordItem `json:"items"`
|
|
|
+ } `json:"list"`
|
|
|
+ CurrentPage int `json:"current_page"`
|
|
|
+ Size int `json:"size"`
|
|
|
+ TotalCount int `json:"total_count"`
|
|
|
+ TotalPages int `json:"total_pages"`
|
|
|
+ HasPrev bool `json:"has_prev"`
|
|
|
+ HasNext bool `json:"has_next"`
|
|
|
+ }
|
|
|
+ // 解析为 PhotoRecordResponse 结构
|
|
|
+ if err := json.Unmarshal(dataJson, &originalResponse); err != nil {
|
|
|
+ return nil, fmt.Errorf("解析原始数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建新的响应结构,将图片路径转换为 base64
|
|
|
+ response := &PhotoRecordResponse{
|
|
|
+ CurrentPage: originalResponse.CurrentPage,
|
|
|
+ Size: originalResponse.Size,
|
|
|
+ TotalCount: originalResponse.TotalCount,
|
|
|
+ TotalPages: originalResponse.TotalPages,
|
|
|
+ HasPrev: originalResponse.HasPrev,
|
|
|
+ HasNext: originalResponse.HasNext,
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, list := range originalResponse.List {
|
|
|
+ var base64Items []string
|
|
|
+ for idx, item := range list.Items {
|
|
|
+ if idx >= 1 {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ // 将图片路径转换为 base64
|
|
|
+ base64Image, err := t.compressAndEncodeImage(item.PhotoRecord.ImagePath, 400)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("转换图片失败 %s: %v\n", item.PhotoRecord.ImagePath, err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ base64Items = append(base64Items, base64Image)
|
|
|
+ }
|
|
|
+
|
|
|
+ photoRecordList := PhotoRecordList{
|
|
|
+ GoodsArtNo: list.GoodsArtNo,
|
|
|
+ ActionTime: list.ActionTime,
|
|
|
+ Items: base64Items,
|
|
|
+ }
|
|
|
+ response.List = append(response.List, photoRecordList)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("数据列表获取成功,共 %d 条记录\n", len(response.List))
|
|
|
+ return response, nil
|
|
|
+}
|
|
|
+
|
|
|
+// RemoveBackground 批量处理抠图操作
|
|
|
+func (t *CameraMachineHandler) RemoveBackground(goodsArtNos []string) (string, error) {
|
|
|
+ t.handlerRequest = NewHandlerRequests(t.ctx, t.token, cameraMachineUrl)
|
|
|
+ postData := map[string]interface{}{}
|
|
|
+ postData["goods_art_nos"] = goodsArtNos
|
|
|
+ postData["token"] = t.token
|
|
|
+ request, err := t.handlerRequest.MakePostRequest(RemoveBackground, postData)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ // 将返回结果转换为 map 以提取 data 字段
|
|
|
+ resultMap, ok := request.(map[string]interface{})
|
|
|
+ if !ok {
|
|
|
+ return "", fmt.Errorf("无法将响应转换为 map")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取 data 字段
|
|
|
+ dataValue, exists := resultMap["data"]
|
|
|
+ if !exists {
|
|
|
+ return "", fmt.Errorf("响应中不存在 data 字段")
|
|
|
+ }
|
|
|
+ // 解析 data 字段为 map
|
|
|
+ dataMap, ok := dataValue.(map[string]interface{})
|
|
|
+ if !ok {
|
|
|
+ return "", fmt.Errorf("data 字段不是有效的 map 类型")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取 output_folder 值
|
|
|
+ outputFolder, exists := dataMap["output_folder"]
|
|
|
+ if !exists {
|
|
|
+ return "", fmt.Errorf("data 字段中不存在 output_folder")
|
|
|
+ }
|
|
|
+
|
|
|
+ outputFolderPath, ok := outputFolder.(string)
|
|
|
+ if !ok {
|
|
|
+ return "", fmt.Errorf("output_folder 不是字符串类型")
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("输出文件夹路径: %s\n", outputFolderPath)
|
|
|
+ return outputFolderPath, nil
|
|
|
+}
|