feat: Phase 1 完成 — 基础设施 + 用户认证 + 管理端 + 端到端验证

Phase 1 (基础设施与用户认证) 全部 11 个 Task 开发完成:

后端 (Go):
- Auth 模块: 注册/登录/JWT(有状态)/Profile/密码修改
- Admin 模块: 用户列表/详情/禁用/启用/角色分配/创建用户
- 中间件: JWT认证 + RBAC角色权限 + 请求日志 + CORS + Panic恢复
- Dockerfile 多阶段构建 + Docker Compose 全栈部署

前台 (uni-app):
- 登录/注册页面 + 自定义 TabBar + 个人中心
- 请求封装 + 状态管理 (Pinia)

管理端 (Vue 3 + Element Plus):
- 登录/仪表盘/用户列表/用户详情
- Axios 封装 + 路由守卫 + Pinia 状态管理

验证:
- 全流程 API 端到端测试通过
- Playwright 页面自动化验证通过
- code-reviewer 代码审查通过

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-03-02 11:31:24 +08:00
parent e9bd4dd204
commit 4d03215fe4
63 changed files with 15908 additions and 102 deletions

View File

@@ -0,0 +1,16 @@
# 日志和临时文件
logs/
*.log
# IDE 配置
.idea/
.vscode/
*.swp
*.swo
# 测试文件
*_test.go
# Git
.git
.gitignore

View File

@@ -0,0 +1,48 @@
# ============================================================
# EchoChat Go 后端服务 Dockerfile
# 多阶段构建:编译阶段 → 运行阶段alpine 最小镜像)
# ============================================================
# --- 第一阶段:编译 ---
FROM golang:1.23-alpine AS builder
# 安装编译依赖CGO 需要 gcc
RUN apk add --no-cache gcc musl-dev
WORKDIR /build
# 先复制依赖文件利用 Docker 缓存层
COPY go.mod go.sum ./
RUN go mod download
# 复制全部源码并编译
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /build/echochat ./cmd/server
# --- 第二阶段:运行 ---
FROM alpine:3.19
# 安装运行时依赖(时区数据 + CA 证书)
RUN apk add --no-cache ca-certificates tzdata
# 设置时区为上海
ENV TZ=Asia/Shanghai
WORKDIR /app
# 从编译阶段复制二进制文件
COPY --from=builder /build/echochat .
# 复制配置文件目录
COPY --from=builder /build/config ./config
# 创建日志目录
RUN mkdir -p /app/logs
EXPOSE 8085
# 健康检查
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q -O /dev/null http://localhost:8085/health || exit 1
CMD ["./echochat"]

View File

