- handlers/api.go:APIOK/APIError/bindJSON,统一 {ok,redirect,data} /
{ok:false,code,error} 响应契约(code=i18n 键,error 按请求语言翻译)
- i18n:新增 api_error/api_unauthorized/api_forbidden/api_invalid_request(中英)
- middleware/auth.go:AuthRequired/AdminRequired 按 /api 前缀分支:
JSON 401/403(页面保持 302),新增 isAPIRequest + apiAuthError
- main.go:路由注册提取为 registerRoutes;建立 /api 分组并搬移附件三件套
(admin/my)与 /api/profile/avatar(旧 /admin|my/articles/attachments、
/profile/avatar 路由移除)
- handlers/login_ratelimit.go:loginRateLimiter 导出为 LoginRateLimiter
- templates/layouts/base.html:blogAPI/blogForm/blogShowError 共享 fetch 助手
- main_test.go:TestRegisterRoutesSmoke 冒烟测试(注册期 gin 静态/参数
冲突即 panic + 关键 /api 路由断言)
- go build/vet/test ./... 全绿
53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
package handlers
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// APIOK 返回统一成功响应。
|
||
// redirect 非空时附带相对跳转地址(原 302 目标,可含 ?saved=1 等 query);
|
||
// data 额外字段会合并进响应。
|
||
func APIOK(c *gin.Context, redirect string, data gin.H) {
|
||
resp := gin.H{"ok": true}
|
||
if redirect != "" {
|
||
resp["redirect"] = redirect
|
||
}
|
||
for k, v := range data {
|
||
resp[k] = v
|
||
}
|
||
c.JSON(http.StatusOK, resp)
|
||
}
|
||
|
||
// APIError 返回统一错误响应:{ok:false, code, error}。
|
||
// trKey 是 i18n 键,直接作为 code 返回;error 为该键按请求语言的翻译文案。
|
||
// trKey 为空时回退到通用键 "api_error"。
|
||
func APIError(c *gin.Context, status int, trKey string) {
|
||
tr := getTr(c)
|
||
code := trKey
|
||
if code == "" {
|
||
code = "api_error"
|
||
}
|
||
msg := tr[code]
|
||
if msg == "" {
|
||
msg = tr["api_error"]
|
||
}
|
||
c.JSON(status, gin.H{
|
||
"ok": false,
|
||
"code": code,
|
||
"error": msg,
|
||
})
|
||
}
|
||
|
||
// bindJSON 将 JSON 请求体绑定到 v。
|
||
// 绑定失败时返回 400 + api_invalid_request,并返回 false。
|
||
// 使用前必须保证请求是 JSON(Content-Type: application/json)。
|
||
func bindJSON(c *gin.Context, v interface{}) bool {
|
||
if err := c.ShouldBindJSON(v); err != nil {
|
||
APIError(c, http.StatusBadRequest, "api_invalid_request")
|
||
return false
|
||
}
|
||
return true
|
||
}
|