diff --git a/go/projects/lyra/AGENTS.md b/go/projects/lyra/AGENTS.md new file mode 100644 index 0000000..2a9b534 --- /dev/null +++ b/go/projects/lyra/AGENTS.md @@ -0,0 +1,33 @@ +# lyra 项目约定 + +## Redis 缓存键 + +- 格式:`<项目>:cache:<资源>:<查询>:v<版本>`,例如 `CategoryListCacheKey = "lyra:cache:category:list:v1"`。 +- 常量命名:`<资源><查询>CacheKey`,集中定义在 `internal/pkg/constant/cacheKey.go`,业务代码引用常量,不硬编码键字符串。 +- 分段含义:项目名(本项目为 `lyra`)隔离不同应用;`cache` 区分缓存与其他 Redis 用途;版本用于隔离结构不兼容的缓存数据。 +- 参数化查询的键必须包含影响查询结果的条件参数;键中不得出现密码、令牌等敏感信息。 + +## 日志 + +- 请求链路(middleware、handler、service、model)需要日志时使用 `util.Logger(ctx)`,使用结构化属性记录上下文与错误。 +- `util` 只从 context 读取 request ID;request ID 由 `RequestIDMiddleware` 注入,不由 `util` 生成。 +- Gin 调用链向下传递 `c.Request.Context()`,不要继续传 `*gin.Context`。 +- 禁止在请求处理中使用 `fmt.Printf` 或包级 `slog` 记录业务日志。 +- 无请求上下文的进程启动、初始化日志可直接使用 `slog`,不强制统一为 `util.Logger`。 + +## 错误处理 + +- 底层错误在合适的处理边界记录一次,避免每层重复记录。 +- 公开响应只返回约定的业务码与对外文案,不暴露底层错误、SQL、连接信息等内部细节。 + +## 启动与组装 + +- `cmd/lyra/bootstrap.go` 负责数据库、Redis 等基础设施初始化;`cmd/lyra/dependency.go` 负责依赖组装;`cmd/lyra/main.go` 负责入口流程编排。 +- `internal/config` 只定义配置结构与加载,不创建客户端;数据库/Redis 初始化留在 `cmd/lyra`,不另建占位包。 +- 不全局创建数据库或 Redis 客户端,实例由入口创建并注入。 +- 运行路径以 module 根为工作目录解析(例如 `etc/lyra.toml`)。 + +## 测试 + +- 默认自动测试应独立于真实 PostgreSQL、Redis 和真实端口。 +- 需要真实服务的联调测试单独说明和执行,不混入默认测试。 diff --git a/go/projects/lyra/internal/handler/AGENTS.md b/go/projects/lyra/internal/handler/AGENTS.md index 0a7bd36..6a11689 100644 --- a/go/projects/lyra/internal/handler/AGENTS.md +++ b/go/projects/lyra/internal/handler/AGENTS.md @@ -2,5 +2,6 @@ - 负责参数绑定、API DTO 与内部模型的相互转换、响应输出。 - 引用 internal/api 的 DTO,使用 Gin 完成绑定与响应。 +- 响应统一使用 `httpx.OK` / `httpx.Fail`,遵循 `internal/httpx/AGENTS.md` 的状态码、业务码与错误隔离规则。 - 调用 service 时传递标准 context.Context,不下传 gin.Context。 - 可按明确业务分包(如 `polaris/`);服务级简单接口保留在根包平铺,不预建空业务包。 diff --git a/go/projects/lyra/internal/httpx/AGENTS.md b/go/projects/lyra/internal/httpx/AGENTS.md index bd3fc71..bb5fdf0 100644 --- a/go/projects/lyra/internal/httpx/AGENTS.md +++ b/go/projects/lyra/internal/httpx/AGENTS.md @@ -1,4 +1,6 @@ # httpx 包 -- 提供公共 HTTP 响应辅助。 -- 出现实际复用需求时再补充具体辅助函数,不预先设计无使用场景的抽象。 +- 提供公共 HTTP 响应辅助,使用现有 `OK` / `Fail` 输出统一响应。 +- `Fail` 区分 HTTP 状态码与业务错误码;公开 message 为空时回退到业务码的默认文案。 +- 不向公开响应写入内部错误或调试信息,避免 debug 泄漏。 +- 响应结构来自 internal/api,不在这里重复定义 DTO。 diff --git a/go/projects/lyra/internal/model/AGENTS.md b/go/projects/lyra/internal/model/AGENTS.md index 6294124..8eda785 100644 --- a/go/projects/lyra/internal/model/AGENTS.md +++ b/go/projects/lyra/internal/model/AGENTS.md @@ -2,5 +2,9 @@ - 承载数据结构定义与数据访问:使用 Squirrel/SQL 构造查询、sqlx 执行与映射结果,驱动为 pgx stdlib。 - SQL 与查询逻辑显式写在这一层,不使用 ORM 自动持久化。 -- 支持在 service 传入的同一事务内执行操作。 +- 事务边界由 service 决定;需要同一事务时由 service 传入事务执行。这是设计目标,当前没有现成的事务接口封装。 +- 查询缓存采用 cache-aside:先读缓存,命中直接返回(空列表也可命中);未命中或缓存读取失败时回源数据库。 +- 仅数据库查询成功后回填缓存;数据库失败不回填。缓存读写故障只记录日志并降级,不改变数据库查询结果。 +- 缓存键使用 `internal/pkg/constant` 中的常量,遵循根 `AGENTS.md` 的 Redis 缓存键约定。 +- 未来写路径应在事务提交后使相关缓存失效;当前 `cache` 包没有删除接口,失效策略属于后续设计约束。 - 使用标准 context.Context,不依赖 Gin,也不依赖 internal/api 的 DTO。 diff --git a/go/projects/lyra/internal/model/category.go b/go/projects/lyra/internal/model/category.go index b14690a..c780dd5 100644 --- a/go/projects/lyra/internal/model/category.go +++ b/go/projects/lyra/internal/model/category.go @@ -3,7 +3,11 @@ package model import ( "context" "fmt" + "time" + "lyra/internal/cache" + "lyra/internal/pkg/constant" + "lyra/internal/util" sq "github.com/Masterminds/squirrel" "github.com/jmoiron/sqlx" @@ -27,7 +31,17 @@ type defaultCategoryModel struct { func NewCategoryModel(db *sqlx.DB, cache *cache.Cache) CategoryModel { return &defaultCategoryModel{db: db, cache: cache} } + +const categoryListCacheTTL = 5 * time.Minute + func (m *defaultCategoryModel) FindAll(ctx context.Context) ([]*Category, error) { + cached, hit, err := m.cache.Get[[]*Category](ctx, constant.CategoryListCacheKey) + if err != nil { + util.Logger(ctx).Error("读取分类列表缓存失败", "key", constant.CategoryListCacheKey, "error", err) + } + if hit { + return cached, nil + } query, args, err := sq. Select("id", "slug", "title", "sort"). @@ -45,5 +59,12 @@ func (m *defaultCategoryModel) FindAll(ctx context.Context) ([]*Category, error) if err != nil { return nil, fmt.Errorf("数据库查询出错: %w", err) } - return rows, err + if err := m.cache.Set(ctx, cache.Entry[[]*Category]{ + Key: constant.CategoryListCacheKey, + Value: rows, + TTL: categoryListCacheTTL, + }); err != nil { + util.Logger(ctx).Error("写入分类列表缓存失败", "key", constant.CategoryListCacheKey, "error", err) + } + return rows, nil } diff --git a/go/projects/lyra/internal/pkg/constant/cacheKey.go b/go/projects/lyra/internal/pkg/constant/cacheKey.go index 9808b45..42b72a5 100644 --- a/go/projects/lyra/internal/pkg/constant/cacheKey.go +++ b/go/projects/lyra/internal/pkg/constant/cacheKey.go @@ -1,2 +1,3 @@ package constant +const CategoryListCacheKey = "lyra:cache:category:list:v1" diff --git a/go/projects/lyra/internal/postgres/AGENTS.md b/go/projects/lyra/internal/postgres/AGENTS.md deleted file mode 100644 index 81d90f3..0000000 --- a/go/projects/lyra/internal/postgres/AGENTS.md +++ /dev/null @@ -1,4 +0,0 @@ -# postgres 包 - -- 负责建立与管理 PostgreSQL 连接,使用 pgx stdlib 驱动。 -- 连接参数来自 internal/config,连接供 model 层通过 sqlx 执行查询使用。 diff --git a/go/projects/lyra/internal/service/AGENTS.md b/go/projects/lyra/internal/service/AGENTS.md index 2bde33b..8d74228 100644 --- a/go/projects/lyra/internal/service/AGENTS.md +++ b/go/projects/lyra/internal/service/AGENTS.md @@ -1,6 +1,7 @@ # service 包 -- 承载业务规则与事务协调,通过 model 完成数据访问。 -- 需要原子性时,在同一事务内协调多次写入,不把各自独立提交的多次写入当成原子业务。 +- 当前目录为占位,尚无 Go 实现;新增代码前先确认职责边界。 +- 预期职责:业务规则与事务协调,通过 model 完成数据访问。 +- 需要原子性时,在同一事务内协调多次写入;当前没有现成事务接口,需在实现时设计,不把各自独立提交的多次写入当成原子业务。 - 使用标准 context.Context,不依赖 Gin,也不依赖 internal/api 的 DTO。 - 可按明确业务分包(如 `polaris/`);服务级简单接口保留在根包平铺,不预建空业务包。