# Project Map — cryptoHermes 代码 agent 工作的地形图。每个目录写明:**职责**、**可以依赖谁**、**不可以依赖谁**、**扩展点在哪**。 完整设计见 `docs/dev.md`。 --- ## 顶层布局 ``` cryptoHermes/ ├── cmd/ 二进制入口 ├── config/ cleanenv 加载 ├── internal/ 业务代码(不可被外部 import) ├── migrations/ SQL(含 hypertable) ├── pkg/ 项目内可复用基础设施 ├── docs/ 设计文档(dev.md 是源头) ├── ai/ harness 工程文档(本目录) ├── docker-compose.yml Timescale + 服务 ├── Dockerfile multi-stage golang:1.23-alpine → alpine:3.20 ├── Makefile 所有验证命令 └── go.mod Go 1.23 ``` --- ## `cmd/` — 二进制入口 | 路径 | 职责 | |---|---| | `cmd/app/main.go` | 主服务。读 config → 调 `internal/app.Run(cfg)` | | `cmd/backfill/main.go` | 历史 K 线回填 CLI。直接装配 binance client + kline repo,不走 fiber | **约束**:`cmd/` 里只允许装配 + 调用,不允许写业务逻辑。 --- ## `config/` — 配置 - `config.go`:cleanenv 加载,`yaml + env` 双源。环境变量优先(见 `env:` tag)。 - `config.yml`:默认配置。**不要在这里放敏感信息**(没有敏感信息可放,因为本服务无私钥)。 **扩展点**:新增配置项 → `config.go` 加字段(同时加 `yaml:` 和 `env:` tag)→ `config.yml` 加默认值 → 在 README "配置" 段补一行。 --- ## `internal/` — 业务核心 Clean Architecture 四层。**依赖方向单向:`controller → usecase → entity ← repo`**。 ``` ┌──────────────┐ │ controller │ HTTP 层:参数校验、调 usecase、序列化 └──────┬───────┘ │ depends on ▼ ┌──────────────┐ │ usecase │ 业务编排。只依赖 ports.go 中的接口 └──────┬───────┘ │ depends on (interface only) ▼ ┌──────────────┐ ┌──────────────┐ │ entity │ ◄────── │ repo │ 外部依赖实现 └──────────────┘ └──────────────┘ (binance / postgres) ``` ### `internal/entity/` — 纯业务实体 - `kline.go`, `ticker.go`, `funding.go`, `open_interest.go`, `long_short_ratio.go`, `taker_volume.go`, `market_context.go` - **价格/成交量字段全部 `string`**,避免 float64 精度问题。落库时 PG 转 `NUMERIC(36,18)`。 - `Kline` 有 `IsClosed bool` 字段——未收线 K 线 (`close_time > now()`) 由 mapper 自动判定。 - `LongShortRatio` 有常量 `RatioTypeGlobalAccount` / `RatioTypeTopTraderPosition` / `RatioTypeTopTraderAccount`。 **不依赖**:fiber、pgx、binance、任何外部库。可以从这里 import 到任何其他层。 ### `internal/usecase/` — 业务接口与编排 - `ports.go`:**全部对外接口在这里定义**。这是 Clean Arch 的边界文件。 - `MarketDataProvider`(K线、ticker24h) - `DerivativesProvider`(funding/OI/多空比/taker volume 共 7 个方法) - `KlineRepository` / `FundingRepository` / `OpenInterestRepository` / `LongShortRatioRepository` / `TakerVolumeRepository` - `market_context.go`:聚合 `/v1/market/context`。errgroup 并发拉 snapshot/funding/OI,DB K 线不足 200 根回源 Binance 补,异步写回。 **约束**(grep 可验证): ```bash grep -r "repo/webapi/binance\|repo/persistent" internal/usecase # 必须无输出 ``` usecase 不可直接 import 任何具体实现。新增业务流程 → 在 `ports.go` 加接口 → 在 usecase 编排 → 由 `internal/app/app.go` 注入实现。 ### `internal/controller/` — HTTP 层 - `restapi/router.go` + `middleware.go`:fiber app、recover、logger、request-id - `restapi/v1/health_routes.go`:`/v1/health` - `restapi/v1/market_routes.go`:`/v1/market/{context,klines,snapshot,derivatives}` **约束**: ```bash grep -r "repo/webapi/binance\|repo/persistent" internal/controller # 必须无输出 ``` controller 只能依赖 usecase 和 entity,不能直接调 binance client 或 postgres repo。 **扩展点**:新增路由 → 加 handler → 通过 `MarketDeps` 注入依赖(不要直接 import repo 包)。 ### `internal/repo/` — 外部依赖实现 | 子目录 | 实现哪些接口 | |---|---| | `webapi/binance/` | `MarketDataProvider`、`DerivativesProvider` | | `persistent/postgres/` | 5 个 `*Repository` | **Binance client**(`webapi/binance/`): - `client.go`:包 `pkg/httpclient`,统一 base URL `https://fapi.binance.com`,错误用 `ExternalAPIError{Source, Path, StatusCode, Message}` 包装。 - `dto.go`:贴近 Binance 原始响应(K 线是数组)。 - `mapper.go`:DTO → entity。**K 线按下标解析**(`row[0]=openTime ... row[10]=takerBuyQuoteVolume`),自动设置 `IsClosed = close_time < now()`。 - `market.go`:`GetKlines` / `GetKlinesRange` / `GetTicker24h` - `derivatives.go`:funding × 2 + OI × 2 + 多空比 × 2 + taker volume **Postgres repos**(`persistent/postgres/`): - 5 个 repo,每个有 `UpsertMany`(`ON CONFLICT DO UPDATE`)和 `FindRecent`(按时间倒序拉,返回前反转成正序)。 - `kline_repo.go` 的 `UpsertMany` 会过滤掉 `IsClosed=false` 的 K 线。 - 使用 pgxpool + transaction。 **扩展点**: - 新增数据源(CoinGlass / ETF)→ 新建 `webapi/coinglass/`,实现新的 Provider 接口(先在 `ports.go` 定义)。 - 新增表 → 新 migration + 新 repo + 在 `ports.go` 加新接口。 ### `internal/worker/` — Collector - `collector.go`:被 cron 调度的采集逻辑。每个 `Collect*` 方法遍历 `symbols × intervals/periods`,拉数据 → upsert。 - **不直接被 controller 调用**,只通过 `internal/app/scheduler.go` 触发。 ### `internal/app/` — 装配与生命周期 - `app.go`:DI 装配的唯一位置。 - 顺序:pool → binanceClient → repos → contextUC → collector → scheduler → fiber app - graceful shutdown:监听 SIGINT/SIGTERM,10s 超时,先停 cron、再停 fiber、最后关 pool。 - `scheduler.go`:cron spec 在这里。 **这里是 Clean Arch 的"组合根"**——所有具体类型在这里被实例化并注入抽象接口。 --- ## `pkg/` — 项目内可复用基础设施 | 包 | 职责 | |---|---| | `pkg/httpclient/` | 通用 HTTP client + 重试(429/5xx 最多 2 次)+ `x/time/rate` 限流 | | `pkg/logger/` | slog 包装(按 env 选 JSON / Text) | | `pkg/postgres/` | pgxpool 初始化 + Ping | **约束**:`pkg/` 不依赖 `internal/`,反向依赖。可以被外部项目 import(虽然目前没有)。 --- ## `migrations/` — SQL 10 个文件(5 张表 × up/down)。 每个 `.up.sql` 末尾必须有: ```sql SELECT create_hypertable('', '', chunk_time_interval => , if_not_exists => TRUE, migrate_data => TRUE); ``` chunk 间隔: - K 线 / OI / LS / taker → 1 周 = 604800000 ms - funding → 30 天 = 2592000000 ms **改 migration 前必读 `ai/risk-guardrails.md` 中"DB migration"段**。 --- ## 数据流(控制流) ### 写路径(采集) ``` cron tick └→ internal/app/scheduler.go └→ internal/worker/collector.go (Collect*) ├→ binance.GetXxx (HTTP → DTO → entity) └→ repo.UpsertMany (entity → SQL upsert) ``` ### 读路径(API) ``` HTTP request └→ internal/controller/restapi/v1/market_routes.go └→ usecase.MarketContext.Build ├→ marketData.GetTicker24h ┐ ├→ derivatives.GetCurrent… ├ errgroup 并发 ├→ derivatives.GetCurrent… ┘ ├→ klineRepo.FindRecent × 5 周期 │ └ DB 不足 → 回源 binance.GetKlines(异步写回) └→ fundingRepo / oiRepo / lsRepo.FindRecent ``` --- ## 不可逾越的边界(自动验证见 AGENTS.md §6) 1. **controller ↛ binance/postgres**:必须经 usecase。 2. **usecase ↛ binance/postgres 包**:只依赖 `ports.go` 接口。 3. **entity ↛ 任何外部库**:可被所有层 import。 4. **pkg ↛ internal**:基础设施不知道业务。 5. **任何代码 ↛ 私钥/签名**:grep `apikey|hmac|x-mbx-apikey|secret_key|api_secret` 必须无 Go 代码命中。 --- ## 关键扩展点速查 | 场景 | 改哪 | |---|---| | 新增 REST 路由 | `internal/controller/restapi/v1/`,依赖通过 `MarketDeps` 注入 | | 新增业务编排 | `internal/usecase/ports.go` 加接口 → 新 usecase 文件 → `internal/app/app.go` 装配 | | 新增数据表 | 新 migration → 新 entity(可选)→ 新 repo → `ports.go` 接口 → 装配 | | 新增数据源 | `ai/specs/.md` → 新 `internal/repo/webapi//` → 新 Provider 接口 | | 新增定时任务 | `internal/worker/collector.go` 加方法 → `internal/app/scheduler.go` 注册 cron | | 改配置项 | `config/config.go` 加字段 → `config/config.yml` 加默认 → README 记录 |