Skip to content

常见问题

本页优先回答会阻塞开发的实际问题。组件的完整 API 和配置项请查阅手册,架构选择请参阅设计哲学

快速定位

现象先看这里
新项目无法编译、提示不能导入 internal检查 CLI 和自定义模板版本
旧项目请求直接 panic更新旧版模板代码
配置没有生效配置文件加载规则
m.DB()m.Redis() 初始化失败检查实例配置
测试或多应用之间实例串用使用显式 Scope
错误响应状态码与预期不同标准响应规则
Trace、Metric 没有上报显式初始化 exporter
SQL 或 Redis 操作变慢数据访问排查

启动与配置

如何隔离测试或同进程中的多个应用?

包级 m.* 入口使用进程默认 Scope。需要隔离时,为每个边界创建独立配置和 Scope:

go
scopeA := m.NewScope(configA)
scopeB := m.NewScope(configB)

serverA := scopeA.Server()
serverB := scopeB.Server()

同一 Scope 内的同名实例会复用,不同 Scope 不共享 Server、DB、Redis 或 Logger。资源仍由各自的 m.App 通过 m.WithServerm.WithCloser 管理。

为什么 maltose new 生成的项目无法导入 internal 包?

当前 CLI 会同时更新 go.mod 和引用模板 module 的 Go import。如果仍然遇到 use of internal package not allowed

  1. 执行 go install github.com/graingo/maltose/cmd/maltose@latest 更新 CLI。
  2. 检查 go.mod 第一行是否与项目源码中的 import 前缀一致。
  3. 使用 --repo-url 指定自定义模板时,确保模板有合法的 module 声明,且内部 import 均以该 module 为前缀。

修正后执行:

bash
go mod tidy
go test ./...

为什么旧版 quickstart 请求会 panic?

旧版 quickstart 的 Controller 可能仍包含:

go
panic("implement me")

当前官方模板已经提供可直接请求的 Hello 示例,新生成的 Controller 骨架也会返回对应响应类型的零值,不再使用占位 panic。已有项目不会被 CLI 自动覆盖,请手动实现这些 Controller 后再提供服务。

为什么配置文件没有生效?

按以下顺序检查:

  1. 默认实例会在常用配置目录中查找 config.yamlconfig.ymlconfig.jsonconfig.toml
  2. 具名配置 m.Config("redis") 会优先查找对应的 redis.* 文件。
  3. 文件适配器不会展开 ${DB_HOST:default} 之类的环境变量表达式。
  4. 自定义 Adapter 必须在 m.Server()m.DB()m.Redis() 等组件首次读取配置前设置。

显式选择配置文件的方式:

go
adapter, err := mcfg.NewAdapterFile()
if err != nil {
	panic(err)
}
if err := adapter.SetFile(os.Getenv("APP_CONFIG")); err != nil {
	panic(err)
}
m.Config().SetAdapter(adapter)
bash
APP_CONFIG=config/config.prod.yaml go run .

默认文件适配器不监听磁盘变化。远程适配器是否能实时看到更新,取决于其监听和缓存策略。完整说明见配置管理

为什么 m.DB()m.Redis() 初始化失败?

m.DB()m.Redis() 是配置驱动的应用实例。为了保持启动代码简洁,它们在配置或初始化失败时会 panic。需要显式处理错误时使用:

go
db, err := m.TryDB()
redisClient, err := m.TryRedis()

默认实例可以使用扁平结构:

yaml
database:
  type: mysql
  host: 127.0.0.1
  port: "3306"
  user: root
  password: secret
  db_name: app

也可以使用具名结构:

yaml
database:
  default:
    type: mysql
    dsn: root:secret@tcp(127.0.0.1:3306)/app?parseTime=true

确认配置后,再检查网络、账号权限和数据库是否可用。数据库生成命令读取 .env,应用运行时读取 mcfg,两者是不同配置源。

HTTP 与响应

错误响应会返回 200 吗?

启用 mhttp.MiddlewareResponse() 后,框架会同时返回业务码和对应的 HTTP 状态码:

业务错误HTTP 状态码
参数校验失败400
未认证401
无权限403
未找到404
其他内部错误500

成功响应使用 200。详见标准响应错误处理

如何自定义响应格式?

不要挂载 MiddlewareResponse(),改为注册自己的响应中间件。自定义中间件应在 r.Next() 后检查响应是否已经写出,再处理错误或 Controller 返回值:

go
func CustomResponse() mhttp.MiddlewareFunc {
	return func(r *mhttp.Request) {
		r.Next()
		if r.Writer.Written() {
			return
		}
		if len(r.Errors) > 0 {
			err := r.Errors.Last().Err
			r.JSON(http.StatusInternalServerError, map[string]any{
				"success": false,
				"message": err.Error(),
			})
			return
		}
		r.JSON(http.StatusOK, map[string]any{
			"success": true,
			"data":    r.GetHandlerResponse(),
		})
	}
}

