response.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Package response provides standardized HTTP response helpers.
  2. package response
  3. import (
  4. "log"
  5. "math"
  6. "net/http"
  7. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  8. "github.com/Wei-Shaw/sub2api/internal/util/logredact"
  9. "github.com/gin-gonic/gin"
  10. )
  11. // Response 标准API响应格式
  12. type Response struct {
  13. Code int `json:"code"`
  14. Message string `json:"message"`
  15. Reason string `json:"reason,omitempty"`
  16. Metadata map[string]string `json:"metadata,omitempty"`
  17. Data any `json:"data,omitempty"`
  18. }
  19. // PaginatedData 分页数据格式(匹配前端期望)
  20. type PaginatedData struct {
  21. Items any `json:"items"`
  22. Total int64 `json:"total"`
  23. Page int `json:"page"`
  24. PageSize int `json:"page_size"`
  25. Pages int `json:"pages"`
  26. }
  27. // Success 返回成功响应
  28. func Success(c *gin.Context, data any) {
  29. c.JSON(http.StatusOK, Response{
  30. Code: 0,
  31. Message: "success",
  32. Data: data,
  33. })
  34. }
  35. // Created 返回创建成功响应
  36. func Created(c *gin.Context, data any) {
  37. c.JSON(http.StatusCreated, Response{
  38. Code: 0,
  39. Message: "success",
  40. Data: data,
  41. })
  42. }
  43. // Accepted 返回异步接受响应 (HTTP 202)
  44. func Accepted(c *gin.Context, data any) {
  45. c.JSON(http.StatusAccepted, Response{
  46. Code: 0,
  47. Message: "accepted",
  48. Data: data,
  49. })
  50. }
  51. // Error 返回错误响应
  52. func Error(c *gin.Context, statusCode int, message string) {
  53. c.JSON(statusCode, Response{
  54. Code: statusCode,
  55. Message: message,
  56. Reason: "",
  57. Metadata: nil,
  58. })
  59. }
  60. // ErrorWithDetails returns an error response compatible with the existing envelope while
  61. // optionally providing structured error fields (reason/metadata).
  62. func ErrorWithDetails(c *gin.Context, statusCode int, message, reason string, metadata map[string]string) {
  63. c.JSON(statusCode, Response{
  64. Code: statusCode,
  65. Message: message,
  66. Reason: reason,
  67. Metadata: metadata,
  68. })
  69. }
  70. // ErrorFrom converts an ApplicationError (or any error) into the envelope-compatible error response.
  71. // It returns true if an error was written.
  72. func ErrorFrom(c *gin.Context, err error) bool {
  73. if err == nil {
  74. return false
  75. }
  76. statusCode, status := infraerrors.ToHTTP(err)
  77. // Log internal errors with full details for debugging
  78. if statusCode >= 500 && c.Request != nil {
  79. log.Printf("[ERROR] %s %s\n Error: %s", c.Request.Method, c.Request.URL.Path, logredact.RedactText(err.Error()))
  80. }
  81. ErrorWithDetails(c, statusCode, status.Message, status.Reason, status.Metadata)
  82. return true
  83. }
  84. // BadRequest 返回400错误
  85. func BadRequest(c *gin.Context, message string) {
  86. Error(c, http.StatusBadRequest, message)
  87. }
  88. // Unauthorized 返回401错误
  89. func Unauthorized(c *gin.Context, message string) {
  90. Error(c, http.StatusUnauthorized, message)
  91. }
  92. // Forbidden 返回403错误
  93. func Forbidden(c *gin.Context, message string) {
  94. Error(c, http.StatusForbidden, message)
  95. }
  96. // NotFound 返回404错误
  97. func NotFound(c *gin.Context, message string) {
  98. Error(c, http.StatusNotFound, message)
  99. }
  100. // InternalError 返回500错误
  101. func InternalError(c *gin.Context, message string) {
  102. Error(c, http.StatusInternalServerError, message)
  103. }
  104. // Paginated 返回分页数据
  105. func Paginated(c *gin.Context, items any, total int64, page, pageSize int) {
  106. pages := int(math.Ceil(float64(total) / float64(pageSize)))
  107. if pages < 1 {
  108. pages = 1
  109. }
  110. Success(c, PaginatedData{
  111. Items: items,
  112. Total: total,
  113. Page: page,
  114. PageSize: pageSize,
  115. Pages: pages,
  116. })
  117. }
  118. // PaginationResult 分页结果(与pagination.PaginationResult兼容)
  119. type PaginationResult struct {
  120. Total int64
  121. Page int
  122. PageSize int
  123. Pages int
  124. }
  125. // PaginatedWithResult 使用PaginationResult返回分页数据
  126. func PaginatedWithResult(c *gin.Context, items any, pagination *PaginationResult) {
  127. if pagination == nil {
  128. Success(c, PaginatedData{
  129. Items: items,
  130. Total: 0,
  131. Page: 1,
  132. PageSize: 20,
  133. Pages: 1,
  134. })
  135. return
  136. }
  137. Success(c, PaginatedData{
  138. Items: items,
  139. Total: pagination.Total,
  140. Page: pagination.Page,
  141. PageSize: pagination.PageSize,
  142. Pages: pagination.Pages,
  143. })
  144. }
  145. // ParsePagination 解析分页参数
  146. func ParsePagination(c *gin.Context) (page, pageSize int) {
  147. page = 1
  148. pageSize = 20
  149. if p := c.Query("page"); p != "" {
  150. if val, err := parseInt(p); err == nil && val > 0 {
  151. page = val
  152. }
  153. }
  154. // 支持 page_size 和 limit 两种参数名
  155. if ps := c.Query("page_size"); ps != "" {
  156. if val, err := parseInt(ps); err == nil && val > 0 && val <= 1000 {
  157. pageSize = val
  158. }
  159. } else if l := c.Query("limit"); l != "" {
  160. if val, err := parseInt(l); err == nil && val > 0 && val <= 1000 {
  161. pageSize = val
  162. }
  163. }
  164. return page, pageSize
  165. }
  166. func parseInt(s string) (int, error) {
  167. var result int
  168. for _, c := range s {
  169. if c < '0' || c > '9' {
  170. return 0, nil
  171. }
  172. result = result*10 + int(c-'0')
  173. }
  174. return result, nil
  175. }