Files
openteam/server/internal/api/router.go
T
Sakurasan 360c6b33a6 M0+M1: 基建 + 用户/密钥/核心代理
后端 (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 上游, 端到端验证通过
2026-08-15 13:10:47 +08:00

85 lines
2.3 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 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
}