From 3f163843769b0bad59ce94cdf796a9efab295f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E8=82=B2=E6=96=B0?= <2635131263@qq.com> Date: Sat, 17 Oct 2026 10:32:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(lyra):=20=E5=90=AF=E5=8A=A8=20HTTP=20?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 注册 GET /hello,使用统一响应结构并添加路由回归测试 - 使用 lyra.toml 配置 Gin 模式与监听地址,处理启动错误 - 明确 handler/service 业务分包约定并同步文档 --- go/projects/lyra/README.md | 35 +++++++++-------- go/projects/lyra/cmd/lyra/main.go | 23 ++++++++--- go/projects/lyra/docs/architecture.md | 39 ++++++++++++------- .../2026-10-17-business-package-layout.md | 30 ++++++++++++++ go/projects/lyra/etc/config.toml | 7 ---- go/projects/lyra/etc/lyra.toml | 7 ++++ go/projects/lyra/internal/handler/AGENTS.md | 1 + go/projects/lyra/internal/handler/hello.go | 13 +++++++ go/projects/lyra/internal/router/AGENTS.md | 5 ++- go/projects/lyra/internal/router/router.go | 11 ++++++ .../lyra/internal/router/router_test.go | 28 +++++++++++++ go/projects/lyra/internal/service/AGENTS.md | 1 + 12 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 go/projects/lyra/docs/plans/2026-10-17-business-package-layout.md delete mode 100644 go/projects/lyra/etc/config.toml create mode 100644 go/projects/lyra/etc/lyra.toml create mode 100644 go/projects/lyra/internal/handler/hello.go create mode 100644 go/projects/lyra/internal/router/router.go create mode 100644 go/projects/lyra/internal/router/router_test.go diff --git a/go/projects/lyra/README.md b/go/projects/lyra/README.md index 0b0907a..0ea0e8e 100644 --- a/go/projects/lyra/README.md +++ b/go/projects/lyra/README.md @@ -5,37 +5,40 @@ ## 当前状态 -已搭建目录骨架与程序入口 `main.go`,各内部目录的职责与依赖约束记录在 `AGENTS.md` 中。 -`internal/api/response.go` 定义公共响应结构;仅含 `AGENTS.md` 的目录暂不构成 Go 包。 -业务逻辑、数据库访问、HTTP API 与迁移文件均未实现;架构文档中标注示例的 -文件(links 等)尚未创建。当前运行程序后直接退出——不连接数据库, -也不启动 HTTP 服务。 +已搭建目录骨架、程序入口 `main.go` 与 HTTP 通路:启动时加载 `etc/lyra.toml`, +注册 `GET /hello`(经 `httpx.OK` 输出响应)并启动 HTTP 服务;路由注册配有 +httptest 回归测试,不监听实际端口、不连接数据库。各内部目录的职责与依赖 +约束记录在 `AGENTS.md` 中。数据库访问、迁移文件与真实业务逻辑均未实现; +架构文档中标注“待新增”的文件(links 等)尚未创建。 ## 目录职责 | 路径 | 职责 | |---|---| -| `cmd/lyra/` | 程序入口:启动、依赖组装、退出清理(待实现) | -| `internal/api/` | HTTP 请求/响应 DTO,按资源分文件 | +| `cmd/lyra/` | 程序入口:启动、依赖组装、退出清理 | +| `internal/api/` | HTTP 请求/响应 DTO,按资源分文件(平铺) | | `internal/config/` | 配置定义与加载 | -| `internal/postgres/` | PostgreSQL 连接(pgx stdlib) | -| `internal/router/` | 路由注册、项目路径前缀 | -| `internal/handler/` | 参数绑定、DTO 转换、响应输出 | -| `internal/service/` | 业务规则、事务协调 | -| `internal/model/` | 数据结构定义与数据访问(SQL) | -| `internal/httpx/` | 公共响应辅助,按需实现 | +| `internal/postgres/` | PostgreSQL 连接(pgx stdlib,待实现) | +| `internal/router/` | 路由注册、项目路径前缀(router.go,平铺) | +| `internal/handler/` | 参数绑定、DTO 转换、响应输出;可按业务分包,服务级简单接口平铺在根包 | +| `internal/service/` | 业务规则、事务协调;可按业务分包,服务级简单接口平铺在根包 | +| `internal/model/` | 数据结构定义与数据访问(SQL,平铺) | +| `internal/httpx/` | 公共响应辅助 | | `migrations/` | Goose SQL 迁移文件(暂无) | | `docs/` | 架构与计划文档 | ## 运行 -Go 命令在本目录(module 根)下执行: +Go 命令在本目录(module 根)下执行;配置路径 `etc/lyra.toml` 按当前工作目录 +解析,换目录运行会找不到配置: ```bash -go run ./cmd/lyra # 当前直接退出,不连接数据库、不启动 HTTP -go test ./... # 当前无测试文件,仅验证各包可编译 +go run ./cmd/lyra # 启动 HTTP 服务,监听地址与端口见 etc/lyra.toml +go test ./... # 含路由回归测试 ``` +配置中的 `app.env` 直接传给 `gin.SetMode`,仅支持 `debug`/`release`/`test`。 + ## 技术栈 已引入 Gin 与 Viper(读取 TOML 配置),go.sum 已生成; diff --git a/go/projects/lyra/cmd/lyra/main.go b/go/projects/lyra/cmd/lyra/main.go index 02dfe18..53ce603 100644 --- a/go/projects/lyra/cmd/lyra/main.go +++ b/go/projects/lyra/cmd/lyra/main.go @@ -1,20 +1,31 @@ // Command lyra 是 Lyra 的程序入口,负责启动、依赖组装与退出清理。 -// -// 当前仅搭建目录骨架:运行后直接退出,不连接数据库,也不启动 HTTP 服务。 package main import ( "fmt" "lyra/internal/config" + "lyra/internal/router" + + "github.com/gin-gonic/gin" ) func main() { // 加载配置(internal/config) - cfg, err := config.Load("etc/config.toml") + cfg, err := config.Load("etc/lyra.toml") if err != nil { panic(err) } - fmt.Printf("%+v\n", cfg) - // TODO: 组装依赖——建立 PostgreSQL 连接(internal/postgres)、注册路由(internal/router)。 - // TODO: 启动 HTTP 服务,并在退出时关闭连接、释放资源。 + // TODO: 组装依赖——建立 PostgreSQL 连接(internal/postgres)。 + gin.SetMode(cfg.App.Env) + gEngine := gin.Default() + //没有反向代理,在启动前关闭代理信任: + if err := gEngine.SetTrustedProxies(nil); err != nil { + panic(err) + } + router.RegisterRoutes(gEngine) + // TODO: 退出时关闭连接、释放资源。 + addr := fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port) + if err := gEngine.Run(addr); err != nil { + panic(err) + } } diff --git a/go/projects/lyra/docs/architecture.md b/go/projects/lyra/docs/architecture.md index 70e761e..3a1f47b 100644 --- a/go/projects/lyra/docs/architecture.md +++ b/go/projects/lyra/docs/architecture.md @@ -1,8 +1,8 @@ # Lyra 目标结构 -日期:2026-10-16 +日期:2026-10-17 -本文记录已确认的目标目录结构与职责边界,并与当前真实实现同步。目录树是**目标结构**,并非全部已存在:当前只有 `cmd/lyra/main.go`、`internal/config/config.go`、`internal/api/{code,response}.go`、`internal/httpx/response.go` 是 Go 代码;`internal/{handler,model,postgres,router,service}` 仅含 `AGENTS.md`(记录职责与依赖约束),暂不构成 Go 包。选型与讨论过程见 [plans/2026-10-16-lyra-architecture.md](plans/2026-10-16-lyra-architecture.md)。 +本文记录已确认的目标目录结构与职责边界,并与当前真实实现同步。目录树是**目标结构**,并非全部已存在:标注“待新增”的文件或目录尚未创建,当前已有 Go 代码的是 `cmd/lyra`、`internal/{api,config,handler,httpx,router}`;`internal/{model,postgres,service}` 仅含 `AGENTS.md`(记录职责与依赖约束),暂不构成 Go 包。选型与讨论过程见 [plans/2026-10-16-lyra-architecture.md](plans/2026-10-16-lyra-architecture.md);handler/service 业务分包的调整记录见 [plans/2026-10-17-business-package-layout.md](plans/2026-10-17-business-package-layout.md)。 ## 已确认技术栈 @@ -11,10 +11,12 @@ ## 当前进度 -- 程序入口按相对工作目录的 `etc/config.toml` 加载配置(Viper,`mapstructure` 标签),打印结果后退出;不启动 HTTP 服务,也不连接数据库。 +- 程序入口在 module 根目录以 `go run ./cmd/lyra` 启动:按相对当前工作目录的 `etc/lyra.toml` 加载配置(Viper,`mapstructure` 标签),已启动 HTTP 服务,监听地址与端口来自配置文件;换目录执行会找不到配置。 +- `app.env` 当前直接传给 `gin.SetMode`,取值仅 `debug`/`release`/`test`(Gin 的合法值);没有 dev/prod 等环境映射。 +- 已注册 `GET /hello`:`internal/router/router.go` 的 `RegisterRoutes` 将其指向 `handler.Hello`,经 `httpx.OK` 输出 `api.Response`;路由注册配有 httptest 回归测试,不监听实际端口、不连接数据库。 - 已定义公共响应契约:`api.Response`(success/data/error)、`api.ErrorInfo`(code/message/debug)与错误码类型 `api.ErrorCode` 及其默认文案 `Message()`。 - 已实现响应输出辅助 `httpx.OK`、`httpx.Fail`。 -- 尚未实现:业务逻辑、路由注册、数据库访问与迁移 SQL;配置校验、环境变量覆盖与默认值;`ErrorInfo.Debug` 的填充与按环境开关。日志方案未定。 +- 尚未实现:真实业务逻辑(`/hello` 只是 HTTP 通路示例)、数据库访问与迁移 SQL;配置校验、环境变量覆盖与默认值;`ErrorInfo.Debug` 的填充与按环境开关。日志方案未定。 ## 目录结构 @@ -22,7 +24,7 @@ lyra/ ├── cmd/lyra/main.go # 启动、依赖组装、退出清理 ├── internal/ -│ ├── api/ # HTTP 请求/响应 DTO、错误码与响应契约 +│ ├── api/ # HTTP 请求/响应 DTO、错误码与响应契约(平铺,按资源分文件) │ │ ├── response.go # Response/ErrorInfo 结构 │ │ ├── code.go # ErrorCode 与错误码常量、默认文案 │ │ ├── links.go # links 资源 DTO 示例(待新增) @@ -33,12 +35,15 @@ lyra/ │ ├── postgres/ │ │ └── postgres.go # 数据库连接(待新增) │ ├── router/ -│ │ └── router.go # 路由注册、项目路径前缀(待新增) +│ │ └── router.go # 路由注册、项目路径前缀 │ ├── handler/ -│ │ └── links.go # 参数绑定、DTO 转换、响应输出(待新增) +│ │ ├── hello.go # GET /hello 处理(服务级简单接口,平铺在根包) +│ │ ├── links.go # 参数绑定、DTO 转换、响应输出(待新增) +│ │ └── polaris/ # 按明确业务分包的示例(待新增,不预建空包) │ ├── service/ │ │ ├── links.go # 业务规则、事务协调(待新增) -│ │ └── links_test.go # 测试与源文件同目录(待新增) +│ │ ├── links_test.go # 测试与源文件同目录(待新增) +│ │ └── polaris/ # 按明确业务分包的示例(待新增,不预建空包) │ ├── model/ │ │ ├── link.go # Link 数据结构定义(待新增) │ │ ├── link_model.go # 数据访问对象:CRUD 与 SQL(待新增) @@ -46,8 +51,8 @@ lyra/ │ └── httpx/ │ └── response.go # 公共响应输出辅助(OK/Fail) ├── etc/ -│ └── config.toml # 运行配置(App/HTTP) -├── migrations/ # Goose SQL 迁移文件(暂无) +│ └── lyra.toml # 运行配置(App/HTTP) +├── migrations/ # Goose SQL 迁移文件(暂无迁移,仅 README) ├── docs/ │ ├── plans/ │ └── architecture.md # 本文 @@ -58,21 +63,25 @@ lyra/ - 标注“待新增”的文件只是职责分布示例,随实现按需新增,不预建空文件或空目录;`internal/` 各目录均以 `AGENTS.md` 记录职责与依赖约束。 - model 下的 `link*.go` 展示一个资源在各层的文件分布;分类、设置等资源按同样方式在 api/handler/service/model 新增对应文件。具体迁移文件名随首个迁移确定。 +- `handler/polaris/`、`service/polaris/` 仅示意业务分包的形态:实际分包与否随业务边界明确时决定,路由、api、model 不照搬同样的分包。 ## 组织原则 -- 不按业务项目建代码子目录(无 polaris 等项目目录);项目通过 `/api/polaris` 等路由前缀区分,代码按技术职责分组,包内文件按资源划分。 -- 不保留 platform/modules 分类层;配置在 internal/config,连接在 internal/postgres,HTTP 辅助在 internal/httpx。 -- 简单接口不强制走完整三层,也不按每张表机械复制层级。 +- handler、service 可以按明确业务分包(如 `polaris/`),仅在业务边界清晰时新增;服务级简单接口(如 `hello`)保留在各自根包内平铺,不强制分包。 +- router、api、model 等其他层暂时平铺,不按业务项目建代码子目录;项目通过 `/api/polaris` 等路由前缀区分,包内文件按资源划分。 +- 公共基础能力全局唯一:配置在 internal/config,连接在 internal/postgres,HTTP 辅助在 internal/httpx,不随业务项目复制或按项目再拆。 +- 不保留 platform/modules 分类层;简单接口不强制走完整三层,也不按每张表机械复制层级。 +- 不预建空业务包,分包随实际业务代码一起落地。 ## 层间边界 -- 主调用链为 handler→service→model→PostgreSQL;service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。 +- 真实业务的主调用链为 handler→service→model→PostgreSQL;服务级简单接口可以不经过 service/model(当前 `/hello` 即是)。service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。 - 请求/响应 DTO 集中在 internal/api,与内部模型分开;即使初期字段相近,也不默认把数据库模型直接暴露为响应,接受必要的显式转换代码。 - handler 引用 API DTO 完成绑定与转换;service/model 不依赖 Gin,也不依赖 internal/api DTO;api 包不反向引用 handler/service/model,避免传输契约与实现耦合或循环引用。 - internal/api 拥有响应契约与业务错误码(`Response`/`ErrorInfo`/`ErrorCode`);不依赖 Gin。 - internal/httpx 负责响应输出:`OK` 固定 HTTP 200;`Fail` 接收 HTTP 状态码与业务错误码,`message` 为空时回退错误码默认文案。HTTP 状态码表示传输/结果类别,`ErrorCode` 表示自定义业务错误,两者分开。 -- 配置由入口 `main` 加载后组装并传递(约定/目标):`internal/config` 只负责配置定义与加载,运行配置来自 `etc/config.toml`;当前 `main` 仅加载并打印,尚未向其他组件传递。 +- internal/router 负责把 handler 挂到路由上:当前注册 `GET /hello` 指向 `handler.Hello`,后续按业务项目路径前缀(如 `/api/polaris`)扩展。 +- 配置由入口 `main` 加载后组装并传递:`internal/config` 只负责配置定义与加载,运行配置来自 `etc/lyra.toml`;当前 `app.env` 直接传给 `gin.SetMode`(仅 `debug`/`release`/`test`),HTTP 地址端口供启动使用,数据库相关配置尚未有消费方。 - model 承载数据结构定义与数据访问:Squirrel/SQL 构造、sqlx 执行与结果映射,驱动为 pgx stdlib;不是 ORM 自动持久化,也不要求每种类型机械建立一套结构。 - service/model 使用标准 context.Context,不接收 gin.Context。 diff --git a/go/projects/lyra/docs/plans/2026-10-17-business-package-layout.md b/go/projects/lyra/docs/plans/2026-10-17-business-package-layout.md new file mode 100644 index 0000000..32b9192 --- /dev/null +++ b/go/projects/lyra/docs/plans/2026-10-17-business-package-layout.md @@ -0,0 +1,30 @@ +# Handler 与 Service 的业务分包调整 + +## 已确认范围 + +- handler、service 可以按明确业务分包;服务级简单接口保留在根包。 +- 将 `/hello` 从 `internal/handler/lyra` 移至 `internal/handler`,同步路由引用;入口路由文件使用 `router.go`。 +- router、api、model 等其他层暂时保持平铺,公共基础能力不随业务重复拆分。 +- 不预建空业务包,不改变 `/hello` 路径、响应契约或配置环境语义,不顺带改变启动错误处理。 +- 同步架构文档、README、相关目录约定与过时的入口注释;历史方案保留为历史记录。 + +## 工作分工与顺序 + +1. 代码执行 lane:负责 handler 移动、router 引用和文件名、入口过时注释,以及路由回归测试。 +2. 文档执行 lane:并行负责 architecture.md、README.md、必要的目录 AGENTS.md,解释业务分包与当前实现状态。 +3. 父代理:整合两条 lane 的结果,审查最终差异并登记验证结论。 + +## 验证路径与归属 + +- 代码 lane:新增或利用 `httptest` 路由测试,不监听实际端口、不连接数据库,确认 `GET /hello` 返回 HTTP 200 且维持现有响应契约。 +- 父代理:在 Lyra module 根执行 `go test ./...`、`go vet ./...`,确认移动后的包引用与编译正确;若执行 lane 已完成同等检查且最终代码未变则复用证据。 +- 文档 lane:核对当前配置文件名、HTTP 启动状态与目录组织说明,不把未来规划写成已实现能力。 +- 父代理:检查最终 diff、旧包路径残留和敏感信息风险,保留用户既有未提交改动。 + +## 状态 + +- 代码与文档 lane 均已完成,最终差异已审查。 +- `/hello` 已移至 handler 根包,路由引用已更新;未创建空业务包。 +- 架构文档、README 与 handler/service/router 的目录约定已同步。 +- 验证通过:`go test ./...`、`go vet ./...`、`git diff --check`;路由测试确认 HTTP 200 与原有 JSON 响应一致。 +- 验证未监听实际端口、未连接数据库;保留工作区既有配置与其他用户改动。 diff --git a/go/projects/lyra/etc/config.toml b/go/projects/lyra/etc/config.toml deleted file mode 100644 index c5c267b..0000000 --- a/go/projects/lyra/etc/config.toml +++ /dev/null @@ -1,7 +0,0 @@ -[app] -name = "lyra" -env = "dev" - -[http] -host = "0.0.0.0" -port = 7777 diff --git a/go/projects/lyra/etc/lyra.toml b/go/projects/lyra/etc/lyra.toml new file mode 100644 index 0000000..03132a7 --- /dev/null +++ b/go/projects/lyra/etc/lyra.toml @@ -0,0 +1,7 @@ +[app] +name = "lyra" +env = "debug" #gin支持 release | test | debug 3种mode,如果需要自定义,需要映射到这3种mode中去,gin的默认模式是debug模式 + +[http] +host = "0.0.0.0" +port = 7777 diff --git a/go/projects/lyra/internal/handler/AGENTS.md b/go/projects/lyra/internal/handler/AGENTS.md index 912f319..0a7bd36 100644 --- a/go/projects/lyra/internal/handler/AGENTS.md +++ b/go/projects/lyra/internal/handler/AGENTS.md @@ -3,3 +3,4 @@ - 负责参数绑定、API DTO 与内部模型的相互转换、响应输出。 - 引用 internal/api 的 DTO,使用 Gin 完成绑定与响应。 - 调用 service 时传递标准 context.Context,不下传 gin.Context。 +- 可按明确业务分包(如 `polaris/`);服务级简单接口保留在根包平铺,不预建空业务包。 diff --git a/go/projects/lyra/internal/handler/hello.go b/go/projects/lyra/internal/handler/hello.go new file mode 100644 index 0000000..3d38317 --- /dev/null +++ b/go/projects/lyra/internal/handler/hello.go @@ -0,0 +1,13 @@ +package handler + +import ( + "lyra/internal/httpx" + + "github.com/gin-gonic/gin" +) + +func Hello() gin.HandlerFunc { + return func(c *gin.Context) { + httpx.OK(c, "Hello, World!") + } +} diff --git a/go/projects/lyra/internal/router/AGENTS.md b/go/projects/lyra/internal/router/AGENTS.md index 6ccc072..c1107a8 100644 --- a/go/projects/lyra/internal/router/AGENTS.md +++ b/go/projects/lyra/internal/router/AGENTS.md @@ -1,5 +1,6 @@ # router 包 -- 负责路由注册与项目路径前缀,例如 `/api/polaris`。 +- 负责路由注册与项目路径前缀,例如 `/api/polaris`;入口文件为 `router.go`,对外暴露 `RegisterRoutes`。 - Gin 的路由与请求处理逻辑留在 HTTP 层;service 和 model 不依赖 Gin。 -- 路由随资源实现逐步补充,不按业务项目建立代码子目录。 +- 路由随资源实现逐步补充;包暂时平铺,不按业务项目建立代码子目录。 +- 路由变更以 httptest 回归测试守护,不监听实际端口、不连接数据库。 diff --git a/go/projects/lyra/internal/router/router.go b/go/projects/lyra/internal/router/router.go new file mode 100644 index 0000000..1866232 --- /dev/null +++ b/go/projects/lyra/internal/router/router.go @@ -0,0 +1,11 @@ +package router + +import ( + "lyra/internal/handler" + + "github.com/gin-gonic/gin" +) + +func RegisterRoutes(router *gin.Engine) { + router.GET("/hello", handler.Hello()) +} diff --git a/go/projects/lyra/internal/router/router_test.go b/go/projects/lyra/internal/router/router_test.go new file mode 100644 index 0000000..6a4f8ef --- /dev/null +++ b/go/projects/lyra/internal/router/router_test.go @@ -0,0 +1,28 @@ +package router_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "lyra/internal/router" + + "github.com/gin-gonic/gin" +) + +func TestHelloRoute(t *testing.T) { + gin.SetMode(gin.TestMode) + engine := gin.New() + router.RegisterRoutes(engine) + + recorder := httptest.NewRecorder() + engine.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/hello", nil)) + + if recorder.Code != http.StatusOK { + t.Fatalf("GET /hello status = %d, want %d", recorder.Code, http.StatusOK) + } + want := `{"success":true,"data":"Hello, World!"}` + if got := recorder.Body.String(); got != want { + t.Fatalf("GET /hello body = %q, want %q", got, want) + } +} diff --git a/go/projects/lyra/internal/service/AGENTS.md b/go/projects/lyra/internal/service/AGENTS.md index c4a6730..2bde33b 100644 --- a/go/projects/lyra/internal/service/AGENTS.md +++ b/go/projects/lyra/internal/service/AGENTS.md @@ -3,3 +3,4 @@ - 承载业务规则与事务协调,通过 model 完成数据访问。 - 需要原子性时,在同一事务内协调多次写入,不把各自独立提交的多次写入当成原子业务。 - 使用标准 context.Context,不依赖 Gin,也不依赖 internal/api 的 DTO。 +- 可按明确业务分包(如 `polaris/`);服务级简单接口保留在根包平铺,不预建空业务包。