@@ -0,0 +1,202 @@
// Package controller 提供 admin 模块的 HTTP 请求处理
package controller
import (
"strconv"
"github.com/echochat/backend/app/admin/service"
"github.com/echochat/backend/app/dto"
"github.com/echochat/backend/pkg/logs"
"github.com/echochat/backend/pkg/middleware"
"github.com/echochat/backend/pkg/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// UserManageController 管理端用户管理控制器
type UserManageController struct {
userManageService *service.UserManageService
}
// NewUserManageController 创建用户管理控制器实例
func NewUserManageController(svc *service.UserManageService) *UserManageController {
return &UserManageController{userManageService: svc}
}
// GetUserList 获取用户列表
// GET /api/v1/admin/users?page=1&page_size=10&keyword=xxx&status=1
func (ctl *UserManageController) GetUserList(c *gin.Context) {
funcName := "controller.user_manage_controller.GetUserList"
ctx := c.Request.Context()
var req dto.UserListRequest
if err := c.ShouldBindQuery(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数错误: "+err.Error())
return
}
logs.Info(ctx, funcName, "获取用户列表",
zap.Int("page", req.Page),
zap.Int("page_size", req.PageSize),
zap.String("keyword", req.Keyword),
)
resp, err := ctl.userManageService.GetUserList(ctx, &req)
if err != nil {
logs.Error(ctx, funcName, "获取用户列表失败", zap.Error(err))
utils.ResponseError(c, "获取用户列表失败")
return
}
utils.ResponseOK(c, resp)
}
// GetUserDetail 获取用户详情
// GET /api/v1/admin/users/:id
func (ctl *UserManageController) GetUserDetail(c *gin.Context) {
funcName := "controller.user_manage_controller.GetUserDetail"
ctx := c.Request.Context()
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
utils.ResponseBadRequest(c, "无效的用户 ID")
return
}
logs.Info(ctx, funcName, "获取用户详情", zap.Int64("user_id", userID))
detail, err := ctl.userManageService.GetUserDetail(ctx, userID)
if err != nil {
if err == service.ErrUserNotFound {
utils.ResponseNotFound(c, "用户不存在")
return
}
logs.Error(ctx, funcName, "获取用户详情失败", zap.Error(err))
utils.ResponseError(c, "获取用户详情失败")
return
}
utils.ResponseOK(c, detail)
}
// UpdateUserStatus 更新用户状态(启用/禁用)
// PUT /api/v1/admin/users/:id/status
func (ctl *UserManageController) UpdateUserStatus(c *gin.Context) {
funcName := "controller.user_manage_controller.UpdateUserStatus"
ctx := c.Request.Context()
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
utils.ResponseBadRequest(c, "无效的用户 ID")
return
}
var req dto.UpdateUserStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数错误: "+err.Error())
return
}
adminUserID, ok := middleware.GetCurrentUserID(c)
if !ok {
utils.ResponseUnauthorized(c, "无法获取当前用户信息")
return
}
logs.Info(ctx, funcName, "更新用户状态",
zap.Int64("target_user_id", userID),
zap.Int("status", req.Status),
zap.Int64("admin_user_id", adminUserID),
)
if err := ctl.userManageService.UpdateUserStatus(ctx, userID, req.Status, adminUserID); err != nil {
switch err {
case service.ErrUserNotFound:
utils.ResponseNotFound(c, "用户不存在")
case service.ErrCannotDisableSelf:
utils.ResponseBadRequest(c, "不能禁用自己的账号")
case service.ErrInvalidStatus:
utils.ResponseBadRequest(c, "无效的用户状态")
default:
logs.Error(ctx, funcName, "更新用户状态失败", zap.Error(err))
utils.ResponseError(c, "更新用户状态失败")
}
return
}
utils.ResponseOK(c, nil)
}
// AssignRole 分配角色
// PUT /api/v1/admin/users/:id/role
func (ctl *UserManageController) AssignRole(c *gin.Context) {
funcName := "controller.user_manage_controller.AssignRole"
ctx := c.Request.Context()
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
utils.ResponseBadRequest(c, "无效的用户 ID")
return
}
var req dto.AssignRoleRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数错误: "+err.Error())
return
}
logs.Info(ctx, funcName, "分配角色",
zap.Int64("user_id", userID),
zap.String("role_code", req.RoleCode),
)
if err := ctl.userManageService.AssignUserRole(ctx, userID, req.RoleCode); err != nil {
switch err {
case service.ErrUserNotFound:
utils.ResponseNotFound(c, "用户不存在")
case service.ErrInvalidRole:
utils.ResponseBadRequest(c, "无效的角色代码")
default:
logs.Error(ctx, funcName, "分配角色失败", zap.Error(err))
utils.ResponseError(c, "分配角色失败")
}
return
}
utils.ResponseOK(c, nil)
}
// CreateUser 管理员手动创建用户
// POST /api/v1/admin/users
func (ctl *UserManageController) CreateUser(c *gin.Context) {
funcName := "controller.user_manage_controller.CreateUser"
ctx := c.Request.Context()
var req dto.AdminCreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
logs.Warn(ctx, funcName, "参数校验失败", zap.Error(err))
utils.ResponseBadRequest(c, "参数错误: "+err.Error())
return
}
logs.Info(ctx, funcName, "管理员创建用户",
zap.String("username", req.Username),
zap.String("email", logs.MaskEmail(req.Email)),
)
userInfo, err := ctl.userManageService.CreateUser(ctx, &req)
if err != nil {
if err == service.ErrUserExists {
utils.ResponseBadRequest(c, "用户名或邮箱已被注册")
return
}
logs.Error(ctx, funcName, "创建用户失败", zap.Error(err))
utils.ResponseError(c, "创建用户失败")
return
}
utils.ResponseOK(c, userInfo)
}

View File

