Files
server/app/http/controllers/admin/user_controller.go
T
2026-01-16 15:49:34 +08:00

333 lines
9.9 KiB
Go

package admin
import (
"encoding/json"
"fmt"
"time"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/queue"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
apperrors "goravel/app/errors"
"goravel/app/http/helpers"
adminrequests "goravel/app/http/requests/admin"
"goravel/app/http/response"
"goravel/app/jobs"
"goravel/app/models"
"goravel/app/services"
"goravel/app/utils"
)
type UserController struct {
userService services.UserService
}
func NewUserController() *UserController {
return &UserController{
userService: services.NewUserService(),
}
}
// Index 用户列表
func (r *UserController) Index(ctx http.Context) http.Response {
page := helpers.GetIntQuery(ctx, "page", 1)
pageSize := helpers.GetIntQuery(ctx, "page_size", 10)
filters := services.UserFilters{
Username: ctx.Request().Query("username", ""),
Email: ctx.Request().Query("email", ""),
Phone: ctx.Request().Query("phone", ""),
Status: ctx.Request().Query("status", ""),
}
users, total, err := r.userService.GetList(filters, page, pageSize)
if err != nil {
if businessErr, ok := apperrors.GetBusinessError(err); ok {
return response.Error(ctx, http.StatusInternalServerError, businessErr.Code)
}
return response.Error(ctx, http.StatusInternalServerError, err.Error())
}
return response.Success(ctx, http.Json{
"list": users,
"total": total,
"page": page,
"page_size": pageSize,
})
}
// Show 用户详情
func (r *UserController) Show(ctx http.Context) http.Response {
id := helpers.GetUintRoute(ctx, "id")
user, err := r.userService.GetByID(id)
if err != nil {
if businessErr, ok := apperrors.GetBusinessError(err); ok {
return response.Error(ctx, http.StatusNotFound, businessErr.Code)
}
return response.Error(ctx, http.StatusNotFound, err.Error())
}
return response.Success(ctx, http.Json{
"user": user,
})
}
// Store 创建用户
func (r *UserController) Store(ctx http.Context) http.Response {
// 使用请求验证
var userCreate adminrequests.UserCreate
errors, err := ctx.Request().ValidateRequest(&userCreate)
if err != nil {
return response.Error(ctx, http.StatusBadRequest, err.Error())
}
if errors != nil {
return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All())
}
// 使用服务方法创建用户(包含验证、密码加密、默认货币设置)
user, err := r.userService.CreateWithValidation(
userCreate.Username,
userCreate.Password,
userCreate.Nickname,
userCreate.Email,
userCreate.Phone,
userCreate.Status,
)
if err != nil {
if businessErr, ok := apperrors.GetBusinessError(err); ok {
return response.Error(ctx, http.StatusBadRequest, businessErr.Code)
}
return response.ErrorWithLog(ctx, "user", err, map[string]any{
"username": userCreate.Username,
})
}
return response.Success(ctx, http.Json{
"user": user,
})
}
// Update 更新用户
func (r *UserController) Update(ctx http.Context) http.Response {
id := helpers.GetUintRoute(ctx, "id")
// 使用请求验证
var userUpdate adminrequests.UserUpdate
errors, err := ctx.Request().ValidateRequest(&userUpdate)
if err != nil {
return response.Error(ctx, http.StatusBadRequest, err.Error())
}
if errors != nil {
return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All())
}
// 使用服务方法验证用户是否存在(排除当前用户)
if err := r.userService.ValidateUserExists("", userUpdate.Email, userUpdate.Phone, id); err != nil {
if businessErr, ok := apperrors.GetBusinessError(err); ok {
return response.Error(ctx, http.StatusBadRequest, businessErr.Code)
}
return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrUpdateFailed.Code)
}
user := models.User{
Nickname: userUpdate.Nickname,
Email: userUpdate.Email,
Phone: userUpdate.Phone,
Status: userUpdate.Status,
}
// 如果提供了密码,则加密
if userUpdate.Password != "" {
hashedPassword, err := facades.Hash().Make(userUpdate.Password)
if err != nil {
return response.Error(ctx, http.StatusInternalServerError, "password_encrypt_failed")
}
user.Password = hashedPassword
}
if err := r.userService.Update(id, &user); err != nil {
if businessErr, ok := apperrors.GetBusinessError(err); ok {
return response.Error(ctx, http.StatusInternalServerError, businessErr.Code)
}
return response.Error(ctx, http.StatusInternalServerError, err.Error())
}
return response.Success(ctx, http.Json{
"user": user,
})
}
// Destroy 删除用户
func (r *UserController) Destroy(ctx http.Context) http.Response {
id := helpers.GetUintRoute(ctx, "id")
if err := r.userService.Delete(id); err != nil {
if businessErr, ok := apperrors.GetBusinessError(err); ok {
return response.Error(ctx, http.StatusInternalServerError, businessErr.Code)
}
return response.Error(ctx, http.StatusInternalServerError, err.Error())
}
return response.Success(ctx, "delete_success", http.Json{})
}
// UpdateBalance 更新用户余额
func (r *UserController) UpdateBalance(ctx http.Context) http.Response {
// 从路由参数获取 user_id
userID := helpers.GetUintRoute(ctx, "id")
amount := cast.ToFloat64(ctx.Request().Input("amount", "0"))
logType := ctx.Request().Input("type", "")
source := ctx.Request().Input("source", "manual")
description := ctx.Request().Input("description", "")
remark := ctx.Request().Input("remark", "")
var sourceID *uint
if sourceIDStr := ctx.Request().Input("source_id", ""); sourceIDStr != "" {
id := cast.ToUint(sourceIDStr)
sourceID = &id
}
var operatorID *uint
adminID, err := helpers.GetAdminIDFromContext(ctx)
if err == nil && adminID > 0 {
operatorID = &adminID
}
if userID == 0 {
return response.Error(ctx, http.StatusBadRequest, "user_id_required")
}
if amount == 0 {
return response.Error(ctx, http.StatusBadRequest, "amount_cannot_be_zero")
}
if logType == "" {
return response.Error(ctx, http.StatusBadRequest, "balance_type_required")
}
if err := r.userService.UpdateBalance(userID, amount, logType, source, sourceID, description, operatorID, remark); err != nil {
// response.Error 会自动检测 BusinessError 并处理占位符替换
return response.Error(ctx, http.StatusBadRequest, err)
}
return response.Success(ctx, "balance_update_success", http.Json{})
}
// ResetPassword 重置用户密码(管理员操作)
func (r *UserController) ResetPassword(ctx http.Context) http.Response {
id := helpers.GetUintRoute(ctx, "id")
// 使用请求验证
var resetPasswordRequest adminrequests.ResetPassword
errors, err := ctx.Request().ValidateRequest(&resetPasswordRequest)
if err != nil {
return response.Error(ctx, http.StatusBadRequest, err.Error())
}
if errors != nil {
return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All())
}
// 使用服务方法重置密码
if err := r.userService.ResetPassword(id, resetPasswordRequest.Password); err != nil {
if businessErr, ok := apperrors.GetBusinessError(err); ok {
return response.Error(ctx, http.StatusBadRequest, businessErr.Code)
}
return response.ErrorWithLog(ctx, "password", err, map[string]any{
"user_id": id,
})
}
return response.Success(ctx, "password_reset_success", http.Json{})
}
// Export 导出用户列表
func (r *UserController) Export(ctx http.Context) http.Response {
adminID, err := helpers.GetAdminIDFromContext(ctx)
if err != nil {
return response.Error(ctx, http.StatusUnauthorized, "unauthorized")
}
// 防重复点击
lockKey := fmt.Sprintf("export:users:lock:%d", adminID)
lock := facades.Cache().Lock(lockKey, 10*time.Second)
if !lock.Get() {
return response.Error(ctx, http.StatusTooManyRequests, "export_in_progress")
}
// 获取存储驱动配置
disk := utils.GetConfigValue("storage", "file_disk", "")
if disk == "" {
disk = utils.GetConfigValue("storage", "export_disk", "")
}
if disk == "" {
disk = "local"
}
exportRecord := models.Export{
AdminID: adminID,
Type: models.ExportTypeUsers,
Status: models.ExportStatusProcessing,
Disk: disk,
Path: "",
}
if err := facades.Orm().Query().Create(&exportRecord); err != nil {
return response.ErrorWithLog(ctx, "export", err)
}
// 构建筛选条件(POST 请求,从 body 获取参数)
filtersMap := map[string]any{
"username": ctx.Request().Input("username", ""),
"nickname": ctx.Request().Input("nickname", ""),
"email": ctx.Request().Input("email", ""),
"phone": ctx.Request().Input("phone", ""),
"order_by": ctx.Request().Input("order_by", "id:desc"),
}
if statusStr := ctx.Request().Input("status", ""); statusStr != "" {
filtersMap["status"] = cast.ToUint(statusStr)
}
lang := utils.GetCurrentLanguage(ctx)
timezone := helpers.GetCurrentTimezone(ctx)
exportArgsStruct := jobs.ExportUsersArgs{
ExportID: exportRecord.ID,
AdminID: adminID,
Filters: filtersMap,
Type: "users",
Language: lang,
Timezone: timezone,
}
exportArgsJSON, err := json.Marshal(exportArgsStruct)
if err != nil {
facades.Log().Errorf("序列化导出参数失败: export_id=%d, error=%v", exportRecord.ID, err)
exportRecord.Status = models.ExportStatusFailed
exportRecord.ErrorMsg = err.Error()
facades.Orm().Query().Save(&exportRecord)
return response.ErrorWithLog(ctx, "export", err)
}
facades.Log().Infof("提交用户导出任务到队列: export_id=%d", exportRecord.ID)
exportArgs := []queue.Arg{
{
Type: "string",
Value: string(exportArgsJSON),
},
}
if err := facades.Queue().Job(&jobs.ExportUsers{}, exportArgs).OnQueue("long-running").Dispatch(); err != nil {
lock.Release()
facades.Log().Errorf("提交导出任务失败: export_id=%d, error=%v", exportRecord.ID, err)
exportRecord.Status = models.ExportStatusFailed
exportRecord.ErrorMsg = err.Error()
facades.Orm().Query().Save(&exportRecord)
return response.ErrorWithLog(ctx, "export", err)
}
return response.Success(ctx, http.Json{
"export_id": exportRecord.ID,
"message": "export_task_submitted",
})
}