Files
goaiapi/src/go/web.go
XiuChengWu 1216ad0145 重构项目结构并纳入版本管理
- 源码移至 src/go/,编译产物输出到 build/(不入库)
- 新增 .gitignore:排除 data/、build/、*.exe、*.log、说明.md
- 新增 build.ps1:从 src/go 编译到 build/relay.exe
- 移除 说明.md 的版本跟踪(含上游密钥,仅保留在本地)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 22:03:45 +08:00

104 lines
2.5 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 main
import (
"encoding/json"
"log"
"net/http"
"strconv"
)
// startWebUI 在独立端口起一个只读管理界面,对外提供 dashboard + JSON API。
// 单独端口避免和反代端口冲突(反代会把所有路径转发上游)。
func startWebUI(addr string, rec *Recorder) {
mux := webMux(rec)
log.Printf("Web 管理界面启动: http://%s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Printf("[!] Web 管理界面退出: %v", err)
}
}
// webMux 组装管理界面的路由,单独抽出便于测试。
func webMux(rec *Recorder) http.Handler {
mux := http.NewServeMux()
// 首页dashboard HTML
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(dashboardHTML))
})
// 聚合统计
mux.HandleFunc("GET /api/stats", func(w http.ResponseWriter, r *http.Request) {
s, err := rec.Stats()
if err != nil {
writeErr(w, err)
return
}
writeJSON(w, s)
})
// 记录列表(支持 model/format/status/limit/offset 过滤)
mux.HandleFunc("GET /api/records", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
opts := ListOptions{
Model: q.Get("model"),
Format: q.Get("format"),
Limit: atoiDefault(q.Get("limit"), 100),
Offset: atoiDefault(q.Get("offset"), 0),
Status: atoiDefault(q.Get("status"), 0),
}
recs, total, err := rec.List(opts)
if err != nil {
writeErr(w, err)
return
}
if recs == nil {
recs = []Record{}
}
writeJSON(w, map[string]any{"total": total, "records": recs})
})
// 单条记录原文详情(含完整请求/响应)
mux.HandleFunc("GET /api/records/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil {
http.Error(w, "无效 id", http.StatusBadRequest)
return
}
b, err := rec.ReadBlob(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
writeJSON(w, b)
})
return mux
}
func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err := json.NewEncoder(w).Encode(v); err != nil {
log.Printf("[!] 写 JSON 响应失败: %v", err)
}
}
func writeErr(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
func atoiDefault(s string, def int) int {
if s == "" {
return def
}
n, err := strconv.Atoi(s)
if err != nil {
return def
}
return n
}