214 lines
6.0 KiB
Go
214 lines
6.0 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/bizdev"
|
|
"chat-agent-system/internal/chat"
|
|
"chat-agent-system/internal/config"
|
|
"chat-agent-system/internal/coordinator"
|
|
"chat-agent-system/internal/intent"
|
|
)
|
|
|
|
// Server 提供基于 HTTP RESTful 与 SSE 的商务与研发参谋接口服务
|
|
type Server struct {
|
|
cfg *config.Config
|
|
svc *bizdev.Service
|
|
rec intent.Recognizer
|
|
engine *coordinator.CoordinatorEngine
|
|
sessions map[string]*chat.Session
|
|
sessionsMu sync.RWMutex
|
|
httpServer *http.Server
|
|
}
|
|
|
|
// NewServer 构造函数
|
|
func NewServer(cfg *config.Config, svc *bizdev.Service) *Server {
|
|
if cfg == nil {
|
|
cfg, _ = config.Load("")
|
|
}
|
|
if svc == nil {
|
|
svc = bizdev.NewService(cfg)
|
|
}
|
|
|
|
rec := intent.NewRecognizer()
|
|
coordEngine := coordinator.NewCoordinatorEngine(svc)
|
|
|
|
s := &Server{
|
|
cfg: cfg,
|
|
svc: svc,
|
|
rec: rec,
|
|
engine: coordEngine,
|
|
sessions: make(map[string]*chat.Session),
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Routes 构建 HTTP 路由分发器
|
|
func (s *Server) Routes() http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
// 1. 健康检查与系统自检
|
|
mux.HandleFunc("GET /health", s.handleHealth)
|
|
mux.HandleFunc("GET /api/v1/health", s.handleHealth)
|
|
|
|
// 2. 项目状态与会话查询
|
|
mux.HandleFunc("GET /api/v1/projects", s.handleGetProject)
|
|
|
|
// 3. 对话协同服务 (标准 JSON 阻塞)
|
|
mux.HandleFunc("POST /api/v1/chat", s.handleChat)
|
|
|
|
// 4. 对话协同服务 (核心 SSE 流式长连接)
|
|
mux.HandleFunc("POST /api/v1/chat/stream", s.handleChatStream)
|
|
mux.HandleFunc("GET /api/v1/chat/stream", s.handleChatStream)
|
|
|
|
// 5. 独立任务 / 阶段执行
|
|
mux.HandleFunc("POST /api/v1/tasks/run", s.handleRunTask)
|
|
mux.HandleFunc("POST /api/v1/tasks/run/stream", s.handleRunTaskStream)
|
|
mux.HandleFunc("GET /api/v1/tasks/run/stream", s.handleRunTaskStream)
|
|
|
|
// 6. 多级上下文与偏好记忆管理
|
|
mux.HandleFunc("GET /api/v1/context/user", s.handleGetUserContext)
|
|
mux.HandleFunc("PUT /api/v1/context/user/preference", s.handleUpdateUserPreference)
|
|
mux.HandleFunc("GET /api/v1/context/project", s.handleGetProjectContext)
|
|
|
|
// 7. 受控文档编制、审核、批准生命周期与认领治理
|
|
mux.HandleFunc("GET /api/v1/docgov/document", s.handleDocGovGetDocument)
|
|
mux.HandleFunc("POST /api/v1/docgov/claim", s.handleDocGovClaim)
|
|
mux.HandleFunc("POST /api/v1/docgov/submit-review", s.handleDocGovSubmitReview)
|
|
mux.HandleFunc("POST /api/v1/docgov/review-decision", s.handleDocGovReviewDecision)
|
|
mux.HandleFunc("POST /api/v1/docgov/approver-decision", s.handleDocGovApproverDecision)
|
|
mux.HandleFunc("POST /api/v1/docgov/proposal/decide", s.handleDocGovDecideProposal)
|
|
mux.HandleFunc("GET /api/v1/docgov/comments", s.handleDocGovListComments)
|
|
mux.HandleFunc("POST /api/v1/docgov/comments", s.handleDocGovAddComment)
|
|
mux.HandleFunc("GET /api/v1/docgov/audit-logs", s.handleDocGovListAuditLogs)
|
|
|
|
return s.corsMiddleware(mux)
|
|
}
|
|
|
|
// Start 启动 HTTP 守护进程
|
|
func (s *Server) Start() error {
|
|
addr := fmt.Sprintf("%s:%d", s.cfg.HTTPHost, s.cfg.HTTPPort)
|
|
s.httpServer = &http.Server{
|
|
Addr: addr,
|
|
Handler: s.Routes(),
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
}
|
|
|
|
return s.httpServer.ListenAndServe()
|
|
}
|
|
|
|
// Shutdown 优雅停机
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
if s.httpServer != nil {
|
|
return s.httpServer.Shutdown(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getOrCreateSession 获取或动态构建会话
|
|
func (s *Server) getOrCreateSession(
|
|
sessionID string,
|
|
projectName string,
|
|
customerName string,
|
|
deviceType string,
|
|
intendedUse string,
|
|
targetMarket string,
|
|
workDir string,
|
|
userName string,
|
|
userRole string,
|
|
) *chat.Session {
|
|
if sessionID == "" {
|
|
sessionID = fmt.Sprintf("sess_%d", time.Now().UnixNano())
|
|
}
|
|
|
|
s.sessionsMu.RLock()
|
|
existing, exists := s.sessions[sessionID]
|
|
s.sessionsMu.RUnlock()
|
|
if exists && existing != nil {
|
|
return existing
|
|
}
|
|
|
|
s.sessionsMu.Lock()
|
|
defer s.sessionsMu.Unlock()
|
|
// 双重检查
|
|
if existing, exists := s.sessions[sessionID]; exists && existing != nil {
|
|
return existing
|
|
}
|
|
|
|
if userName == "" {
|
|
userName = "用户"
|
|
}
|
|
if userRole == "" {
|
|
userRole = "engineer"
|
|
}
|
|
|
|
u := coordinator.UserInfo{
|
|
ID: "u_" + sessionID,
|
|
Name: userName,
|
|
Role: userRole,
|
|
ProjectRole: "composer",
|
|
}
|
|
|
|
var userCtx *coordinator.UserProjectContext
|
|
if workDir != "" {
|
|
assembler := coordinator.NewContextAssembler()
|
|
userCtx, _ = assembler.AssembleFromWorkspace(workDir, u)
|
|
}
|
|
|
|
if userCtx == nil {
|
|
if projectName == "" {
|
|
projectName = "医疗器械研发项目"
|
|
}
|
|
if customerName == "" {
|
|
customerName = "水木东方客户"
|
|
}
|
|
userCtx = &coordinator.UserProjectContext{
|
|
SnapshotID: fmt.Sprintf("ctx_%d", time.Now().UnixNano()),
|
|
AssembledAt: time.Now(),
|
|
User: u,
|
|
WorkDir: workDir,
|
|
Project: coordinator.ProjectInfo{
|
|
Name: projectName,
|
|
CustomerName: customerName,
|
|
DeviceType: deviceType,
|
|
IntendedUse: intendedUse,
|
|
TargetMarket: targetMarket,
|
|
CurrentWorkflow: "intake",
|
|
CurrentStage: bizdev.StageV01_Triage,
|
|
},
|
|
BlackboardFacts: make(map[string]string),
|
|
AvailableActions: coordinator.ComputeAvailableActions(bizdev.StageV01_Triage, u.ProjectRole),
|
|
}
|
|
if targetMarket != "" {
|
|
userCtx.BlackboardFacts["target_market"] = targetMarket
|
|
}
|
|
if deviceType != "" {
|
|
userCtx.BlackboardFacts["device_type"] = deviceType
|
|
}
|
|
}
|
|
|
|
sess := chat.NewSession(sessionID, userCtx, s.rec, s.engine)
|
|
if s.svc != nil {
|
|
sess.CtxMgr = s.svc.ContextManager()
|
|
}
|
|
s.sessions[sessionID] = sess
|
|
return sess
|
|
}
|
|
|
|
func (s *Server) corsMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept")
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|