- Replace `interface{}` with `any` type alias across all packages
- Use built-in `min()`/`max()` for parameter clamping
- Use `slices.SortFunc`, `slices.Min`, `slices.Max` for cleaner code
- Use `strings.Cut()` for simpler string parsing in auth middleware
- Use `errors.Is()` for proper error comparison in handlers
- Update dependencies: golang.org/x/image 0.37.0 -> 0.38.0
- Add Wire code generation guidelines to ARCHITECTURE.md
- Disable Go cache in CI build workflow
193 lines
5.5 KiB
Go
193 lines
5.5 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 any `json:"data,omitempty"`
|
||
}
|
||
|
||
// ResponseSnakeCase 统一响应结构(snake_case)
|
||
type ResponseSnakeCase struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Data any `json:"data,omitempty"`
|
||
}
|
||
|
||
// Success 成功响应
|
||
func Success(c *gin.Context, data any) {
|
||
c.JSON(http.StatusOK, Response{
|
||
Code: 0,
|
||
Message: "success",
|
||
Data: data,
|
||
})
|
||
}
|
||
|
||
// SuccessWithMessage 成功响应带消息
|
||
func SuccessWithMessage(c *gin.Context, message string, data any) {
|
||
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 any `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 any, 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,
|
||
})
|
||
}
|