应使用封装好的Response结构体而非iris.Map,因其能统一字段名、自动映射HTTP状态码、可控序列化(如time.Time和nil处理),且所有响应(含error)必须统一格式以保障前后端一致性。

直接用封装好的响应结构体 + ctx.JSON(),别动原生方法,也别在每个 handler 里重复写 status/code/data 嵌套逻辑。
为什么不能裸用 map 或 iris.Map
裸写 ctx.JSON(iris.Map{"code": 0, "msg": "ok", "data": user}) 看似快,但会带来三个实际问题:
- 字段名不一致:前端期望
success,你写成code,后续所有接口都要改 - 缺失状态码控制:
iris.Map无法自动映射 HTTP 状态码(比如业务错误该返回 400,但 JSON body 里还是{"code": 1}),前端难做统一拦截 - 时间/空值处理失控:没有自定义
json.MarshalJSON()方法时,time.Time字段输出 RFC3339,nil指针变成null,但前端可能期望字符串""或忽略该字段
怎么定义一个靠谱的统一响应结构体
推荐定义一个导出结构体,带 json 标签和自定义序列化逻辑:
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
func (r *Response) WithStatus(statusCode int) *Response {
r.Code = statusCode
return r
}
func Success(data interface{}) *Response {
return &Response{Code: 0, Msg: "success", Data: data}
}
func Fail(code int, msg string) *Response {
return &Response{Code: code, Msg: msg, Data: nil}
}
用法示例:ctx.JSON(iris.StatusOK, Success(user)) 或 ctx.JSON(iris.StatusBadRequest, Fail(4001, "参数校验失败"))。
注意:Data 字段必须是 interface{},否则无法接收 struct/map/slice;omitempty 标签让空 data 不出现在响应中。
ctx.JSON() 的状态码参数不能省
ctx.JSON() 第一个参数是真实 HTTP 状态码,它决定浏览器 Network 面板里显示的 status,不是靠 body 里的 code 字段。常见错误:
- ✅ 正确:
ctx.JSON(iris.StatusOK, Success(user))→ HTTP 200 +{"code":0,"msg":"success","data":{...}} - ❌ 错误:
ctx.JSON(iris.StatusOK, Fail(500, "server error"))→ HTTP 200,但 body 说失败,前端难区分 - ✅ 更准:
ctx.JSON(iris.StatusInternalServerError, Fail(500, "server error"))
建议封装一个 writeResp(ctx iris.Context, statusCode int, resp *Response) 函数,把状态码和 body 绑定起来,避免漏传。
容易被忽略的边界:error 响应也要走统一结构
很多开发者只对成功路径用 Success(),错误直接 ctx.StatusCode(iris.StatusUnauthorized); ctx.JSON(iris.Map{"error": "token expired"}) —— 这破坏了响应一致性。
真正要做的:所有响应,无论成功失败,都走同一结构体。例如:
if err := ctx.ReadJSON(&input); err != nil {
ctx.JSON(iris.StatusBadRequest, Fail(4000, "请求格式错误"))
return
}
连数据库查询失败、权限校验失败、配置加载失败……都该返回 Fail(xxx, "xxx"),只是 HTTP 状态码按语义选(401/403/404/500)。否则前端得写七八种 error 解析逻辑。


















