mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
docs(architecture): update architectural guidelines to reflect feature-based service design
- Updated AGENTS.md, new-api SKILL.md, and new-async-task SKILL.md to remove references to the deleted global internal/service/ package. - Documented feature-based local logics/services design within internal/apps/<module>/. - Updated package comments in internal/diskcache to point to pkg/cache/disk.
This commit is contained in:
@@ -18,8 +18,7 @@ description: "Wavelet 项目专用:当新增或修改自定义业务 API、新
|
||||
| 目录/包名 | 职责定位 | 框架依赖限制 | 常见包含内容 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`internal/router/`** | 路由分发层 | 依赖 Gin 框架 | `router.go` 核心路由、`custom.go` (自定义路由注册入口) |
|
||||
| **`internal/apps/custom/`** | 应用入口层与本地逻辑层 | 依赖 Gin 框架 (路由/Handler 部分) | 接收 HTTP 请求、解析请求体(JSON/Query)、校验基础参数、提取 Session。对于**模块内闭环的简单业务逻辑**,直接在其下的 `logics.go` 或 `*_logic.go` 中实现。 |
|
||||
| **`internal/service/`** | 核心跨模块业务服务层 | **禁止**依赖 Gin/HTTP 框架 | 仅存放**复杂、跨模块/跨领域交互,或被多端复用**(如同时被 Handler、后台 Asynq 任务、Cobra CLI 命令行调用)的业务核心逻辑。只接受 standard `context.Context`。 |
|
||||
| **`internal/apps/custom/`** | 功能模块层 (Feature Module) | 依赖 Gin 框架 (仅限路由/Handler 部分) | 高度内聚的业务功能块。接收 HTTP 请求、解析请求体、校验基础参数、提取 Session。业务服务逻辑直接实现在当前目录下的 `service.go` 或 `logics.go` 中,不依赖 Gin/HTTP 框架。 |
|
||||
| **`internal/model/`** | 数据模型层 | 依赖 GORM / SQL 基础 | GORM 实体定义、表结构、主键生成、单表极简 SQL 查询方法。 |
|
||||
| **`internal/db/`** | 数据存储层 | 依赖 SQL 驱动 / GORM 连接 | PostgreSQL, SQLite 等数据库连接管理与 Goose 数据库迁移文件。 |
|
||||
|
||||
@@ -33,13 +32,11 @@ description: "Wavelet 项目专用:当新增或修改自定义业务 API、新
|
||||
internal/
|
||||
├── router/
|
||||
│ └── custom.go # [修改/创建] 仅用于注册定制路由,将路由委托给 apps/custom
|
||||
├── apps/
|
||||
│ └── custom/
|
||||
│ ├── routers.go # [新建] HTTP Handlers (Gin),负责参数绑定、校验与响应
|
||||
│ ├── logics.go # [新建] 承载模块内闭环的简单业务逻辑(保持该逻辑仅局限在当前模块)
|
||||
│ └── errs.go # [新建] 仅存放业务特有的错误常量定义(可选)
|
||||
└── service/
|
||||
└── custom.go # [新建/可选] 仅当出现跨模块交互、复杂多表事务或需要被 Task/CLI 复用时才创建
|
||||
└── apps/
|
||||
└── custom/
|
||||
├── routers.go # [新建] HTTP Handlers (Gin),负责参数绑定、校验与响应
|
||||
├── logics.go # [新建] 承载功能模块内闭环的业务逻辑(可以使用 logics.go 或 service.go)
|
||||
└── errs.go # [新建] 仅存放业务特有的错误常量定义(可选)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -49,11 +46,11 @@ internal/
|
||||
### 步骤 1:如果有数据库变更,编写数据库迁移
|
||||
如果需要新表或字段,请参考 [database-migration](../database-migration/SKILL.md) 技能,在 `internal/db/migrator/goose/` 目录下编写迁移文件。在 `internal/model/` 中定义 GORM 数据模型。
|
||||
|
||||
### 步骤 2:判断业务逻辑的归属与放置
|
||||
在编写具体逻辑前,必须明确逻辑是属于**本地简单业务**还是**跨模块复杂业务**:
|
||||
- **方案 A(推荐,轻量化优先)**:直接在 `internal/apps/custom/logics.go` 下定义函数。该函数虽然在 `apps` 目录下,但同样应该**保持纯 Go 参数**(不直接操作 `*gin.Context`),仅供 Handler 层直接调用。
|
||||
- **方案 B(当满足“跨模块”、“复杂事务”、“多入口调用”时)**:在 `internal/service/` 下创建独立的业务 Service 方法,以实现逻辑复用和领域解耦。
|
||||
参考示例:[service_example.go](file:///Users/ryan/DEV/Go/Wavelet/.agent/skills/new-api/references/service_example.go)
|
||||
### 步骤 2:在模块内实现业务服务与逻辑 (Service / Logics)
|
||||
在编写具体逻辑前,建议选择以下结构实现业务逻辑(均置于 `internal/apps/custom/` 下):
|
||||
- **方案 A(轻量化函数形式,推荐)**:在 `logics.go` 中定义独立的纯 Go 函数,这些函数不强依赖 `*gin.Context`。
|
||||
- **方案 B(面向对象/结构体形式)**:在 `service.go` 中定义 Service 结构体和构造函数,如 `type CustomService struct`,并将逻辑作为其方法。这适用于需要注入依赖(如 DB 连接、外部 client 等)或有状态管理的对象。
|
||||
参考示例:[logics_example.go](file:///Users/ryan/DEV/Go/Wavelet/.agent/skills/new-api/references/logics_example.go) 和 [service_example.go](file:///Users/ryan/DEV/Go/Wavelet/.agent/skills/new-api/references/service_example.go)
|
||||
|
||||
### 步骤 3:在 `internal/apps/custom/` 下编写 HTTP Handler
|
||||
创建应用路由文件 `routers.go`,定义接口的请求和响应 DTO,编写 Handler 绑定参数并调用 Service,编写 Swagger 注释。
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
// ProcessLocalBusiness 示例的模块内部闭环业务逻辑
|
||||
// 1. 虽然存放在 apps/custom/logics.go 下,但依然遵循纯 Go 规范,不强依赖 gin.Context,以便逻辑清晰和便于单元测试。
|
||||
// 2. 仅用于当前应用模块私有的简单业务,避免滥用全局的 internal/service 从而导致 Service 臃肿。
|
||||
// 1. 存放在 apps/custom/logics.go 下,遵循纯 Go 规范,不强依赖 gin.Context,以便逻辑清晰和便于单元测试。
|
||||
// 2. 用于当前应用模块内的简单业务或通用过程。
|
||||
func ProcessLocalBusiness(ctx context.Context, userID int64, param string) (string, error) {
|
||||
if param == "" {
|
||||
return "", errors.New("param cannot be empty")
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CustomService 示例业务 Service 结构体
|
||||
// CustomService 示例业务 Service 结构体(通常放在 internal/apps/custom/service.go 中)
|
||||
type CustomService struct {
|
||||
// 这里可以注入数据库连接、配置对象或者其他基础服务的客户端
|
||||
// 例如:db *gorm.DB
|
||||
|
||||
@@ -41,7 +41,7 @@ description: "Wavelet 项目专用:新增或修改 Asynq 异步任务、后台
|
||||
- 成功返回 `&task.TaskResult{Message: ..., Detail: ...}`。
|
||||
- 失败返回 error,由任务框架处理状态和重试。
|
||||
- 不要吞掉关键错误。
|
||||
- 复杂 SQL 放到 `internal/model/` 或 `internal/service/`。
|
||||
- 复杂 SQL 放到 `internal/model/` 或模块内的业务服务层(如 `internal/apps/<module>/service.go` 或 `logics.go`)。
|
||||
- 新增 Go 文件后检查许可证头,必要时运行 `make license`。
|
||||
|
||||
### 注册
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
## 严格遵循事项 (Guardrails)
|
||||
|
||||
- 切勿删除 `frontend/node_modules`;如果需要刷新依赖,请使用 `pnpm install` 重新安装。
|
||||
- 保持 `internal/util/` 不引入任何框架。不要从 `internal/util/` 及其子包中导入 Gin、GORM、sessions 或其他 HTTP/框架包。
|
||||
- 保持 `internal/util/` 绝对纯净且不引入任何框架。禁止从 `internal/util/` 及其子包中导入 Gin、GORM、sessions 等 HTTP/Web/数据库相关框架包(例如,Web 会话选项已收敛至 `internal/apps/oauth/session.go`)。
|
||||
- 编写测试用例时,禁止使用硬编码的相对路径(如 `"uploads/test_cache"`)在源码目录下创建临时测试目录,必须统一使用 Go 内置的 `t.TempDir()` 以避免污染源码目录。
|
||||
- 所有 HTTP 路由仅在 `internal/router/router.go` 中注册。
|
||||
- 当 API Handler 发生变化时,更新 Swagger 文档(运行 `make swagger`)。
|
||||
- 在提交更改前运行 `make code-check`。
|
||||
@@ -69,22 +70,29 @@
|
||||
- `internal/cmd/`:用于 API、worker、scheduler、root init 的 Cobra 命令。
|
||||
- `internal/config/`:Viper 加载和配置结构体。运行时代码应使用 `config.Config.<Section>.<Field>`。
|
||||
- `internal/router/`:唯一的 HTTP 路由注册点。
|
||||
- `internal/apps/`:按领域组织的 HTTP Handler 和模块逻辑;管理端模块位于 `internal/apps/admin/`。
|
||||
- `internal/apps/`:按功能(Feature-based)组织的 HTTP Handler、中间件、内部服务与模块逻辑。移除全局 service 层,模块内部业务逻辑(如验证码业务逻辑管理器 `internal/apps/cap/manager.go`)均收敛于各自模块中;管理端模块位于 `internal/apps/admin/`。
|
||||
- `internal/apps/upload/`:上传记录、文件访问控制、本地/S3 文件响应、下载及图片 WebP 压缩。业务应复用这些入口,不直接操作底层文件。
|
||||
- `internal/model/`:GORM 实体和模型级业务方法。
|
||||
- `internal/db/`:PostgreSQL、Redis、ClickHouse、GORM 日志、ID 生成和 goose SQL 迁移的布线。
|
||||
- `internal/diskcache/`:平台级磁盘字节缓存,通过 `diskcache.GetGlobalCache()` 提供 TTL、最大空间限制、LRU 淘汰、清空、状态统计和配置热更新。写入时使用 `DefaultExpiration`(全局默认 TTL)、正数 `time.Duration`(业务 TTL)或 `NoExpiration`(无 TTL,仍受空间限制和 LRU 淘汰)。
|
||||
- `internal/storage/`:S3 兼容对象存储适配,提供对象上传、读取、删除、CDN/代理读取及远端对象本地缓存。
|
||||
- `internal/task/`:Asynq 任务框架;参见 `new-async-task` 了解变更。
|
||||
- `internal/service/`:当 Handler/Model 层次过于狭窄时使用的复杂业务服务。
|
||||
- `internal/common/`:共享的响应、绑定(bind)、常量以及通用错误。
|
||||
- `internal/util/`:纯实用工具,不导入任何框架。
|
||||
- `internal/logger/`:Zap 和 OTel 日志助手。
|
||||
- `internal/common/`:共享的通用模型及响应(如 `internal/common/response`)、绑定(bind)、常量以及通用错误。
|
||||
- `internal/util/`:纯底层工具包,无任何 HTTP/数据库框架依赖。
|
||||
- `internal/listener/`:事件监听器和消息/Webhook 消费者。
|
||||
- `internal/otel_trace/`:链路追踪(tracing)助手。
|
||||
- `internal/testhelper/`:后端测试共享辅助能力。
|
||||
- `internal/buildinfo/`:暴露在发布/构建工作流中注入的元数据(如版本号、编译时间等)。
|
||||
- `internal/httppool/`:管理全局共享且经过优化的 HTTP 传输客户端及连接池,并集成 OTel 链路追踪。
|
||||
|
||||
公共底层包 (`pkg/`):
|
||||
- `pkg/cache/disk/`:纯底层的通用本地磁盘缓存引擎。
|
||||
- `pkg/cap/`:底层的通用验证码验证和生成库。
|
||||
- `pkg/httppool/`:管理全局共享且经过优化的 HTTP 传输客户端及连接池,集成 OTel 链路追踪。
|
||||
- `pkg/logger/`:Zap 和 OTel 日志助手。
|
||||
- `pkg/push/`:推送渠道客户端集成(Lark/Telegram/Email)。
|
||||
- `pkg/mail/`:邮件发送客户端。
|
||||
- `pkg/trace/`:OpenTelemetry 链路追踪配置。
|
||||
- `pkg/util/`:纯底层无副作用的系统工具(Crypto/Password/UUID)。
|
||||
|
||||
前端目录:
|
||||
|
||||
@@ -137,15 +145,15 @@ Handler 规范:
|
||||
|
||||
- Handler 命名为 动词 + 名词,例如 `ListUsers`。
|
||||
- 使用 `ShouldBindQuery` 或 `ShouldBindJSON` 进行绑定。
|
||||
- 成功时通过 `util.OK(data)`、`util.OKNil()` 或 `response.RespondSuccess` 返回。
|
||||
- 失败时通过 `util.Err(msg)` 或 `response.RespondFailure` 返回。
|
||||
- 成功时统一通过导入 `"github.com/Rain-kl/Wavelet/internal/common/response"` 使用 `response.OK(data)` 或 `response.OKNil()` 返回。
|
||||
- 失败时统一通过 `response.Err(msg)` 返回。
|
||||
- API 响应的外层结构必须为 `{ "error_msg": "", "data": ... }`。
|
||||
- 分页响应在 `data` 下使用 `{ "total": 0, "results": [] }`。
|
||||
- 每个 HTTP API 都需要有完整的 Swagger 注释;在 API 变更后运行 `make swagger`。
|
||||
|
||||
错误处理与日志:
|
||||
|
||||
- 任何关键错误在被吞掉、转换为通用响应,或由后台 worker 忽略之前,都必须通过 `internal/logger` 打印日志。
|
||||
- 任何关键错误在被吞掉、转换为通用响应,或由后台 worker 忽略之前,都必须通过 `pkg/logger` 打印日志。
|
||||
- 禁止用 `_ = ...` 静默丢弃重要错误。如果某个错误因为 best-effort 操作或确认无害而需要忽略,必须添加简短注释说明原因。
|
||||
- Handler 可以返回对用户安全的错误信息,但如果底层运行错误对生产问题排查有价值,仍然必须记录日志。
|
||||
- 避免重复刷日志:在真正处理或抑制错误的边界记录一次,然后返回或响应。
|
||||
@@ -176,7 +184,7 @@ Handler 规范:
|
||||
|
||||
- 简单查询可以直接从 model 层使用 GORM。
|
||||
- 管理员代码应首选 `db.DB(ctx)` 以获得链路追踪感知的 DB 访问。
|
||||
- 不要在 Handler 中放置复杂的 SQL;将其移至 `internal/model/` 或 `internal/service/`。
|
||||
- 不要在 Handler 中放置复杂的 SQL;将其移至 `internal/model/` 或模块内的业务服务层(如 `internal/apps/<module>/service.go` 或 `logics.go`)。
|
||||
- 在 `internal/db/migrator/goose/` 下使用 goose SQL 迁移;不要添加基于 GORM AutoMigrate 的 Schema 升级。
|
||||
- 不要创建物理数据库外键。改为关系字段添加显式索引。
|
||||
- 数据库默认值必须与 Go 模型零值(`nil`、`0`、`false`、`""`)匹配,以避免意外的插入。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package diskcache wraps the generic pkg/diskcache to provide database configuration integration.
|
||||
// Package diskcache wraps the generic pkg/cache/disk to provide database configuration integration.
|
||||
package diskcache
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user