feat(api): 统一公开接口为 /api/v1 前缀

将通用生成、Gemini、可灵、火山、健康检查与 OpenAPI 的推荐入口统一到 /api/v1,并保留历史路径作为兼容别名。同步更新代理配置、接入文档、接口清单和前缀回归测试。\n\n验证:go vet ./...;go test ./...;pnpm openapi;pnpm lint;pnpm test;pnpm build;公开 OpenAPI 71 个方法与接口清单机器比对一致。
This commit is contained in:
2026-07-22 08:48:32 +08:00
parent f7a5f2e808
commit 7c5a999e32
38 changed files with 2294 additions and 5169 deletions
+47 -1
View File
@@ -75,6 +75,21 @@ func geminiGenerateContentModelFromPath(prefix string, requestPath string) (stri
return model, true
}
// geminiGenerateContent godoc
// @Summary Gemini generateContent 兼容接口
// @Description 使用统一 /api/v1 前缀接收 Gemini generateContent 请求;旧 /v1 和 /v1beta 路径保留兼容。
// @Tags gemini-compatible
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param model path string true "模型名称"
// @Param input body map[string]interface{} true "Gemini generateContent 请求"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 502 {object} ErrorEnvelope
// @Router /api/v1/models/{model}:generateContent [post]
func (s *Server) geminiGenerateContent(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
@@ -395,6 +410,18 @@ func geminiUsageMetadataFromOutput(output map[string]any) map[string]any {
return meta
}
// geminiFilesUpload godoc
// @Summary Gemini Files 上传接口
// @Description 使用统一 /api/v1 前缀启动或直接完成 Gemini Files 上传。
// @Tags gemini-compatible
// @Accept octet-stream
// @Produce json
// @Security BearerAuth
// @Param version path string true "Gemini 版本(v1 或 v1beta"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Router /api/v1/gemini/upload/{version}/files [post]
func (s *Server) geminiFilesUpload(w http.ResponseWriter, r *http.Request) {
if err := validateGeminiFilesVersion(r.PathValue("version")); err != nil {
writeError(w, http.StatusBadRequest, err.Error(), clients.ErrorCode(err))
@@ -412,6 +439,18 @@ func (s *Server) geminiFilesUpload(w http.ResponseWriter, r *http.Request) {
})
}
// geminiFilesUploadFinalize godoc
// @Summary 完成 Gemini Files 分段上传
// @Tags gemini-compatible
// @Accept octet-stream
// @Produce json
// @Security BearerAuth
// @Param version path string true "Gemini 版本(v1 或 v1beta"
// @Param uploadID path string true "上传会话 ID"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Router /api/v1/gemini/upload/{version}/files/{uploadID} [post]
func (s *Server) geminiFilesUploadFinalize(w http.ResponseWriter, r *http.Request) {
if err := validateGeminiFilesVersion(r.PathValue("version")); err != nil {
writeError(w, http.StatusBadRequest, err.Error(), clients.ErrorCode(err))
@@ -440,11 +479,18 @@ func (s *Server) startGeminiFilesUpload(w http.ResponseWriter, r *http.Request)
CreatedAt: time.Now(),
}
s.geminiUploadSessions.Store(uploadID, session)
w.Header().Set("X-Goog-Upload-URL", absoluteRequestURL(r, "/upload/"+session.Version+"/files/"+uploadID))
w.Header().Set("X-Goog-Upload-URL", absoluteRequestURL(r, geminiUploadPath(r, session.Version, uploadID)))
w.Header().Set("X-Goog-Upload-Status", "active")
writeJSON(w, http.StatusOK, map[string]any{})
}
func geminiUploadPath(r *http.Request, version string, uploadID string) string {
if strings.HasPrefix(r.URL.Path, "/api/v1/gemini/upload/") {
return "/api/v1/gemini/upload/" + version + "/files/" + uploadID
}
return "/upload/" + version + "/files/" + uploadID
}
func (s *Server) finalizeGeminiFilesUpload(w http.ResponseWriter, r *http.Request, uploadID string, session geminiUploadSession) {
if uploadID == "" {
uploadID = newGeminiUploadID()