mhttp.Server 仍有一个始终启用的基础响应兜底;只要自定义中间件已经写出响应,基础兜底就会跳过。

是否支持 WebSocket?

支持。mhttp.Request 内嵌 Gin Context,可将 r.Writerr.Request 交给 gorilla/websocket 等库完成 Upgrade。框架不提供单独的 WebSocket 协议封装,连接生命周期、并发写入和 Origin 校验由应用负责。

数据库、Redis 与缓存

数据库连接池应该设置多大?

不存在适用于所有应用的固定公式。建议先确定数据库允许的总连接数,并为各服务实例预留连接预算,再结合 database/sql.DBStats 调整:

  • WaitCountWaitDuration 持续增长:连接可能不足,或 SQL 本身过慢。
  • OpenConnections 长期接近上限:检查并发、事务和慢查询。
  • 空闲连接频繁归零又快速增长:可以适当增加 max_idle_connection
yaml
database:
  max_idle_connection: 10
  max_open_connection: 100
  max_idle_time: 10s
  max_lifetime: 0s

这些是框架默认值,不是生产环境推荐值。生产值应通过压测和运行指标确定。

如何排查慢 SQL?

配置 slow_threshold 后,超过阈值的查询会记录 sql slow 日志,主要字段为 elapsed_msrowssql

yaml
database:
  slow_threshold: 500ms
  logger:
    level: info

排查顺序通常是:确认请求 Trace → 找到 sql slow → 使用 EXPLAIN/EXPLAIN ANALYZE → 检查索引、扫描行数、锁等待和返回数据量。

Redis 的 KEYSClear 可以在生产使用吗?

不建议对大数据集使用 KEYS *mcache Redis Adapter 的 KeysValuesData 已使用增量 SCAN。生产环境应通过 redis.WithKeyPrefix("app:cache:") 设置命名空间;此时批量读取和 Clear 只处理该前缀。不设置前缀时保留兼容行为,Clear 会清空当前 DB,因此仍建议使用独立 Redis DB。

需要执行自定义匹配或分批处理时,可以直接使用 Redis Client:

go
iter := m.Redis().Client().Scan(ctx, 0, "user:*", 100).Iterator()
for iter.Next(ctx) {
	key := iter.Val()
	// 分批处理 key
}
if err := iter.Err(); err != nil {
	return err
}

不要用固定的“1 MB”或“1 万元素”作为所有场景的绝对边界;应结合序列化成本、网络延迟、Redis 慢日志和内存占用确定阈值。

可观测性

为什么写了 observability 配置却没有数据?

配置文件不会自动创建 exporter。推荐使用统一入口初始化,并把 Provider 交给应用生命周期管理:

go
telemetry, err := observability.FromConfig(ctx, m.Config())
if err != nil {
	return err
}

app := m.NewApp(
	m.WithServer(m.Server()),
	m.WithCloser(telemetry),
)
return app.Run()

随后才能创建自定义指标:

go
counter := mmetric.NewMustCounter(
	"orders.created",
	mmetric.NewMetricOption().WithHelp("Total created orders"),
)
counter.Inc(ctx, mmetric.WithAttributes(attribute.String("status", "success")))

需要独立控制时仍可分别调用 otlptrace.Initotlpmetric.Init。OTLP 数据通常由 OpenTelemetry Collector 再转发到 Jaeger、Prometheus 等后端。详见统一初始化链路追踪指标监控

框架自带健康检查吗?

有。mhttp.Server 默认注册 /health,返回 {"status":"ok"},适合作为基础存活检查。它不会检查数据库、Redis 等下游依赖;如果需要 readiness 语义,应单独实现依赖检查接口,避免把短暂的下游抖动直接等同于进程死亡。

工具链与架构边界

maltose gen modelgen dao 从哪里读取数据库配置?

这两个命令从项目根目录的 .env 读取数据库连接,而应用运行时从 mcfg 读取 config.*。建议由同一套部署变量生成两份配置,避免环境漂移。其他命令和参数见 CLI 命令参考

Maltose 和 GoFrame、Gin 是什么关系?

Maltose 参考了 GoFrame 的工程化思路,但不是 GoFrame 的封装或分支。HTTP 层基于 Gin,数据库层基于 GORM;Maltose 在其上提供配置驱动的实例管理、分层约定、代码生成和可观测性集成。

Maltose 适合微服务吗?

Maltose 适合单体应用和以 HTTP 为主的中小型服务。框架提供 HTTP、配置、数据库、Redis、日志、Trace、Metric、健康检查和优雅停机;不内置服务注册、gRPC Server、消息队列或 API 网关。这些能力可以在应用中按需集成,跨服务传播 TraceContext 时优先使用 mclient 或 OpenTelemetry Propagator。

Released under the MIT License.