jim800121chen adab000987 feat(camera): camera/media 推論改走 tunnel proxy(塊1,取代 501 stub)
推論工作區後端塊1:把 camera/media 9 條路徑從 501 stub 換成走
newProxyHandler 的 tunnel proxy 宣告(比照 devices.go 慣例,新增
camera.go 而非塞 stubs.go)。

- GET /camera/stream → streaming:true(MJPEG multipart 長連線逐 chunk flush)
- 其餘 8 條(camera list/start/stop、media upload×3、batch-images/:index、
  seek)→ request-response(upload 大檔的 request body 由 proxy 直接
  streaming 送出,streaming flag 只控 response)
- 掛在已套 AuthMiddleware 的 apiGroup 下,沿用剝 Origin 修正

WS inference:<deviceId> 結果推播為塊2(ForwardWebSocket);MJPEG <img>
的 stream 認證分支(R-C4)待 security 定案短期 stream-token 方案,本塊未做。

Reviewer 0C/0M/1Mi/3Sug 通過。+camera_test.go 路由黑箱測試(路徑不再
501、無 session 回 502 TUNNEL_DISCONNECTED),全套 api 回歸綠。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 04:36:06 +08:00

63 lines
3.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.

// camera.go — /api/camera/* 與 /api/media/* 的 route 宣告。
//
// 這兩組 endpoint 全部走 tunnel proxy實際的攝影機 / 圖片 / 影片 / 批次推論
// 都執行在 local agentUSB 插著攝影機、跑著 local-tool 的那台機器。api-server
// 只負責「面向瀏覽器 + auth」把請求原樣中繼給 local agent見 proxy.go
//
// 為什麼從 stubs.go 的 501 搬出來獨立成檔:
// - 對齊 devices.go 的慣例(每個 domain 一個 register 檔stubs.go 只留真正
// 還沒 handler 的 endpoint。
// - camera/media 與 devices 走的是同一套 newProxyHandlerpattern 已在 B5 生產跑通
// /api/devices/scan 等)。這裡只是「宣告路徑 + 決定 streaming flag」。
//
// streaming flag 的判斷:
// - GET /camera/stream → streamingMJPEG multipart/x-mixed-replace 長連線)
// - GET /media/batch-images/:i → **非 streaming**:回單張 jpeg一次讀完
// request-response 即可。
// - 其餘list / start / stop / upload / seek→ 非 streaming request-response。
// upload 類的 request body 由 http.NewRequestWithContext 以 streaming 方式送出
// proxy.go 直接把 c.Request.Body 當 upstream body大 multipart 也不會一次
// 載入記憶體streaming flag 只控制「response 是否逐 chunk flush」與 request
// body 上傳無關,所以 upload 用 proxyOptions{} 即可。
//
// 對齊 .autoflow/04-architecture/camera-e2e-effort-estimate.md §2.1 / §3.1 與
// local-tool/server/internal/api/router.go:108-118路徑與 local agent 完全一致,
// 無需 rewritePath
//
// POC 對照edge-ai-platform relay/server.go:213-228flusher 逐 chunk 送 MJPEG
// 對應到 visionA 的 proxy.go writeProxyResponse streaming 分支 —— 那段已在 api-server
// 實作好camera/media 只需宣告路徑即可享用。
package api
import (
"github.com/gin-gonic/gin"
)
// registerCameraRoutes 註冊 /api/camera/* 與 /api/media/* 的 tunnel proxy routes。
//
// 掛在 apiGroup已套 AuthMiddleware底下所以每條路徑都受 OIDC session 保護。
// MJPEG <img src> 帶不了 Authorization header 的認證問題R-M3/R-C4由前端 + api-server
// 的 query-token / cookie 機制處理,不在本檔範圍(本檔只負責把已認證請求中繼出去)。
func registerCameraRoutes(g *gin.RouterGroup, deps Deps) {
// request-response 類body / response 一次讀完即可。
proxy := newProxyHandler(deps, proxyOptions{})
// streaming 類response 是 MJPEG 長連線,需逐 chunk flush。
streamProxy := newProxyHandler(deps, proxyOptions{streaming: true})
// --- Camera即時攝影機 ---
g.GET("/camera/list", proxy) // 列出可用攝影機
g.POST("/camera/start", proxy) // 開 camera + 起推論,回 streamUrl
g.POST("/camera/stop", proxy) // 停止 pipeline
g.GET("/camera/stream", streamProxy) // MJPEG multipart/x-mixed-replace 長連線
// --- Media圖片 / 影片 / 批次推論) ---
// 注意media 的「結果畫面」也走上面的 /camera/streampipeline.go 共用同一個
// MJPEGStreamer所以 media 端點本身都是 request-response回 streamUrl + metadata
g.POST("/media/upload/image", proxy) // multipart 圖片上傳
g.POST("/media/upload/video", proxy) // multipart 影片上傳大檔body streaming 送出)
g.POST("/media/upload/batch-images", proxy) // 多檔(最多 50 張)
g.GET("/media/batch-images/:index", proxy) // 回單張 jpeg非 streaming
g.POST("/media/seek", proxy) // 影片 seek
}