feat(api): add auth and admin management endpoints

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WeiCHEN
2026-09-08 23:19:04 +08:00
co-authored by Cursor
parent f7bc32798b
commit d548f773bc
4 changed files with 788 additions and 1 deletions
+290
View File
@@ -0,0 +1,290 @@
package auth
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"strings"
"sync"
"chat-agent-system/internal/config"
)
// RegisterRequest 注册入参 (仅用户名、密码、姓名为必填)
type RegisterRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
EmployeeID string `json:"employee_id,omitempty"`
Department string `json:"department,omitempty"`
Email string `json:"email,omitempty"`
}
// RegisterResponse 注册响应
type RegisterResponse struct {
Status AccountStatus `json:"status"`
Message string `json:"message"`
UserID int64 `json:"user_id"`
}
// CreateUserRequest 管理员手动创建账号入参
type CreateUserRequest struct {
Username string `json:"username"`
Name string `json:"name"`
InitialPassword string `json:"initial_password"`
RoleID string `json:"role_id"`
Position string `json:"position"`
Department string `json:"department,omitempty"`
EmployeeID string `json:"employee_id,omitempty"`
}
// Service 认证与账号治理业务逻辑
type Service struct {
store AuthStore
cfg *config.Config
tokensMu sync.RWMutex
tokens map[string]int64 // token -> userID
}
// NewService 实例化认证服务
func NewService(store AuthStore, cfg *config.Config) *Service {
if store == nil {
store = NewMemoryStore(cfg)
}
return &Service{
store: store,
cfg: cfg,
tokens: make(map[string]int64),
}
}
// generateToken 生成随机安全会话 Token
func generateToken() string {
b := make([]byte, 24)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
// Login 登录鉴权
func (s *Service) Login(ctx context.Context, username, password string) (*SessionInfo, error) {
username = strings.TrimSpace(username)
if username == "" || password == "" {
return nil, errors.New("用户名或密码不能为空")
}
user, err := s.store.GetUserByUsername(ctx, username)
if err != nil {
return nil, errors.New("用户名或密码错误")
}
if !CheckPassword(user.PasswordHash, password) {
return nil, errors.New("用户名或密码错误")
}
if user.Status == StatusPendingApproval {
return nil, errors.New("账号正在等待管理员审核中,暂无法登录")
}
if user.Status == StatusDisabled {
return nil, errors.New("账号已被停用,禁止登录")
}
token := generateToken()
s.tokensMu.Lock()
s.tokens[token] = user.ID
s.tokensMu.Unlock()
// 敏感信息脱敏
user.PasswordHash = ""
env := "dev"
if s.cfg != nil && s.cfg.Env != "" {
env = s.cfg.Env
}
return &SessionInfo{
Token: token,
User: user,
MustChangePassword: user.MustChangePassword,
Env: env,
AllowDevSwitch: env == "dev",
}, nil
}
// Register 用户自主注册 (职位严格由系统固定为访客待分配)
func (s *Service) Register(ctx context.Context, req RegisterRequest) (*RegisterResponse, error) {
req.Username = strings.TrimSpace(req.Username)
req.Name = strings.TrimSpace(req.Name)
if req.Username == "" || req.Password == "" || req.Name == "" {
return nil, errors.New("用户名、密码与真实姓名均为必填项")
}
sysCfg, err := s.store.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("读取系统准入策略失败: %w", err)
}
if !sysCfg.AllowRegistration {
return nil, errors.New("系统当前已暂停自主注册,请联系系统管理员分配账号")
}
passHash, err := HashPassword(req.Password)
if err != nil {
return nil, fmt.Errorf("密码哈希处理失败: %w", err)
}
status := StatusActive
msg := "注册成功,已激活,请返回登录"
if sysCfg.RequireApproval {
status = StatusPendingApproval
msg = "注册成功!您的账号正在等待管理员审核与职位指派,审核通过后方可登录。"
}
account := &UserAccount{
Username: req.Username,
Name: req.Name,
PasswordHash: passHash,
Status: status,
MustChangePassword: false,
RoleID: "role-guest",
Position: "访客/待指派",
EmployeeID: req.EmployeeID,
Department: req.Department,
Email: req.Email,
}
created, err := s.store.CreateUser(ctx, account)
if err != nil {
return nil, err
}
return &RegisterResponse{
Status: status,
Message: msg,
UserID: created.ID,
}, nil
}
// ChangePassword 修改密码 (处理普通改密与首次登录强制改密)
func (s *Service) ChangePassword(ctx context.Context, userID int64, oldPass, newPass string) error {
if strings.TrimSpace(newPass) == "" {
return errors.New("新密码不能为空")
}
user, err := s.store.GetUserByID(ctx, userID)
if err != nil {
return fmt.Errorf("用户不存在: %w", err)
}
if !CheckPassword(user.PasswordHash, oldPass) {
return errors.New("原密码不正确")
}
newHash, err := HashPassword(newPass)
if err != nil {
return fmt.Errorf("新密码加密失败: %w", err)
}
_, err = s.store.UpdateUserPassword(ctx, userID, newHash, false)
return err
}
// GetUserByToken 根据会话 Token 查找登录用户
func (s *Service) GetUserByToken(ctx context.Context, token string) (*UserAccount, error) {
s.tokensMu.RLock()
userID, ok := s.tokens[token]
s.tokensMu.RUnlock()
if !ok {
return nil, errors.New("会话已过期或未登录")
}
user, err := s.store.GetUserByID(ctx, userID)
if err != nil {
return nil, err
}
user.PasswordHash = ""
return user, nil
}
// Logout 注销会话
func (s *Service) Logout(token string) {
s.tokensMu.Lock()
defer s.tokensMu.Unlock()
delete(s.tokens, token)
}
// ListUsers 列出所有用户
func (s *Service) ListUsers(ctx context.Context) ([]*UserAccount, error) {
return s.store.ListUsers(ctx)
}
// CreateUserByAdmin 管理员手动开通账号 (自动标记 mustChangePassword = true)
func (s *Service) CreateUserByAdmin(ctx context.Context, req CreateUserRequest) (*UserAccount, error) {
if req.Username == "" || req.Name == "" || req.InitialPassword == "" {
return nil, errors.New("用户名、姓名与初始密码为必填")
}
hash, err := HashPassword(req.InitialPassword)
if err != nil {
return nil, err
}
roleID := req.RoleID
if roleID == "" {
roleID = "role-engineer"
}
position := req.Position
if position == "" {
position = "方案工程师"
}
account := &UserAccount{
Username: req.Username,
Name: req.Name,
PasswordHash: hash,
Status: StatusActive,
MustChangePassword: true, // 核心约束:管理员创建的账号首次登录必须修改密码
RoleID: roleID,
Position: position,
Department: req.Department,
EmployeeID: req.EmployeeID,
}
created, err := s.store.CreateUser(ctx, account)
if err != nil {
return nil, err
}
created.PasswordHash = ""
return created, nil
}
// UpdateUserRole 管理员为用户指定职位与角色
func (s *Service) UpdateUserRole(ctx context.Context, userID int64, roleID, position string) (*UserAccount, error) {
u, err := s.store.UpdateUserRole(ctx, userID, roleID, position)
if err != nil {
return nil, err
}
u.PasswordHash = ""
return u, nil
}
// UpdateUserStatus 管理员审批通过/停用账号
func (s *Service) UpdateUserStatus(ctx context.Context, userID int64, status AccountStatus) (*UserAccount, error) {
u, err := s.store.UpdateUserStatus(ctx, userID, status)
if err != nil {
return nil, err
}
u.PasswordHash = ""
return u, nil
}
// GetConfig 获取准入策略
func (s *Service) GetConfig(ctx context.Context) (SystemAuthConfig, error) {
return s.store.GetConfig(ctx)
}
// UpdateConfig 更新准入策略
func (s *Service) UpdateConfig(ctx context.Context, cfg SystemAuthConfig) error {
return s.store.UpdateConfig(ctx, cfg)
}
+290
View File
@@ -0,0 +1,290 @@
package server
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"chat-agent-system/internal/auth"
)
// extractBearerToken 从 Authorization 请求头解析 Bearer Token
func extractBearerToken(r *http.Request) string {
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
return strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
}
return ""
}
// authenticateUser 鉴权中间件辅助方法
func (s *Server) authenticateUser(r *http.Request) (*auth.UserAccount, error) {
token := extractBearerToken(r)
if token == "" {
token = r.Header.Get("X-Session-Token")
}
return s.authSvc.GetUserByToken(r.Context(), token)
}
// handleAuthLogin 用户名密码登录
func (s *Server) handleAuthLogin(w http.ResponseWriter, r *http.Request) {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的 JSON 请求体", http.StatusBadRequest)
return
}
session, err := s.authSvc.Login(r.Context(), req.Username, req.Password)
if err != nil {
errMsg := err.Error()
if strings.Contains(errMsg, "审核") {
http.Error(w, errMsg, http.StatusForbidden)
return
}
if strings.Contains(errMsg, "停用") {
http.Error(w, errMsg, http.StatusForbidden)
return
}
http.Error(w, errMsg, http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(session)
}
// handleAuthRegister 用户自主注册 (职位严格由系统固定为访客)
func (s *Server) handleAuthRegister(w http.ResponseWriter, r *http.Request) {
var req auth.RegisterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的 JSON 请求体", http.StatusBadRequest)
return
}
resp, err := s.authSvc.Register(r.Context(), req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(resp)
}
// handleAuthLogout 注销登录
func (s *Server) handleAuthLogout(w http.ResponseWriter, r *http.Request) {
token := extractBearerToken(r)
if token != "" {
s.authSvc.Logout(token)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
// handleAuthChangePassword 修改密码 (支持普通用户与首次强制改密)
func (s *Server) handleAuthChangePassword(w http.ResponseWriter, r *http.Request) {
user, err := s.authenticateUser(r)
if err != nil {
http.Error(w, "未登录或登录已过期", http.StatusUnauthorized)
return
}
var req struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的 JSON 请求体", http.StatusBadRequest)
return
}
if err := s.authSvc.ChangePassword(r.Context(), user.ID, req.OldPassword, req.NewPassword); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
// handleAuthMe 获取当前登录用户身份
func (s *Server) handleAuthMe(w http.ResponseWriter, r *http.Request) {
user, err := s.authenticateUser(r)
if err != nil {
http.Error(w, "未登录或登录已过期", http.StatusUnauthorized)
return
}
env := "dev"
if s.cfg != nil && s.cfg.Env != "" {
env = s.cfg.Env
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(map[string]any{
"user": user,
"must_change_password": user.MustChangePassword,
"env": env,
"allow_dev_switch": env == "dev",
})
}
// handleAdminUsers 管理员查询或新建用户
func (s *Server) handleAdminUsers(w http.ResponseWriter, r *http.Request) {
user, err := s.authenticateUser(r)
if err != nil || user.RoleID != "role-admin" {
http.Error(w, "权限不足,仅系统管理员可访问", http.StatusForbidden)
return
}
if r.Method == http.MethodGet {
users, err := s.authSvc.ListUsers(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(map[string]any{"users": users})
return
}
if r.Method == http.MethodPost {
var req auth.CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的 JSON 请求体", http.StatusBadRequest)
return
}
created, err := s.authSvc.CreateUserByAdmin(r.Context(), req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(created)
return
}
http.Error(w, "不支持的请求方法", http.StatusMethodNotAllowed)
}
// handleAdminUpdateRole 管理员为用户指定职位与角色
func (s *Server) handleAdminUpdateRole(w http.ResponseWriter, r *http.Request) {
user, err := s.authenticateUser(r)
if err != nil || user.RoleID != "role-admin" {
http.Error(w, "权限不足,仅系统管理员可访问", http.StatusForbidden)
return
}
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if len(parts) < 5 {
http.Error(w, "无效的用户路径参数", http.StatusBadRequest)
return
}
targetID, err := strconv.ParseInt(parts[3], 10, 64)
if err != nil {
http.Error(w, "无效的用户ID", http.StatusBadRequest)
return
}
var req struct {
RoleID string `json:"role_id"`
Position string `json:"position"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的请求体", http.StatusBadRequest)
return
}
updated, err := s.authSvc.UpdateUserRole(r.Context(), targetID, req.RoleID, req.Position)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(updated)
}
// handleAdminUpdateStatus 管理员审批通过/停用账号
func (s *Server) handleAdminUpdateStatus(w http.ResponseWriter, r *http.Request) {
user, err := s.authenticateUser(r)
if err != nil || user.RoleID != "role-admin" {
http.Error(w, "权限不足,仅系统管理员可访问", http.StatusForbidden)
return
}
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if len(parts) < 5 {
http.Error(w, "无效的用户路径参数", http.StatusBadRequest)
return
}
targetID, err := strconv.ParseInt(parts[3], 10, 64)
if err != nil {
http.Error(w, "无效的用户ID", http.StatusBadRequest)
return
}
var req struct {
Status auth.AccountStatus `json:"status"`
RoleID string `json:"role_id,omitempty"`
Position string `json:"position,omitempty"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的请求体", http.StatusBadRequest)
return
}
if req.RoleID != "" {
_, _ = s.authSvc.UpdateUserRole(r.Context(), targetID, req.RoleID, req.Position)
}
updated, err := s.authSvc.UpdateUserStatus(r.Context(), targetID, req.Status)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(updated)
}
// handleAdminConfig 获取或更新系统准入配置
func (s *Server) handleAdminConfig(w http.ResponseWriter, r *http.Request) {
user, err := s.authenticateUser(r)
if err != nil || user.RoleID != "role-admin" {
http.Error(w, "权限不足,仅系统管理员可访问", http.StatusForbidden)
return
}
if r.Method == http.MethodGet {
cfg, err := s.authSvc.GetConfig(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(cfg)
return
}
if r.Method == http.MethodPut || r.Method == http.MethodPost {
var req auth.SystemAuthConfig
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "无效的请求体", http.StatusBadRequest)
return
}
if err := s.authSvc.UpdateConfig(r.Context(), req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(req)
return
}
http.Error(w, "不支持的方法", http.StatusMethodNotAllowed)
}
+184
View File
@@ -0,0 +1,184 @@
package server_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"chat-agent-system/internal/auth"
"chat-agent-system/internal/config"
"chat-agent-system/internal/server"
)
func setupTestServer() (*httptest.Server, *auth.Service) {
cfg := &config.Config{
Env: "dev",
AdminUsername: "admin",
AdminPassword: "admin123",
AuthRegistrationEnabled: true,
AuthRequireApproval: false,
}
srv := server.NewServer(cfg, nil)
ts := httptest.NewServer(srv.Routes())
return ts, srv.AuthService()
}
func TestAuthHandlers_LoginAndMe(t *testing.T) {
ts, _ := setupTestServer()
defer ts.Close()
// 1. Admin login with .env credentials
loginReq := map[string]string{
"username": "admin",
"password": "admin123",
}
body, _ := json.Marshal(loginReq)
resp, err := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
if err != nil {
t.Fatalf("Login request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 OK, got %d", resp.StatusCode)
}
var session auth.SessionInfo
if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
t.Fatalf("Failed to decode session: %v", err)
}
if session.User == nil || session.User.RoleID != "role-admin" {
t.Errorf("Expected role-admin, got %+v", session.User)
}
if !session.AllowDevSwitch {
t.Errorf("Expected AllowDevSwitch=true in dev env")
}
// 2. Fetch /api/v1/auth/me with Authorization header
req, _ := http.NewRequest("GET", ts.URL+"/api/v1/auth/me", nil)
req.Header.Set("Authorization", "Bearer "+session.Token)
client := &http.Client{}
meResp, err := client.Do(req)
if err != nil {
t.Fatalf("Me request failed: %v", err)
}
defer meResp.Body.Close()
if meResp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 OK for /auth/me, got %d", meResp.StatusCode)
}
}
func TestAuthHandlers_RegisterAndApproval(t *testing.T) {
ts, authSvc := setupTestServer()
defer ts.Close()
// 1. Turn on RequireApproval
_ = authSvc.UpdateConfig(t.Context(), auth.SystemAuthConfig{
AllowRegistration: true,
RequireApproval: true,
})
// 2. Register user
regReq := map[string]string{
"username": "new_doctor",
"password": "DoctorPass123",
"name": "王医生",
}
body, _ := json.Marshal(regReq)
resp, err := http.Post(ts.URL+"/api/v1/auth/register", "application/json", bytes.NewReader(body))
if err != nil {
t.Fatalf("Register request failed: %v", err)
}
defer resp.Body.Close()
var regResp map[string]any
_ = json.NewDecoder(resp.Body).Decode(&regResp)
if regResp["status"] != "pending_approval" {
t.Errorf("Expected pending_approval, got %v", regResp["status"])
}
// 3. Trying to login should fail because of pending approval
loginBody, _ := json.Marshal(map[string]string{
"username": "new_doctor",
"password": "DoctorPass123",
})
loginResp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(loginBody))
if loginResp.StatusCode != http.StatusForbidden {
t.Errorf("Expected 403 Forbidden for pending user, got %d", loginResp.StatusCode)
}
}
func TestAuthHandlers_AdminCreateUser_MustChangePassword(t *testing.T) {
ts, _ := setupTestServer()
defer ts.Close()
// 1. Admin login to obtain token
loginBody, _ := json.Marshal(map[string]string{
"username": "admin",
"password": "admin123",
})
resp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(loginBody))
var adminSession auth.SessionInfo
_ = json.NewDecoder(resp.Body).Decode(&adminSession)
// 2. Admin creates a new user
createUserReq := map[string]string{
"username": "qa_engineer",
"name": "周质量",
"initial_password": "TempPassword123",
"role_id": "role-director",
"position": "质量总监",
}
reqBody, _ := json.Marshal(createUserReq)
req, _ := http.NewRequest("POST", ts.URL+"/api/v1/admin/users", bytes.NewReader(reqBody))
req.Header.Set("Authorization", "Bearer "+adminSession.Token)
client := &http.Client{}
createResp, err := client.Do(req)
if err != nil {
t.Fatalf("Admin create user request failed: %v", err)
}
defer createResp.Body.Close()
if createResp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 OK, got %d", createResp.StatusCode)
}
// 3. New user logs in with initial password -> must_change_password must be true
userLoginBody, _ := json.Marshal(map[string]string{
"username": "qa_engineer",
"password": "TempPassword123",
})
uResp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(userLoginBody))
var uSession auth.SessionInfo
_ = json.NewDecoder(uResp.Body).Decode(&uSession)
if !uSession.MustChangePassword {
t.Errorf("Expected MustChangePassword to be true for admin created user")
}
// 4. Change password
changeBody, _ := json.Marshal(map[string]string{
"old_password": "TempPassword123",
"new_password": "NewPermanentPass888",
})
chReq, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/change-password", bytes.NewReader(changeBody))
chReq.Header.Set("Authorization", "Bearer "+uSession.Token)
chResp, err := client.Do(chReq)
if err != nil || chResp.StatusCode != http.StatusOK {
t.Fatalf("Change password failed: %v", err)
}
// 5. Login again with new password -> must_change_password must be false
newLoginBody, _ := json.Marshal(map[string]string{
"username": "qa_engineer",
"password": "NewPermanentPass888",
})
nResp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(newLoginBody))
var nSession auth.SessionInfo
_ = json.NewDecoder(nResp.Body).Decode(&nSession)
if nSession.MustChangePassword {
t.Errorf("Expected MustChangePassword to be false after changing password")
}
}
+24 -1
View File
@@ -10,6 +10,7 @@ import (
"sync"
"time"
"chat-agent-system/internal/auth"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/chat"
"chat-agent-system/internal/config"
@@ -23,6 +24,7 @@ type Server struct {
svc *bizdev.Service
rec intent.Recognizer
engine *coordinator.CoordinatorEngine
authSvc *auth.Service
sessions map[string]*chat.Session
sessionsMu sync.RWMutex
httpServer *http.Server
@@ -39,17 +41,25 @@ func NewServer(cfg *config.Config, svc *bizdev.Service) *Server {
rec := intent.NewRecognizer()
coordEngine := coordinator.NewCoordinatorEngine(svc)
authStore := auth.NewMemoryStore(cfg)
authSvc := auth.NewService(authStore, cfg)
s := &Server{
cfg: cfg,
svc: svc,
rec: rec,
engine: coordEngine,
authSvc: authSvc,
sessions: make(map[string]*chat.Session),
}
return s
}
// AuthService 获取认证服务
func (s *Server) AuthService() *auth.Service {
return s.authSvc
}
// Routes 构建 HTTP 路由分发器
func (s *Server) Routes() http.Handler {
mux := http.NewServeMux()
@@ -94,7 +104,20 @@ func (s *Server) Routes() http.Handler {
mux.HandleFunc("POST /api/v1/docgov/comments", s.handleDocGovAddComment)
mux.HandleFunc("GET /api/v1/docgov/audit-logs", s.handleDocGovListAuditLogs)
// 9. 现代 SPA 前端页面与静态资产托管
// 9. 用户认证、安全改密与超级管理员后台接口
mux.HandleFunc("POST /api/v1/auth/login", s.handleAuthLogin)
mux.HandleFunc("POST /api/v1/auth/register", s.handleAuthRegister)
mux.HandleFunc("POST /api/v1/auth/logout", s.handleAuthLogout)
mux.HandleFunc("POST /api/v1/auth/change-password", s.handleAuthChangePassword)
mux.HandleFunc("GET /api/v1/auth/me", s.handleAuthMe)
mux.HandleFunc("GET /api/v1/admin/users", s.handleAdminUsers)
mux.HandleFunc("POST /api/v1/admin/users", s.handleAdminUsers)
mux.HandleFunc("PUT /api/v1/admin/users/{id}/role", s.handleAdminUpdateRole)
mux.HandleFunc("PUT /api/v1/admin/users/{id}/status", s.handleAdminUpdateStatus)
mux.HandleFunc("GET /api/v1/admin/config", s.handleAdminConfig)
mux.HandleFunc("PUT /api/v1/admin/config", s.handleAdminConfig)
// 10. 现代 SPA 前端页面与静态资产托管
mux.HandleFunc("GET /", s.serveSPA)
return s.corsMiddleware(mux)