refactor(repository): 收敛 model/repository 分层为唯一持久化入口

将 OpenFlare 与平台业务的数据访问从 model 与 apps 直连迁入 repository,
model 仅保留实体与无 IO 规则;补充 code-check 架构守卫与开发规范。
This commit is contained in:
ryan
2026-07-24 17:00:17 +08:00
parent 23a5488203
commit 943818f7d4
184 changed files with 5592 additions and 4364 deletions
+1 -1
View File
@@ -115,7 +115,7 @@ internal/
## 核心开发步骤 (Step-by-Step Flow)
### 步骤 1:数据库定义与迁移
如果自定义功能涉及新表或字段,请参考 [database-migration](../database-migration/SKILL.md) 技能,在 `internal/infra/persistence/migrator/goose/` 目录下编写迁移文件并在 `internal/model/` 中定义 GORM 数据模型。
如果自定义功能涉及新表或字段,请参考 [database-migration](../database-migration/SKILL.md) 技能,在 `internal/infra/persistence/migrator/goose/` 目录下编写迁移文件,在 `internal/model/` 中定义 GORM 实体(无 CRUD / 无 DB 访问),并在 `internal/repository/` 中实现数据访问(**repository 为唯一持久化入口**)。
### 步骤 2:在模块内实现业务逻辑 (`logics.go` / `service.go`)
业务逻辑逻辑应当实现于 `internal/apps/custom/` 目录下:
+3 -2
View File
@@ -19,7 +19,8 @@ description: "Wavelet 项目专用:新增或修改 Asynq 异步任务、后台
- `internal/infra/task/worker/worker.go`:Worker 路由和队列
- `internal/infra/task/scheduler/scheduler.go`:定时调度
- `internal/apps/admin/task/routers.go`:Admin 任务 API
- `internal/model/task_execution.go`:执行记录和日志持久化
- `internal/model/task_execution.go`:执行记录实体与 DTO
- `internal/repository/task_execution.go`:执行记录和日志持久化
需要模板时阅读 [references/CODE-EXAMPLES.md](references/CODE-EXAMPLES.md)。
@@ -42,7 +43,7 @@ description: "Wavelet 项目专用:新增或修改 Asynq 异步任务、后台
- 成功返回 `&task.TaskResult{Message: ..., Detail: ...}`。
- 失败返回 error,由任务框架处理状态和重试。
- 不要吞掉关键错误。
- 复杂 SQL 放到 `internal/model/` 或模块内的业务服务层(如 `internal/apps/<module>/service.go` 或 `logics.go`)。
- 持久化只通过 `internal/repository/`(唯一入口);业务编排放模块内 `logics.go` / `service.go`。`internal/model` 仅实体/DTO,禁止 CRUD 与 DB 访问。
### 注册
+11 -9
View File
@@ -14,7 +14,7 @@ description: "Wavelet 项目专用:当新增或修改启动时设置、数据
Wavelet 当前有两套设置入口:
- 启动时设置:来自 `config.yaml` 或环境变量,适合进程启动前必须确定、通常不热更新的基础配置。
- 系统设置:保存于数据库 `system_configs`,经 `model.SystemConfig` 和 Redis hash 缓存读取,支持运行时热更新。管理入口是 `/admin/system` 和 `/admin/settings`。
- 系统设置:保存于数据库 `system_configs`,经 `model.SystemConfig` 实体(key 常量在 model)与 `repository` 读取层(含 Redis hash 缓存)访问,支持运行时热更新。管理入口是 `/admin/system` 和 `/admin/settings`。
系统设置分三种使用语义:
@@ -30,7 +30,8 @@ Wavelet 当前有两套设置入口:
修改前快速查看这些文件,确认当前实现没有漂移:
- `internal/model/system_configs.go`: 配置 key 常量、`SystemConfig` 模型、`GetByKey`、`GetBoolByKey`、`GetIntByKey`、`GetDecimalByKey` 等读取方法。
- `internal/model/system_configs.go`: 配置 key 常量(`ConfigKey*`)、`SystemConfig` 实体与字段语义;**不含**持久化读取 API。
- `internal/repository/system_config.go`: 配置读取与缓存(`GetSystemConfigByKey`、`GetBoolByKey`、`GetIntByKey`、`GetDecimalByKey`、`ListVisibleSystemConfigs` 等)。
- `internal/infra/persistence/migrator/goose/postgres/*.sql` 和 `internal/infra/persistence/migrator/goose/sqlite/*.sql`: `system_configs` 表结构、初始化 seed、后续升级迁移。
- `internal/infra/persistence/migrator/migrator.go`: goose 迁移入口和 PostgreSQL/SQLite 方言选择。
- `internal/testhelper/test_helper.go`: Go 测试用默认系统配置 seed。
@@ -61,12 +62,13 @@ Wavelet 当前有两套设置入口:
- 如果相关 Go 包测试依赖默认配置,同步 `internal/testhelper/test_helper.go` 的 `seedDefaultConfigs` 和公共 key 列表。
3. 读取配置。
- 后端业务代码优先使用 `model.GetBoolByKey`、`model.GetIntByKey`、`model.GetDecimalByKey` 或 `SystemConfig.GetByKey`。
- 后端业务代码通过 `internal/repository` 读取:`repository.GetBoolByKey`、`repository.GetIntByKey`、`repository.GetDecimalByKey` 或 `repository.GetSystemConfigByKey`;key 常量仍用 `model.ConfigKey*`。
- 禁止新增或调用 `model.Get*ByKey` / `model.ListVisibleSystemConfigs` 等数据访问 API(model 无 CRUD)。
- 运行时可热更新的规则不要放进 `config.Config`;启动时设置才走 `internal/infra/config/model.go` 和 `config.example.yaml`。
- 不要在 handler 或业务代码里直接读 `os.Getenv()`。
4. 如果前端需要未登录或全局消费,暴露为公共可见配置。
- 把该配置的 `visibility` 设为 `1`,`GetPublicConfig` 会通过 `model.ListVisibleSystemConfigs` 返回所有可见 key/value。
- 把该配置的 `visibility` 设为 `1`,`GetPublicConfig` 会通过 `repository.ListVisibleSystemConfigs` 返回所有可见 key/value。
- `/api/v1/config/public` 的 `data` 是动态对象:后端返回 `map[string]string`,前端类型是 `Record<string, string | undefined>`。
- 前端读取时按配置 key 访问,必要时在消费侧把字符串转换为 boolean/number/JSON。
- 检查使用方的 query key,更新后需要 invalidate `["public-config"]`。
@@ -99,9 +101,9 @@ Wavelet 当前有两套设置入口:
### 布尔公共设置
- model key:`ConfigKeyFeatureEnabled = "feature_enabled"`
- model key:`ConfigKeyFeatureEnabled = "feature_enabled"`(定义在 `internal/model`)
- goose SQL 默认值:`value='false'`,`type` 按语义选 `"system"` 或 `"business"`,`visibility=1`。
- 后端读取:`model.GetBoolByKey(ctx, model.ConfigKeyFeatureEnabled)`。
- 后端读取:`repository.GetBoolByKey(ctx, model.ConfigKeyFeatureEnabled)`。
- 公共响应:`/api/v1/config/public` 的 `data.feature_enabled` 为字符串 `"true"` 或 `"false"`。
- 前端图形控件:`Switch`,保存时写 `"true"` / `"false"`。
@@ -109,13 +111,13 @@ Wavelet 当前有两套设置入口:
- model key:`ConfigKeyMaxSomething = "max_something"`。
- goose SQL 默认值:例如 `"5"`,`type` 通常为 `"business"`,只有前端公共消费时才设 `visibility=1`。
- 后端读取:`model.GetIntByKey` 或 `model.GetDecimalByKey`。
- 后端读取:`repository.GetIntByKey` 或 `repository.GetDecimalByKey`。
- 前端图形控件:`Input type="number"` 或合适的 shadcn 数值控件;保存前做最小必要校验,错误用 toast。
### JSON 设置
- 默认值使用合法 JSON,例如 `"{}"` 或 `"[]"`。
- 在 model 或 service 层提供解析函数,像 `GetMenuDisplayConfig` 一样把 JSON 解析错误包装成清晰错误。
- 在 repository 或业务 logics 中提供解析函数,像 `repository.GetMenuDisplayConfig` 一样把 JSON 解析错误包装成清晰错误;不要在 model 中做 IO。
- 前端不要直接拼接 JSON 字符串;用 `JSON.stringify` 写入,用类型化对象在组件中操作。
## 验证
@@ -125,7 +127,7 @@ Wavelet 当前有两套设置入口:
- 新增或修改系统配置默认值、visibility 或公共配置读取:至少运行相关 Go 包测试,例如:
```bash
go test ./internal/model ./internal/apps/config ./internal/apps/admin/system_config
go test ./internal/repository ./internal/apps/config ./internal/apps/admin/system_config
```
- 新增 goose 迁移后,至少用当前数据库方言跑一次迁移;如果 SQL 同时改了 PostgreSQL 和 SQLite,尽量覆盖两种方言。涉及 schema/seed 的任务还应遵循 database-migration skill。