You've already forked VptPassiveAdapter
feat(api): 新增VIID接口处理人脸上传功能
- 实现人脸数据结构定义与解析 - 添加系统注册、保活、注销代理逻辑 - 支持系统时间获取接口 - 处理人脸图片Base64解码及缩略图生成 - 异步执行人脸数据上传流程 - 提供成功与错误响应封装方法
This commit is contained in:
248
api/viid_handler.go
Normal file
248
api/viid_handler.go
Normal file
@@ -0,0 +1,248 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user