2026-01-04 19:27:53 +08:00
|
|
|
|
// Package response provides standardized HTTP response helpers.
|
2025-12-18 13:50:39 +08:00
|
|
|
|
package response
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-01-27 19:13:01 +08:00
|
|
|
|
"log"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
"math"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
|
2025-12-31 23:42:01 +08:00
|
|
|
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
2025-12-18 13:50:39 +08:00
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Response 标准API响应格式
|
|
|
|
|
|
type Response struct {
|
2025-12-25 20:52:47 +08:00
|
|
|
|
Code int `json:"code"`
|
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
|
Reason string `json:"reason,omitempty"`
|
|
|
|
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
|
|
|
|
Data any `json:"data,omitempty"`
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PaginatedData 分页数据格式(匹配前端期望)
|
|
|
|
|
|
type PaginatedData struct {
|
2025-12-20 16:19:40 +08:00
|
|
|
|
Items any `json:"items"`
|
|
|
|
|
|
Total int64 `json:"total"`
|
|
|
|
|
|
Page int `json:"page"`
|
|
|
|
|
|
PageSize int `json:"page_size"`
|
|
|
|
|
|
Pages int `json:"pages"`
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Success 返回成功响应
|
2025-12-20 16:19:40 +08:00
|
|
|
|
func Success(c *gin.Context, data any) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
c.JSON(http.StatusOK, Response{
|
|
|
|
|
|
Code: 0,
|
|
|
|
|
|
Message: "success",
|
|
|
|
|
|
Data: data,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Created 返回创建成功响应
|
2025-12-20 16:19:40 +08:00
|
|
|
|
func Created(c *gin.Context, data any) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
c.JSON(http.StatusCreated, Response{
|
|
|
|
|
|
Code: 0,
|
|
|
|
|
|
Message: "success",
|
|
|
|
|
|
Data: data,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Error 返回错误响应
|
|
|
|
|
|
func Error(c *gin.Context, statusCode int, message string) {
|
|
|
|
|
|
c.JSON(statusCode, Response{
|
2025-12-25 20:52:47 +08:00
|
|
|
|
Code: statusCode,
|
|
|
|
|
|
Message: message,
|
|
|
|
|
|
Reason: "",
|
|
|
|
|
|
Metadata: nil,
|
2025-12-18 13:50:39 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 20:52:47 +08:00
|
|
|
|
// ErrorWithDetails returns an error response compatible with the existing envelope while
|
|
|
|
|
|
// optionally providing structured error fields (reason/metadata).
|
|
|
|
|
|
func ErrorWithDetails(c *gin.Context, statusCode int, message, reason string, metadata map[string]string) {
|
|
|
|
|
|
c.JSON(statusCode, Response{
|
|
|
|
|
|
Code: statusCode,
|
|
|
|
|
|
Message: message,
|
|
|
|
|
|
Reason: reason,
|
|
|
|
|
|
Metadata: metadata,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ErrorFrom converts an ApplicationError (or any error) into the envelope-compatible error response.
|
|
|
|
|
|
// It returns true if an error was written.
|
|
|
|
|
|
func ErrorFrom(c *gin.Context, err error) bool {
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
statusCode, status := infraerrors.ToHTTP(err)
|
2026-01-27 19:13:01 +08:00
|
|
|
|
|
|
|
|
|
|
// Log internal errors with full details for debugging
|
2026-01-27 19:26:44 +08:00
|
|
|
|
if statusCode >= 500 && c.Request != nil {
|
2026-01-27 19:13:01 +08:00
|
|
|
|
log.Printf("[ERROR] %s %s\n Error: %s", c.Request.Method, c.Request.URL.Path, err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 20:52:47 +08:00
|
|
|
|
ErrorWithDetails(c, statusCode, status.Message, status.Reason, status.Metadata)
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
|
// BadRequest 返回400错误
|
|
|
|
|
|
func BadRequest(c *gin.Context, message string) {
|
|
|
|
|
|
Error(c, http.StatusBadRequest, message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unauthorized 返回401错误
|
|
|
|
|
|
func Unauthorized(c *gin.Context, message string) {
|
|
|
|
|
|
Error(c, http.StatusUnauthorized, message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Forbidden 返回403错误
|
|
|
|
|
|
func Forbidden(c *gin.Context, message string) {
|
|
|
|
|
|
Error(c, http.StatusForbidden, message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NotFound 返回404错误
|
|
|
|
|
|
func NotFound(c *gin.Context, message string) {
|
|
|
|
|
|
Error(c, http.StatusNotFound, message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// InternalError 返回500错误
|
|
|
|
|
|
func InternalError(c *gin.Context, message string) {
|
|
|
|
|
|
Error(c, http.StatusInternalServerError, message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Paginated 返回分页数据
|
2025-12-20 16:19:40 +08:00
|
|
|
|
func Paginated(c *gin.Context, items any, total int64, page, pageSize int) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
pages := int(math.Ceil(float64(total) / float64(pageSize)))
|
|
|
|
|
|
if pages < 1 {
|
|
|
|
|
|
pages = 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Success(c, PaginatedData{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: page,
|
|
|
|
|
|
PageSize: pageSize,
|
|
|
|
|
|
Pages: pages,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 21:26:19 +08:00
|
|
|
|
// PaginationResult 分页结果(与pagination.PaginationResult兼容)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
type PaginationResult struct {
|
|
|
|
|
|
Total int64
|
|
|
|
|
|
Page int
|
|
|
|
|
|
PageSize int
|
|
|
|
|
|
Pages int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PaginatedWithResult 使用PaginationResult返回分页数据
|
2025-12-20 16:19:40 +08:00
|
|
|
|
func PaginatedWithResult(c *gin.Context, items any, pagination *PaginationResult) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
if pagination == nil {
|
|
|
|
|
|
Success(c, PaginatedData{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: 0,
|
|
|
|
|
|
Page: 1,
|
|
|
|
|
|
PageSize: 20,
|
|
|
|
|
|
Pages: 1,
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Success(c, PaginatedData{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: pagination.Total,
|
|
|
|
|
|
Page: pagination.Page,
|
|
|
|
|
|
PageSize: pagination.PageSize,
|
|
|
|
|
|
Pages: pagination.Pages,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ParsePagination 解析分页参数
|
|
|
|
|
|
func ParsePagination(c *gin.Context) (page, pageSize int) {
|
|
|
|
|
|
page = 1
|
|
|
|
|
|
pageSize = 20
|
|
|
|
|
|
|
|
|
|
|
|
if p := c.Query("page"); p != "" {
|
|
|
|
|
|
if val, err := parseInt(p); err == nil && val > 0 {
|
|
|
|
|
|
page = val
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 支持 page_size 和 limit 两种参数名
|
|
|
|
|
|
if ps := c.Query("page_size"); ps != "" {
|
2026-01-18 22:13:47 +08:00
|
|
|
|
if val, err := parseInt(ps); err == nil && val > 0 && val <= 1000 {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
pageSize = val
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if l := c.Query("limit"); l != "" {
|
2026-01-18 22:13:47 +08:00
|
|
|
|
if val, err := parseInt(l); err == nil && val > 0 && val <= 1000 {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
pageSize = val
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return page, pageSize
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func parseInt(s string) (int, error) {
|
|
|
|
|
|
var result int
|
|
|
|
|
|
for _, c := range s {
|
|
|
|
|
|
if c < '0' || c > '9' {
|
|
|
|
|
|
return 0, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
result = result*10 + int(c-'0')
|
|
|
|
|
|
}
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|