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:
freedakgmail
2026-06-17 17:52:47 +08:00
parent 97feb42afb
commit c949204662
55 changed files with 4341 additions and 25 deletions
+34 -3
View File
@@ -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)