Files
Agent-System/internal/server/handlers_context.go
T
weichen f3f23a3018 feat(errcode): 用稳定错误码记录 HTTP 与异步失败
失败响应此前不写日志,request_id 无法对照原因。注册表不覆盖处理器已经给出的 HTTP 状态。
2026-09-28 02:27:16 +00:00

147 lines
4.4 KiB
Go

package server
import (
"encoding/json"
"net/http"
"strconv"
"chat-agent-system/internal/ctxmgr"
)
// -----------------------------------------------------------------------------
// 多级上下文与偏好记忆 RESTful 控制器
// -----------------------------------------------------------------------------
// handleGetUserContext 获取单用户私有上下文与个性化偏好
// user_id 仅允许等于当前认证用户本人(或系统管理员),不再默认回落 user id=1。
func (s *Server) handleGetUserContext(w http.ResponseWriter, r *http.Request) {
user, ok := s.requirePrincipal(w, r)
if !ok {
return
}
userIDStr := r.URL.Query().Get("user_id")
projectIDStr := r.URL.Query().Get("project_id")
sessionID := r.URL.Query().Get("session_id")
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
if userID <= 0 {
userID = user.ID
}
if userID != user.ID && !s.isSystemAdminUser(r.Context(), user) {
writeJSONResponse(w, http.StatusForbidden, map[string]string{
"error_code": "ERR_FORBIDDEN",
"message": "无权查询其他用户的上下文",
})
return
}
projectID, _ := strconv.ParseInt(projectIDStr, 10, 64)
if s.ctxMgr() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized")
return
}
// FIND-SEC-01 回归:session_id 必须归属于目标用户本人,否则拒绝。
// 会话不存在与归属他人同样返回 403,避免泄露会话存在性。
if sessionID != "" {
ownerID, err := s.ctxMgr().GetSessionOwnerID(r.Context(), sessionID)
if err != nil || ownerID <= 0 || ownerID != userID {
writeJSONResponse(w, http.StatusForbidden, map[string]string{
"error_code": "ERR_FORBIDDEN",
"message": "无权访问该会话上下文",
})
return
}
}
uCtx, err := s.ctxMgr().GetUserContext(r.Context(), projectID, userID, sessionID)
if err != nil {
writeDomainError(w, r, http.StatusInternalServerError, err)
return
}
writeJSONResponse(w, http.StatusOK, uCtx)
}
// handleUpdateUserPreference 定期整理或前端保存用户习惯偏好
func (s *Server) handleUpdateUserPreference(w http.ResponseWriter, r *http.Request) {
user, ok := s.requireAuthenticatedUser(w, r)
if !ok {
return
}
var req UpdateUserPreferenceRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeDomainError(w, r, http.StatusBadRequest, err)
return
}
if req.UserID <= 0 {
req.UserID = user.ID
}
if req.UserID != user.ID && !s.isSystemAdminUser(r.Context(), user) {
writeJSONResponse(w, http.StatusForbidden, map[string]string{
"error_code": "ERR_FORBIDDEN",
"message": "无权修改其他用户的偏好",
})
return
}
if s.ctxMgr() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized")
return
}
pref := &ctxmgr.UserPreference{
UserID: req.UserID,
CommunicationStyle: req.CommunicationStyle,
FocusAreas: req.FocusAreas,
OutputFormatPref: req.OutputFormatPref,
CustomInstructions: req.CustomInstructions,
}
if err := s.ctxMgr().UpdateUserPreference(r.Context(), pref); err != nil {
writeDomainError(w, r, http.StatusInternalServerError, err)
return
}
writeJSONResponse(w, http.StatusOK, map[string]any{"status": "ok", "user_id": req.UserID})
}
// handleGetProjectContext 获取项目级多方协同事实黑板与里程碑决策流
func (s *Server) handleGetProjectContext(w http.ResponseWriter, r *http.Request) {
user, ok := s.requireAuthenticatedUser(w, r)
if !ok {
return
}
projectIDStr := r.URL.Query().Get("project_id")
projectID, _ := strconv.ParseInt(projectIDStr, 10, 64)
if projectID <= 0 {
writeSafeError(w, r, http.StatusBadRequest, "missing or invalid project_id query param")
return
}
// Membership first: allowAnyStageAccess assumes the caller already passed
// project membership (see ResolveStageAccess). Without this guard any
// authenticated user can read an activated project's collaborative context.
if !s.enforceChatProjectAccess(w, r, user, projectID) {
return
}
if !s.allowAnyStageAccess(r.Context(), projectID, user.ID) {
s.writeWorkflowAccessDenied(w)
return
}
if s.ctxMgr() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized")
return
}
pCtx, err := s.ctxMgr().GetProjectContext(r.Context(), projectID)
if err != nil {
writeDomainError(w, r, http.StatusInternalServerError, err)
return
}
writeJSONResponse(w, http.StatusOK, pCtx)
}