From fff3a7589c5e717ce72b9d55f704d5b2724810f9 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 2 Sep 2026 22:39:13 +0800 Subject: [PATCH] docs(plugin): unify plugin development template based on custom_example --- .agents/skills/new-api/SKILL.md | 54 +-- AGENTS.md | 6 +- backend/downstream/README.md | 147 +++++++ .../plugins/custom_example/consts/consts.go | 1 + .../custom_example/controller/hello/hello.go | 5 +- docs/WAVELET_DEVELOPER_GUIDE.md | 402 +++++++----------- ...08-27-cordis-downstream-developer-guide.md | 17 +- ...cordis-plugin-layered-architecture-spec.md | 74 ++-- 8 files changed, 364 insertions(+), 342 deletions(-) create mode 100644 backend/downstream/README.md diff --git a/.agents/skills/new-api/SKILL.md b/.agents/skills/new-api/SKILL.md index 5093307b..99cb3d9b 100644 --- a/.agents/skills/new-api/SKILL.md +++ b/.agents/skills/new-api/SKILL.md @@ -14,50 +14,38 @@ description: "Wavelet 项目专用:当新增或修改业务 API、Handler、 在 Cordis 架构中,**业务 API 不再集中在旧的 `internal/router/` 或 `internal/apps/` 目录**。 所有业务能力均封装为**高内聚、扁平自包含的插件 (Plugin)**。每个插件自主管理自身的路由声明、中间件挂载、服务逻辑、数据模型与迁移脚本。 -### 插件目录推荐结构 (`backend/plugins/domain//` 或下游 `custom_plugins//`) +### 插件目录标准结构 (`backend/downstream/plugins//` 或 `backend/plugins/domain//`) -#### 模式 1:极简单文件自包含(适用于极简微型插件 / 单一实体 / <500行) -```text -backend/plugins/domain/demo/ -├── plugin.go # 插件入口:实现 core.Plugin,通过 ctx.Router() 挂载路由 -├── handlers.go # HTTP 控制器单文件:参数校验、上下文提取、调用 Service、信封响应 -├── service.go # 业务服务层单文件:纯 Go 逻辑,仅依赖 context.Context -├── repository.go # 数据库访问层单文件:GORM 查询、SQL 防注入与转义 -├── models.go # GORM 数据实体定义(自带表前缀)与 DTO -├── errs.go # 模块内错误常量定义(camelCase 字符串) -└── migrations/ # 专属嵌入式 Goose SQL 迁移脚本 - └── 20260827000001_create_demo_table.sql -``` -> ⚠️ **严禁**:当需要拆分多个 Handler/Service 文件时,**严禁在根目录平铺 `handlers_*.go`、`service_*.go`、`repository_*.go` 等前缀文件**,必须立即采用模式 2(独立子包分层)。 +所有标准插件与下游定制插件,**统一以 [`backend/downstream/plugins/custom_example`](file:///Users/ryan/Code/Go/Wavelet/backend/downstream/plugins/custom_example) 为基准模板**,严格采用物理子包隔离的分层架构: -#### 模式 2:标准独立子包分层架构(适用于标准/中大型业务插件 / 官方推荐标准) ```text -backend/plugins/domain/order/ +backend/downstream/plugins/custom_example/ (或 backend/plugins/domain/order/) ├── plugin.go # 插件根入口:实现 core.Plugin,装配各子包并向 Cordis 注册 │ -├── handler/ # package handler:HTTP 控制器与路由声明(或 controller/) -│ ├── router.go # 路由组声明与中间件挂载 -│ └── order.go # 订单 Handler(直接以业务命名,禁止 handlers_order.go) +├── consts/ # package consts:常量、配置键名与错误码定义 +│ └── consts.go │ -├── service/ # package service:业务逻辑层(用例编排、事件发布) -│ ├── service.go # Service 接口与组装 -│ └── order.go # 订单业务用例实现(直接以业务命名,禁止 service_order.go) +├── controller/ # package controller:HTTP 控制器与路由声明 (参数绑定、会话获取、信封响应) +│ └── hello/ # 业务分组/实体子包 +│ └── hello.go # 接口处理 Handler(直接以业务命名,禁止 controller_hello.go) │ -├── repository/ # package repository:数据持久化访问层 (DAL) -│ ├── repository.go # 仓储抽象与通用工厂 -│ └── order.go # 订单仓储实现(直接以业务命名,禁止 repository_order.go) +├── service/ # package service:业务逻辑层(用例编排、事务控制、事件发布) +│ └── order.go # 订单业务用例实现(纯 Go 逻辑,禁止依赖 *gin.Context) │ -├── model/ # package model (或 models/):纯数据实体与 DTO(无外部依赖) -│ ├── entity.go # 数据库映射实体 (TableName() 带插件专属前缀) -│ ├── dto.go # 请求与响应 DTO -│ └── events.go # 领域事件定义 +├── dao/ # package dao:数据访问持久化层 DAL (GORM CRUD、SQL 转义防注入) +│ └── order.go # 订单数据访问实现(直接以业务命名,禁止 dao_order.go) │ -├── errs/ # package errs:错误常量与错误码 (或根目录 errs.go) -│ └── errs.go +├── model/ # package model:纯数据实体与 DTO(无外部依赖) +│ ├── entity/ # 数据库映射实体 (TableName() 带插件专属前缀) +│ │ └── order.go +│ └── do/ # 请求 Request DTO 与响应 Response DTO、领域对象 +│ └── order.go │ -└── migrations/ # 专属嵌入式 Goose SQL 迁移脚本 - └── 20260827000001_create_orders_table.sql +└── migrations/ # 专属嵌入式 Goose SQL 双方言迁移脚本 (//go:embed) + ├── postgres/ # PostgreSQL 迁移脚本 + └── sqlite/ # SQLite 迁移脚本 ``` +> ⚠️ **严禁**:严禁在根目录平铺 `handlers_*.go`、`service_*.go`、`dao_*.go` 等前缀文件,子包内文件直接按业务实体命名。严格约束 `controller -> service -> dao -> model` 单向依赖。 --- diff --git a/AGENTS.md b/AGENTS.md index 2b5d4edf..7142461b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,9 +102,9 @@ Strong success criteria let you loop independently. Weak criteria ("make it work - **自包含插件 (`backend/plugins/`)**: - 所有业务功能与驱动实现均以插件形式存在(`backend/plugins/drivers/`、`backend/plugins/infra/`、`backend/plugins/domain/` 或下游 `backend/downstream/`)。 - 每个插件实现 `core.Plugin`(`Name() string` 与 `Apply(ctx *core.Context) error`)。 - - **分层模式选型**: - - **模式 1(极简单文件分层,微型插件)**:单 package 极简结构(仅单文件 `plugin.go`, `handlers.go`, `service.go`, `repository.go`, `models.go`, `errs.go`, `migrations/`)。 - - **模式 2(标准独立子包分层,推荐标准)**:多 package 物理隔离(`plugin.go`, `handler/`, `service/`, `repository/`, `model/`, `errs/`, `migrations/`)。**严禁在根包平铺 `handlers_*`、`service_*`、`repository_*` 等前缀文件**,子包内文件直接按业务命名(如 `user.go`, `config.go`),严格约束 `handler -> service -> repository -> model` 单向依赖。 + - **统一插件分层架构与标准模板**: + - **开发模板唯一基准**:所有插件统一以 `backend/downstream/plugins/custom_example` 为基准模板构建。 + - **物理子包隔离规范**:统一采用物理子包结构(`plugin.go`, `consts/`, `controller/`, `service/`, `dao/`, `model/` [含 `entity/`, `do/`], `migrations/` [含 `postgres/`, `sqlite/`])。**严禁在根包平铺 `handlers_*`、`service_*`、`dao_*` 等前缀文件**,子包内文件直接按业务实体命名(如 `hello.go`, `user.go`),严格约束 `controller -> service -> dao -> model` 单向依赖。 - **插件通信与依赖隔离**: - **严禁跨包 import internal/私有实现**:插件之间严禁直接 import 对方具体实现包代码。 - **单向服务契约调用**:调用方仅面向 `backend/core/contracts` 编程,在 `Apply` 中通过 `core.Provide[contracts.XxxService](ctx, svc)` 注册服务,通过 `core.Inject[contracts.XxxService](ctx)` 或 `ctx.Using(func(svc contracts.XxxService) { ... })` 声明式解析。 diff --git a/backend/downstream/README.md b/backend/downstream/README.md new file mode 100644 index 00000000..afceb751 --- /dev/null +++ b/backend/downstream/README.md @@ -0,0 +1,147 @@ +# 下游插件开发指南 (Downstream Custom Plugins) + +本目录为 Wavelet 下游业务定制插件(Deployment-specific plugins)的专属开发目录。 + +所有插件开发均以标准模板 [`custom_example`](./plugins/custom_example) 为基准进行构建。 + +--- + +## 目录结构 + +```text +downstream/ +├── README.md +└── plugins/ + └── custom_example/ # 标准插件开发基准模板(可直接复制并重命名开发新插件) + ├── plugin.go # 插件入口:实现 core.Plugin (Name & Apply) + ├── consts/ # 常量与错误码定义 + │ └── consts.go + ├── controller/ # 控制器层 (HTTP API 接口、参数绑定与信封响应) + │ └── hello/ + │ └── hello.go + ├── service/ # 业务逻辑层 (用例编排、事务控制与领域事件) + ├── dao/ # 数据访问层 (GORM CRUD、SQL 防注入与转义) + ├── model/ # 数据模型与实体定义 + │ ├── do/ # Domain Object 领域对象与 DTO + │ └── entity/ # 数据表映射实体 (TableName() 带专属前缀) + └── migrations/ # Goose SQL 双方言独立数据库迁移 + ├── postgres/ # PostgreSQL 迁移脚本 + └── sqlite/ # SQLite 迁移脚本 +``` + +--- + +## 插件开发规范与分层职责 + +每个下游插件均需遵循标准分层架构(`controller -> service -> dao -> model`): + +1. **`plugin.go` (插件装配入口)**: + - 实现 `core.Plugin` 接口(`Name() string` 与 `Apply(ctx *core.Context) error`)。 + - 负责在 `Apply` 中注册路由组(`ctx.Router()`)、异步任务(`ctx.Task()`)、定时调度(`ctx.Schedule()`)与数据库迁移(`ctx.Migrations()`)。 + - 依赖注入统一使用 `core.Inject` 或 `ctx.Using` 获取平台服务(如 `contracts.AuthService`、`contracts.DBService`、`contracts.CacheService`)。 + +2. **`controller/` (控制器层 / Handler)**: + - 负责 HTTP API 请求参数绑定(`c.ShouldBindJSON` / `c.ShouldBindQuery`)、用户会话获取(`oauth.GetCurrentUser`)。 + - 调用 Service 层处理业务,严禁直接包含复杂业务逻辑或直接执行 SQL 操作。 + - 统一使用 `response.OK` 或 `response.Abort*` 返回标准信封响应。 + +3. **`service/` (业务逻辑层)**: + - 纯 Go 业务用例,方法入参首位统一为 `context.Context`。 + - 严禁依赖 `*gin.Context` 或 HTTP 相关对象,确保逻辑具备可移植性与可测试性。 + - 涉及数据修改可通过 `ctx.Events().Emit()` 发射强类型领域事件。 + +4. **`dao/` (数据访问层 / Repository)**: + - 负责底层数据库交互,通过 `contracts.DBService` 获取受保护的 GORM 数据库句柄。 + - 模糊查询必须调用 `pkg/util.EscapeLike` 并显式声明 `ESCAPE '\\'` 防注入。 + - 遵循**表单一所有者原则**:严禁跨过其他所有者插件直读或修改其他插件的数据表。 + +5. **`model/` (模型与 DTO)**: + - `model/entity/`:数据库表映射结构体,`TableName()` 必须带有插件专属前缀(如 `w_custom_*`)。 + - `model/do/`:业务领域对象、入参校验 Request DTO 与响应 Response DTO。 + +6. **`consts/` (常量与错误定义)**: + - 定义插件内部常量、配置键名及驼峰式(camelCase)错误标识字符串。 + +7. **`migrations/` (双方言 SQL 迁移)**: + - 包含 `postgres/` 与 `sqlite/` 双方言 Goose SQL 迁移文件,使用 `//go:embed` 打包并在 `Apply()` 中注册。 + +--- + +## 快速上手 + +### 1. 基于模板复制创建新插件 + +```bash +cp -r backend/downstream/plugins/custom_example backend/downstream/plugins/my_plugin +``` + +### 2. 实现插件入口 (`plugin.go`) + +```go +package my_plugin + +import ( + "Wavelet/core" + "Wavelet/core/contracts" + "net/http" + + "github.com/gin-gonic/gin" +) + +type Plugin struct{} + +func New() *Plugin { + return &Plugin{} +} + +func (p *Plugin) Name() string { + return "my_plugin" +} + +func (p *Plugin) Apply(ctx *core.Context) error { + // 通过容器解析认证服务 + var authSvc contracts.AuthService + if err := core.Using[contracts.AuthService](ctx, func(svc contracts.AuthService) { authSvc = svc }); err != nil { + return err + } + + // 注册带鉴权中间件的路由组 + g := ctx.Router().Group("/api/v1/my-plugin", authSvc.RequireAuthMiddleware().(gin.HandlerFunc)) + g.GET("/hello", func(c *gin.Context) { + user, err := authSvc.GetCurrentUser(c.Request.Context()) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Hello " + user.Username}) + }) + + return nil +} +``` + +### 3. 在应用装配入口注册插件 (`cmd/app.go`) + +在 `cmd/app.go` 中的 `newWaveletApp` 函数内注册你的新插件: + +```go +app.Use( + database.New(), + cache.New(), + logger.New(), + storage.New(), + // ... 官方平台插件 ... + my_plugin.New(), // 注册下游定制插件 + driver_http.New(), + driver_asynq_worker.New(), + driver_asynq_cron.New(), +) +``` + +--- + +## 严格红线与规范 (Guardrails) + +- **严禁跨插件直接 import**:下游插件可依赖 `core/`、`core/contracts/`、`pkg/`、`plugins/infra/`,**严禁直接 import `plugins/domain/*` 内部私有实现**,一律通过 `contracts` 接口或事件总线调用。 +- **禁止 GORM AutoMigrate**:数据表结构定义必须通过 `migrations/` 下嵌入的 Goose SQL 管理。 +- **Goroutine 并发安全**:严禁裸 `go func()`,后台并发任务统一使用 `backend/pkg/util.Go`。 diff --git a/backend/downstream/plugins/custom_example/consts/consts.go b/backend/downstream/plugins/custom_example/consts/consts.go index d709a2be..7b332a57 100644 --- a/backend/downstream/plugins/custom_example/consts/consts.go +++ b/backend/downstream/plugins/custom_example/consts/consts.go @@ -1 +1,2 @@ +// Package consts defines constants and error codes for custom_example plugin. package consts diff --git a/backend/downstream/plugins/custom_example/controller/hello/hello.go b/backend/downstream/plugins/custom_example/controller/hello/hello.go index f72082fb..b86a314b 100644 --- a/backend/downstream/plugins/custom_example/controller/hello/hello.go +++ b/backend/downstream/plugins/custom_example/controller/hello/hello.go @@ -1,5 +1,2 @@ -// ================================================================================= -// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. -// ================================================================================= - +// Package hello provides HTTP API handlers for the custom_example plugin. package hello diff --git a/docs/WAVELET_DEVELOPER_GUIDE.md b/docs/WAVELET_DEVELOPER_GUIDE.md index 29c0b694..af901386 100644 --- a/docs/WAVELET_DEVELOPER_GUIDE.md +++ b/docs/WAVELET_DEVELOPER_GUIDE.md @@ -33,10 +33,9 @@ - [第二部分:整个项目的目录结构划分与包职责定义](#第二部分整个项目的目录结构划分与包职责定义) - [第三部分:框架核心提供给插件调用的公用能力矩阵 (Context Capability Matrix)](#第三部分框架核心提供给插件调用的公用能力矩阵-context-capability-matrix) - [第四部分:Cordis 插件分层开发规范与代码模板 (Plugin Layered Architecture & Code Templates)](#第四部分cordis-插件分层开发规范与代码模板-plugin-layered-architecture--code-templates) - - [1. 分型与选型策略 (模式 1 vs 模式 2)](#1-分型与选型策略-模式-1-vs-模式-2) - - [2. 模式 1:扁平自包含分层规范与完整代码模板](#2-模式-1扁平自包含分层规范与完整代码模板) - - [3. 模式 2:严格子包物理分层规范与完整代码模板](#3-模式-2严格子包物理分层规范与完整代码模板) - - [4. 各层核心职责边界与严格禁止防线 (Guardrails)](#4-各层核心职责边界与严格禁止防线-guardrails) + - [1. 统一标准分层架构与目录规范 (以 custom_example 为基准)](#1-统一标准分层架构与目录规范-以-custom_example-为基准) + - [2. 核心分层代码模板与实现范例](#2-核心分层代码模板与实现范例) + - [3. 各层核心职责边界与严格禁止防线 (Guardrails)](#3-各层核心职责边界与严格禁止防线-guardrails) --- @@ -603,9 +602,9 @@ Wavelet/ │ └── admin/ # 系统管理台与监控面板插件 │ └── downstream/ # 【下游二开项目模板与脚手架】 - ├── custom_plugins/ # 下游自定义业务插件 - ├── config.yaml # 声明启用的插件与配置文件 - └── main.go # 下游项目组合启动入口 + ├── README.md # 下游插件开发指南 + └── plugins/ # 下游自定义业务插件目录 + └── custom_example/ # 官方标准插件开发基准模板(含完整分层结构) ``` ### 各层职责与禁止规则 (Guardrails): @@ -615,11 +614,12 @@ Wavelet/ 2. **`core/contracts/`**: - **职责**:仅定义公开的 Go Interface 和公共 DTO。 - **严禁**:严禁包含任何具体实现逻辑或 SQL 操作。 -3. **`plugins/`**: - - **职责**:所有业务逻辑和驱动实现的归宿。遵循标准分层架构(Layered Architecture / MVC 变体)。 - - **分层模式选型**: - - **模式 1(极简单文件分层,极简微型插件专用)**:单 package 内部仅各保留 1 个对应文件(`plugin.go`, `handlers.go`, `service.go`, `repository.go`, `models.go`, `errs.go`, `migrations/`)。仅适用于单一实体、极小代码量 (<500行) 的微型插件。 - - **模式 2(标准独立子包分层架构,官方推荐标准)**:按职责严格物理分包(`plugin.go`, `handler/`, `service/`, `repository/`, `model/`, `errs/`, `migrations/`)。**子包内文件以纯业务实体命名(如 `user.go`、`config.go`),严禁在根包平铺 `handlers_*`、`service_*`、`repository_*` 等前缀文件**。编译器级强约束 `handler -> service -> repository -> model` 单向依赖。 +3. **`plugins/` 与 `downstream/`**: + - **职责**:所有业务逻辑和驱动实现的归宿。遵循统一标准分层架构(Layered Architecture / MVC 变体)。 + - **统一分层架构与标准开发模板**: + - **唯一基准模板**:以 [`backend/downstream/plugins/custom_example`](file:///Users/ryan/Code/Go/Wavelet/backend/downstream/plugins/custom_example) 为全项目统一基准模板。 + - **物理子包隔离结构**:包含 `plugin.go`, `consts/`, `controller/`, `service/`, `dao/`, `model/` [含 `entity/`, `do/`], `migrations/` [含 `postgres/`, `sqlite/`]。 + - **命名与依赖禁令**:**严禁在根包平铺 `handlers_*`、`service_*`、`dao_*` 等前缀文件**,子包内文件直接以纯业务实体命名(如 `hello.go`、`user.go`)。严格约束 `controller -> service -> dao -> model` 单向依赖。 - **严禁**:插件之间严禁跨包 import 内部私有代码,跨插件调用一律走 `contracts` 接口或 `EventBus`。 --- @@ -652,116 +652,105 @@ Wavelet/ # 第四部分:Cordis 插件分层开发规范与代码模板 (Plugin Layered Architecture & Code Templates) -为了统一规范 Wavelet 所有官方插件与下游业务二开插件的研发质量,每个插件在内部遵循 **标准分层架构(Layered Architecture / MVC 变体)**。 +为了统一规范 Wavelet 所有官方插件与下游业务二开插件的研发质量,全项目插件**统一以 [`backend/downstream/plugins/custom_example`](file:///Users/ryan/Code/Go/Wavelet/backend/downstream/plugins/custom_example) 为基准开发模板**,遵循物理子包隔离的标准分层架构(`controller -> service -> dao -> model`)。 -## 1. 分型与选型策略 (模式 1 vs 模式 2) +## 1. 统一标准分层架构与目录规范 (以 `custom_example` 为基准) -根据业务复杂度和规模采用不同的物理包组织方式: - -``` - ┌────────────────────────┐ - │ 插件分层模式选型策略 │ - └───────────┬────────────┘ - │ - ┌───────────────────────┴───────────────────────┐ - ▼ ▼ -【模式 1:极简单文件分层】 【模式 2:标准独立子包分层】 -适合:极简微型/Demo插件 (<500行) 适合:标准/中大型业务插件 (推荐标准) -结构:单 Package,每层仅对应 1 个同名文件 结构:严格分包 handler/, service/, repository/, model/ -禁令:严禁根目录平铺 handlers_* 等前缀文件 规范:子包内以业务实体命名 (如 user.go, order.go) +```text +backend/downstream/plugins/custom_example/ (或 backend/plugins/domain//) +├── plugin.go # [插件根入口] 实现 core.Plugin,负责装配依赖、路由与扩展点注册 +│ +├── consts/ # package consts:常量定义、配置键名与模块错误标识 +│ └── consts.go +│ +├── controller/ # package controller:HTTP API 接入层 (参数绑定、会话提取、信封响应) +│ └── hello/ # 业务分组子包 +│ └── hello.go # 业务接口 Handler 实现(直接以业务命名,禁止 controller_hello.go) +│ +├── service/ # package service:核心业务逻辑层 (业务用例、事务编排、事件发布) +│ └── hello.go # 业务用例实现(纯 Go 逻辑,禁止依赖 *gin.Context) +│ +├── dao/ # package dao:数据持久化访问层 DAL (GORM CRUD、SQL 转义防注入) +│ └── hello.go # 数据库访问实现(直接以业务命名,禁止 dao_hello.go) +│ +├── model/ # package model:纯数据实体与传输对象 (零 Web/数据库框架依赖) +│ ├── entity/ # 数据库映射实体 (TableName() 必须带专属表前缀) +│ │ └── hello.go +│ └── do/ # 领域对象、请求 Request DTO 与响应 Response DTO +│ └── hello.go +│ +└── migrations/ # Goose SQL 独立迁移嵌入目录 (//go:embed) + ├── postgres/ # PostgreSQL 专属迁移 SQL + │ └── 20260901000001_init_hello.sql + └── sqlite/ # SQLite 专属迁移 SQL + └── 20260901000001_init_hello.sql ``` -| 维度 | 模式 1:极简单文件分层 (Single-File Flat) | 模式 2:标准独立子包分层 (Standard Sub-packages) | -| :--- | :--- | :--- | -| **适用场景** | 极简微型插件、单一实体(仅用于小型工具/示例) | 标准业务插件、包含多实体/多接口(**官方推荐标准**) | -| **代码量规模** | 通常 < 500 行 | 通常 ≥ 500 行(如 `upload`, `auth`, `admin`, `order`) | -| **Go 包形态** | 单一 Go Package,各层级仅各 1 个同名文件 | 按职责严格物理子目录分包,编译级强约束单向依赖 | -| **命名禁令** | **严禁在根目录平铺 `handlers_*`、`service_*` 文件** | **子包内文件直接以业务命名(如 `user.go`),禁止带 `handler_*` 前缀** | +> ⚠️ **严禁规则**: +> - **严禁在根目录平铺文件**:严禁在插件根目录下创建 `handlers_*.go`、`service_*.go`、`dao_*.go` 等前缀文件。 +> - **严禁跨层违规调用**:严格约束 `controller -> service -> dao -> model` 单向依赖。 +> - **严禁跨插件私有导入**:跨插件调用一律走 `contracts` 接口或 `EventBus`。 --- -## 2. 模式 1:极简单文件分层规范与完整代码模板 +## 2. 核心分层代码模板与实现范例 -### 2.1 目录结构 -```text -backend/plugins/domain// -├── plugin.go # [Cordis 接入层] 实现 core.Plugin,负责 Apply 组装与扩展点注册 -├── handlers.go # [Handler 层] 单一文件:Gin API Handler -├── service.go # [Service 层] 单一文件:核心业务用例 -├── repository.go # [Repository 层] 单一文件:GORM / DB 操作 -├── models.go # [Model 层] 单一文件:实体与 DTO -├── errs.go # [Error 层] 单一文件:错误常量 -├── plugin_test.go # 插件级单元与集成测试 -└── migrations/ # Goose SQL 嵌入文件 (//go:embed) - └── 20260828000001_init_.sql -``` - -> ⚠️ **严禁规则**:当单一文件膨胀或需要拆分多个业务实体时,**严禁在根目录创建 `handlers_user.go`, `handlers_admin.go`, `service_user.go` 等前缀文件**,必须立即重构并迁移为 **模式 2(标准独立子包分层架构)**! - -### 2.2 核心代码模板 (模式 1) - -#### (1) `plugin.go` (插件入口与装配) +#### (1) `plugin.go` (插件装配入口) ```go -package order +package hello import ( "embed" - "reflect" "github.com/Rain-kl/Wavelet/core" "github.com/Rain-kl/Wavelet/core/contracts" "github.com/gin-gonic/gin" ) -//go:embed migrations/*.sql -var orderMigrations embed.FS +//go:embed migrations/postgres/*.sql +var pgMigrations embed.FS -const PluginName = "domain.order" +//go:embed migrations/sqlite/*.sql +var sqliteMigrations embed.FS -type Plugin struct { - svc *OrderService -} +type Plugin struct{} func New() *Plugin { return &Plugin{} } func (p *Plugin) Name() string { - return PluginName -} - -func (p *Plugin) Inject() []reflect.Type { - return []reflect.Type{ - reflect.TypeFor[contracts.DBService](), - } + return "custom_example" } func (p *Plugin) Apply(ctx *core.Context) error { // 1. 注册专属数据库迁移 - ctx.Migrations().Register("order", orderMigrations) + ctx.Migrations().Register("custom_example", pgMigrations) - // 2. 初始化持久层与服务层 - repo := newOrderRepository(ctx) - p.svc = newOrderService(ctx, repo) + // 2. 解析依赖并装配各层 + var authSvc contracts.AuthService + if err := core.Using[contracts.AuthService](ctx, func(svc contracts.AuthService) { authSvc = svc }); err != nil { + return err + } - // 3. 注册 HTTP 路由组 - authSvc, _ := core.Inject[contracts.AuthService](ctx) - group := ctx.Router().Group("/api/v1/orders") - if authSvc != nil { - group.Use(authSvc.RequireAuthMiddleware()) - } - { - group.POST("", p.handleCreateOrder) - group.GET("/:id", p.handleGetOrderDetail) - } + // 3. 注册 HTTP 路由组与中间件 + g := ctx.Router().Group("/api/v1/custom", authSvc.RequireAuthMiddleware().(gin.HandlerFunc)) + g.GET("/hello", func(c *gin.Context) { + user, err := authSvc.GetCurrentUser(c.Request.Context()) + if err != nil { + c.JSON(401, gin.H{"error": "unauthorized"}) + return + } + c.JSON(200, gin.H{"message": "Hello " + user.Username}) + }) return nil } ``` -#### (2) `handlers.go` (Controller 层) +#### (2) `controller/hello/hello.go` (HTTP 控制器层) ```go -package order +package hello import ( "net/http" @@ -771,41 +760,39 @@ import ( "github.com/gin-gonic/gin" ) -// @Summary 创建订单 -// @Description 创建新的用户订单 -// @Tags Order -// @Accept json -// @Produce json -// @Param request body CreateOrderRequest true "创建参数" -// @Success 200 {object} response.Envelope{data=OrderDTO} "成功" -// @Failure 400 {object} response.Envelope "参数错误" -// @Router /api/v1/orders [post] -func (p *Plugin) handleCreateOrder(c *gin.Context) { - var req CreateOrderRequest - if err := c.ShouldBindJSON(&req); err != nil { - response.AbortBadRequest(c, errBindParamsFailed) - return - } +type Controller struct { + svc HelloService +} +func NewController(svc HelloService) *Controller { + return &Controller{svc: svc} +} + +// @Summary 获取欢迎信息 +// @Tags Hello +// @Produce json +// @Success 200 {object} response.Envelope{data=do.HelloResponse} "成功" +// @Router /api/v1/custom/hello [get] +func (ctrl *Controller) GetHello(c *gin.Context) { user, ok := oauth.GetCurrentUser(c) if !ok { - response.AbortUnauthorized(c, errUnauthorized) + response.AbortUnauthorized(c, "errUnauthorized") return } - order, err := p.svc.CreateOrder(c.Request.Context(), user.ID, req) + res, err := ctrl.svc.SayHello(c.Request.Context(), user.ID) if err != nil { - response.AbortInternal(c, errCreateOrderFailed) + response.AbortInternal(c, "errInternalServer") return } - c.JSON(http.StatusOK, response.OK(order)) + c.JSON(http.StatusOK, response.OK(res)) } ``` -#### (3) `service.go` (Service 业务逻辑层) +#### (3) `service/hello.go` (业务逻辑层) ```go -package order +package service import ( "context" @@ -813,44 +800,31 @@ import ( "github.com/Rain-kl/Wavelet/core" ) -type OrderService struct { - ctx *core.Context - repo *orderRepository +type HelloService struct { + ctx *core.Context + dao HelloDAO } -func newOrderService(ctx *core.Context, repo *orderRepository) *OrderService { - return &OrderService{ctx: ctx, repo: repo} +func NewHelloService(ctx *core.Context, dao HelloDAO) *HelloService { + return &HelloService{ctx: ctx, dao: dao} } -func (s *OrderService) CreateOrder(ctx context.Context, userID string, req CreateOrderRequest) (*OrderDTO, error) { - order := &OrderModel{ - UserID: userID, - Amount: req.Amount, - Status: "pending", - } - - if err := s.repo.Create(ctx, order); err != nil { +func (s *HelloService) SayHello(ctx context.Context, userID string) (*do.HelloResponse, error) { + record, err := s.dao.GetByUserID(ctx, userID) + if err != nil { return nil, err } // 发射领域事件 - s.ctx.Events().Emit(ctx, "order:created", OrderCreatedEvent{ - OrderID: order.ID, - UserID: order.UserID, - Amount: order.Amount, - }) + s.ctx.Events().Emit(ctx, "custom:hello_visited", map[string]any{"user_id": userID}) - return &OrderDTO{ - ID: order.ID, - Amount: order.Amount, - Status: order.Status, - }, nil + return &do.HelloResponse{Message: "Hello " + record.Name}, nil } ``` -#### (4) `repository.go` (Repository 数据访问层) +#### (4) `dao/hello.go` (数据访问持久化层 DAL) ```go -package order +package dao import ( "context" @@ -861,178 +835,104 @@ import ( "gorm.io/gorm" ) -type orderRepository struct { +type HelloDAO struct { ctx *core.Context } -func newOrderRepository(ctx *core.Context) *orderRepository { - return &orderRepository{ctx: ctx} +func NewHelloDAO(ctx *core.Context) *HelloDAO { + return &HelloDAO{ctx: ctx} } -func (r *orderRepository) getDB(ctx context.Context) *gorm.DB { - if dbSvc, err := core.Inject[contracts.DBService](r.ctx); err == nil && dbSvc != nil { +func (d *HelloDAO) getDB(ctx context.Context) *gorm.DB { + if dbSvc, err := core.Inject[contracts.DBService](d.ctx); err == nil && dbSvc != nil { return dbSvc.GetDB().WithContext(ctx) } return nil } -func (r *orderRepository) Create(ctx context.Context, order *OrderModel) error { - return r.getDB(ctx).Create(order).Error +func (d *HelloDAO) GetByUserID(ctx context.Context, userID string) (*entity.HelloEntity, error) { + var item entity.HelloEntity + err := d.getDB(ctx).Where("user_id = ?", userID).First(&item).Error + return &item, err } -func (r *orderRepository) SearchByKeyword(ctx context.Context, keyword string) ([]OrderModel, error) { - var list []OrderModel +func (d *HelloDAO) SearchByKeyword(ctx context.Context, keyword string) ([]entity.HelloEntity, error) { + var list []entity.HelloEntity // SQL LIKE 防注入与通配符转义规范 safeKeyword := util.EscapeLike(keyword) + "%" - err := r.getDB(ctx).Where("status LIKE ? ESCAPE '\\'", safeKeyword).Find(&list).Error + err := d.getDB(ctx).Where("name LIKE ? ESCAPE '\\'", safeKeyword).Find(&list).Error return list, err } ``` -#### (5) `models.go` 与 `errs.go` +#### (5) `model/entity/` 与 `model/do/` ```go -// models.go -package order +// model/entity/hello.go +package entity import "time" -type OrderModel struct { +type HelloEntity struct { ID string `gorm:"column:id;primaryKey;size:64" json:"id"` UserID string `gorm:"column:user_id;index;size:64;not null" json:"user_id"` - Amount int64 `gorm:"column:amount;not null" json:"amount"` - Status string `gorm:"column:status;size:32;index;not null;default:'pending'" json:"status"` + Name string `gorm:"column:name;size:128;not null" json:"name"` CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` } -func (OrderModel) TableName() string { - return "w_orders" -} - -type CreateOrderRequest struct { - Amount int64 `json:"amount" binding:"required,gt=0"` -} - -type OrderDTO struct { - ID string `json:"id"` - Amount int64 `json:"amount"` - Status string `json:"status"` -} - -type OrderCreatedEvent struct { - OrderID string `json:"order_id"` - UserID string `json:"user_id"` - Amount int64 `json:"amount"` +func (HelloEntity) TableName() string { + return "w_custom_hello" } ``` ```go -// errs.go -package order +// model/do/hello.go +package do + +type HelloResponse struct { + Message string `json:"message"` +} + +type CreateHelloRequest struct { + Name string `json:"name" binding:"required,max=128"` +} +``` + +#### (6) `consts/consts.go` (常量与错误定义) +```go +package consts const ( - errBindParamsFailed = "errBindParamsFailed" - errUnauthorized = "errUnauthorized" - errCreateOrderFailed = "errCreateOrderFailed" + PluginName = "custom_example" + + // 模块内部错误码标识 (camelCase) + ErrUserNotFound = "errUserNotFound" + ErrInvalidParams = "errInvalidParams" + ErrOperationFail = "errOperationFail" ) ``` --- -## 3. 模式 2:标准独立子包物理分层规范与完整代码模板 (推荐标准) - -用于标准与中大型业务插件,各层使用独立的 Go package 物理隔离。 - -### 3.1 目录结构与文件命名规约 -```text -backend/plugins/domain/order/ -├── plugin.go # [插件根入口] 实现 core.Plugin,装配各子包并向 Cordis 注册 -│ -├── handler/ # package handler:HTTP API 接入层(或 controller/) -│ ├── router.go # 路由组挂载与中间件绑定 -│ └── order.go # 订单相关 Handler(以业务直接命名,禁止 handlers_order.go) -│ -├── service/ # package service:核心业务逻辑层 -│ ├── service.go # 业务用例接口定义 (Service Interface) -│ └── order.go # 订单业务用例实现(以业务直接命名,禁止 service_order.go) -│ -├── repository/ # package repository:数据访问持久化层 (DAL) -│ ├── repository.go # 仓储通用方法与工厂 -│ └── order.go # 订单仓储持久化实现(以业务直接命名,禁止 repository_order.go) -│ -├── model/ # package model (或 models/):纯领域实体与传输对象(零外部框架依赖) -│ ├── entity.go # 数据库映射实体 (TableName() 必须带 w__ 前缀) -│ ├── dto.go # 请求与响应 DTO -│ └── events.go # 领域事件结构体 -│ -├── errs/ # package errs:错误常量与错误码定义 (或根目录 errs.go) -│ └── errs.go -│ -└── migrations/ # Goose SQL 独立迁移嵌入文件 (//go:embed) - └── 20260828000001_init_order.sql -``` - -### 3.2 模式 2 核心装配代码范例 (`plugin.go`) -```go -package order - -import ( - "embed" - - "github.com/Rain-kl/Wavelet/core" - "github.com/Rain-kl/Wavelet/core/contracts" - "github.com/Rain-kl/Wavelet/plugins/domain/order/handler" - "github.com/Rain-kl/Wavelet/plugins/domain/order/repository" - "github.com/Rain-kl/Wavelet/plugins/domain/order/service" -) - -//go:embed migrations/*.sql -var orderMigrations embed.FS - -type Plugin struct{} - -func (p *Plugin) Name() string { - return "domain.order" -} - -func (p *Plugin) Apply(ctx *core.Context) error { - // 1. 注册迁移 - ctx.Migrations().Register("order", orderMigrations) - - // 2. 构造数据层与服务层 - repo := repository.NewOrderRepository(ctx) - svc := service.NewOrderService(ctx, repo) - - // 3. 构造 Handler 并挂载路由 - h := handler.NewOrderHandler(svc) - authSvc, _ := core.Inject[contracts.AuthService](ctx) - handler.RegisterRoutes(ctx.Router(), h, authSvc) - - return nil -} -``` - ---- - -## 4. 各层核心职责边界与严格禁止防线 (Guardrails) +## 3. 各层核心职责边界与严格禁止防线 (Guardrails) ```text ┌──────────────────────────────────────────────────────────────────┐ -│ Controller / Handler 层 (HTTP 接入) │ +│ Controller 层 (HTTP API 接入) │ │ • 参数绑定 ShouldBindJSON • 用户会话 oauth.GetCurrentUser │ │ • 统一信封 response.OK/Abort* • 严禁 SQL 操作 / 严禁重度业务 │ └─────────────────────────────────┬────────────────────────────────┘ - │ 调用 Service (入参 context.Context) + │ 调用 Service (入参首位 context.Context) ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Service 层 (业务用例 & 领域逻辑) │ │ • 纯 Go 逻辑 (零 Web 依赖) • 事务编排 ctx.DB().Transaction │ │ • 领域事件 ctx.Events().Emit • 严禁 import gin / c.JSON │ └─────────────────────────────────┬────────────────────────────────┘ - │ 调用 Repository 接口 + │ 调用 DAO 接口 ▼ ┌──────────────────────────────────────────────────────────────────┐ -│ Repository 层 (数据持久化 DAL) │ +│ DAO 层 (数据持久化 DAL) │ │ • GORM CRUD 与查询 • EscapeLike 通配符安全转义 │ │ • 严禁反向依赖 Service/Controller • 严禁越权读写其他插件数据表 │ └─────────────────────────────────┬────────────────────────────────┘ @@ -1040,12 +940,12 @@ func (p *Plugin) Apply(ctx *core.Context) error { ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Model 层 (纯实体 & DTO) │ -│ • TableName() 带专属表前缀 • 请求/响应结构体 │ +│ • TableName() 带专属表前缀 • entity/ 与 do/ 明确拆分 │ │ • 零值与 DB 默认值匹配 • 无任何上层包依赖 │ └──────────────────────────────────────────────────────────────────┘ ``` -1. **表单一所有者原则 (Single Owner Principle)**:数据表有且仅由所属插件操作(表名统一前缀 `w__*`),跨插件一律通过公开契约 Interface 或 EventBus 协同。 +1. **表单一所有者原则 (Single Owner Principle)**:数据表有且仅由所属插件声明与维护(表名统一前缀 `w__*`),跨插件一律通过公开契约 Interface 或 EventBus 协同。 2. **LIKE 查询安全防注入**:所有涉及用户输入的模糊查询,必须经过 `util.EscapeLike` 转义通配符并显式声明 `ESCAPE '\\'` 语法。 3. **Goroutine 安全**:并发任务统一使用 `util.Go`,杜绝直接使用裸 `go func()`。 diff --git a/docs/superpowers/specs/2026-08-27-cordis-downstream-developer-guide.md b/docs/superpowers/specs/2026-08-27-cordis-downstream-developer-guide.md index 50af64b9..7547c863 100644 --- a/docs/superpowers/specs/2026-08-27-cordis-downstream-developer-guide.md +++ b/docs/superpowers/specs/2026-08-27-cordis-downstream-developer-guide.md @@ -550,9 +550,9 @@ Wavelet/ │ └── admin/ # 系统管理台与监控面板插件 │ └── downstream/ # 【下游二开项目模板与脚手架】 - ├── custom_plugins/ # 下游自定义业务插件 - ├── config.yaml # 声明启用的插件与配置文件 - └── main.go # 下游项目组合启动入口 + ├── README.md # 下游插件开发指南 + └── plugins/ # 下游自定义业务插件目录 + └── custom_example/ # 官方标准插件开发基准模板(含完整分层结构) ``` ### 各层职责与禁止规则 (Guardrails): @@ -562,11 +562,12 @@ Wavelet/ 2. **`core/contracts/`**: - **职责**:仅定义公开的 Go Interface 和公共 DTO。 - **严禁**:严禁包含任何具体实现逻辑或 SQL 操作。 -3. **`plugins/`**: - - **职责**:所有业务逻辑和驱动实现的归宿。遵循标准分层架构(Layered Architecture / MVC 变体)。 - - **分层模式选型**: - - **模式 1(极简单文件分层,微型插件专用)**:单 package 极简结构(仅单文件 `plugin.go`, `handlers.go`, `service.go`, `repository.go`, `models.go`, `errs.go`, `migrations/`)。适用于单一实体、极小代码量 (<500行) 的微型插件。 - - **模式 2(标准独立子包分层架构,官方推荐标准)**:按职责严格物理分包(`plugin.go`, `handler/`, `service/`, `repository/`, `model/`, `errs/`, `migrations/`)。**子包内文件以纯业务实体命名(如 `user.go`、`config.go`),严禁在根包平铺 `handlers_*`、`service_*`、`repository_*` 等前缀文件**。编译器级强约束 `handler -> service -> repository -> model` 单向依赖。 +3. **`plugins/` 与 `downstream/`**: + - **职责**:所有业务逻辑和驱动实现的归宿。遵循统一标准分层架构(Layered Architecture / MVC 变体)。 + - **统一分层架构与标准开发模板**: + - **唯一基准模板**:以 [`backend/downstream/plugins/custom_example`](file:///Users/ryan/Code/Go/Wavelet/backend/downstream/plugins/custom_example) 为全项目统一基准模板。 + - **物理子包隔离结构**:包含 `plugin.go`, `consts/`, `controller/`, `service/`, `dao/`, `model/` [含 `entity/`, `do/`], `migrations/` [含 `postgres/`, `sqlite/`]。 + - **命名与依赖禁令**:**严禁在根包平铺 `handlers_*`、`service_*`、`dao_*` 等前缀文件**,子包内文件直接以纯业务实体命名(如 `hello.go`、`user.go`)。严格约束 `controller -> service -> dao -> model` 单向依赖。 - **严禁**:插件之间严禁跨包 import 内部私有代码,跨插件调用一律走 `contracts` 接口或 `EventBus`。 --- diff --git a/docs/superpowers/specs/2026-08-28-cordis-plugin-layered-architecture-spec.md b/docs/superpowers/specs/2026-08-28-cordis-plugin-layered-architecture-spec.md index 5fd6dd9b..fcef8684 100644 --- a/docs/superpowers/specs/2026-08-28-cordis-plugin-layered-architecture-spec.md +++ b/docs/superpowers/specs/2026-08-28-cordis-plugin-layered-architecture-spec.md @@ -6,58 +6,46 @@ --- -## 1. 架构总览与分型原则 (Architecture & Selection Strategy) +## 1. 架构总览与统一标准规范 (Architecture & Unified Standard Spec) -在 Wavelet 的 Cordis 微内核架构中,系统通过 **微内核 (`core/`) + 服务契约 (`core/contracts/`) + 自包含插件 (`plugins/`)** 实现高度解耦与单向依赖。 -为了规范插件内部代码组织,插件遵循 **标准分层架构(Layered Architecture / MVC 变体)**,并根据业务复杂度提供两套标准物理包结构: - -| 模式 | 适用场景 | 复杂度特征 | 物理结构形式 | 命名规范核心禁令 | -| :--- | :--- | :--- | :--- | :--- | -| **模式 1:极简单文件自包含**
(Single-File Flat) | 极简微型插件 | 仅有 1 个单一实体、代码量 < 500 行(如极简工具、Demo) | 单 Package,每个层级仅对应 1 个同名文件 (`handlers.go`, `service.go`, `models.go`, `repository.go`) | **严禁在根目录平铺 `handlers_*`、`service_*` 等前缀文件** | -| **模式 2:独立子包分层架构**
(Strict Sub-packages) | 标准/中大型业务插件(**官方推荐标准**) | 包含多实体/多接口、代码量 ≥ 500 行(如 `upload`, `auth`, `admin`, `order` 等) | 严格按层独立子包 (`handler/`, `service/`, `repository/`, `model/`, `errs/`) | **子包内文件直接以业务命名(如 `user.go`, `config.go`),禁止带 `handler_*` / `service_*` 前缀** | +在 Wavelet 的 Cordis 微内核架构中,系统通过 **微内核 (`core/`) + 服务契约 (`core/contracts/`) + 自包含插件 (`plugins/` 及 `downstream/plugins/`)** 实现高度解耦与单向依赖。 +所有插件统一以 [`backend/downstream/plugins/custom_example`](file:///Users/ryan/Code/Go/Wavelet/backend/downstream/plugins/custom_example) 为基准模板,严格遵循物理子包隔离的分层架构(`controller -> service -> dao -> model`)。 --- -## 2. 模式 1:极简单文件自包含规范 (Single-File Flat Package) +## 2. 统一标准分层目录结构 (以 `custom_example` 为基准) -仅适用于极简小型插件(整个插件代码极少且各层只有一个文件)。 - -### 2.1 目录结构 ```text -backend/plugins/domain// -├── plugin.go # [Cordis 接入层] 实现 core.Plugin,负责 Apply 组装与扩展点注册 -├── handlers.go # [Handler 层] 单一文件:Gin API Handler -├── service.go # [Service 层] 单一文件:核心业务用例 -├── repository.go # [Repository 层] 单一文件:GORM / DB 操作 -├── models.go # [Model 层] 单一文件:实体与 DTO -├── errs.go # [Error 层] 单一文件:错误常量 -├── plugin_test.go # 插件测试 -└── migrations/ # Goose SQL 嵌入文件 - └── 20260828000001_init_.sql -``` - -> ⚠️ **严禁规则**:当单一文件膨胀或需要拆分多个业务实体时,**严禁在根目录创建 `handlers_user.go`, `handlers_admin.go`, `service_user.go` 等前缀文件**,必须立即重构并迁移为 **模式 2(独立子包分层架构)**! - ---- - -## 3. 模式 2:标准独立子包分层架构 (Standard Sub-package Architecture - 推荐规范) - -适用于绝大多数业务插件。各层使用独立的 Go package 物理隔离,**在子包内以纯业务实体命名文件**。 - -### 3.1 目录结构与文件命名规约 -```text -backend/plugins/domain// +backend/downstream/plugins/custom_example/ (或 backend/plugins/domain//) ├── plugin.go # [插件根入口] 实现 core.Plugin,装配各子包并向 Cordis 注册 │ -├── handler/ # package handler:HTTP API 接入层(或 controller/) -│ ├── router.go # 路由组挂载与中间件绑定 -│ ├── auth.go # 认证相关 Handler(直接命名为 auth.go,禁止 handlers_auth.go) -│ ├── user.go # 用户相关 Handler(直接命名为 user.go,禁止 handlers_user.go) -│ ├── config.go # 配置相关 Handler(直接命名为 config.go,禁止 handlers_config.go) -│ └── logs.go # 日志相关 Handler(直接命名为 logs.go,禁止 handlers_logs.go) +├── consts/ # package consts:常量与模块内部错误码定义 +│ └── consts.go │ -├── service/ # package service:核心领域业务逻辑层 -│ ├── service.go # 顶层 Service 组合与构造工厂 +├── controller/ # package controller:HTTP API 接入层 (参数绑定、会话获取、统一信封响应) +│ └── hello/ # 业务分组/实体子包 +│ └── hello.go # 接口处理 Handler(直接以业务命名,禁止 controller_hello.go) +│ +├── service/ # package service:核心业务用例层 (业务用例、事务编排、事件发布) +│ └── hello.go # 业务用例实现(纯 Go 逻辑,禁止依赖 *gin.Context) +│ +├── dao/ # package dao:数据持久化访问层 DAL (GORM CRUD、SQL 转义防注入) +│ └── hello.go # 数据访问实现(直接以业务命名,禁止 dao_hello.go) +│ +├── model/ # package model:纯数据实体与传输对象 (零 Web/数据库框架依赖) +│ ├── entity/ # 数据库映射实体 (TableName() 必须带 w__ 前缀) +│ │ └── hello.go +│ └── do/ # 领域对象、请求 Request DTO 与响应 Response DTO +│ └── hello.go +│ +└── migrations/ # Goose SQL 独立迁移嵌入目录 (//go:embed) + ├── postgres/ # PostgreSQL 专属迁移 SQL + │ └── 20260901000001_init.sql + └── sqlite/ # SQLite 专属迁移 SQL + └── 20260901000001_init.sql +``` + +> ⚠️ **严禁规则**:严禁在插件根目录下平铺 `handlers_*.go`、`service_*.go`、`dao_*.go` 等前缀文件,子包内文件直接按业务实体命名。严格约束 `controller -> service -> dao -> model` 单向依赖。 │ ├── auth.go # 认证业务逻辑(直接命名为 auth.go,禁止 service_auth.go) │ ├── user.go # 用户业务逻辑(直接命名为 user.go,禁止 service_user.go) │ ├── config.go # 配置业务逻辑(直接命名为 config.go,禁止 service_config.go)