feat(govai): 0617 优化首批 — 安全/私有化/深度研究/服务层/可观测性
借鉴 odysseus 的能力设计,全程净室实现、零 AGPL 代码、不引入 AGPL 依赖。
T1 提示注入防护: pkg/promptguard 包裹外部/知识库内容为不可信数据,buildMessages 移出 system 指令区。 T2 安全 CI: .github/workflows(ci+security: govulncheck/gitleaks/actionlint/hadolint/trivy)+dependabot+.hadolint.yaml;go.mod 加 toolchain go1.25.11 修复 20 个 stdlib CVE。 T3 管理员 2FA: 迁移 000016 + RFC6238 TOTP/备份码(pkg/auth, 零依赖) + 登录流程集成(后端)。 T4 本地模型: LLM/embedding 支持本地 vLLM/Ollama(OpenAI 兼容, 鉴权头条件发送, NoAuth) + docs/local-deploy.md。 T6 深度研究: 迁移 000017 + Python research-worker(净室多步流水线, 检索避开 SearXNG) + Go research 服务/handler/路由。 T7 service 层: 新增 internal/service/{research,twofa}, 2FA 业务逻辑从胖 handler 下沉, 接口注入可单测。 T10 缓存/可观测性: internal/cache(Redis+内存, 优雅降级) 接入 store 热点列表; Prometheus 指标+/metrics; docs/openapi.yaml。 验证: go build/vet/test ./... 全绿(8 包); research-worker 12 单测过; 真实 PG 应用迁移并烟测。
This commit is contained in:
@@ -4,10 +4,13 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/enterprise-ai-platform/server/internal/cache"
|
||||
"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/internal/service/research"
|
||||
"github.com/enterprise-ai-platform/server/internal/service/twofa"
|
||||
"github.com/enterprise-ai-platform/server/pkg/auth"
|
||||
"github.com/enterprise-ai-platform/server/pkg/dify"
|
||||
"github.com/enterprise-ai-platform/server/pkg/embedding"
|
||||
@@ -16,6 +19,7 @@ import (
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
@@ -27,6 +31,7 @@ func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.H
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(mw.Metrics)
|
||||
r.Use(middleware.Timeout(15 * time.Minute))
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"http://localhost:*", "https://*"},
|
||||
@@ -48,21 +53,26 @@ func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.H
|
||||
if cfg.LLM.AnthropicKey != "" {
|
||||
llmMgr.Register("anthropic", llm.NewAnthropicProvider(cfg.LLM.AnthropicKey, cfg.LLM.AnthropicBaseURL, cfg.LLM.AnthropicModel))
|
||||
}
|
||||
// 本地推理(私有化):OpenAI 兼容端点(vLLM / Ollama 等),密钥可空
|
||||
if cfg.LLM.LocalBaseURL != "" {
|
||||
llmMgr.Register("local", llm.NewOpenAIProvider(cfg.LLM.LocalKey, cfg.LLM.LocalBaseURL, cfg.LLM.LocalModel))
|
||||
}
|
||||
if cfg.LLM.Provider != "" {
|
||||
llmMgr.SetFallback(cfg.LLM.Provider)
|
||||
}
|
||||
|
||||
// Embedding client(向量化服务,支持 DashScope / OpenAI 兼容 API)
|
||||
// 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,
|
||||
NoAuth: cfg.Embedding.NoAuth,
|
||||
})
|
||||
|
||||
// Handlers
|
||||
authH := handler.NewAuthHandler(pool, jwtMgr)
|
||||
storeH := handler.NewStoreHandler(pool)
|
||||
authH := handler.NewAuthHandler(pool, jwtMgr, twofa.NewService(twofa.NewPgxStore(pool)))
|
||||
storeH := handler.NewStoreHandler(pool, cache.NewRedis(rdb))
|
||||
chatH := handler.NewLLMChatHandler(pool, llmMgr, cfg.LLM.Provider, rdb, cfg.PPTWorker.URL, embedClient)
|
||||
favH := handler.NewFavoriteHandler(pool)
|
||||
adminH := handler.NewAdminHandler(pool)
|
||||
@@ -73,12 +83,20 @@ func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.H
|
||||
pptH := handler.NewPPTHandler(pool, rdb, cfg.PPTWorker.URL)
|
||||
platformH := handler.NewPlatformHandler(pool)
|
||||
|
||||
// 深度研究(service 层编排 + Redis 队列下发给 research-worker)
|
||||
researchBackend := research.NewRedisBackend(rdb)
|
||||
researchSvc := research.NewService(research.NewPgxRepository(pool), researchBackend, researchBackend)
|
||||
researchH := handler.NewResearchHandler(researchSvc)
|
||||
|
||||
// Auth middleware
|
||||
requireAuth := mw.Auth(jwtMgr)
|
||||
requireAdmin := mw.RequireRole("admin")
|
||||
// Health check
|
||||
r.Get("/health", handler.HealthCheck)
|
||||
|
||||
// Prometheus 指标(供监控抓取)
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
// API v1 routes
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
// Public: auth
|
||||
@@ -90,6 +108,11 @@ func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.H
|
||||
r.With(requireAuth).Get("/me", authH.Me)
|
||||
r.With(requireAuth).Put("/profile", authH.UpdateProfile)
|
||||
r.With(requireAuth).Post("/switch-org", authH.SwitchOrg)
|
||||
// 两步验证(2FA / TOTP)
|
||||
r.With(requireAuth).Get("/2fa/status", authH.Status2FA)
|
||||
r.With(requireAuth).Post("/2fa/enroll", authH.Enroll2FA)
|
||||
r.With(requireAuth).Post("/2fa/verify", authH.Verify2FA)
|
||||
r.With(requireAuth).Post("/2fa/disable", authH.Disable2FA)
|
||||
})
|
||||
|
||||
// Organizations (public read)
|
||||
@@ -178,6 +201,14 @@ func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.H
|
||||
r.Get("/tasks/{taskId}/download", pptH.DownloadTask)
|
||||
})
|
||||
|
||||
// 深度研究 (requires auth)
|
||||
r.With(requireAuth).Route("/research", func(r chi.Router) {
|
||||
r.Post("/tasks", researchH.CreateTask)
|
||||
r.Get("/tasks", researchH.ListTasks)
|
||||
r.Get("/tasks/{taskId}", researchH.GetTaskStatus)
|
||||
r.Post("/tasks/{taskId}/cancel", researchH.CancelTask)
|
||||
})
|
||||
|
||||
// Admin (requires admin role)
|
||||
r.With(requireAuth, requireAdmin).With(mw.AuditLog(pool)).Route("/admin", func(r chi.Router) {
|
||||
r.Get("/apps", adminH.ListAllApps)
|
||||
|
||||
Reference in New Issue
Block a user