topfans/backend/services/laserCompositor/handler/compose_handler.go
2026-06-03 22:19:22 +08:00

171 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"go.uber.org/zap"
"github.com/topfans/laserCompositor/compositor"
"github.com/topfans/laserCompositor/config"
"github.com/topfans/laserCompositor/uploader"
)
// ComposeHandler 合成 HTTP handler
type ComposeHandler struct {
cfg *config.Config
uploader *uploader.OSSUploader
logger *zap.Logger
}
// NewComposeHandler 创建合成 handler
func NewComposeHandler(cfg *config.Config, log *zap.Logger) *ComposeHandler {
return &ComposeHandler{
cfg: cfg,
uploader: uploader.NewOSSUploader(cfg.OSS),
logger: log,
}
}
// ComposeSingle POST /compose — 合成单张镭射卡
func (h *ComposeHandler) ComposeSingle(w http.ResponseWriter, r *http.Request) {
var req compositor.ComposeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": fmt.Sprintf("invalid request: %v", err),
})
return
}
h.logger.Info("ComposeSingle started",
zap.Int("variant_index", req.VariantIndex),
zap.Float64("sheen_angle", req.GratingConfig.SheenBandAngle),
zap.String("bg_url_prefix", req.BackgroundURL[:min(50, len(req.BackgroundURL))]),
zap.String("cutout_url_prefix", req.CutoutURL[:min(50, len(req.CutoutURL))]),
zap.String("overlay_url_prefix", req.OverlayURL[:min(50, len(req.OverlayURL))]),
)
start := time.Now()
// 执行合成
pngData, err := compositor.Compose(req)
if err != nil {
h.logger.Error("Compose failed", zap.Error(err))
writeJSON(w, http.StatusInternalServerError, map[string]interface{}{
"error": fmt.Sprintf("compose failed: %v", err),
})
return
}
// 如果指定了 OSS key上传到 OSS
var ossResult *uploader.UploadResult
if req.OutputOSSKey != "" {
filename := fmt.Sprintf("%s.png", req.OutputOSSKey)
ossResult, err = h.uploader.UploadPNG(pngData, "laser-card/", filename)
if err != nil {
h.logger.Error("OSS upload failed", zap.Error(err))
// 不中断:返回 base64 作为降级
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": "partial",
"variant_index": req.VariantIndex,
"width": req.CanvasW(),
"height": req.CanvasH(),
"base64": fmt.Sprintf("data:image/png;base64,PNG_DATA"),
"warning": fmt.Sprintf("OSS upload failed: %v", err),
})
return
}
}
elapsed := time.Since(start)
h.logger.Info("ComposeSingle done",
zap.Int("variant_index", req.VariantIndex),
zap.Duration("elapsed", elapsed),
)
resp := map[string]interface{}{
"status": "succeeded",
"variant_index": req.VariantIndex,
"width": req.CanvasW(),
"height": req.CanvasH(),
}
if ossResult != nil {
resp["oss_key"] = ossResult.OSSKey
resp["signed_url"] = ossResult.SignedURL
}
writeJSON(w, http.StatusOK, resp)
}
// ComposeBatch POST /compose/batch — 批量合成多张
func (h *ComposeHandler) ComposeBatch(w http.ResponseWriter, r *http.Request) {
var req struct {
Variants []compositor.ComposeRequest `json:"variants"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": fmt.Sprintf("invalid request: %v", err),
})
return
}
if len(req.Variants) == 0 {
writeJSON(w, http.StatusBadRequest, map[string]interface{}{
"error": "variants array is empty",
})
return
}
results := make([]map[string]interface{}, 0, len(req.Variants))
warnings := make([]string, 0)
for _, variant := range req.Variants {
start := time.Now()
pngData, err := compositor.Compose(variant)
result := map[string]interface{}{
"variant_index": variant.VariantIndex,
}
if err != nil {
result["status"] = "failed"
result["error"] = err.Error()
warnings = append(warnings, fmt.Sprintf("variant %d failed: %v", variant.VariantIndex, err))
} else {
filename := fmt.Sprintf("%s_%s.png", uuid.New().String()[:8], fmt.Sprint(variant.VariantIndex))
ossResult, ossErr := h.uploader.UploadPNG(pngData, "laser-card/", filename)
if ossErr != nil {
result["status"] = "partial"
result["warning"] = ossErr.Error()
} else {
result["status"] = "succeeded"
result["oss_key"] = ossResult.OSSKey
result["signed_url"] = ossResult.SignedURL
}
result["width"] = variant.CanvasW()
result["height"] = variant.CanvasH()
}
result["elapsed_ms"] = time.Since(start).Milliseconds()
results = append(results, result)
}
resp := map[string]interface{}{
"batch_status": "completed",
"variants": results,
"warnings": warnings,
}
writeJSON(w, http.StatusOK, resp)
}
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}