goctl生成的API服务启动panic因找不到etc/api.yaml,应内联配置或确保YAML路径正确;model层sql: no rows错误需手动处理sql.ErrNoRows;zrpc熔断需显式启用;Gateway到RPC链路追踪需透传context并两端启用trace。

goctl 生成的 api 服务启动就 panic:找不到 config.yaml
默认生成的 goctl api 项目会读取 etc/api.yaml,但如果你删了配置文件或没放对位置,rest.MustNewServer 就会 panic 报错:open etc/api.yaml: no such file or directory。
实际开发中很多人直接删掉 etc/ 目录图省事,结果运行就崩。正确做法不是删配置,而是把配置内联进代码里:
- 在
main.go中声明var c config.Config后,直接赋值:c.Host = "0.0.0.0"、c.Port = 8000、c.RestConf = rest.RestConf{...} - 确保
rest.MustNewServer(c.RestConf)传的是已初始化的结构体,而不是空指针或零值 - 如果仍想用外部 YAML,路径必须是相对二进制所在目录的
etc/api.yaml,不是项目根目录下
model 层报错:sql: no rows in result set
这是最常被误认为是框架问题的“假错误”——本质是业务逻辑没处理空查询结果。比如用 FindOne 查一条不存在的记录,sqlx 默认返回 sql.ErrNoRows,而 go-zero 的 model 模板里没做判空就直接解包,panic 就来了。
别急着改框架,先看生成的 FindOne 方法签名:func (m *defaultArticleModel) FindOne(ctx context.Context, id int64) (*Article, error)。它明确返回 error,你必须自己处理:
立即学习“go语言免费学习笔记(深入)”;
- 调用处不能直接
article, _ := m.FindOne(...),要写if err != nil { ... } - 常见写法是:
if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } - 注意:go-zero 不自动包装
sql.ErrNoRows,也不提供全局 NotFound 错误变量,得你自己定义
RPC 调用超时却没触发熔断
你以为开了 zrpc 就自带熔断?错。go-zero 的熔断器(circuitbreaker)默认是关闭的,只在配置里显式启用才生效。没配的话,哪怕 RPC 连续失败 100 次,也不会降级或拒绝后续请求。
检查你的 rpc.yaml 是否包含这段:
rpc:
timeout: 1000
circuitBreaker:
enabled: true
errorPercent: 60
minimumRequests: 20
sleepWindow: 60000
关键点:
-
enabled: true必须显式写,不写就是 false -
minimumRequests是窗口期内最少请求数,低于它不统计失败率 - 超时错误(
context.DeadlineExceeded)算作失败,但网络连不上(connection refused)不算,得靠健康检查兜底
API Gateway 转发到 RPC 时 Context 丢失 traceID
链路追踪断在 gateway → rpc 这一跳,大概率是因为没透传 context.Context。go-zero 的 zrpc 客户端默认不自动注入 traceID,需要你在调用前手动塞进去。
典型错误写法:resp, err := svc.client.GetTenant(ctx, &req) —— 看似传了 ctx,但这个 ctx 是空的,没带 span。
正确姿势:
- 在 API 的
xxxLogic方法里,用svc.ctx(来自NewxxxLogic构造)作为父 context - 调用 RPC 前,用
trace.Tracer().StartSpanFromContext(svc.ctx, "rpc.GetTenant")创建子 span - 或者更简单:确保 gateway 启动时加载了
trace中间件,且 RPC client 初始化时绑定了trace.UnaryClientInterceptor()
最容易被忽略的是:RPC server 端也得开 trace,否则 span 只有起点没有终点,Jaeger 里只显示半截调用链。


















