diff --git a/.agent/skills/new-api/SKILL.md b/.agent/skills/new-api/SKILL.md index 556e2eba..9752dec3 100644 --- a/.agent/skills/new-api/SKILL.md +++ b/.agent/skills/new-api/SKILL.md @@ -3,140 +3,143 @@ name: "new-api" description: "Wavelet 项目专用:当新增或修改自定义业务 API、新增业务路由、新增 service 层核心逻辑时必须使用。本技能指导包职责划分、推荐文件结构、路由解耦、Swagger 文档生成与质量门禁验证。" --- -# 新增业务 API / 接口开发规范 +# 新增业务 API 开发与路由注册规范 -本技能涵盖 Wavelet 的业务接口开发规范。开始开发前先阅读仓库根目录 [AGENTS.md](file:///Users/ryan/DEV/Go/Wavelet/AGENTS.md),遵守项目级核心规则。 - -为了保持核心路由入口的稳定性,**所有新增的定制业务接口路由统一注册在独立的 go 文件中,严禁直接堆叠到 `router.go`**。 +本技能是 Wavelet 项目接口开发与路由注册的唯一指导规范。在开发任何新接口前,请严格按照本指南进行架构决策与路由注册。 --- -## 包职责划分 (Package Responsibilities) +## 核心路由准则与防线 (Routing Governance & Guardrails) -按照 Go 语言最佳实践与 Google 的开发风格,接口开发应该进行严格的分层,以避免循环依赖和逻辑混乱。 +Wavelet 后端路由采用了**严格的框架层与业务层隔离机制**。请牢记以下开发原则: -| 目录/包名 | 职责定位 | 框架依赖限制 | 常见包含内容 | +1. **禁止修改框架级路由文件**: + - 以下文件属于系统框架/平台级接口,**禁止为了添加自定义业务接口而进行任何修改**: + - `internal/router/router.go`(核心入口委派) + - `internal/router/root/default.go`(公开文件服务、robots.txt、Swagger 及 /api/health 路由) + - `internal/router/root/frontend.go`(前端静态服务) + - `internal/router/v1/v1.go`(V1 分发层协调器) + - `internal/router/v1/admin.go`(框架管理员端管理接口) + - `internal/router/v1/user.go`(框架普通用户端基础接口、OAuth及公开接口) +2. **仅允许在 `custom.go` 中注册业务接口**: + - 所有的自定义/业务相关接口注册,有且仅有以下两个合法的承载点: + - [internal/router/root/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/custom.go)(用于挂载到根路径的特殊业务接口) + - [internal/router/v1/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/v1/custom.go)(用于挂载在 API V1 下的标准自定义业务接口) + +--- + +## 路由归属判定表 (Where should I register my new API?) + +根据接口的**访问路径特征**和**访问身份/限制条件**,决定将新开发的 API 挂载至何处: + +| 目标 API 路径特征 | 访问身份/条件限制 | 对应的路由注册入口 | 是否允许修改 | | :--- | :--- | :--- | :--- | -| **`internal/router/`** | 核心路由入口 | 无/仅用于启动服务 | `router.go` (全局路由配置与服务启动) | -| **`internal/router/root/`** | 根路径路由注册包 | 依赖 Gin 框架 | `root.go` (统一入口), `frontend.go` (前端服务), `default.go` (默认根路由,包含 Swagger、文件服务及 robots.txt), `custom.go` (根路径自定义路由) | -| **`internal/router/v1/`** | 路由分发层 | 依赖 Gin 框架 | `v1.go` (v1 路由统一入口), `admin.go`, `user.go`, `public.go`, `custom.go` (各分类路由注册实现) | -| **`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 数据库迁移文件。 | +| **`/my-custom-path`** (挂载在根路径下的特殊业务接口) | 自定义控制 | `root/custom.go` 中的 `RegisterCustomRootRoutes` | **允许修改 (业务自定义入口)** | +| **`/api/v1/custom/...`** (API v1 下的定制业务接口) | 自定义控制 | `v1/custom.go` 中的 `RegisterCustomRoutes` | **允许修改 (业务自定义入口)** | +| **`/api/v1/admin/...`** (系统管理员管理端接口) | 需要管理员登录 (`admin.LoginAdminRequired()`) | `v1/admin.go` | **禁止修改 (仅限系统框架路由)** | +| **`/api/v1/user/...`** (框架普通用户基础接口) | 需要普通用户登录 (`oauth.LoginRequired()`) | `v1/user.go` | **禁止修改 (仅限系统框架路由)** | +| **`/api/v1/public/...`** (Captcha、Config 等系统公开接口) | 所有人 (无条件 / 公开) | `v1/user.go` | **禁止修改 (仅限系统框架路由)** | +| **`GET /f/:id`**, **`GET /robots.txt`**, **`GET /api/health`** (系统级默认及公开接口) | 所有人 (无条件 / 公开) | `root/default.go` | **禁止修改 (仅限系统框架路由)** | --- -## 建议创建/修改的文件结构 +## 两个自定义路由包的用法与区别 (Root Custom vs V1 Custom) -当新增一套定制的业务接口(例如名为 `custom` 的业务模块)时,根据逻辑复杂度建议采用以下文件结构: +### 1. 根路径自定义包:`root/custom.go` + +* **适用场景**:适用于需要**直接挂载在主域名根路径下**的特殊自定义业务接口(如第三方 Webhook 回调、特定的短链接重定向、外部数据接口等,不需要 `/api/v1` 前缀)。 +* **用法示例**: + 在 [root/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/custom.go) 中实现: + ```go + package root + + import ( + "github.com/Rain-kl/Wavelet/internal/apps/custom" + "github.com/gin-gonic/gin" + ) + + // RegisterCustomRootRoutes registers custom business routes that belong to the root path. + func RegisterCustomRootRoutes(r *gin.Engine) { + // 挂载到根路径下,如 GET /my-custom-webhook + r.GET("/my-custom-webhook", custom.HandleRootWebhook) + } + ``` + *(注:该函数已由 `root.go` 自动加载,你无需修改任何其他核心文件。)* + +### 2. V1 API 自定义包:`v1/custom.go` + +* **适用场景**:适用于普通的**自定义业务 API**,需要规范挂载在标准 API V1 路径下(即自动带有 `/api/v1/custom/...` 前缀,可选择性配置用户/管理员登录中间件)。 +* **用法示例**: + 在 [v1/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/v1/custom.go) 中实现: + ```go + package v1 + + import ( + "github.com/Rain-kl/Wavelet/internal/apps/custom" + "github.com/gin-gonic/gin" + ) + + // RegisterCustomRoutes registers standard custom API routes under /api/v1. + func RegisterCustomRoutes(apiV1Router *gin.RouterGroup) { + customRouter := apiV1Router.Group("/custom") + { + // 挂载到 /api/v1/custom 下,例如:POST /api/v1/custom/action + customRouter.POST("/action", custom.DoActionHandler) + } + } + ``` + *(注:该函数已由 `v1/v1.go` 自动加载,你无需修改任何其他核心文件。)* + +--- + +## 建议创建/修改的文件结构 (Recommended Directory Structure) + +当新增一套定制的业务接口(例如名为 `custom` 的业务模块)时,建议采用以下标准文件结构: ```text internal/ ├── router/ -│ ├── router.go # 核心路由入口,委派路由给各分类 -│ ├── root/ # 根路径路由注册包 -│ │ ├── root.go # 统一路由注册入口 -│ │ ├── frontend.go # 前端静态服务注册(支持 embed_frontend 编译标签) -│ │ ├── default.go # 默认根路由注册(/f/:id, /robots.txt, Swagger 等) -│ │ └── custom.go # [修改/创建] 仅用于注册挂载到根路径的自定义路由 +│ ├── root/ +│ │ └── custom.go # [修改] 若为根路径 API,在此处注册,将路由委派给 apps/custom │ └── v1/ -│ ├── v1.go # [新建] v1 路由统一入口,集中注册 v1 下的所有路径 -│ └── custom.go # [修改/创建] 仅用于注册 v1 下的定制路由,将路由委托给 apps/custom +│ └── custom.go # [修改] 若为 v1 API,在此处注册,将路由委派给 apps/custom └── apps/ └── custom/ ├── routers.go # [新建] HTTP Handlers (Gin),负责参数绑定、校验与响应 - ├── logics.go # [新建] 承载功能模块内闭环的业务逻辑(可以使用 logics.go 或 service.go) - └── errs.go # [新建] 仅存放业务特有的错误常量定义(可选) + ├── logics.go # [新建] 业务逻辑层:承载模块内闭环的纯 Go 业务逻辑,不依赖 gin.Context + └── errs.go # [新建] 存放模块特有的业务错误常量定义(可选) ``` --- ## 核心开发步骤 (Step-by-Step Flow) -### 步骤 1:如果有数据库变更,编写数据库迁移 -如果需要新表或字段,请参考 [database-migration](../database-migration/SKILL.md) 技能,在 `internal/db/migrator/goose/` 目录下编写迁移文件。在 `internal/model/` 中定义 GORM 数据模型。 +### 步骤 1:数据库定义与迁移 +如果自定义功能涉及新表或字段,请参考 [database-migration](../database-migration/SKILL.md) 技能,在 `internal/db/migrator/goose/` 目录下编写迁移文件并在 `internal/model/` 中定义 GORM 数据模型。 -### 步骤 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) +### 步骤 2:在模块内实现业务逻辑 (`logics.go` / `service.go`) +业务逻辑逻辑应当实现于 `internal/apps/custom/` 目录下: +- **优先使用纯函数(`logics.go`)**:定义接收 `context.Context` 且不依赖 `*gin.Context` 的函数,易于单元测试。 +- **有状态服务(`service.go`)**:若需注入依赖(如 DB 连接、外部客户端等),可定义 Service 结构体和构造函数。 -### 步骤 3:在 `internal/apps/custom/` 下编写 HTTP Handler -创建应用路由文件 `routers.go`,定义接口的请求和响应 DTO,编写 Handler 绑定参数并调用 Service,编写 Swagger 注释。 -参考示例:[handler_example.go](file:///Users/ryan/DEV/Go/Wavelet/.agent/skills/new-api/references/handler_example.go) +### 步骤 3:编写 HTTP Handler (`routers.go`) +在 `internal/apps/custom/routers.go` 中编写 Handler: +- 负责请求参数绑定与校验(使用 `ShouldBindJSON`/`ShouldBindQuery`)。 +- 负责提取 Session / 用户身份。 +- 调用业务逻辑层,并使用 `github.com/Rain-kl/Wavelet/internal/common/response` 统一返回响应: + - 成功时返回:`response.OK(data)` 或 `response.OKNil()` + - 失败时返回:`response.Err(msg)` +- 编写规范的 Swagger 注释。 -### 步骤 4:在 `internal/router/v1/custom.go` 中注册路由 -创建路由挂载函数: -```go -package v1 - -import ( - "github.com/Rain-kl/Wavelet/internal/apps/custom" - "github.com/gin-gonic/gin" -) - -func RegisterCustomRoutes(apiV1Router *gin.RouterGroup) { - customRouter := apiV1Router.Group("/custom") - { - customRouter.POST("/action", custom.DoActionHandler) - } -} -``` -并在 [v1.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/v1/v1.go) 中调用 `RegisterCustomRoutes(apiV1Router)`。 -最后在 [router.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/router.go) 中的 `/v1` 路由组内通过 `v1.RegisterV1Routes(apiV1Router, apiGroup)` 统一加载。 - -### 步骤 4.2:若需要注册根路径下的自定义路由 (Root Custom Routes) -若自定义接口需要挂载到根路径(例如 `/my-custom-endpoint`),则不应该挂载在 `/v1` 下,而是应当在 [custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/custom.go) 中进行注册: -```go -package root - -import ( - "github.com/Rain-kl/Wavelet/internal/apps/custom" - "github.com/gin-gonic/gin" -) - -// RegisterCustomRootRoutes registers custom business routes that belong to the root path. -func RegisterCustomRootRoutes(r *gin.Engine) { - r.GET("/my-custom-endpoint", custom.DoRootActionHandler) -} -``` -并在 [root.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/root.go) 内被统一调用(由核心路由 `router.go` 间接调用)。 +### 步骤 4:在自定义包中注册路由并委派 +根据 **路由归属判定表**,在 [root/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/custom.go) 或 [v1/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/v1/custom.go) 中编写注册代码,将路由路径绑定到步骤 3 中编写的 Handler。 --- -## 质量验证与门禁 (Verification Quality Gates) +## 质量验证门禁 (Quality Gates) -每当新增或修改 API 接口时,必须严格执行以下验证: - -1. **生成授权许可**: - 新增 Go 文件后,运行自动添加许可证头部命令: - ```bash - make license - ``` - -2. **生成 Swagger 文档**: - 在 Handler 编写完 `@Summary` 等 Swagger 注释后,必须生成更新: - ```bash - make swagger - ``` - *注意:若 Swagger 生成失败,请仔细排查注释格式或数据类型引用是否规范。* - -3. **静态代码检查与 Linting**: - 运行 `golangci-lint` 与前端 TypeScript 门禁,确保没有代码风格和类型安全隐患: - ```bash - make code-check - ``` - -4. **编译与功能测试**: - 运行整包编译与自动化测试: - ```bash - make build-test - ``` - ---- - -## 相关 Skills -* [go-context](../go-context/SKILL.md):了解如何在 Service 层正确传递取消信号和追踪 Trace。 -* [go-error-handling](../go-error-handling/SKILL.md):了解如何优雅地将业务错误向上传递,并在 Handler 层决定响应状态码。 -* [database-migration](../database-migration/SKILL.md):当新增接口需要额外表结构或默认配置种子时配合使用。 +每次新增或修改接口后,必须运行并验证以下各项: +1. **自动授权许可**:`make license`(新增 Go 文件时自动添加许可头) +2. **重新生成 Swagger 文档**:`make swagger`(若有 Swagger 注释修改) +3. **静态代码及风格检查**:`make code-check`(确保通过 golangci-lint 和前端 TS 检查) +4. **自动化单元测试**:`go test ./...`(确保所有测试 100% 通过) diff --git a/AGENTS.md b/AGENTS.md index d468a613..ec0a7432 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ - `internal/`:私有 Go 后端代码。 - `pkg/`:公共 Go 库/工具包(留作扩展或存放不依赖特定业务的通用代码)。 - `scripts/`:本地和 CI 辅助脚本。 -- `support-files/`:部署和数据库辅助文件。 +- `support-files/`:部署 and 数据库辅助文件。 - `bin/`:本地编译生成的二进制可执行文件。 - `data/`:本地运行时数据文件目录(如 PostgreSQL、Redis 数据等)。 - `uploads/`:本地文件上传存储目录。 @@ -106,28 +106,6 @@ - `frontend/scripts/`:前端构建和维护脚本。 - `frontend/.next/`、`frontend/out/`、`frontend/node_modules/`:本地生成或安装的产物,不作为业务源码编辑。 -重要的公共组件: - -- `frontend/components/common/admin/task-manager.tsx`:任务管理和分发入口。 -- `frontend/components/common/admin/task-executions.tsx`:任务执行日志和重试 UI。 -- `frontend/components/common/admin/task-schedules.tsx`:定时任务管理 UI。 -- `frontend/components/common/admin/system.tsx`:系统参数管理。 -- `frontend/components/common/admin/files.tsx`:上传文件管理。 -- `frontend/components/common/admin/users.tsx`:用户管理。 -- `frontend/components/common/admin/access-analytics.tsx`:访问分析与图表展示。 -- `frontend/components/common/admin/access-logs.tsx`:访问日志审计 UI。 -- `frontend/components/common/admin/app-logs.tsx`:应用日志查看 UI。 -- `frontend/components/common/admin/database-manage.tsx`:数据库备份、恢复与管理 UI。 -- `frontend/components/common/admin/file-list.tsx`:管理员文件列表管理组件。 -- `frontend/components/common/admin/file-stats.tsx`:文件存储状态与统计 UI。 -- `frontend/components/common/admin/status.tsx`:系统运行状态与监控 UI。 -- `frontend/components/common/admin/storage-config-tab.tsx`:存储策略与配置 tab 页。 -- `frontend/components/common/admin/system-logs.tsx`:系统日志查看组件。 -- `frontend/components/common/general/manage-pannel.tsx`:通用列表/详情管理器。 -- `frontend/components/common/general/password-dialog.tsx`:敏感操作密码确认对话框。 -- `frontend/components/common/settings/system-settings.tsx`:管理员图形化系统设置。 -- `frontend/components/common/user/file-manager.tsx`:用户端文件管理组件。 - ## 开发要求 @@ -160,25 +138,19 @@ Handler 规范: 路由与模块: -- 仅在 `internal/router/router.go` 中注册路由。 -- 在 `internal/apps//` 中,使用: - - `routers.go` 或 `controllers.go` 作为 HTTP Handler。 - - `middlewares.go` 作为模块特定的中间件。 - - `errs.go` 仅包含字符串错误常量。 - - `constants.go` 包含非错误的业务常量。 -- 对于管理(Admin)模块,首选 `internal/apps/admin//`。 -- 如果 Handler 文件超过 600 行、包含复杂的多个步骤逻辑,或混合了独立领域,请将业务逻辑拆分到 `logic.go` 或 `logics.go` 中。保持 `routers.go` 仅用于绑定、调用逻辑和响应。 +- 仅在 `internal/router/router.go` 中作为统一高层入口进行路由分发委派,不允许在 `router.go` 中直接挂载业务 Handler。 +- 关于所有的路由归属划分、接口开发隔离防线以及详细的注册和开发步骤,请直接阅读并严格遵循 [new-api](file:///Users/ryan/DEV/Go/Wavelet/.agent/skills/new-api/SKILL.md) 技能。 中间件: -- 全局中间件属于路由设置:`gin.Recovery()`、`otelgin.Middleware()`、日志中间件和 session 中间件。 +- 全局中间件属于路由设置:`gin.Recovery()`、`otelgin.Middleware()`、日志中间件 and session 中间件。 - 对于登录路由组,使用 `oauth.LoginRequired()`。 - 对于管理路由组,使用 `admin.LoginAdminRequired()`。 配置管理: - 运行时代码从 `config.Config` 中读取配置,绝对不要直接从 `os.Getenv()` 中读取。 -- 当添加配置时,同时更新 `config.example.yaml` 和 `internal/config/model.go`。 +- 当添加配置时,同时更新 `config.example.yaml` and `internal/config/model.go`。 数据库操作: @@ -195,40 +167,34 @@ Handler 规范: - 不要从 `internal/util/` 中导入 `github.com/gin-gonic/gin`、`gorm.io/gorm`、`github.com/gin-contrib/sessions` 或 HTTP 中间件/框架包。 - 如果实用工具逻辑需要 web 胶水,请将纯验证/计算保留在 `internal/util/` 中,并将 Gin 中间件/响应处理放在 `internal/apps/` 中。 -管理(Admin)模块工作流: +新增接口与模块开发工作流: -1. 在 `internal/model/` 中定义或扩展模型。 -2. 在 `internal/db/migrator/goose/` 下添加 goose SQL 迁移。 -3. 创建 `internal/apps/admin//routers.go` 和可选的 `errs.go`。 -4. 在 `internal/router/router.go` 中注册路由。 -5. 运行 `make swagger`。 +- 关于自定义业务接口(如 Admin/User/Custom 模块等)的详细包职责、文件结构和核心开发步骤,请直接阅读并严格遵循 [new-api](file:///Users/ryan/DEV/Go/Wavelet/.agent/skills/new-api/SKILL.md) 技能。 ### 前端规则 在进行任何 Next.js 工作之前,请在 `node_modules/next/dist/docs/` 中找到并阅读相关文档。您的训练数据已过时 —— 这些文档是唯一的真理来源。 +请直接查看并参考项目提供的示例和 Demo 代码:[frontend/app/(main)/admin/demo](file:///Users/ryan/DEV/Go/Wavelet/frontend/app/(main)/admin/demo)。 + 样式规范: - shadcn/ui 基础组件应该使用它们的 `variant` 系统和全局 CSS 变量。当组件的变体(variant)应该拥有某种外观时,不要在业务 `className` 中硬编码颜色、背景或阴影。 - 如果现有的变体不足以满足需求,请扩展 shadcn/ui 组件的变体,而不是硬编码一次性的颜色。 -- 使用 Lucide 图标来满足常见的图标需求。将自定义图标作为命名导出放在 `frontend/components/icons/` 中。 页面标题栏规范 (新人开发与重构必读): -- **结构极简与高度专注**:标题栏应绝对干净,**禁止**在标题正下方放置任何描述性文本、小字副标题或段落(如 `