@@ -0,0 +1,106 @@
// Package dao 提供 admin 模块的数据库访问操作
package dao
import (
"context"
"fmt"
"github.com/echochat/backend/app/auth/model"
"github.com/echochat/backend/app/dto"
"github.com/echochat/backend/pkg/logs"
"go.uber.org/zap"
"gorm.io/gorm"
)
// UserManageDAO 管理端用户数据访问对象
// 提供面向管理后台的用户查询操作(分页、搜索、筛选)
type UserManageDAO struct {
db *gorm.DB
}
// NewUserManageDAO 创建 UserManageDAO 实例
func NewUserManageDAO(db *gorm.DB) *UserManageDAO {
return &UserManageDAO{db: db}
}
// ListUsers 分页查询用户列表
// 支持按用户名/邮箱关键词搜索 + 按状态筛选
// SQL 执行两次查询COUNT 总数 + LIMIT/OFFSET 分页数据
func (d *UserManageDAO) ListUsers(ctx context.Context, req *dto.UserListRequest) ([]model.User, int64, error) {
funcName := "dao.user_manage_dao.ListUsers"
logs.Info(ctx, funcName, "查询用户列表",
zap.Int("page", req.Page),
zap.Int("page_size", req.PageSize),
zap.String("keyword", req.Keyword),
)
var users []model.User
var total int64
query := d.db.WithContext(ctx).Model(&model.User{})
// 关键词搜索:模糊匹配用户名或邮箱
if req.Keyword != "" {
like := fmt.Sprintf("%%%s%%", req.Keyword)
query = query.Where("username LIKE ? OR email LIKE ?", like, like)
}
// 状态筛选
if req.Status != nil {
query = query.Where("status = ?", *req.Status)
}
// 先查询总数
if err := query.Count(&total).Error; err != nil {
logs.Error(ctx, funcName, "查询用户总数失败", zap.Error(err))
return nil, 0, err
}
// 分页查询数据,按创建时间倒序
offset := (req.Page - 1) * req.PageSize
if err := query.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&users).Error; err != nil {
logs.Error(ctx, funcName, "查询用户列表失败", zap.Error(err))
return nil, 0, err
}
logs.Info(ctx, funcName, "查询用户列表成功",
zap.Int64("total", total),
zap.Int("count", len(users)),
)
return users, total, nil
}
// CountUsers 统计用户总数(不含筛选条件)
func (d *UserManageDAO) CountUsers(ctx context.Context) (int64, error) {
funcName := "dao.user_manage_dao.CountUsers"
var count int64
err := d.db.WithContext(ctx).Model(&model.User{}).Count(&count).Error
if err != nil {
logs.Error(ctx, funcName, "统计用户总数失败", zap.Error(err))
}
return count, err
}
// UpdateUserStatus 更新用户账号状态
// 仅更新 status 字段,不触发其他字段的更新
func (d *UserManageDAO) UpdateUserStatus(ctx context.Context, userID int64, status int) error {
funcName := "dao.user_manage_dao.UpdateUserStatus"
logs.Info(ctx, funcName, "更新用户状态",
zap.Int64("user_id", userID),
zap.Int("status", status),
)
err := d.db.WithContext(ctx).
Model(&model.User{}).
Where("id = ?", userID).
Update("status", status).Error
if err != nil {
logs.Error(ctx, funcName, "更新用户状态失败",
zap.Int64("user_id", userID),
zap.Error(err),
)
}
return err
}

View File

@@ -0,0 +1,17 @@
// Package admin 提供管理后台功能模块
package admin
import (
"github.com/echochat/backend/app/admin/controller"
"github.com/echochat/backend/app/admin/dao"
"github.com/echochat/backend/app/admin/service"
"github.com/google/wire"
)
// AdminSet Admin 模块依赖注入 Provider Set
// 提供管理端的 DAO、Service 和 Controller 组件
var AdminSet = wire.NewSet(
dao.NewUserManageDAO,
service.NewUserManageService,
controller.NewUserManageController,
)

View File

@@ -0,0 +1,29 @@
// Package admin 提供管理后台功能模块
package admin
import (
"github.com/echochat/backend/app/admin/controller"
"github.com/echochat/backend/app/constants"
"github.com/echochat/backend/pkg/middleware"
"github.com/gin-gonic/gin"
)
// RegisterRoutes 注册 admin 模块的所有路由
// 所有管理端 API 需要 JWT + admin 角色双重中间件
func RegisterRoutes(
r *gin.Engine,
ctrl *controller.UserManageController,
jwtAuth gin.HandlerFunc,
) {
// 管理端路由组JWT 认证 + admin/super_admin 角色检查
adminGroup := r.Group("/api/v1/admin")
adminGroup.Use(jwtAuth, middleware.RequireRole(constants.RoleAdmin, constants.RoleSuperAdmin))
{
// 用户管理
adminGroup.GET("/users", ctrl.GetUserList)
adminGroup.GET("/users/:id", ctrl.GetUserDetail)
adminGroup.PUT("/users/:id/status", ctrl.UpdateUserStatus)
adminGroup.PUT("/users/:id/role", ctrl.AssignRole)
adminGroup.POST("/users", ctrl.CreateUser)
}
}

