From 3e8e8796617d5c6287fd6e49372ab94fe48dfc4e Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 15 Jun 2026 16:47:35 +0800 Subject: [PATCH] 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//. - Updated package comments in internal/diskcache to point to pkg/cache/disk. --- .agent/skills/new-api/SKILL.md | 25 +++++++--------- .../new-api/references/logics_example.go | 4 +-- .../new-api/references/service_example.go | 2 +- .agent/skills/new-async-task/SKILL.md | 2 +- AGENTS.md | 30 ++++++++++++------- internal/diskcache/cache.go | 2 +- 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/.agent/skills/new-api/SKILL.md b/.agent/skills/new-api/SKILL.md index f081b0eb..2bd1ae4c 100644 --- a/.agent/skills/new-api/SKILL.md +++ b/.agent/skills/new-api/SKILL.md @@ -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 注释。 diff --git a/.agent/skills/new-api/references/logics_example.go b/.agent/skills/new-api/references/logics_example.go index 61310347..c24c2048 100644 --- a/.agent/skills/new-api/references/logics_example.go +++ b/.agent/skills/new-api/references/logics_example.go @@ -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") diff --git a/.agent/skills/new-api/references/service_example.go b/.agent/skills/new-api/references/service_example.go index 5435a393..97ad6699 100644 --- a/.agent/skills/new-api/references/service_example.go +++ b/.agent/skills/new-api/references/service_example.go @@ -12,7 +12,7 @@ import ( "go.uber.org/zap" ) -// CustomService 示例业务 Service 结构体 +// CustomService 示例业务 Service 结构体(通常放在 internal/apps/custom/service.go 中) type CustomService struct { // 这里可以注入数据库连接、配置对象或者其他基础服务的客户端 // 例如:db *gorm.DB diff --git a/.agent/skills/new-async-task/SKILL.md b/.agent/skills/new-async-task/SKILL.md index 567989fb..92774fe4 100644 --- a/.agent/skills/new-async-task/SKILL.md +++ b/.agent/skills/new-async-task/SKILL.md @@ -41,7 +41,7 @@ description: "Wavelet 项目专用:新增或修改 Asynq 异步任务、后台 - 成功返回 `&task.TaskResult{Message: ..., Detail: ...}`。 - 失败返回 error,由任务框架处理状态和重试。 - 不要吞掉关键错误。 -- 复杂 SQL 放到 `internal/model/` 或 `internal/service/`。 +- 复杂 SQL 放到 `internal/model/` 或模块内的业务服务层(如 `internal/apps//service.go` 或 `logics.go`)。 - 新增 Go 文件后检查许可证头,必要时运行 `make license`。 ### 注册 diff --git a/AGENTS.md b/AGENTS.md index c59b8638..d468a613 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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.
.`。 - `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//service.go` 或 `logics.go`)。 - 在 `internal/db/migrator/goose/` 下使用 goose SQL 迁移;不要添加基于 GORM AutoMigrate 的 Schema 升级。 - 不要创建物理数据库外键。改为关系字段添加显式索引。 - 数据库默认值必须与 Go 模型零值(`nil`、`0`、`false`、`""`)匹配,以避免意外的插入。 diff --git a/internal/diskcache/cache.go b/internal/diskcache/cache.go index d2525b9f..b8d96835 100644 --- a/internal/diskcache/cache.go +++ b/internal/diskcache/cache.go @@ -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 (