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", }) }