Files
GovAI/server/cmd/server/router.go
T
selfrelease 96930b585c feat: 优化模型配置管理 - 动态使用优先级最高的Provider
## 主要改进

### 1. 数据库驱动的模型配置
- 新增 GetActiveProviderWithModel() 方法,从数据库获取优先级最高的 Provider 及其默认模型
- 支持多 Provider 配置,通过 priority 字段控制优先级
- 实现 5 分钟缓存机制,减少数据库查询

### 2. 应用模型配置优化
- 移除应用层硬编码模型配置
- 应用自动使用优先级最高的 Provider 的默认模型
- 支持应用级模型覆盖(可选)

### 3. 工具脚本
- check-providers: 查询数据库中的 Provider 和应用配置
- fix-providers: 修复 Provider 配置(补全 config 和 models 字段)
- clear-app-models: 清空应用硬编码模型配置

### 4. 代码质量
- 删除未使用的 getProvider() 方法
- 修复 fmt.Println 冗余换行警告
- 统一代码格式

## 技术细节

**降级策略**:
数据库 Provider (优先级) → 环境变量 Provider → 应用配置模型

**当前配置**:
- 优先级 110: 本地LLM (qwen2.5-7b-instruct)
- 优先级 100: 阿里云百炼 (qwen-plus)

所有 41 个应用现在自动使用本地模型,无需手动配置。
2026-06-22 18:46:44 +08:00

