feat(backend): unified response helpers, refresh token, security hardening
- new resp package (OK/Page/Fail/BadRequest/.../InternalErr): unified error
envelope {detail, code?}, sanitized 500s (full error logged server-side,
generic message to client - stops DB error leakage), pagination envelope
{items,total,page,page_size}; migrate 200+ inline c.JSON error sites
- refresh token: 7d rotation tokens (SHA-256 hashed at rest), public
POST /auth/refresh with single-use rotation, POST /auth/logout revokes
all user refresh tokens (endpoint previously missing - frontend 404)
- security: gh-ost command line password masked in task status payload;
Instance.MarshalJSON guarantees encrypted passwords never leave the API
(removes reliance on per-handler manual masking); production (DEBUG=false)
with default JWT secret now auto-generates and persists a random secret
- safe.Go recover wrapper for background goroutines (instance probe,
gh-ost async)
- delete dead service layer (zero callers)
- fix pre-existing go vet unkeyed bson.E literals
This commit is contained in:
1 parent
d5b8b8116c
commit
16f1f7d65f
26 files changed
+916
-963
No files matched your search
@@ -0,0 +1,70 @@
|
||||
// Package resp 统一 API 响应助手(渐进式契约统一):
|
||||
// - 成功:直接返回业务数据;分页统一 {items, total, page, page_size} 信封
|
||||
// - 失败:HTTP 状态码语义保持不变,错误体 {detail, code?}(兼容既有前端与测试)
|
||||
// - 内部错误:服务端记录完整 error,客户端只收通用文案,杜绝 DB 错误原文泄漏
|
||||
//
|
||||
// 新代码禁止直接 c.JSON 错误响应;存量调用点在迭代中逐步迁移到本包。
|
||||
package resp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// OK 200:直接返回业务数据
|
||||
func OK(c *gin.Context, data any) {
|
||||
c.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// Created 201:资源创建成功
|
||||
func Created(c *gin.Context, data any) {
|
||||
c.JSON(http.StatusCreated, data)
|
||||
}
|
||||
|
||||
// Page 200:统一分页信封 {items, total, page, page_size}
|
||||
func Page(c *gin.Context, items any, total int64, page, pageSize int) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"items": items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// Message 200:仅含 {message} 的轻量确认(删除/测试类端点)
|
||||
func Message(c *gin.Context, msg string) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": msg})
|
||||
}
|
||||
|
||||
// Fail 通用失败:{detail}
|
||||
func Fail(c *gin.Context, status int, detail string) {
|
||||
c.JSON(status, gin.H{"detail": detail})
|
||||
}
|
||||
|
||||
// FailCode 失败并携带机器可读错误码:{detail, code}
|
||||
func FailCode(c *gin.Context, status int, code, detail string) {
|
||||
c.JSON(status, gin.H{"detail": detail, "code": code})
|
||||
}
|
||||
|
||||
func BadRequest(c *gin.Context, detail string) { Fail(c, http.StatusBadRequest, detail) }
|
||||
func Unauthorized(c *gin.Context, detail string) { Fail(c, http.StatusUnauthorized, detail) }
|
||||
func Forbidden(c *gin.Context, detail string) { Fail(c, http.StatusForbidden, detail) }
|
||||
func NotFound(c *gin.Context, detail string) { Fail(c, http.StatusNotFound, detail) }
|
||||
|
||||
// InternalErr 500:服务端 log 完整错误,客户端只收通用文案(防 DB/内部错误原文泄漏)
|
||||
func InternalErr(c *gin.Context, err error) {
|
||||
log.Printf("[resp] internal error on %s %s: %v", c.Request.Method, c.Request.URL.Path, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "服务器内部错误,请稍后重试", "code": "internal"})
|
||||
}
|
||||
|
||||
// InternalMsg 500:无底层 error、需要给用户可读文案时使用
|
||||
func InternalMsg(c *gin.Context, userMsg string) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": userMsg})
|
||||
}
|
||||
|
||||
// TooManyRequests 429:携带 retry_after_minutes
|
||||
func TooManyRequests(c *gin.Context, detail string, retryAfterMinutes int) {
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{"detail": detail, "retry_after_minutes": retryAfterMinutes})
|
||||
}
|
||||
Reference in new issue
Block a user