` 或 `CardDescription`)。这样能让用户迅速聚焦于业务主功能,避免低效的信息噪声。 - **容器与对齐机制**: - - 标题容器统一使用 `flex items-center gap-2`。如果右侧有操作按钮(如“新增”、“刷新”),请使用 `justify-between` 布局让操作区与标题双向分布。 - - 为了确保所有页面在进入/切换时,顶部的呼吸感和视觉高度完全一致,页面最外层容器**必须**统一使用 `py-6 px-1` 或 `py-6` 进行上边距对齐。 -- **无下边框**:标题栏下方**严禁**带有横线(禁用 `border-b` / `border-border`),让页面在纵向上保持连贯性与开阔感。 + - 标题容器统一使用 `flex items-center gap-2`。如果右侧有操作按钮(如“新增”、“刷新”),请使用 `justify-between` 布局让操作区与标题双向分布。 + - 为了确保所有页面在进入/切换时,顶部的呼吸感和视觉高度完全一致,页面最外层容器**必须**统一使用 `py-6 px-1` 或 `py-6` 进行上边距对齐。 - **图标标准**:图标作为视觉辅助点缀,**必须**直接嵌套在标题容器中,直接使用 Lucide 图标组件,样式大小限制为 `size-5 text-primary`。**严禁**为图标包裹任何背景小卡片、圆角边框或额外的修饰容器。 - **标题文字标准**:标题文字使用且仅使用 `h1 className="text-2xl font-semibold tracking-tight"`。不要自行定义字号、字量(如使用 `font-bold`)或添加任何渐变色,保持整个系统的字形规范化。 - **Tabs 模块化与文件拆分规范**:凡是带有多个 Tab 页切换的复杂页面,**禁止**将所有 Tab 的渲染逻辑堆积在同一个主文件内。每个 Tab 的具体渲染内容必须单独拆分为独立的 React 组件文件(如 `tabs/events-tab.tsx`)。主页面文件应该仅用于导入子组件、注册 Tabs 触发器以及管理 Tab 的切换激活状态。这有利于防止单文件过大(避免单文件行数超过 600 行限制),并大幅度提高代码的可读性与编译维护效率。 - **扁平化结构与避免冗余中间件**:为了消除无意义的“中间代理文件”,所有作为路由物理入口的 Tabs 状态维护、骨架及外层布局代码,**必须**直接定义在 Next.js 的 `app/` 页面文件(即 `page.tsx`)中。禁止在 `page.tsx` 中仅写一个单纯的 `` 转发,而在外部新建一个同名中转容器。 - **复杂度驱动的组件拆分规范**:组件的拆分不应局限于“跨页面复用”。当一个路由页面的复杂度变高时(如渲染逻辑膨胀、存在大型嵌套弹窗或多层状态管理,如单文件代码行数超过 600 行),必须主动将其拆分为子组件以维持单文件的高可读性与低耦合度。拆分时遵循就近原则:特定于该路由且不复用的子组件应放置在最邻近该路由的特征目录(Feature Folder,如 `components/` 局部文件夹)中;只有真正具备跨页面复用价值的通用业务/基础 UI 组件才应存放在全局 `components/` 共享目录下。 - - **最佳实践标杆案例(数据管理 `/admin/database`)**: - 该页面由于整合了“运行状态概览”、“物理表网格浏览器”、“磁盘缓存管理”和“SQL 交互控台”多个复杂大区块,重构前单文件接近 1000 行。 - 重构后,主页面 `page.tsx` 仅做高级页面骨架与排版排布,维护全局刷新机制与终端视图切换;而“数据表浏览器 (`table-browser.tsx`)”、“缓存管理 (`cache-manager.tsx`)”与“SQL 终端 (`sql-console.tsx`)”等独立高状态密度区块均被抽离为局部子组件,存放在 `frontend/app/(main)/admin/database/components/`。这保证了代码结构层次清晰、单文件小巧好维护。所有复杂页面的新开发和重构必须遵循此模式。 - + - **最佳实践标杆案例(数据管理 `/admin/database`)**: + 该页面由于整合了“运行状态概览”、“物理表网格浏览器”、“磁盘缓存管理”和“SQL 交互控台”多个复杂大区块,重构前单文件接近 1000 行。 + 重构后,主页面 `page.tsx` 仅做高级页面骨架与排版排布,维护全局刷新机制与终端视图切换;而“数据表浏览器 (`table-browser.tsx`)”、“缓存管理 (`cache-manager.tsx`)”与“SQL 终端 (`sql-console.tsx`)”等独立高状态密度区块均被抽离为局部子组件,存放在 `frontend/app/(main)/admin/database/components/`。这保证了代码结构层次清晰、单文件小巧好维护。所有复杂页面的新开发和重构必须遵循此模式。 页面宽度: @@ -255,3 +221,4 @@ frontend/lib/services// - 服务类继承 `BaseService`,定义 `basePath`,并暴露有类型的静态方法。 - 在 `frontend/lib/services/index.ts` 中注册新服务。 + diff --git a/internal/router/root/default.go b/internal/router/root/default.go index 61616fe4..9622fb1a 100644 --- a/internal/router/root/default.go +++ b/internal/router/root/default.go @@ -6,6 +6,7 @@ package root import ( _ "github.com/Rain-kl/Wavelet/docs" // Swagger documentation generation setup publicconfig "github.com/Rain-kl/Wavelet/internal/apps/config" + "github.com/Rain-kl/Wavelet/internal/apps/health" "github.com/Rain-kl/Wavelet/internal/apps/upload" "github.com/Rain-kl/Wavelet/internal/config" "github.com/gin-gonic/gin" @@ -25,4 +26,7 @@ func RegisterDefaultRootRoutes(r *gin.Engine) { if !config.Config.App.IsProduction() { r.GET(config.Config.App.APIPrefix+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) } + + // 4. Health check + r.GET(config.Config.App.APIPrefix+"/health", health.Health) } diff --git a/internal/router/v1/public.go b/internal/router/v1/public.go deleted file mode 100644 index cfda0a64..00000000 --- a/internal/router/v1/public.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - -// Package v1 contains router registrations for API V1 -package v1 - -import ( - capApp "github.com/Rain-kl/Wavelet/internal/apps/cap" - publicconfig "github.com/Rain-kl/Wavelet/internal/apps/config" - "github.com/Rain-kl/Wavelet/internal/apps/health" - "github.com/gin-gonic/gin" -) - -// RegisterPublicRoutes registers all public routes (captcha, health, config). -func RegisterPublicRoutes(apiV1Router *gin.RouterGroup, apiGroup *gin.RouterGroup) { - // CAPTCHA - registerCaptchaRoutes(apiGroup) - - // Health - apiGroup.GET("/health", health.Health) - - // Config (public) - registerConfigRoutes(apiV1Router) -} - -func registerCaptchaRoutes(apiGroup *gin.RouterGroup) { - capGroup := apiGroup.Group("/cap") - { - capGroup.POST("/challenge", capApp.Challenge) - capGroup.POST("/redeem", capApp.Redeem) - } -} - -func registerConfigRoutes(apiV1Router *gin.RouterGroup) { - configRouter := apiV1Router.Group("/config") - { - configRouter.GET("/public", publicconfig.GetPublicConfig) - } -} diff --git a/internal/router/v1/user.go b/internal/router/v1/user.go index f9647101..d6ccd33e 100644 --- a/internal/router/v1/user.go +++ b/internal/router/v1/user.go @@ -8,6 +8,7 @@ import ( "context" capApp "github.com/Rain-kl/Wavelet/internal/apps/cap" + publicconfig "github.com/Rain-kl/Wavelet/internal/apps/config" "github.com/Rain-kl/Wavelet/internal/apps/oauth" "github.com/Rain-kl/Wavelet/internal/apps/upload" "github.com/Rain-kl/Wavelet/internal/apps/user" @@ -15,18 +16,39 @@ import ( "github.com/gin-gonic/gin" ) -// RegisterUserRoutes registers all user-related, oauth and upload routes. -func RegisterUserRoutes(apiV1Router *gin.RouterGroup) { - // OAuth +// RegisterUserRoutes registers all user-related, oauth, upload, and public routes. +func RegisterUserRoutes(apiV1Router *gin.RouterGroup, apiGroup *gin.RouterGroup) { + // 1. CAPTCHA + registerCaptchaRoutes(apiGroup) + + // 2. Config (public) + registerConfigRoutes(apiV1Router) + + // 3. OAuth registerOAuthRoutes(apiV1Router) - // User + // 4. User registerUserRoutes(apiV1Router) - // Upload + // 5. Upload registerUploadRoutes(apiV1Router) } +func registerCaptchaRoutes(apiGroup *gin.RouterGroup) { + capGroup := apiGroup.Group("/cap") + { + capGroup.POST("/challenge", capApp.Challenge) + capGroup.POST("/redeem", capApp.Redeem) + } +} + +func registerConfigRoutes(apiV1Router *gin.RouterGroup) { + configRouter := apiV1Router.Group("/config") + { + configRouter.GET("/public", publicconfig.GetPublicConfig) + } +} + func registerOAuthRoutes(apiV1Router *gin.RouterGroup) { apiV1Router.GET("/oauth/sources", oauth.GetLoginSources) apiV1Router.GET("/oauth/login", oauth.GetLoginURL) diff --git a/internal/router/v1/v1.go b/internal/router/v1/v1.go index c8681d56..42213796 100644 --- a/internal/router/v1/v1.go +++ b/internal/router/v1/v1.go @@ -10,15 +10,12 @@ import ( // RegisterV1Routes registers all routes under API V1. func RegisterV1Routes(apiV1Router *gin.RouterGroup, apiGroup *gin.RouterGroup) { - // 1. Public (captcha, health, config) - RegisterPublicRoutes(apiV1Router, apiGroup) + // 1. User & Public routes (OAuth, User, Upload, CAPTCHA, Health, Config) + RegisterUserRoutes(apiV1Router, apiGroup) - // 2. OAuth, User, Upload - RegisterUserRoutes(apiV1Router) - - // 3. Admin + // 2. Admin routes RegisterAdminRoutes(apiV1Router) - // 4. Register custom business routes + // 3. Register custom business routes RegisterCustomRoutes(apiV1Router) }