96930b585c
## 主要改进 ### 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 个应用现在自动使用本地模型,无需手动配置。
256 lines
9.4 KiB
Go
256 lines
9.4 KiB
Go
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, "接口开发中")
|
||
}
|