package api import ( "ZhenTuLocalPassiveAdapter/config" "ZhenTuLocalPassiveAdapter/logger" "ZhenTuLocalPassiveAdapter/util" "context" "encoding/base64" "io" "net/http" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // Request Structs for Handler type FaceUploadRequest struct { FaceListObject FaceListObject `json:"FaceListObject"` } type FaceListObject struct { FaceObject []FaceObject `json:"FaceObject"` } type FaceObject struct { FaceID string `json:"FaceID"` DeviceID string `json:"DeviceID"` ShotTime string `json:"ShotTime"` FaceAppearTime string `json:"FaceAppearTime"` SubImageList SubImageList `json:"SubImageList"` // Position info LeftTopX *int `json:"LeftTopX,omitempty"` LeftTopY *int `json:"LeftTopY,omitempty"` RightBtmX *int `json:"RightBtmX,omitempty"` RightBtmY *int `json:"RightBtmY,omitempty"` } type SubImageList struct { SubImageInfoObject []SubImageInfoObject `json:"SubImageInfoObject"` } type SubImageInfoObject struct { ImageID string `json:"ImageID"` Type string `json:"Type"` // "11"=Face, "14"=Source FileFormat string `json:"FileFormat"` Data string `json:"Data"` // Base64 Width *int `json:"Width,omitempty"` Height *int `json:"Height,omitempty"` } // Register Routes func RegisterVIIDRoutes(r *gin.Engine) { viid := r.Group("/VIID") { sys := viid.Group("/System") { sys.POST("/Register", HandleRegister) sys.POST("/Keepalive", HandleKeepalive) sys.POST("/UnRegister", HandleUnRegister) sys.GET("/Time", HandleSystemTime) } viid.POST("/Faces", HandleUploadFaces) } } // HTTP Handlers func HandleRegister(c *gin.Context) { proxyHelper(c, "/VIID/System/Register") } func HandleKeepalive(c *gin.Context) { proxyHelper(c, "/VIID/System/Keepalive") } func HandleUnRegister(c *gin.Context) { proxyHelper(c, "/VIID/System/UnRegister") } func proxyHelper(c *gin.Context, path string) { resp, err := ProxyRequest(c.Request.Context(), c.Request.Method, path, c.Request.Body, c.Request.Header) if err != nil { logger.Error("Proxy request failed", zap.String("path", path), zap.Error(err)) c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } defer resp.Body.Close() // Copy Headers for k, v := range resp.Header { c.Writer.Header()[k] = v } c.Status(resp.StatusCode) io.Copy(c.Writer, resp.Body) } func HandleSystemTime(c *gin.Context) { now := time.Now() timeStr := now.Format("20060102150405") // VIID format // Determine TimeZone string (e.g., "Asia/Shanghai") // Simple fixed string or system local timeZone := "Local" loc, err := time.LoadLocation("Local") if err == nil { timeZone = loc.String() } resp := map[string]interface{}{ "SystemTimeObject": map[string]string{ "VIIDServerID": "00000000000000000001", // Placeholder "TimeMode": "2", // 2: Atomic Clock / NTP "LocalTime": timeStr, "TimeZone": timeZone, }, } c.JSON(http.StatusOK, resp) } func HandleUploadFaces(c *gin.Context) { var req FaceUploadRequest if err := c.ShouldBindJSON(&req); err != nil { logger.Error("Invalid JSON in UploadFaces", zap.Error(err)) sendErrorResponse(c, "/VIID/Faces", "", "Invalid JSON") return } var lastFaceID string for _, face := range req.FaceListObject.FaceObject { lastFaceID = face.FaceID deviceID := face.DeviceID // 1. Parse Images var faceImg, srcImg []byte var imgWidth, imgHeight int for _, subImg := range face.SubImageList.SubImageInfoObject { data, err := base64.StdEncoding.DecodeString(subImg.Data) if err != nil { logger.Error("Base64 decode failed", zap.String("faceId", face.FaceID), zap.Error(err)) continue } if subImg.Type == "11" { // Face faceImg = data } else if subImg.Type == "14" { // Source srcImg = data if subImg.Width != nil { imgWidth = *subImg.Width } if subImg.Height != nil { imgHeight = *subImg.Height } } } // 2. Construct Position Info pos := FacePositionInfo{ ImgWidth: imgWidth, ImgHeight: imgHeight, ShotTime: parseShotTime(face.ShotTime, face.FaceAppearTime), } if face.LeftTopX != nil { pos.LeftTopX = *face.LeftTopX } if face.LeftTopY != nil { pos.LeftTopY = *face.LeftTopY } if face.RightBtmX != nil { pos.RightBtmX = *face.RightBtmX } if face.RightBtmY != nil { pos.RightBtmY = *face.RightBtmY } // 3. Generate Thumbnail (if Source Image and Coordinates exist) var thumbImg []byte if len(srcImg) > 0 && pos.LeftTopX > 0 && pos.LeftTopY > 0 { // Basic validation // Use Source Image to generate thumbnail // The thumbnail is 1/2 original size, centered on face var err error thumbImg, err = util.GenerateThumbnailFromCoords(srcImg, pos.LeftTopX, pos.LeftTopY, pos.RightBtmX, pos.RightBtmY) if err != nil { logger.Error("Failed to generate thumbnail", zap.String("faceId", face.FaceID), zap.Error(err)) // Continue without thumbnail? or fail? // Usually continue, but log error. } } // 4. Execute Upload Flow scenicId := config.Config.Viid.ScenicId go func(fID, dID string, fData, tData, sData []byte, p FacePositionInfo) { // Create a detached context with timeout ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() if err := UploadFaceData(ctx, scenicId, dID, fData, tData, sData, p); err != nil { logger.Error("Failed to process face upload", zap.String("faceId", fID), zap.Error(err)) } }(face.FaceID, deviceID, faceImg, thumbImg, srcImg, pos) } // Respond Success sendSuccessResponse(c, "/VIID/Faces", lastFaceID, "OK") } // Helpers func sendErrorResponse(c *gin.Context, url, id, msg string) { resp := VIIDBaseResponse{ ResponseStatusObject: ResponseStatusObject{ ID: id, RequestURL: url, StatusCode: "1", StatusString: msg, LocalTime: time.Now().Format("20060102150405"), }, } c.JSON(http.StatusBadRequest, resp) } func sendSuccessResponse(c *gin.Context, url, id, msg string) { resp := VIIDBaseResponse{ ResponseStatusObject: ResponseStatusObject{ ID: id, RequestURL: url, StatusCode: "0", StatusString: msg, LocalTime: time.Now().Format("20060102150405"), }, } c.JSON(http.StatusOK, resp) } func parseShotTime(t1, t2 string) string { if t1 != "" { return t1 } if t2 != "" { return t2 } return time.Now().Format("20060102150405") }