后端 (Go/Gin/GORM): - 配置(viper+env)、SQLite/Postgres 迁移、argon2id、AES-GCM 渠道密钥、JWT+refresh cookie - 用户注册/登录/刷新/登出、API Key CRUD(仅存哈希、明文一次展示) - 代理网关: /v1/chat/completions、/v1/responses、/v1/models 直通 OpenAI 渠道 非流式+流式(SSE 零缓冲转发), 用量捕获(chat 末块/responses completed 嵌套), OpenAI 错误格式(401/402/404/502), 余额检查 - 异步批量记账 + 余额流水 + 日聚合, admin 用户/余额/配置 API - 单测: crypto/jwt/apikey/流式 usage 提取 前端 (Vue3+TS+Vite+Tailwind v4): - taste-skill 设计 tokens: 深色仪表盘, 石墨+信号铜色, Outfit+JetBrains Mono - Landing/登录/注册, 控制台(仪表盘图表/密钥管理/用量明细) - 基础组件 Button/Input/Badge/Modal, ECharts 用量图 部署: docker-compose(nginx+api+postgres), 双 Dockerfile, nginx SSE 反代 联调: scripts/mockupstream 本地 mock 上游, 端到端验证通过
85 lines
2.3 KiB
Go
85 lines
2.3 KiB
Go
package api
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/openteam/server/internal/api/middleware"
|
||
"github.com/openteam/server/internal/app"
|
||
"github.com/openteam/server/internal/proxy"
|
||
)
|
||
|
||
// NewRouter 装配所有路由:
|
||
// - /v1/* 代理端点(Bearer API Key,OpenAI 格式错误体)
|
||
// - /api/v1/* 管理 API(会话 JWT)
|
||
func NewRouter(a *app.App, gw *proxy.Gateway) *gin.Engine {
|
||
if a.Cfg.Env == "production" {
|
||
gin.SetMode(gin.ReleaseMode)
|
||
}
|
||
r := gin.New()
|
||
r.Use(gin.Logger(), gin.Recovery(), middleware.CORS())
|
||
|
||
h := NewHandler(a)
|
||
|
||
// --- 代理端点(对外)---
|
||
proxyGroup := r.Group("/v1")
|
||
{
|
||
proxyGroup.Any("/chat/completions", gw.Auth, gw.Handle)
|
||
proxyGroup.Any("/responses", gw.Auth, gw.Handle)
|
||
proxyGroup.Any("/models", gw.Auth, gw.Handle)
|
||
}
|
||
// 未匹配的 /v1/* 返回 OpenAI 风格 404(需先认证)
|
||
r.NoRoute(func(c *gin.Context) {
|
||
if len(c.Request.URL.Path) >= 3 && c.Request.URL.Path[:3] == "/v1" {
|
||
gw.Auth(c)
|
||
if !c.IsAborted() {
|
||
gw.Handle(c)
|
||
}
|
||
return
|
||
}
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||
})
|
||
|
||
// --- 管理 API ---
|
||
api := r.Group("/api/v1")
|
||
{
|
||
auth := api.Group("/auth")
|
||
{
|
||
auth.POST("/register", h.Register)
|
||
auth.POST("/login", h.Login)
|
||
auth.POST("/refresh", h.Refresh)
|
||
auth.POST("/logout", h.Logout)
|
||
auth.GET("/me", middleware.SessionAuth(a), h.Me)
|
||
}
|
||
|
||
user := api.Group("", middleware.SessionAuth(a))
|
||
{
|
||
user.GET("/user/profile", h.UserProfile)
|
||
user.GET("/user/balance", h.UserBalance)
|
||
user.GET("/user/models", h.UserModels)
|
||
user.GET("/usage/summary", h.UsageSummary)
|
||
user.GET("/usage/stats", h.UsageStats)
|
||
user.GET("/usage/logs", h.UsageLogs)
|
||
user.POST("/keys", h.CreateKey)
|
||
user.GET("/keys", h.ListKeys)
|
||
user.PATCH("/keys/:id", h.PatchKey)
|
||
user.DELETE("/keys/:id", h.DeleteKey)
|
||
}
|
||
|
||
admin := api.Group("/admin", middleware.SessionAuth(a), middleware.AdminOnly)
|
||
{
|
||
admin.GET("/users", h.AdminUsers)
|
||
admin.PATCH("/users/:id", h.AdminPatchUser)
|
||
admin.POST("/users/:id/balance", h.AdminAdjustBalance)
|
||
admin.GET("/config", h.AdminConfig)
|
||
admin.PUT("/config", h.AdminPutConfig)
|
||
// 渠道/模型/用量管理(M4);充值审核(M5 预留)
|
||
}
|
||
}
|
||
|
||
r.GET("/healthz", func(c *gin.Context) {
|
||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||
})
|
||
return r
|
||
}
|