2026-04-22 16:01:59 +08:00
|
|
|
|
package response
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
2026-03-10 20:52:50 +08:00
|
|
|
|
|
2026-04-22 16:01:59 +08:00
|
|
|
|
apperrors "with_you/internal/errors"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Response 统一响应结构
|
|
|
|
|
|
type Response struct {
|
2026-03-30 04:49:35 +08:00
|
|
|
|
Code int `json:"code"`
|
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
|
Data any `json:"data,omitempty"`
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ResponseSnakeCase 统一响应结构(snake_case)
|
|
|
|
|
|
type ResponseSnakeCase struct {
|
2026-03-30 04:49:35 +08:00
|
|
|
|
Code int `json:"code"`
|
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
|
Data any `json:"data,omitempty"`
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Success 成功响应
|
2026-03-30 04:49:35 +08:00
|
|
|
|
func Success(c *gin.Context, data any) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
c.JSON(http.StatusOK, Response{
|
|
|
|
|
|
Code: 0,
|
|
|
|
|
|
Message: "success",
|
|
|
|
|
|
Data: data,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SuccessWithMessage 成功响应带消息
|
2026-03-30 04:49:35 +08:00
|
|
|
|
func SuccessWithMessage(c *gin.Context, message string, data any) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
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 {
|
2026-03-30 04:49:35 +08:00
|
|
|
|
List any `json:"list"`
|
|
|
|
|
|
Total int64 `json:"total"`
|
|
|
|
|
|
Page int `json:"page"`
|
|
|
|
|
|
PageSize int `json:"page_size"`
|
|
|
|
|
|
TotalPages int `json:"total_pages"`
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Paginated 分页成功响应
|
2026-03-30 04:49:35 +08:00
|
|
|
|
func Paginated(c *gin.Context, list any, total int64, page, pageSize int) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
totalPages := int(total) / pageSize
|
|
|
|
|
|
if int(total)%pageSize > 0 {
|
|
|
|
|
|
totalPages++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Success(c, PaginatedResponse{
|
|
|
|
|
|
List: list,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: page,
|
|
|
|
|
|
PageSize: pageSize,
|
|
|
|
|
|
TotalPages: totalPages,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-03-10 20:52:50 +08:00
|
|
|
|
|
|
|
|
|
|
// HandleServiceError 统一处理 Service 错误
|
2026-03-13 09:38:18 +08:00
|
|
|
|
// 如果 err 是 *apperrors.AppError,返回对应的业务错误码和消息
|
2026-03-10 20:52:50 +08:00
|
|
|
|
// 如果 err 是其他错误,返回 false(调用方应处理通用错误)
|
|
|
|
|
|
// 返回 true 表示错误已处理,false 表示需要调用方继续处理
|
|
|
|
|
|
func HandleServiceError(c *gin.Context, err error) bool {
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-03-13 09:38:18 +08:00
|
|
|
|
if se, ok := apperrors.As(err); ok {
|
|
|
|
|
|
statusCode := statusCodeFromAppErrorCode(se.Code)
|
|
|
|
|
|
ErrorWithStringCode(c, statusCode, se.Code, se.Message)
|
2026-03-10 20:52:50 +08:00
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// HandleError 统一处理错误(带默认消息)
|
2026-03-13 09:38:18 +08:00
|
|
|
|
// 如果 err 是 *apperrors.AppError,返回对应的业务错误码和消息
|
2026-03-10 20:52:50 +08:00
|
|
|
|
// 如果 err 是其他错误,返回 InternalServerError 并使用 defaultMessage
|
|
|
|
|
|
func HandleError(c *gin.Context, err error, defaultMessage string) {
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-03-13 09:38:18 +08:00
|
|
|
|
if se, ok := apperrors.As(err); ok {
|
|
|
|
|
|
statusCode := statusCodeFromAppErrorCode(se.Code)
|
|
|
|
|
|
ErrorWithStringCode(c, statusCode, se.Code, se.Message)
|
2026-03-10 20:52:50 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
InternalServerError(c, defaultMessage)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 09:38:18 +08:00
|
|
|
|
// 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
|
2026-03-21 01:35:57 +08:00
|
|
|
|
case "UNAUTHORIZED":
|
2026-03-13 09:38:18 +08:00
|
|
|
|
return http.StatusUnauthorized
|
2026-03-21 01:35:57 +08:00
|
|
|
|
case "INVALID_CREDENTIALS":
|
|
|
|
|
|
return http.StatusBadRequest
|
2026-03-13 09:38:18 +08:00
|
|
|
|
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
|
2026-03-10 20:52:50 +08:00
|
|
|
|
default:
|
|
|
|
|
|
return http.StatusBadRequest
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-13 09:38:18 +08:00
|
|
|
|
|
|
|
|
|
|
// ErrorWithStringCode 带字符串错误码的错误响应
|
|
|
|
|
|
func ErrorWithStringCode(c *gin.Context, statusCode int, code string, message string) {
|
2026-04-08 02:28:10 +08:00
|
|
|
|
c.JSON(statusCode, gin.H{
|
|
|
|
|
|
"code": statusCode,
|
|
|
|
|
|
"message": message,
|
|
|
|
|
|
"error_code": code,
|
2026-03-13 09:38:18 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|