View File

@@ -0,0 +1,272 @@
// Package service 提供 admin 模块的核心业务逻辑
package service
import (
"context"
"errors"
authDAO "github.com/echochat/backend/app/auth/dao"
"github.com/echochat/backend/app/auth/model"
adminDAO "github.com/echochat/backend/app/admin/dao"
"github.com/echochat/backend/app/constants"
"github.com/echochat/backend/app/dto"
"github.com/echochat/backend/pkg/logs"
"github.com/echochat/backend/pkg/utils"
"go.uber.org/zap"
"gorm.io/gorm"
)
var (
ErrUserNotFound = errors.New("用户不存在")
ErrUserExists = errors.New("用户名或邮箱已被注册")
ErrInvalidStatus = errors.New("无效的用户状态")
ErrInvalidRole = errors.New("无效的角色代码")
ErrCannotDisableSelf = errors.New("不能禁用自己的账号")
)
// UserManageService 管理端用户管理服务
type UserManageService struct {
userManageDAO *adminDAO.UserManageDAO
userDAO *authDAO.UserDAO
roleDAO *authDAO.RoleDAO
}
// NewUserManageService 创建用户管理服务实例
func NewUserManageService(
userManageDAO *adminDAO.UserManageDAO,
userDAO *authDAO.UserDAO,
roleDAO *authDAO.RoleDAO,
) *UserManageService {
return &UserManageService{
userManageDAO: userManageDAO,
userDAO: userDAO,
roleDAO: roleDAO,
}
}
// GetUserList 获取用户列表(分页 + 搜索 + 筛选)
func (s *UserManageService) GetUserList(ctx context.Context, req *dto.UserListRequest) (*dto.UserListResponse, error) {
funcName := "service.user_manage_service.GetUserList"
logs.Info(ctx, funcName, "获取用户列表")
users, total, err := s.userManageDAO.ListUsers(ctx, req)
if err != nil {
return nil, err
}
list := make([]dto.AdminUserInfo, 0, len(users))
for _, user := range users {
roles, roleErr := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
if roleErr != nil {
logs.Warn(ctx, funcName, "获取用户角色失败,降级为空角色列表",
zap.Int64("user_id", user.ID), zap.Error(roleErr))
roles = []string{}
}
list = append(list, *s.buildAdminUserInfo(&user, roles))
}
return &dto.UserListResponse{
Total: total,
List: list,
}, nil
}
// GetUserDetail 获取单个用户详情(含角色信息)
func (s *UserManageService) GetUserDetail(ctx context.Context, userID int64) (*dto.AdminUserInfo, error) {
funcName := "service.user_manage_service.GetUserDetail"
logs.Info(ctx, funcName, "获取用户详情", zap.Int64("user_id", userID))
user, err := s.userDAO.FindByID(ctx, userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrUserNotFound
}
return nil, err
}
roles, roleErr := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
if roleErr != nil {
logs.Warn(ctx, funcName, "获取用户角色失败,降级为空角色列表",
zap.Int64("user_id", user.ID), zap.Error(roleErr))
roles = []string{}
}
return s.buildAdminUserInfo(user, roles), nil
}
// UpdateUserStatus 启用/禁用用户
// adminUserID 用于防止管理员禁用自己
func (s *UserManageService) UpdateUserStatus(ctx context.Context, userID int64, status int, adminUserID int64) error {
funcName := "service.user_manage_service.UpdateUserStatus"
logs.Info(ctx, funcName, "更新用户状态",
zap.Int64("target_user_id", userID),
zap.Int("status", status),
zap.Int64("admin_user_id", adminUserID),
)
if userID == adminUserID {
return ErrCannotDisableSelf
}
if status != constants.UserStatusActive && status != constants.UserStatusDisabled {
return ErrInvalidStatus
}
// 确认用户存在
_, err := s.userDAO.FindByID(ctx, userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrUserNotFound
}
return err
}
if err := s.userManageDAO.UpdateUserStatus(ctx, userID, status); err != nil {
return err
}
logs.Info(ctx, funcName, "用户状态更新成功",
zap.Int64("user_id", userID),
zap.Int("new_status", status),
)
return nil
}
// AssignUserRole 分配角色给用户
func (s *UserManageService) AssignUserRole(ctx context.Context, userID int64, roleCode string) error {
funcName := "service.user_manage_service.AssignUserRole"
logs.Info(ctx, funcName, "分配角色",
zap.Int64("user_id", userID),
zap.String("role_code", roleCode),
)
// 验证用户存在
_, err := s.userDAO.FindByID(ctx, userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrUserNotFound
}
return err
}
// 查找角色
role, err := s.roleDAO.FindByCode(ctx, roleCode)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrInvalidRole
}
return err
}
// 分配角色RoleDAO.AssignRole 内部会处理重复分配)
if err := s.roleDAO.AssignRole(ctx, userID, role.ID); err != nil {
return err
}
logs.Info(ctx, funcName, "角色分配成功",
zap.Int64("user_id", userID),
zap.String("role_code", roleCode),
)
return nil
}
// CreateUser 管理员手动创建用户
func (s *UserManageService) CreateUser(ctx context.Context, req *dto.AdminCreateUserRequest) (*dto.AdminUserInfo, error) {
funcName := "service.user_manage_service.CreateUser"
logs.Info(ctx, funcName, "管理员创建用户",
zap.String("username", req.Username),
zap.String("email", logs.MaskEmail(req.Email)),
)
existing, findErr := s.userDAO.FindByUsername(ctx, req.Username)
if findErr != nil && !errors.Is(findErr, gorm.ErrRecordNotFound) {
return nil, findErr
}
if existing != nil {
return nil, ErrUserExists
}
existing, findErr = s.userDAO.FindByEmail(ctx, req.Email)
if findErr != nil && !errors.Is(findErr, gorm.ErrRecordNotFound) {
return nil, findErr
}
if existing != nil {
return nil, ErrUserExists
}
// 加密密码
hashedPassword, err := utils.HashPassword(req.Password)
if err != nil {
return nil, err
}
nickname := req.Nickname
if nickname == "" {
nickname = req.Username
}
user := &model.User{
Username: req.Username,
Email: req.Email,
PasswordHash: hashedPassword,
Nickname: nickname,
Status: constants.UserStatusActive,
}
if err := s.userDAO.Create(ctx, user); err != nil {
return nil, err
}
roleCode := req.RoleCode
if roleCode == "" {
roleCode = constants.RoleUser
}
role, roleErr := s.roleDAO.FindByCode(ctx, roleCode)
if roleErr != nil {
logs.Warn(ctx, funcName, "角色查找失败,将跳过角色分配",
zap.String("role_code", roleCode), zap.Error(roleErr))
} else {
if assignErr := s.roleDAO.AssignRole(ctx, user.ID, role.ID); assignErr != nil {
logs.Warn(ctx, funcName, "角色分配失败",
zap.Int64("user_id", user.ID), zap.Error(assignErr))
}
}
roles, rolesErr := s.roleDAO.GetUserRoleCodes(ctx, user.ID)
if rolesErr != nil {
logs.Warn(ctx, funcName, "获取新建用户角色失败",
zap.Int64("user_id", user.ID), zap.Error(rolesErr))
roles = []string{}
}
logs.Info(ctx, funcName, "管理员创建用户成功", zap.Int64("user_id", user.ID))
return s.buildAdminUserInfo(user, roles), nil
}
// buildAdminUserInfo 从 model.User 构建 dto.AdminUserInfo
func (s *UserManageService) buildAdminUserInfo(user *model.User, roles []string) *dto.AdminUserInfo {
if roles == nil {
roles = []string{}
}
info := &dto.AdminUserInfo{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Nickname: user.Nickname,
Avatar: user.Avatar,
Gender: user.Gender,
Status: user.Status,
StatusText: constants.UserStatusMap[user.Status],
Roles: roles,
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"),
}
if user.Phone != nil {
info.Phone = *user.Phone
}
if user.LastLoginAt != nil {
info.LastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05")
}
if user.LastLoginIP != nil {
info.LastLoginIP = *user.LastLoginIP
}
return info
}

