Map INVALID_CREDENTIALS to http.StatusBadRequest (400) instead of http.StatusUnauthorized (401) to better align with client error semantics. Update ErrorWithStringCode to return the HTTP status code in the 'code' field and set 'data' to nil, removing the legacy compatibility workaround that set 'code' to 0. BREAKING CHANGE: Response structure for ErrorWithStringCode changed (Code field now returns status code, Data is nil) and INVALID_CREDENTIALS status changed from 401 to 400.
193 lines
5.6 KiB
Go
193 lines
5.6 KiB
Go
package response
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
apperrors "carrot_bbs/internal/errors"
|
||
)
|
||
|
||
// Response 统一响应结构
|
||
type Response struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Data interface{} `json:"data,omitempty"`
|
||
}
|
||
|
||
// ResponseSnakeCase 统一响应结构(snake_case)
|
||
type ResponseSnakeCase struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Data interface{} `json:"data,omitempty"`
|
||
}
|
||
|
||
// Success 成功响应
|
||
func Success(c *gin.Context, data interface{}) {
|
||
c.JSON(http.StatusOK, Response{
|
||
Code: 0,
|
||
Message: "success",
|
||
Data: data,
|
||
})
|
||
}
|
||
|
||
// SuccessWithMessage 成功响应带消息
|
||
func SuccessWithMessage(c *gin.Context, message string, data interface{}) {
|
||
c.JSON(http.StatusOK, Response{
|
||
Code: 0,
|
||
Message: message,
|
||
Data: data,
|
||
})
|
||
}
|
||
|
||
// Error 错误响应
|
||
func Error(c *gin.Context, code int, message string) {
|
||
c.JSON(http.StatusBadRequest, Response{
|
||
Code: code,
|
||
Message: message,
|
||
})
|
||
}
|
||
|
||
// ErrorWithStatusCode 带状态码的错误响应
|
||
func ErrorWithStatusCode(c *gin.Context, statusCode int, code int, message string) {
|
||
c.JSON(statusCode, Response{
|
||
Code: code,
|
||
Message: message,
|
||
})
|
||
}
|
||
|
||
// BadRequest 参数错误
|
||
func BadRequest(c *gin.Context, message string) {
|
||
ErrorWithStatusCode(c, http.StatusBadRequest, 400, message)
|
||
}
|
||
|
||
// Unauthorized 未授权
|
||
func Unauthorized(c *gin.Context, message string) {
|
||
if message == "" {
|
||
message = "unauthorized"
|
||
}
|
||
ErrorWithStatusCode(c, http.StatusUnauthorized, 401, message)
|
||
}
|
||
|
||
// Forbidden 禁止访问
|
||
func Forbidden(c *gin.Context, message string) {
|
||
if message == "" {
|
||
message = "forbidden"
|
||
}
|
||
ErrorWithStatusCode(c, http.StatusForbidden, 403, message)
|
||
}
|
||
|
||
// NotFound 资源不存在
|
||
func NotFound(c *gin.Context, message string) {
|
||
if message == "" {
|
||
message = "resource not found"
|
||
}
|
||
ErrorWithStatusCode(c, http.StatusNotFound, 404, message)
|
||
}
|
||
|
||
// InternalServerError 服务器内部错误
|
||
func InternalServerError(c *gin.Context, message string) {
|
||
if message == "" {
|
||
message = "internal server error"
|
||
}
|
||
ErrorWithStatusCode(c, http.StatusInternalServerError, 500, message)
|
||
}
|
||
|
||
// PaginatedResponse 分页响应
|
||
type PaginatedResponse struct {
|
||
List interface{} `json:"list"`
|
||
Total int64 `json:"total"`
|
||
Page int `json:"page"`
|
||
PageSize int `json:"page_size"`
|
||
TotalPages int `json:"total_pages"`
|
||
}
|
||
|
||
// Paginated 分页成功响应
|
||
func Paginated(c *gin.Context, list interface{}, total int64, page, pageSize int) {
|
||
totalPages := int(total) / pageSize
|
||
if int(total)%pageSize > 0 {
|
||
totalPages++
|
||
}
|
||
|
||
Success(c, PaginatedResponse{
|
||
List: list,
|
||
Total: total,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
TotalPages: totalPages,
|
||
})
|
||
}
|
||
|
||
// HandleServiceError 统一处理 Service 错误
|
||
// 如果 err 是 *apperrors.AppError,返回对应的业务错误码和消息
|
||
// 如果 err 是其他错误,返回 false(调用方应处理通用错误)
|
||
// 返回 true 表示错误已处理,false 表示需要调用方继续处理
|
||
func HandleServiceError(c *gin.Context, err error) bool {
|
||
if err == nil {
|
||
return false
|
||
}
|
||
if se, ok := apperrors.As(err); ok {
|
||
statusCode := statusCodeFromAppErrorCode(se.Code)
|
||
ErrorWithStringCode(c, statusCode, se.Code, se.Message)
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// HandleError 统一处理错误(带默认消息)
|
||
// 如果 err 是 *apperrors.AppError,返回对应的业务错误码和消息
|
||
// 如果 err 是其他错误,返回 InternalServerError 并使用 defaultMessage
|
||
func HandleError(c *gin.Context, err error, defaultMessage string) {
|
||
if err == nil {
|
||
return
|
||
}
|
||
if se, ok := apperrors.As(err); ok {
|
||
statusCode := statusCodeFromAppErrorCode(se.Code)
|
||
ErrorWithStringCode(c, statusCode, se.Code, se.Message)
|
||
return
|
||
}
|
||
InternalServerError(c, defaultMessage)
|
||
}
|
||
|
||
// statusCodeFromAppErrorCode 根据AppError的错误码字符串获取 HTTP 状态码
|
||
func statusCodeFromAppErrorCode(code string) int {
|
||
switch code {
|
||
case "NOT_FOUND", "USER_NOT_FOUND", "POST_NOT_FOUND", "GROUP_NOT_FOUND",
|
||
"GROUP_REQUEST_NOT_FOUND", "SCHEDULE_COURSE_NOT_FOUND":
|
||
return http.StatusNotFound
|
||
case "UNAUTHORIZED":
|
||
return http.StatusUnauthorized
|
||
case "INVALID_CREDENTIALS":
|
||
return http.StatusBadRequest
|
||
case "FORBIDDEN", "NOT_GROUP_MEMBER", "NOT_GROUP_ADMIN", "NOT_GROUP_OWNER",
|
||
"USER_BANNED", "USER_BLOCKED", "MUTED", "MUTE_ALL_ENABLED",
|
||
"SCHEDULE_FORBIDDEN", "UNAUTHORIZED_NOTIFICATION":
|
||
return http.StatusForbidden
|
||
case "BAD_REQUEST", "INVALID_USERNAME", "INVALID_EMAIL", "INVALID_PHONE",
|
||
"WEAK_PASSWORD", "USERNAME_EXISTS", "EMAIL_EXISTS", "PHONE_EXISTS",
|
||
"USER_ALREADY_EXISTS", "INVALID_OPERATION", "VERIFICATION_CODE_INVALID",
|
||
"VERIFICATION_CODE_EXPIRED", "EMAIL_ALREADY_VERIFIED", "EMAIL_NOT_BOUND",
|
||
"GROUP_FULL", "ALREADY_GROUP_MEMBER", "CANNOT_REMOVE_OWNER", "CANNOT_MUTE_OWNER",
|
||
"CANNOT_JOIN", "JOIN_REQUEST_PENDING", "GROUP_REQUEST_HANDLED",
|
||
"NOT_REQUEST_TARGET", "NO_ELIGIBLE_INVITEE", "NOT_MUTUAL_FOLLOW",
|
||
"INVITE_NOT_ACCEPTED", "STICKER_ALREADY_EXISTS", "INVALID_STICKER_URL",
|
||
"INVALID_SCHEDULE_PAYLOAD", "SCHEDULE_COLOR_DUPLICATED":
|
||
return http.StatusBadRequest
|
||
case "VERIFICATION_CODE_TOO_FREQUENT":
|
||
return http.StatusTooManyRequests
|
||
case "EMAIL_SERVICE_UNAVAILABLE", "VERIFICATION_CODE_UNAVAILABLE", "INTERNAL_ERROR":
|
||
return http.StatusInternalServerError
|
||
default:
|
||
return http.StatusBadRequest
|
||
}
|
||
}
|
||
|
||
// ErrorWithStringCode 带字符串错误码的错误响应
|
||
func ErrorWithStringCode(c *gin.Context, statusCode int, code string, message string) {
|
||
c.JSON(statusCode, ResponseSnakeCase{
|
||
Code: statusCode,
|
||
Message: message,
|
||
Data: nil,
|
||
})
|
||
}
|