feat(lyra): 搭建基础骨架与配置响应组件
- 使用 Viper 加载 TOML 配置 - 定义统一响应、业务错误码及响应辅助函数 - 添加分层职责约束并同步架构文档
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
# Lyra
|
||||||
|
|
||||||
|
链接、分类、设置等资源的后端服务(规划中)。目标结构与技术选型见
|
||||||
|
[docs/architecture.md](docs/architecture.md)。
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
已搭建目录骨架与程序入口 `main.go`,各内部目录的职责与依赖约束记录在 `AGENTS.md` 中。
|
||||||
|
`internal/api/response.go` 定义公共响应结构;仅含 `AGENTS.md` 的目录暂不构成 Go 包。
|
||||||
|
业务逻辑、数据库访问、HTTP API 与迁移文件均未实现;架构文档中标注示例的
|
||||||
|
文件(links 等)尚未创建。当前运行程序后直接退出——不连接数据库,
|
||||||
|
也不启动 HTTP 服务。
|
||||||
|
|
||||||
|
## 目录职责
|
||||||
|
|
||||||
|
| 路径 | 职责 |
|
||||||
|
|---|---|
|
||||||
|
| `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/` | 公共响应辅助,按需实现 |
|
||||||
|
| `migrations/` | Goose SQL 迁移文件(暂无) |
|
||||||
|
| `docs/` | 架构与计划文档 |
|
||||||
|
|
||||||
|
## 运行
|
||||||
|
|
||||||
|
Go 命令在本目录(module 根)下执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/lyra # 当前直接退出,不连接数据库、不启动 HTTP
|
||||||
|
go test ./... # 当前无测试文件,仅验证各包可编译
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
已引入 Gin 与 Viper(读取 TOML 配置),go.sum 已生成;
|
||||||
|
PostgreSQL、Squirrel + sqlx(pgx stdlib 驱动)与 Goose 为目标选型,随后续功能引入。
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Command lyra 是 Lyra 的程序入口,负责启动、依赖组装与退出清理。
|
||||||
|
//
|
||||||
|
// 当前仅搭建目录骨架:运行后直接退出,不连接数据库,也不启动 HTTP 服务。
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"lyra/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 加载配置(internal/config)
|
||||||
|
cfg, err := config.Load("etc/config.toml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v\n", cfg)
|
||||||
|
// TODO: 组装依赖——建立 PostgreSQL 连接(internal/postgres)、注册路由(internal/router)。
|
||||||
|
// TODO: 启动 HTTP 服务,并在退出时关闭连接、释放资源。
|
||||||
|
}
|
||||||
@@ -2,11 +2,19 @@
|
|||||||
|
|
||||||
日期:2026-10-16
|
日期:2026-10-16
|
||||||
|
|
||||||
本文记录已确认的目标目录结构与职责边界。Lyra 当前仅有 go.mod 和文档,代码尚未实施;以下文件为计划形态,不代表已存在。选型与讨论过程见 [plans/2026-10-16-lyra-architecture.md](plans/2026-10-16-lyra-architecture.md)。
|
本文记录已确认的目标目录结构与职责边界,并与当前真实实现同步。目录树是**目标结构**,并非全部已存在:当前只有 `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)。
|
||||||
|
|
||||||
## 已确认技术栈
|
## 已确认技术栈
|
||||||
|
|
||||||
Gin(HTTP 路由)、PostgreSQL、Squirrel + sqlx(数据访问,pgx stdlib 驱动)、Goose(数据库迁移)。
|
- 已引入:Gin v1.12.0(HTTP 路由)、Viper v1.21.0(配置读取,TOML)。`go.sum` 已由 Go 工具生成。
|
||||||
|
- 目标选型(选型已确认,尚未作为代码依赖引入):PostgreSQL、Squirrel + sqlx(pgx stdlib 驱动)、Goose(数据库迁移)。
|
||||||
|
|
||||||
|
## 当前进度
|
||||||
|
|
||||||
|
- 程序入口按相对工作目录的 `etc/config.toml` 加载配置(Viper,`mapstructure` 标签),打印结果后退出;不启动 HTTP 服务,也不连接数据库。
|
||||||
|
- 已定义公共响应契约:`api.Response`(success/data/error)、`api.ErrorInfo`(code/message/debug)与错误码类型 `api.ErrorCode` 及其默认文案 `Message()`。
|
||||||
|
- 已实现响应输出辅助 `httpx.OK`、`httpx.Fail`。
|
||||||
|
- 尚未实现:业务逻辑、路由注册、数据库访问与迁移 SQL;配置校验、环境变量覆盖与默认值;`ErrorInfo.Debug` 的填充与按环境开关。日志方案未定。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
@@ -14,38 +22,42 @@ Gin(HTTP 路由)、PostgreSQL、Squirrel + sqlx(数据访问,pgx stdlib
|
|||||||
lyra/
|
lyra/
|
||||||
├── cmd/lyra/main.go # 启动、依赖组装、退出清理
|
├── cmd/lyra/main.go # 启动、依赖组装、退出清理
|
||||||
├── internal/
|
├── internal/
|
||||||
│ ├── api/ # HTTP 请求/响应 DTO,按资源分文件
|
│ ├── api/ # HTTP 请求/响应 DTO、错误码与响应契约
|
||||||
│ │ ├── links.go # links 资源示例(下同)
|
│ │ ├── response.go # Response/ErrorInfo 结构
|
||||||
│ │ ├── categories.go
|
│ │ ├── code.go # ErrorCode 与错误码常量、默认文案
|
||||||
│ │ └── settings.go
|
│ │ ├── links.go # links 资源 DTO 示例(待新增)
|
||||||
|
│ │ ├── categories.go # 待新增
|
||||||
|
│ │ └── settings.go # 待新增
|
||||||
│ ├── config/
|
│ ├── config/
|
||||||
│ │ └── config.go # 配置定义与加载
|
│ │ └── config.go # 配置定义与加载
|
||||||
│ ├── postgres/
|
│ ├── postgres/
|
||||||
│ │ └── postgres.go # 数据库连接
|
│ │ └── postgres.go # 数据库连接(待新增)
|
||||||
│ ├── router/
|
│ ├── router/
|
||||||
│ │ └── router.go # 路由注册、项目路径前缀
|
│ │ └── router.go # 路由注册、项目路径前缀(待新增)
|
||||||
│ ├── handler/
|
│ ├── handler/
|
||||||
│ │ └── links.go # 参数绑定、DTO 转换、响应输出
|
│ │ └── links.go # 参数绑定、DTO 转换、响应输出(待新增)
|
||||||
│ ├── service/
|
│ ├── service/
|
||||||
│ │ ├── links.go # 业务规则、事务协调
|
│ │ ├── links.go # 业务规则、事务协调(待新增)
|
||||||
│ │ └── links_test.go # 测试与源文件同目录
|
│ │ └── links_test.go # 测试与源文件同目录(待新增)
|
||||||
│ ├── model/
|
│ ├── model/
|
||||||
│ │ ├── link.go # Link 数据结构定义
|
│ │ ├── link.go # Link 数据结构定义(待新增)
|
||||||
│ │ ├── link_model.go # 数据访问对象:CRUD 与 SQL(示例)
|
│ │ ├── link_model.go # 数据访问对象:CRUD 与 SQL(待新增)
|
||||||
│ │ └── link_model_test.go # 查询测试(示例)
|
│ │ └── link_model_test.go # 查询测试(待新增)
|
||||||
│ └── httpx/
|
│ └── httpx/
|
||||||
│ └── response.go # 公共响应辅助,按需实现
|
│ └── response.go # 公共响应输出辅助(OK/Fail)
|
||||||
├── migrations/ # Goose SQL 迁移文件
|
├── etc/
|
||||||
|
│ └── config.toml # 运行配置(App/HTTP)
|
||||||
|
├── migrations/ # Goose SQL 迁移文件(暂无)
|
||||||
├── docs/
|
├── docs/
|
||||||
│ ├── plans/
|
│ ├── plans/
|
||||||
│ └── architecture.md # 本文
|
│ └── architecture.md # 本文
|
||||||
├── README.md
|
├── README.md
|
||||||
├── go.mod
|
├── go.mod
|
||||||
└── go.sum # 引入依赖后由 Go 工具生成
|
└── go.sum # 已生成
|
||||||
```
|
```
|
||||||
|
|
||||||
- 标注示例的文件展示一个资源在各层的文件分布;分类、设置等资源按同样方式在 api/handler/service/model 新增对应文件。model/link.go 定义 Link 数据结构,model/link_model.go 定义数据访问与 SQL,model/link_model_test.go 测试查询,均为示例,实际文件随实现新增。
|
- 标注“待新增”的文件只是职责分布示例,随实现按需新增,不预建空文件或空目录;`internal/` 各目录均以 `AGENTS.md` 记录职责与依赖约束。
|
||||||
- 只创建实际需要的文件;不预建空目录或空测试文件,具体迁移文件名随首个迁移确定。
|
- model 下的 `link*.go` 展示一个资源在各层的文件分布;分类、设置等资源按同样方式在 api/handler/service/model 新增对应文件。具体迁移文件名随首个迁移确定。
|
||||||
|
|
||||||
## 组织原则
|
## 组织原则
|
||||||
|
|
||||||
@@ -58,14 +70,19 @@ lyra/
|
|||||||
- 主调用链为 handler→service→model→PostgreSQL;service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。
|
- 主调用链为 handler→service→model→PostgreSQL;service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。
|
||||||
- 请求/响应 DTO 集中在 internal/api,与内部模型分开;即使初期字段相近,也不默认把数据库模型直接暴露为响应,接受必要的显式转换代码。
|
- 请求/响应 DTO 集中在 internal/api,与内部模型分开;即使初期字段相近,也不默认把数据库模型直接暴露为响应,接受必要的显式转换代码。
|
||||||
- handler 引用 API DTO 完成绑定与转换;service/model 不依赖 Gin,也不依赖 internal/api DTO;api 包不反向引用 handler/service/model,避免传输契约与实现耦合或循环引用。
|
- 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` 仅加载并打印,尚未向其他组件传递。
|
||||||
- model 承载数据结构定义与数据访问:Squirrel/SQL 构造、sqlx 执行与结果映射,驱动为 pgx stdlib;不是 ORM 自动持久化,也不要求每种类型机械建立一套结构。
|
- model 承载数据结构定义与数据访问:Squirrel/SQL 构造、sqlx 执行与结果映射,驱动为 pgx stdlib;不是 ORM 自动持久化,也不要求每种类型机械建立一套结构。
|
||||||
- service/model 使用标准 context.Context,不接收 gin.Context。
|
- service/model 使用标准 context.Context,不接收 gin.Context。
|
||||||
|
|
||||||
## 尚未确定
|
## 尚未确定 / 待实现
|
||||||
|
|
||||||
以下仍为计划中的推荐或待确认项,未定为已决:
|
以下均为计划中的推荐或待确认项,尚未实现,也未定为已决:
|
||||||
|
|
||||||
- 代码尚未实施,也未进行数据库连接或结构修改。
|
- 配置校验、环境变量覆盖与默认值:尚未实现(当前无任何校验、覆盖或默认值)。
|
||||||
|
- `ErrorInfo.Debug` 的填充与按环境(dev)开关:尚未实现,目前仅有字段。
|
||||||
|
- 分页响应约定(如 data/meta 结构)与统一错误处理中间件:尚未引入,仅为历史讨论。
|
||||||
- Goose 的部署执行入口与并发控制(串行执行或数据库锁)方案。
|
- Goose 的部署执行入口与并发控制(串行执行或数据库锁)方案。
|
||||||
- PostgreSQL 按项目划分 schema 的做法(组织边界,不等于权限隔离)。
|
- PostgreSQL 按项目划分 schema 的做法(组织边界,不等于权限隔离)。
|
||||||
- 日志方案(候选标准库 slog)与中间件集成方式。
|
- 日志方案(候选标准库 slog)与中间件集成方式。
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[app]
|
||||||
|
name = "lyra"
|
||||||
|
env = "dev"
|
||||||
|
|
||||||
|
[http]
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 7777
|
||||||
@@ -1,3 +1,49 @@
|
|||||||
module lyra
|
module lyra
|
||||||
|
|
||||||
go 1.27.1
|
go 1.27.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.12.0
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/arch v0.22.0 // indirect
|
||||||
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
|
golang.org/x/net v0.51.0 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
golang.org/x/text v0.34.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||||
|
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# api 包
|
||||||
|
|
||||||
|
- 定义 HTTP 请求/响应 DTO,按资源分文件。
|
||||||
|
- DTO 与内部模型分开:即使初期字段相近,也不把数据库模型直接暴露为响应,由 handler 层完成显式转换。
|
||||||
|
- 不引用 handler、service、model,也不依赖 Gin,避免传输契约与实现耦合或循环引用。
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
// ErrCode 错误码,前3位数表示错误类型,后3位数表示具体错误
|
||||||
|
type ErrorCode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CodeInvalidArgument ErrorCode = 10001
|
||||||
|
CodeInternalError ErrorCode = 10002
|
||||||
|
CodeLinkNotFound ErrorCode = 10003
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e ErrorCode) Message() string {
|
||||||
|
switch e {
|
||||||
|
case CodeInvalidArgument:
|
||||||
|
return "请求参数不正确"
|
||||||
|
case CodeLinkNotFound:
|
||||||
|
return "链接不存在"
|
||||||
|
default:
|
||||||
|
return "服务暂时不可用"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Data any `json:"data,omitempty"`
|
||||||
|
Error *ErrorInfo `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorInfo struct {
|
||||||
|
Code ErrorCode `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Debug string `json:"debug,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# config 包
|
||||||
|
|
||||||
|
- 负责配置定义与加载。
|
||||||
|
- 具体配置项、来源与加载方式随实际需求确定,由程序入口调用配置加载逻辑。
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
App AppConfig `mapstructure:"app"`
|
||||||
|
HTTP HTTPConfig `mapstructure:"http"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Env string `mapstructure:"env"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPConfig struct {
|
||||||
|
Host string `mapstructure:"host"`
|
||||||
|
Port int `mapstructure:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(path string) (*Config, error) {
|
||||||
|
v := viper.New()
|
||||||
|
// 指定文件,按 .toml 扩展名识别格式
|
||||||
|
v.SetConfigFile(path)
|
||||||
|
|
||||||
|
if err := v.ReadInConfig(); err != nil {
|
||||||
|
return nil, fmt.Errorf("读取配置失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
if err := v.Unmarshal(&cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析配置失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# handler 包
|
||||||
|
|
||||||
|
- 负责参数绑定、API DTO 与内部模型的相互转换、响应输出。
|
||||||
|
- 引用 internal/api 的 DTO,使用 Gin 完成绑定与响应。
|
||||||
|
- 调用 service 时传递标准 context.Context,不下传 gin.Context。
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# httpx 包
|
||||||
|
|
||||||
|
- 提供公共 HTTP 响应辅助。
|
||||||
|
- 出现实际复用需求时再补充具体辅助函数,不预先设计无使用场景的抽象。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lyra/internal/api"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OK 成功响应
|
||||||
|
func OK(c *gin.Context, data any) {
|
||||||
|
c.JSON(http.StatusOK, api.Response{
|
||||||
|
Success: true,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail 错误响应
|
||||||
|
func Fail(c *gin.Context, status int, code api.ErrorCode, message string) {
|
||||||
|
if message == "" {
|
||||||
|
message = code.Message()
|
||||||
|
}
|
||||||
|
c.JSON(status, api.Response{
|
||||||
|
Success: false,
|
||||||
|
Error: &api.ErrorInfo{Code: code, Message: message},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# model 包
|
||||||
|
|
||||||
|
- 承载数据结构定义与数据访问:使用 Squirrel/SQL 构造查询、sqlx 执行与映射结果,驱动为 pgx stdlib。
|
||||||
|
- SQL 与查询逻辑显式写在这一层,不使用 ORM 自动持久化。
|
||||||
|
- 支持在 service 传入的同一事务内执行操作。
|
||||||
|
- 使用标准 context.Context,不依赖 Gin,也不依赖 internal/api 的 DTO。
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# postgres 包
|
||||||
|
|
||||||
|
- 负责建立与管理 PostgreSQL 连接,使用 pgx stdlib 驱动。
|
||||||
|
- 连接参数来自 internal/config,连接供 model 层通过 sqlx 执行查询使用。
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# router 包
|
||||||
|
|
||||||
|
- 负责路由注册与项目路径前缀,例如 `/api/polaris`。
|
||||||
|
- Gin 的路由与请求处理逻辑留在 HTTP 层;service 和 model 不依赖 Gin。
|
||||||
|
- 路由随资源实现逐步补充,不按业务项目建立代码子目录。
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# service 包
|
||||||
|
|
||||||
|
- 承载业务规则与事务协调,通过 model 完成数据访问。
|
||||||
|
- 需要原子性时,在同一事务内协调多次写入,不把各自独立提交的多次写入当成原子业务。
|
||||||
|
- 使用标准 context.Context,不依赖 Gin,也不依赖 internal/api 的 DTO。
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# migrations
|
||||||
|
|
||||||
|
未来存放 [Goose](https://pressly.github.io/goose/) SQL 迁移文件的目录。
|
||||||
|
|
||||||
|
当前没有任何迁移文件;首个迁移的文件名与写法随实现确定。部署执行入口、
|
||||||
|
并发控制(串行执行或数据库锁)与 schema 划分方案尚未确定,见
|
||||||
|
[../docs/architecture.md](../docs/architecture.md) 的"尚未确定"部分。
|
||||||
Reference in New Issue
Block a user