View File

@@ -0,0 +1,53 @@
// Package dto 定义数据传输对象
package dto
// UserListRequest 管理端用户列表查询请求参数
type UserListRequest struct {
Page int `form:"page" binding:"required,min=1"` // 页码,从 1 开始
PageSize int `form:"page_size" binding:"required,min=1,max=100"` // 每页数量1-100
Keyword string `form:"keyword"` // 搜索关键词(匹配用户名或邮箱)
Status *int `form:"status"` // 状态筛选1=正常, 2=禁用, 3=注销;为空则不筛选
}
// UserListResponse 管理端用户列表响应
type UserListResponse struct {
Total int64 `json:"total"` // 符合条件的用户总数
List []AdminUserInfo `json:"list"` // 当前页的用户列表
}
// AdminUserInfo 管理端用户信息(比 UserInfo 更详细,包含管理字段)
type AdminUserInfo struct {
ID int64 `json:"id"` // 用户 ID
Username string `json:"username"` // 用户名
Email string `json:"email"` // 邮箱
Nickname string `json:"nickname"` // 昵称
Avatar string `json:"avatar"` // 头像 URL
Gender int `json:"gender"` // 性别0=未知, 1=男, 2=女
Phone string `json:"phone,omitempty"` // 手机号
Status int `json:"status"` // 账号状态
StatusText string `json:"status_text"` // 状态中文描述
Roles []string `json:"roles"` // 角色代码列表
LastLoginAt string `json:"last_login_at,omitempty"` // 最后登录时间
LastLoginIP string `json:"last_login_ip,omitempty"` // 最后登录 IP
CreatedAt string `json:"created_at"` // 注册时间
UpdatedAt string `json:"updated_at"` // 更新时间
}
// UpdateUserStatusRequest 更新用户状态请求
type UpdateUserStatusRequest struct {
Status int `json:"status" binding:"required,oneof=1 2"` // 目标状态1=正常, 2=禁用
}
// AssignRoleRequest 分配角色请求
type AssignRoleRequest struct {
RoleCode string `json:"role_code" binding:"required"` // 角色代码user/admin/super_admin
}
// AdminCreateUserRequest 管理员手动创建用户请求
type AdminCreateUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=50"` // 用户名
Email string `json:"email" binding:"required,email"` // 邮箱
Password string `json:"password" binding:"required,min=6,max=50"` // 初始密码
Nickname string `json:"nickname" binding:"max=50"` // 昵称(选填)
RoleCode string `json:"role_code" binding:"omitempty"` // 初始角色(选填,默认 user
}