256 lines
9.4 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 (
"context"
"net/http"
"time"
"github.com/enterprise-ai-platform/server/internal/config"
"github.com/enterprise-ai-platform/server/internal/handler"
mw "github.com/enterprise-ai-platform/server/internal/middleware"
"github.com/enterprise-ai-platform/server/internal/response"
"github.com/enterprise-ai-platform/server/pkg/auth"
"github.com/enterprise-ai-platform/server/pkg/dify"
"github.com/enterprise-ai-platform/server/pkg/embedding"
"github.com/enterprise-ai-platform/server/pkg/llm"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/redis/go-redis/v9"
)
func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.Handler {
r := chi.NewRouter()
// Global middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(15 * time.Minute))
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"http://localhost:*", "https://*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-Request-ID"},
AllowCredentials: true,
MaxAge: 300,
}))
// Services
jwtMgr := auth.NewJWTManager(cfg.JWT.Secret, cfg.JWT.AccessExpiry, cfg.JWT.RefreshExpiry)
difyClient := dify.NewClient(cfg.Dify.APIURL)
// LLM Manager — direct model calls replacing Dify for chat
llmMgr := llm.NewManager()
llmMgr.SetPool(pool)
// 优先从数据库加载 providers(如果失败,回退到环境变量配置)
if err := llmMgr.LoadProvidersFromDB(context.Background()); err != nil {
// 回退:使用环境变量配置
if cfg.LLM.OpenAIKey != "" {
llmMgr.Register("openai", llm.NewOpenAIProvider(cfg.LLM.OpenAIKey, cfg.LLM.OpenAIBaseURL, cfg.LLM.OpenAIModel))
}
if cfg.LLM.AnthropicKey != "" {
llmMgr.Register("anthropic", llm.NewAnthropicProvider(cfg.LLM.AnthropicKey, cfg.LLM.AnthropicBaseURL, cfg.LLM.AnthropicModel))
}
if cfg.LLM.Provider != "" {
llmMgr.SetFallback(cfg.LLM.Provider)
}
}
// Embedding client(向量化服务,支持 DashScope / OpenAI 兼容 API
embedClient := embedding.NewClient(embedding.Config{
APIKey: cfg.Embedding.APIKey,
BaseURL: cfg.Embedding.BaseURL,
Model: cfg.Embedding.Model,
Dimensions: cfg.Embedding.Dimensions,
})
// Handlers
authH := handler.NewAuthHandler(pool, jwtMgr)
storeH := handler.NewStoreHandler(pool)
chatH := handler.NewLLMChatHandler(pool, llmMgr, cfg.LLM.Provider, rdb, cfg.PPTWorker.URL, embedClient)
favH := handler.NewFavoriteHandler(pool)
adminH := handler.NewAdminHandler(pool)
creatorH := handler.NewCreatorHandler(pool, difyClient)
kbH := handler.NewKnowledgeHandler(pool, embedClient)
docTplH := handler.NewDocTemplateHandler(pool, llmMgr, cfg.LLM.Provider)
analysisH := handler.NewAnalysisTemplateHandler(pool, llmMgr, cfg.LLM.Provider)
pptH := handler.NewPPTHandler(pool, rdb, cfg.PPTWorker.URL)
platformH := handler.NewPlatformHandler(pool)
// Auth middleware
requireAuth := mw.Auth(jwtMgr)
requireAdmin := mw.RequireRole("admin")
// Health check
r.Get("/health", handler.HealthCheck)
// API v1 routes
r.Route("/api/v1", func(r chi.Router) {
// Public: auth
r.Route("/auth", func(r chi.Router) {
r.Post("/register", authH.Register)
r.Post("/login", authH.Login)
r.Post("/refresh", authH.Refresh)
r.With(requireAuth).Post("/logout", authH.Logout)
r.With(requireAuth).Get("/me", authH.Me)
r.With(requireAuth).Put("/profile", authH.UpdateProfile)
r.With(requireAuth).Post("/switch-org", authH.SwitchOrg)
})
// Organizations (public read)
r.Get("/organizations", authH.ListOrganizations)
// Store (public read, auth optional for personalization)
r.Route("/store", func(r chi.Router) {
r.Get("/categories", storeH.ListCategories)
r.Get("/apps", storeH.ListApps)
r.Get("/apps/{slug}", storeH.GetApp)
r.Get("/featured", storeH.Featured)
r.Get("/rankings", storeH.Rankings)
r.With(requireAuth).Get("/recent", storeH.Recent)
})
// App usage (requires auth)
r.With(requireAuth).Route("/apps/{id}", func(r chi.Router) {
r.With(mw.RateLimit(rdb, 30, time.Minute)).Post("/chat", chatH.Chat)
r.Post("/completion", chatH.Completion)
r.Post("/generate-doc", docTplH.GenerateDocument)
r.Post("/generate-analysis", analysisH.GenerateReport)
r.Get("/conversations", chatH.Conversations)
r.Get("/conversations/{convId}/messages", chatH.Messages)
r.Delete("/conversations/{convId}", chatH.DeleteConversation)
r.Put("/conversations/{convId}/name", chatH.RenameConversation)
r.Post("/conversations/batch-delete", chatH.BatchDeleteConversations)
r.Post("/feedback", chatH.Feedback)
r.Post("/favorite", favH.AddFavorite)
r.Delete("/favorite", favH.RemoveFavorite)
r.Post("/rating", favH.AddRating)
r.Get("/ratings", favH.ListRatings)
})
// Document templates (public read)
r.With(requireAuth).Route("/doc-templates", func(r chi.Router) {
r.Get("/", docTplH.ListTemplates)
r.Get("/{templateId}", docTplH.GetTemplate)
})
// Analysis report templates
r.With(requireAuth).Route("/analysis-templates", func(r chi.Router) {
r.Get("/", analysisH.ListTemplates)
r.Get("/{templateId}", analysisH.GetTemplate)
})
// Personal (requires auth)
r.With(requireAuth).Route("/me", func(r chi.Router) {
r.Get("/favorites", favH.ListFavorites)
r.Get("/stats", favH.PersonalStats)
})
// Application management (all authenticated users can manage their own apps, admins can manage all)
r.With(requireAuth).Route("/creator", func(r chi.Router) {
r.Get("/apps", creatorH.ListMyApps)
r.Post("/apps", creatorH.CreateApp)
r.Get("/apps/{id}", creatorH.GetApp)
r.Put("/apps/{id}", creatorH.UpdateApp)
r.Delete("/apps/{id}", creatorH.DeleteApp)
r.Post("/apps/{id}/test", notImplemented)
r.Post("/apps/{id}/submit-review", creatorH.SubmitReview)
r.Post("/apps/{id}/withdraw", creatorH.WithdrawReview)
r.Post("/apps/{id}/request-delist", creatorH.RequestDelist)
r.Get("/templates", creatorH.ListTemplates)
r.Post("/apps/from-template", notImplemented)
})
// Knowledge base (requires auth)
r.With(requireAuth).Route("/knowledge", func(r chi.Router) {
r.Get("/", kbH.ListKnowledgeBases)
r.Post("/", kbH.CreateKnowledgeBase)
r.Post("/reindex", kbH.ReindexAll)
r.Post("/reembed", kbH.ReembedChunks)
r.Put("/{id}", kbH.UpdateKnowledgeBase)
r.Delete("/{id}", kbH.DeleteKnowledgeBase)
r.Post("/{id}/documents", kbH.UploadDocument)
r.Get("/{id}/documents", kbH.ListDocuments)
r.Delete("/{id}/documents/{docId}", kbH.DeleteDocument)
})
// PPT 生成 (requires auth)
r.With(requireAuth).Route("/ppt", func(r chi.Router) {
r.Post("/tasks", pptH.CreateTask)
r.Post("/tasks/upload", pptH.CreateTaskWithFile)
r.Get("/tasks", pptH.ListTasks)
r.Get("/tasks/{taskId}", pptH.GetTaskStatus)
r.Get("/tasks/{taskId}/download", pptH.DownloadTask)
})
// Admin (requires admin role)
r.With(requireAuth, requireAdmin).With(mw.AuditLog(pool)).Route("/admin", func(r chi.Router) {
r.Get("/apps", adminH.ListAllApps)
r.Get("/reviews", adminH.ListPendingReviews)
r.Post("/reviews/{id}/approve", adminH.ApproveReview)
r.Post("/reviews/{id}/reject", adminH.RejectReview)
r.Post("/apps/{id}/delist", adminH.DelistApp)
r.Post("/apps/{id}/relist", adminH.RelistApp)
r.Get("/users", adminH.ListUsers)
r.Put("/users/{id}/role", adminH.UpdateUserRole)
r.Put("/users/{id}/status", adminH.UpdateUserStatus)
r.Get("/departments", notImplemented)
r.Get("/analytics/overview", adminH.Overview)
r.Get("/analytics/usage", adminH.UsageAnalytics)
r.Get("/analytics/cost", notImplemented)
r.Get("/analytics/users", notImplemented)
r.Get("/audit-logs", adminH.ListAuditLogs)
r.Get("/models", notImplemented)
r.Post("/models/providers", notImplemented)
r.Put("/quotas", notImplemented)
})
// Platform (requires super_admin role) - 跨机构平台管理
r.With(requireAuth, mw.RequireSuperAdmin).With(mw.AuditLog(pool)).Route("/platform", func(r chi.Router) {
// 平台总览
r.Get("/overview", platformH.Overview)
r.Get("/org-ranking", platformH.OrgRanking)
// 机构管理
r.Get("/orgs", platformH.ListOrgs)
r.Post("/orgs", platformH.CreateOrg)
r.Put("/orgs/{id}", platformH.UpdateOrg)
r.Delete("/orgs/{id}", platformH.DeleteOrg)
// 全局用户管理
r.Get("/users", platformH.ListAllUsers)
r.Put("/users/{id}/role", platformH.UpdateUserRole)
r.Put("/users/{id}/status", platformH.UpdateUserStatus)
r.Put("/users/{id}/org", platformH.AssignUserOrg)
// 全局应用管理
r.Get("/apps", platformH.ListAllApps)
r.Put("/apps/{id}/featured", platformH.SetFeatured)
r.Post("/apps/{id}/force-delist", platformH.ForceDelist)
// 全局审计日志
r.Get("/audit-logs", platformH.ListAllAuditLogs)
// 模型提供商
r.Get("/providers", platformH.ListProviders)
r.Post("/providers", platformH.CreateProvider)
r.Post("/providers/{id}/test", platformH.TestProvider)
r.Put("/providers/{id}", platformH.UpdateProvider)
r.Delete("/providers/{id}", platformH.DeleteProvider)
// 全局配额
r.Get("/quotas", platformH.ListQuotas)
r.Post("/quotas", platformH.UpsertQuota)
r.Delete("/quotas/{id}", platformH.DeleteQuota)
})
})
return r
}
func notImplemented(w http.ResponseWriter, r *http.Request) {
response.Error(w, http.StatusNotImplemented, 50100, "接口开发中")
}