171 lines
4.7 KiB
Go
171 lines
4.7 KiB
Go
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)
|
||
}
|