View File

@@ -3,7 +3,8 @@
package provider
import (
"github.com/echochat/backend/app/auth/controller"
adminController "github.com/echochat/backend/app/admin/controller"
authController "github.com/echochat/backend/app/auth/controller"
"github.com/echochat/backend/app/auth/service"
"github.com/echochat/backend/config"
"github.com/echochat/backend/pkg/db"
@@ -14,12 +15,13 @@ import (
// App 应用根容器,持有基础设施组件和各模块的 Controller/Service
type App struct {
Config *config.Config
DB *gorm.DB
Redis *redis.Client
AuthService *service.AuthService // Auth 认证服务
AuthController *controller.AuthController // 前台认证控制器
AdminAuthController *controller.AdminAuthController // 后台认证控制器
Config *config.Config
DB *gorm.DB
Redis *redis.Client
AuthService *service.AuthService // Auth 认证服务
AuthController *authController.AuthController // 前台认证控制器
AdminAuthController *authController.AdminAuthController // 后台认证控制器
UserManageController *adminController.UserManageController // 管理端用户管理控制器
}
// NewApp 创建应用实例
@@ -28,16 +30,18 @@ func NewApp(
gormDB *gorm.DB,
redisClient *redis.Client,
authService *service.AuthService,
authCtrl *controller.AuthController,
adminAuthCtrl *controller.AdminAuthController,
authCtrl *authController.AuthController,
adminAuthCtrl *authController.AdminAuthController,
userManageCtrl *adminController.UserManageController,
) *App {
return &App{
Config: cfg,
DB: gormDB,
Redis: redisClient,
AuthService: authService,
AuthController: authCtrl,
AdminAuthController: adminAuthCtrl,
Config: cfg,
DB: gormDB,
Redis: redisClient,
AuthService: authService,
AuthController: authCtrl,
AdminAuthController: adminAuthCtrl,
UserManageController: userManageCtrl,
}
}

View File

@@ -4,6 +4,7 @@
package provider
import (
"github.com/echochat/backend/app/admin"
"github.com/echochat/backend/app/auth"
"github.com/echochat/backend/config"
"github.com/google/wire"
@@ -14,6 +15,7 @@ func InitializeApp(cfg *config.Config) (*App, error) {
wire.Build(
InfraSet,
auth.AuthSet,
admin.AdminSet,
)
return nil, nil
}

View File

@@ -7,6 +7,9 @@
package provider
import (
adminController "github.com/echochat/backend/app/admin/controller"
adminDAO "github.com/echochat/backend/app/admin/dao"
adminService "github.com/echochat/backend/app/admin/service"
"github.com/echochat/backend/app/auth/controller"
"github.com/echochat/backend/app/auth/dao"
"github.com/echochat/backend/app/auth/service"
@@ -35,6 +38,9 @@ func InitializeApp(cfg *config.Config) (*App, error) {
authService := service.NewAuthService(userDAO, roleDAO, jwtConfig, tokenStore)
authController := controller.NewAuthController(authService)
adminAuthController := controller.NewAdminAuthController(authService)
app := NewApp(cfg, gormDB, client, authService, authController, adminAuthController)
userManageDAO := adminDAO.NewUserManageDAO(gormDB)
userManageService := adminService.NewUserManageService(userManageDAO, userDAO, roleDAO)
userManageController := adminController.NewUserManageController(userManageService)
app := NewApp(cfg, gormDB, client, authService, authController, adminAuthController, userManageController)
return app, nil
}

View File

@@ -20,8 +20,12 @@ import (
)
func main() {
// 1. 加载配置
cfg, err := config.Load("config", "config.dev")
// 1. 加载配置(通过 CONFIG_NAME 环境变量指定配置文件名,默认 config.dev
configName := os.Getenv("CONFIG_NAME")
if configName == "" {
configName = "config.dev"
}
cfg, err := config.Load("config", configName)
if err != nil {
fmt.Printf("加载配置失败: %v\n", err)
os.Exit(1)

View File

@@ -0,0 +1,42 @@
# ============================================================
# EchoChat Docker 容器内配置
# 与 config.dev.yaml 的区别:数据库和 Redis 地址使用 Docker 服务名
# ============================================================
server:
port: 8085
mode: debug
database:
host: postgres
port: 5432
user: echochat
password: echochat_dev_2026
dbname: echochat
sslmode: disable
max_idle_conns: 10
max_open_conns: 100
redis:
host: redis
port: 6379
password: ""
db: 0
jwt:
secret: echochat-dev-jwt-secret-2026-please-change-in-production
access_expire_min: 120
refresh_expire_day: 7
issuer: echochat
log:
level: debug
format: text
output_path: stdout
file:
enable: true
dir: logs
max_size: 50
max_backups: 10
max_age: 30
compress: false

View File

@@ -3,6 +3,7 @@ package middleware
import (
"net/http"
"runtime/debug"
"time"
"github.com/echochat/backend/pkg/logs"
"github.com/echochat/backend/pkg/utils"
@@ -28,6 +29,7 @@ func Recovery() gin.HandlerFunc {
Code: 500,
Message: "服务内部错误",
TraceID: logs.GetTraceID(ctx),
Time: time.Now().Format("2006-01-02 15:04:05"),
})
}
}()

View File

@@ -6,6 +6,7 @@ package router
import (
"time"
"github.com/echochat/backend/app/admin"
"github.com/echochat/backend/app/auth"
"github.com/echochat/backend/app/provider"
"github.com/echochat/backend/pkg/middleware"
@@ -30,10 +31,10 @@ func Setup(engine *gin.Engine, app *provider.App) {
// --- 各模块路由注册 ---
auth.RegisterRoutes(engine, app.AuthController, app.AdminAuthController, jwtAuth)
admin.RegisterRoutes(engine, app.UserManageController, jwtAuth)
// [未来] im.RegisterRoutes(engine, app.ImController, jwtAuth)
// [未来] meeting.RegisterRoutes(engine, app.MeetingController, jwtAuth)
// [未来] contact.RegisterRoutes(engine, app.ContactController, jwtAuth)
// [未来] notify.RegisterRoutes(engine, app.NotifyController, jwtAuth)
// [未来] admin.RegisterRoutes(engine, app.AdminController, jwtAuth)
}