# Architecture **Analysis Date:** 2026-03-27 ## Pattern Overview **Overall:** 双进程分层单体(API + Worker),核心代码按 `Handler → Service → Store → Model` 组织。 **Key Characteristics:** - HTTP 入口与异步任务入口分离:`cmd/api/main.go` 负责 Fiber API,`cmd/worker/main.go` 负责 Asynq Worker 与轮询调度。 - 依赖集中在 `internal/bootstrap/` 组装,再分发给 `internal/routes/`、`internal/handler/`、`internal/task/`。 - 数据访问主要走 `internal/store/postgres/*.go`,模型与 DTO 分别位于 `internal/model/*.go` 与 `internal/model/dto/*.go`。 - 路由注册统一经过 `internal/routes/registry.go` 的 `Register()`,同时驱动 Fiber 路由与 OpenAPI 元数据。 - 多租户与权限控制主要依赖认证上下文 + Store 层显式过滤函数,而不是数据库外键或 ORM 关联。 ## Layers **Entrypoint / Composition Layer:** - Purpose: 进程启动、基础设施初始化、依赖装配、生命周期管理。 - Location: `cmd/api/main.go`, `cmd/worker/main.go`, `internal/bootstrap/*.go` - Contains: 配置加载、日志、数据库、Redis、队列、对象存储、Gateway 客户端、Bootstrap 编排。 - Depends on: `pkg/config`, `pkg/database`, `pkg/logger`, `pkg/queue`, `pkg/storage`, `internal/bootstrap` - Used by: 整个应用进程。 **Routing / Transport Layer:** - Purpose: 路由域划分、认证挂载、HTTP 入参与响应封装、OpenAPI 文档注册。 - Location: `internal/routes/*.go`, `internal/handler/**/*.go` - Contains: `RegisterRoutesWithDoc()` 总入口、Admin/Auth/Personal 域路由、Fiber Handler。 - Depends on: `internal/bootstrap.Handlers`, `internal/bootstrap.Middlewares`, `pkg/openapi`, `pkg/response` - Used by: `cmd/api/main.go` **Service Layer:** - Purpose: 业务规则、权限判断、事务编排、幂等、异步任务提交、跨模块协作。 - Location: `internal/service/**/*.go` - Contains: 账号、订单、套餐、轮询、设备、卡、认证、充值、佣金等服务。 - Depends on: `internal/store/postgres`, `pkg/errors`, `pkg/middleware`, `pkg/queue`, `pkg/wechat`, `gorm.DB`, `redis.Client` - Used by: Handler 层、Worker Bootstrap、Task Handler。 **Store Layer:** - Purpose: SQL 查询、分页、显式数据权限过滤、缓存辅助、事务基础能力。 - Location: `internal/store/store.go`, `internal/store/options.go`, `internal/store/postgres/*.go` - Contains: 每个聚合的 PostgreSQL Store,例如 `internal/store/postgres/account_store.go`, `internal/store/postgres/order_store.go`。 - Depends on: `gorm`, `redis`, `pkg/middleware/data_scope.go` - Used by: Service 层、部分 Handler 组装期专用读模型。 **Model Layer:** - Purpose: 持久化模型、DTO、常量化状态值、表名映射。 - Location: `internal/model/*.go`, `internal/model/dto/*.go` - Contains: `model.Account`, `model.Order`, `model.Shop`, `model.PollingConfig` 以及请求/响应 DTO。 - Depends on: GORM tags、标准库类型。 - Used by: Store、Service、Handler、OpenAPI 生成。 **Task / Worker Layer:** - Purpose: Asynq 任务处理、轮询调度、定时任务、批处理导入。 - Location: `internal/task/*.go`, `internal/polling/*.go`, `pkg/queue/*.go` - Contains: `pkg/queue/handler.go` 任务注册器、`internal/task/polling_handler.go`、`internal/task/order_expire.go`、`internal/polling/scheduler.go`。 - Depends on: Worker bootstrap 产出的 stores/services、Redis、Asynq、Gateway。 - Used by: `cmd/worker/main.go` ## Data Flow **API Request Flow:** 1. `cmd/api/main.go` 创建 Fiber 应用并注册 Recover、RequestID、访问日志、压缩中间件。 2. `internal/routes/routes.go` 将请求分发到 `/api/auth`、`/api/admin`、`/api/c/v1`、`/api/callback` 四个域。 3. `pkg/middleware/auth.go` 或 `internal/middleware/personal_auth` 将用户信息写入 Fiber `Locals` 和标准 `context.Context`。 4. Handler 解析 DTO 并调用 Service,例如 `internal/handler/admin/account.go` → `internal/service/account/service.go`。 5. Service 调用一个或多个 Store,必要时使用 `db.WithContext(ctx).Transaction(...)` 编排事务,例如 `internal/service/order/service.go`。 6. Store 在查询中显式调用 `pkg/middleware/data_scope.go` 的过滤函数,例如 `internal/store/postgres/account_store.go`、`internal/store/postgres/order_store.go`。 7. Handler 通过 `pkg/response/response.go` 返回统一响应;异常交由 `internal/middleware.ErrorHandler` 处理。 **Worker Flow:** 1. `cmd/worker/main.go` 初始化 PostgreSQL、Redis、Asynq Client、对象存储、Gateway。 2. `internal/bootstrap/worker.go` 调用 `initWorkerStores()` 与 `initWorkerServices()`,生成 `queue.WorkerBootstrapResult`。 3. `pkg/queue/handler.go` 基于 Bootstrap 结果注册导入、轮询、佣金、超时取消、告警、清理等任务处理器。 4. `internal/polling/scheduler.go` 独立运行轮询调度循环:从 Redis Sorted Set / List 取卡片,再投递到 Asynq。 5. `cmd/worker/main.go` 还额外创建 Asynq Scheduler,注册 `TaskTypeOrderExpire`、`TaskTypeAlertCheck`、`TaskTypeDataCleanup`。 6. 具体任务处理器调用 Service 或直接使用 Store/DB 更新状态,例如 `internal/task/order_expire.go` → `order.Service.CancelExpiredOrders()`。 **State Management:** - HTTP 侧状态主要放在 PostgreSQL;请求态身份与数据权限范围放在 `context.Context`。 - 高速状态、Token、轮询队列、并发信号量、下级店铺缓存放在 Redis,如 `pkg/auth/token.go`、`internal/polling/scheduler.go`、`internal/store/postgres/shop_store.go`。 - 异步处理采用 Redis-backed Asynq 队列,由 `pkg/queue/client.go` 提交、`pkg/queue/server.go` 消费。 ## Key Abstractions **BootstrapResult / WorkerBootstrapResult:** - Purpose: 启动期依赖编排结果。 - Examples: `internal/bootstrap/bootstrap.go`, `internal/bootstrap/worker.go` - Pattern: 显式依赖注入,不使用外部 DI 框架。 **Handlers / Middlewares 容器:** - Purpose: 路由注册时的装配清单。 - Examples: `internal/bootstrap/types.go`, `internal/bootstrap/handlers.go`, `internal/bootstrap/middlewares.go` - Pattern: 结构体聚合所有已构造的 Handler 与 Middleware。 **RouteSpec + Register():** - Purpose: 单次声明同时完成真实路由注册和 OpenAPI 元数据注册。 - Examples: `internal/routes/registry.go`, `internal/routes/account.go`, `internal/routes/personal.go` - Pattern: 代码即文档,DTO 驱动文档生成。 **QueryOptions:** - Purpose: Store 通用分页与排序选项。 - Examples: `internal/store/options.go`, `internal/service/account/service.go`, `internal/service/order/service.go` - Pattern: Service 先构造查询选项,再下传到 Store。 **UserContextInfo + Data Scope Filters:** - Purpose: 承载用户身份与可管理店铺范围,并在 Store 查询时执行过滤。 - Examples: `pkg/middleware/auth.go`, `pkg/middleware/data_scope.go`, `internal/store/postgres/shop_store.go` - Pattern: 中间件预计算 `SubordinateShopIDs`,Store 按字段类型显式调用 `ApplyShopFilter` / `ApplyEnterpriseFilter` / `ApplySellerShopFilter`。 ## Entry Points **API Main:** - Location: `cmd/api/main.go` - Triggers: `go run cmd/api/main.go`、API 容器启动。 - Responsibilities: 加载配置、初始化基础设施、执行 `bootstrap.Bootstrap()`、创建 Fiber、注册中间件和路由、生成运行时 OpenAPI 文件 `logs/openapi.yaml`。 **Worker Main:** - Location: `cmd/worker/main.go` - Triggers: `go run cmd/worker/main.go`、Worker 容器启动。 - Responsibilities: 初始化 Worker 依赖、执行 `bootstrap.BootstrapWorker()`、启动 Asynq Worker、启动轮询调度器和 Asynq Scheduler、优雅关闭。 **OpenAPI Generation CLI:** - Location: `cmd/gendocs/main.go` - Triggers: 手动执行文档生成。 - Responsibilities: 通过 `openapi.BuildDocHandlers()` + `routes.RegisterRoutesWithDoc()` 输出 `docs/admin-openapi.yaml`。 ## Routing Strategy **Domain Split:** - `/api/auth`:统一后台/H5 认证,见 `internal/routes/auth.go` - `/api/admin`:管理后台业务域,见 `internal/routes/admin.go` - `/api/c/v1`:个人客户端域,见 `internal/routes/personal.go` - `/api/callback`:支付回调,无认证,见 `internal/routes/order.go` 与 `internal/handler/callback/payment.go` **Registration Style:** - 所有 HTTP 接口使用 `internal/routes/registry.go` 的 `Register()`,文档规范见 `docs/api-documentation-guide.md`。 - `internal/routes/admin.go` 以 handler 是否为 `nil` 决定是否挂载模块,便于文档生成和裁剪。 - `internal/routes/personal.go` 明确区分公开路由与认证路由,并依赖注册顺序确保公开接口不被 `Use()` 拦截。 ## Error Handling **Strategy:** 统一错误码 + 全局 Fiber ErrorHandler。 **Patterns:** - Handler 层只做参数解析与转发,返回 `pkg/errors` 中的 `AppError`,如 `internal/handler/admin/account.go`。 - Service 层封装业务错误,不直接向外泄露底层错误文本,如 `internal/service/account/service.go`、`internal/service/order/service.go`。 - Worker 任务记录结构化日志,并将可重试与不可重试逻辑写在处理器内部,如 `internal/task/order_expire.go`、`internal/task/polling_handler.go`。 ## Cross-Cutting Concerns **Logging:** - `pkg/logger/logger.go` 初始化 App/Access 日志;`pkg/logger/middleware.go` 记录完整请求/响应摘要。 **Validation:** - Fiber `BodyParser` / `QueryParser` + `validator.v10`,示例见 `internal/handler/auth/handler.go`。 **Authentication:** - 后台/H5 使用 Redis Token + `pkg/middleware/auth.go`;个人客户使用单独中间件,由 `internal/bootstrap/middlewares.go` 创建。 **OpenAPI:** - 路由元数据写在 `RouteSpec`;文档生成器依赖 `cmd/api/docs.go`、`cmd/gendocs/main.go`、`pkg/openapi/handlers.go` 三处手工维护 Handler 清单。 **Persistence Rules:** - 模型显式声明 `TableName()` 与字段列名,如 `internal/model/account.go`, `internal/model/order.go`, `internal/model/shop.go`。 - 数据库结构通过 `migrations/*.sql` 管理,`pkg/database/postgres.go` 明确禁用自动建表。 ## Architectural Deviations **README / 规范与当前实现存在的偏差:** - `README.md` 与 `openspec/config.yaml` 多处描述“GORM Callback 自动注入数据权限过滤”,但当前 `internal/bootstrap/bootstrap.go` 第 67 行明确写明“数据权限过滤已移至 Store 层显式调用 ApplyXxxFilter 函数”。当前保留的 GORM Callback 只有 `pkg/gorm/callback.go` 中的创建人/更新人自动填充。 - `internal/bootstrap/handlers.go` 为客户端场景直接新建多个 Store 和一个 `client_order` Service,而不是完全只消费 `services` 聚合;这说明 Handler 装配阶段存在读模型/适配层级的额外拼装。 - `internal/task/polling_handler.go` 在首次实名激活流程中先构造 Asynq 任务,但实际通过 Redis List `RPush` 提交,文件内注释明确写出“实际应该通过依赖注入 asynq.Client”;这是 Worker 流程中的特殊机制与待收敛点。 --- *Architecture analysis: 2026-03-27*