You've already forked VptPassiveAdapter
- 实现人脸数据结构定义与解析 - 添加系统注册、保活、注销代理逻辑 - 支持系统时间获取接口 - 处理人脸图片Base64解码及缩略图生成 - 异步执行人脸数据上传流程 - 提供成功与错误响应封装方法
249 lines
6.3 KiB
Go
249 lines
6.3 KiB
Go
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")
|
|
}
|