# 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*
