Files
VptPassiveAdapter/api/viid_client.go
Jerry Yan 0b42dad969 feat(api): 添加图像处理配置和服务器端图像处理功能
- 新增 CompressionConfig、ThumbnailConfig 和 ImageProcessingConfig 结构体用于图像处理配置
- 实现 GetEffectiveConfig 方法提供图像处理配置的默认值
- 在 UploadConfig 中添加 ImageProcessing 字段传递服务器配置
- 移除客户端本地缩略图生成功能,改用服务器端处理
- 添加 UploadFaceDataWithProcessing 函数实现带图像处理的上传流程
- 实现 configToImageOptions 函数将服务器配置转换为图像处理选项
- 在 util/image.go 中添加完整的图像处理功能,支持裁切和缩放模式
- 更新依赖添加 golang.org/x/image 用于高质量图像缩放
- 添加 .claude 到 .gitignore 文件
2025-12-30 11:51:17 +08:00

268 lines
7.1 KiB
Go

package api
import (
"ZhenTuLocalPassiveAdapter/config"
"ZhenTuLocalPassiveAdapter/logger"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"go.uber.org/zap"
)
// VIID Request/Response Structures
type DeviceIdObject struct {
DeviceID string `json:"DeviceID"`
}
type RegisterRequest struct {
RegisterObject DeviceIdObject `json:"RegisterObject"`
}
type KeepaliveRequest struct {
KeepaliveObject DeviceIdObject `json:"KeepaliveObject"`
}
type UnRegisterRequest struct {
UnRegisterObject DeviceIdObject `json:"UnRegisterObject"`
}
type VIIDBaseResponse struct {
ResponseStatusObject ResponseStatusObject `json:"ResponseStatusObject"`
}
type ResponseStatusObject struct {
ID string `json:"Id"`
RequestURL string `json:"RequestURL"`
StatusCode string `json:"StatusCode"`
StatusString string `json:"StatusString"`
LocalTime string `json:"LocalTime"`
}
type SystemTimeResponse struct {
ResponseStatusObject struct {
LocalTime string `json:"LocalTime"` // yyyyMMddHHmmss
} `json:"ResponseStatusObject"`
}
// VIID Client Methods
func ProxyRequest(ctx context.Context, method, path string, body io.Reader, header http.Header) (*http.Response, error) {
url := fmt.Sprintf("%s%s", config.Config.Viid.ServerUrl, path)
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return nil, err
}
// Copy headers
for k, v := range header {
req.Header[k] = v
}
return GetAPIClient().Do(req)
}
// Upload Proxy Structures
// CompressionConfig 压缩配置
type CompressionConfig struct {
Enabled bool `json:"enabled"` // 是否启用压缩
Quality int `json:"quality"` // 压缩质量 (1-100)
}
// ThumbnailConfig 缩略图配置
type ThumbnailConfig struct {
Mode string `json:"mode"` // 缩略图模式: "cut"(裁切) 或 "shrink"(缩放)
ShrinkRatio float64 `json:"shrinkRatio"` // 缩放/裁切比例 (0-1)
Quality int `json:"quality"` // 缩略图压缩质量 (1-100)
}
// ImageProcessingConfig 图像处理配置
type ImageProcessingConfig struct {
SourceCompression *CompressionConfig `json:"sourceCompression,omitempty"` // 原图压缩配置
Thumbnail *ThumbnailConfig `json:"thumbnail,omitempty"` // 缩略图配置
}
// GetEffectiveConfig 返回有效的图像处理配置(应用默认值)
// 默认值:原图不压缩;缩略图裁切模式,比例0.5,质量75
func (c *ImageProcessingConfig) GetEffectiveConfig() *ImageProcessingConfig {
effective := &ImageProcessingConfig{
SourceCompression: &CompressionConfig{
Enabled: false, // 默认不压缩原图
Quality: 100,
},
Thumbnail: &ThumbnailConfig{
Mode: "cut", // 默认裁切模式
ShrinkRatio: 0.5, // 默认比例0.5
Quality: 75, // 默认质量75
},
}
if c == nil {
return effective
}
// 覆盖原图压缩配置
if c.SourceCompression != nil {
effective.SourceCompression = c.SourceCompression
}
// 覆盖缩略图配置
if c.Thumbnail != nil {
effective.Thumbnail = c.Thumbnail
}
return effective
}
type UploadConfig struct {
TaskID int64 `json:"taskId"`
FaceUploadURL string `json:"faceUploadUrl"`
ThumbnailUploadURL string `json:"thumbnailUploadUrl"`
SourceUploadURL string `json:"sourceUploadUrl"`
ExpiresAt time.Time `json:"expiresAt"`
ImageProcessing *ImageProcessingConfig `json:"imageProcessing,omitempty"` // 图像处理配置
}
type ProxyResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Data *UploadConfig `json:"data"` // Data can be UploadConfig or string depending on endpoint
}
type FacePositionInfo struct {
LeftTopX int `json:"leftTopX"`
LeftTopY int `json:"leftTopY"`
RightBtmX int `json:"rightBtmX"`
RightBtmY int `json:"rightBtmY"`
ImgWidth int `json:"imgWidth"`
ImgHeight int `json:"imgHeight"`
ShotTime string `json:"shotTime"` // yyyyMMddHHmmss
}
type SubmitResultRequest struct {
TaskID int64 `json:"taskId"`
FacePosition FacePositionInfo `json:"facePosition"`
}
type SubmitFailureRequest struct {
TaskID int64 `json:"taskId"`
ErrorCode string `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
}
// Upload Proxy Methods
func GetUploadConfig(ctx context.Context, scenicId int64, deviceNo string) (*UploadConfig, error) {
url := fmt.Sprintf("%s/proxy/VIID/upload-config?scenicId=%d&deviceNo=%s", config.Config.Viid.ServerUrl, scenicId, deviceNo)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := GetAPIClient().Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result ProxyResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if !result.Success {
return nil, fmt.Errorf("get upload config failed: %s", result.Message)
}
return result.Data, nil
}
func SubmitResult(ctx context.Context, taskID int64, facePos FacePositionInfo) error {
url := fmt.Sprintf("%s/proxy/VIID/submit-result", config.Config.Viid.ServerUrl)
reqData := SubmitResultRequest{
TaskID: taskID,
FacePosition: facePos,
}
jsonData, _ := json.Marshal(reqData)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := GetAPIClient().Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var result struct {
Code int `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
if !result.Success {
return fmt.Errorf("submit result failed: %s", result.Message)
}
return nil
}
func SubmitFailure(ctx context.Context, taskID int64, errorCode, errorMessage string) error {
url := fmt.Sprintf("%s/proxy/VIID/submit-failure", config.Config.Viid.ServerUrl)
reqData := SubmitFailureRequest{
TaskID: taskID,
ErrorCode: errorCode,
ErrorMessage: errorMessage,
}
jsonData, _ := json.Marshal(reqData)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := GetAPIClient().Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func UploadFileToOSS(ctx context.Context, uploadUrl string, data []byte, contentType string) error {
req, err := http.NewRequestWithContext(ctx, "PUT", uploadUrl, bytes.NewReader(data))
if err != nil {
return err
}
req.Header.Set("Content-Length", fmt.Sprintf("%d", len(data)))
req.Header.Set("Content-Type", contentType)
resp, err := GetUploadClient().Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
logger.Error("oss upload failed", zap.String("body", string(body)))
return fmt.Errorf("oss upload failed: %d", resp.StatusCode)
}
return nil
}