统一响应结构体必须同时包含HTTPStatus(用于c.JSON参数及网络层行为)和Code(纯业务码,供前端分支逻辑使用),Message为非空字符串,Data须为可序列化类型,Timestamp用UnixMilli(),分页等元信息不得混入Data。

统一响应结构体必须明确区分 HTTP 状态码和业务码
很多人一上来就定义 Response{Code: 0, Msg: "ok", Data: ...},结果后续被 Nginx 缓存、CDN 透传或前端拦截搞崩溃——因为 Code 被当成 HTTP 状态码用了。真正的分层是:HTTP status code 控制网络层行为(缓存、重试、浏览器提示),business code 控制业务逻辑分支(比如 1001 表示“余额不足”)。所以结构体里两个字段都得有,且命名不能含糊:
-
HTTPStatus字段(int 类型),只用于c.JSON()第一个参数,值为http.StatusOK、http.StatusBadRequest等 -
Code字段(int 类型),纯业务码,后端返回 JSON 体里的"code",前端用它做 toast 提示或跳转 -
Message必须是非空字符串,禁止 nil;生产环境不暴露 SQL 错误、堆栈等敏感信息 -
Data类型用any没问题,但传入前要确保是可序列化类型(struct/map/slice),别传func或带 unexported 字段的 struct
客户端封装必须收口 HTTP 调用链,而不是只包一层 json.Unmarshal
常见错误是写个 CallAPI(url string, req any, resp *T) error,里面只做 http.Post + json.Unmarshal。这漏掉了三类关键错误:网络层失败(timeout/DNS)、HTTP 状态异常(4xx/5xx)、业务码非零(resp.Code != 0)。正确做法是把整条链路压进一个函数:
- 用
context.WithTimeout控制整体超时,不是只设http.Client.Timeout - 收到
resp.StatusCode >= 400时,先读resp.Body再解析,避免 body 泄漏 - 反序列化后必须检查
resp.Code != 0,并构造统一错误类型(如ApiError{Code: resp.Code, Message: resp.Message, RawBody: rawBytes}) - 不要在封装里调
log.Fatal或 panic,错误必须返回给上层由业务决定怎么处理
包组织别按“service/model/handler”分层,按业务域建包
你不会看到 service.UserService.GetUserRequest 这种路径,Go 里接口和结构体不该塞进 service 包。正确姿势是每个业务功能一个独立包,比如 user 包内直接定义:
type Request struct { ID uint64 }type Response struct { Name string }type Client interface { GetUser(ctx context.Context, r Request) (Response, error) }func NewClient(httpClient *http.Client) Client
这样外部使用就是 user.NewClient(c).GetUser(ctx, user.Request{ID: 123}),语义清晰、导入路径短、测试时可轻松 mock user.Client 接口。
立即学习“go语言免费学习笔记(深入)”;
泛型方法别滥用,优先用具体类型+组合
有人一上来就写 func Do[T any](url string, req any, resp *T) error,看似通用,实则掩盖了真实差异:GET 请求要拼 query,POST 要 encode body,某些接口要加签名 Header,某些要自动刷新 token。更稳的方案是:
- 为每类典型请求提供专用方法:
GetJSON、PostJSON、PutForm - 所有方法接收显式
*http.Client和context.Context,不依赖全局变量 - 需要扩展行为(鉴权、trace、重试)用函数选项模式,比如
WithToken("xxx")、WithRetry(3),通过闭包注入,不污染主流程 - 泛型只用在真正需要的地方,比如统一反序列化逻辑:
func unmarshalBody[T any](body io.Reader) (T, error)
最易被忽略的是:不同业务接口的错误恢复策略完全不同——有的该重试(网络抖动),有的该立刻失败(参数错误),硬套一个泛型重试逻辑反而让问题更难定位。


















