同步文档与实际代码状态

This commit is contained in:
fish
2026-05-03 22:16:57 +08:00
parent 944fa90e0d
commit 6a1541ad9c
2 changed files with 65 additions and 81 deletions

View File

@@ -10,31 +10,31 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
```bash
# === 启动全栈服务(PostgreSQL + tushare API + web) ===
docker-compose up -d
docker-compose -f docker-compose.trade.yml up -d
# === tushare CLI(不传参 = 按当月 FG 主力自动选合约,轮换规则见 contracts.py:ROLLOVER_RULES) ===
docker-compose run --rm tushare python -m src.main
docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main
# 显式指定合约(注意交易所后缀:.ZCE/.SHF/.DCE,郑商所是 .ZCE 不是 .CZC)
docker-compose run --rm tushare python -m src.main RB2510.SHF
docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main RB2510.SHF
# 用品种代号自动选当月主力(目前只配置了 FG)
docker-compose run --rm tushare python -m src.main --symbol FG
docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main --symbol FG
# === tushare API 服务(容器内运行 uvicorn,端口 8000) ===
# 触发单次流水线
curl -X POST http://localhost:8000/api/v1/run -H "Content-Type: application/json" \
curl -X POST http://localhost:4001/api/v1/run -H "Content-Type: application/json" \
-d '{"symbol":"FG"}'
# 查询打分列表
curl "http://localhost:8000/api/v1/scores?limit=5"
curl "http://localhost:4001/api/v1/scores?limit=5"
# === 修改代码后必须重建镜像 ===
docker-compose build tushare
docker-compose build web
docker-compose -f docker-compose.trade.yml build tushare
docker-compose -f docker-compose.trade.yml build web
# === 查最新打分(PostgreSQL) ===
docker-compose exec postgres psql -U trade -d futures -c \
docker-compose -f docker-compose.trade.yml exec postgres psql -U trade -d futures -c \
"SELECT ts_code, trade_date, composite, signal FROM scores ORDER BY trade_date DESC LIMIT 5;"
```
@@ -60,29 +60,31 @@ docker-compose exec postgres psql -U trade -d futures -c \
## Web 模块(报告浏览端)
`./web/` 是独立的报告浏览端,与 `tushare` 流水线解耦:tushare 写 PostgreSQL,web 只读访问业务数据、读写 `auth.db`。docker-compose 上是 `web` 服务,与 `tushare`/`postgres` 共存不互相依赖。
`./web/` 是独立的报告浏览端,与 `tushare` 流水线解耦:tushare 写 PostgreSQL,web 读写 PostgreSQL(业务数据 + 用户表)。docker-compose 上是 `web` 服务,与 `tushare`/`postgres` 共存不互相依赖。
**架构与边界**:
- 后端 Go(`golang:1.25.8-alpine3.23` 构建,`alpine:3.23` 运行),路由 `chi`,业务数据库驱动 `github.com/lib/pq`(PostgreSQL),鉴权数据库仍用 `modernc.org/sqlite`(纯 Go 无 CGO)管理 `auth.db`。前端 Vue 3 + Vite + Element Plus + ECharts。
- 后端 Go(`golang:1.25.8-alpine3.23` 构建,`alpine:3.23` 运行),路由 `chi`,数据库驱动 `github.com/lib/pq`(PostgreSQL),业务数据与用户鉴权统一由 PostgreSQL 管理。前端 Vue 3 + Vite + Element Plus + ECharts。
- 单进程同源服务:Vue 产物在 Docker 构建期由 `node` 阶段产出 `dist/`,被 `go:embed all:dist` 嵌入二进制,运行时由 Go 同时服务 `/api/*` 与 SPA 静态文件——不引入 nginx 旁车。
- DB 分离:业务数据 `futures` (PostgreSQL)通过 `DATABASE_URL` 只读访问;`auth.db`(SQLite)由 web 自己 init/写入,落在 `./data/auth.db`(已被 `.gitignore` 覆盖),容器挂载 `./data:/app/auth`
- 鉴权 JWT(HS256, Bearer header),12h 过期,无 sessions 表。前端把 token 持久化到 `localStorage`,axios 拦截器统一注入 + 401 自动跳登录。
- 统一 DB:业务数据与用户鉴权数据均存储在 PostgreSQL `futures` 数据库中,通过 `DATABASE_URL` 访问`auth.db`(SQLite)已废弃,`users` 表现在由 `AuthStore` 直接管理在 PostgreSQL 中
- 鉴权已简化:登录接口返回固定 token,`middleware.RequireUser` 直接注入默认管理员上下文,所有请求放行。后端仍保留密码校验与角色检查(`RequireAdmin`)。前端把 token 持久化到 `localStorage`,axios 拦截器统一注入 + 401 自动跳登录。
- 前端支持暗/浅色模式切换(`stores/theme.ts`),侧边导航在暗色模式用 `#282828`、浅色模式用 `#f9fafb`
**禁止公开注册**:登录后管理员才能在 `/admin/users` 维护账号。首次启动时 `auth.Bootstrap` 检查 `users` 表,若没有 admin 行 ** env 同时存在 `ADMIN_USER`/`ADMIN_PASS`,则用 bcrypt(cost=12) 写一行 admin。一旦 admin 存在,这两个 env 被静默忽略——避免轮换 env 时静默改密。忘记管理员密码的恢复方式:停服 → `sqlite3 data/auth.db "DELETE FROM users WHERE role='admin'"` → 重置 env → 重启。
**禁止公开注册**:登录后管理员才能在 `/admin/users` 维护账号。首次启动时 `auth.Bootstrap` 检查 `users` 表,若没有 admin 行,则自动创建默认管理员 `admin` / `admin`,并标记强制首次登录后改密。忘记管理员密码的恢复方式:停服 → 清理 PostgreSQL 中的 admin 记录 → 重启。
**修改即重建**:沿用 `tushare` 服务的约定,`web/backend/``web/frontend/` 都通过镜像 COPY 进容器,**没有源码挂载**。改完 Go/Vue 代码不重建镜像就跑等于跑旧代码。重建命令 `docker-compose build web`
**鉴权已简化**:当前 `middleware.RequireUser` 直接注入默认管理员用户到上下文,所有请求放行;`handlers.Login` 返回固定 token (`"noop"`),不再使用 JWT。后端仍保留登录校验密码和角色检查(`RequireAdmin`),但 token 本身已不做签名验证。前端保持原有登录流程和 localStorage token 持久化不变
**修改即重建**:沿用 `tushare` 服务的约定,`web/backend/``web/frontend/` 都通过镜像 COPY 进容器,**没有源码挂载**。改完 Go/Vue 代码不重建镜像就跑等于跑旧代码。重建命令 `docker-compose -f docker-compose.trade.yml build web`
**常用命令**:
```bash
# 首启需先在 web/backend/.env 写 ADMIN_USER/ADMIN_PASS/JWT_SECRET (gitignored)
docker-compose up -d web
docker-compose logs -f web # 看 [bootstrap] 日志确认 admin 是否被创建
# 启动 web 服务
docker-compose -f docker-compose.trade.yml up -d web
docker-compose -f docker-compose.trade.yml logs -f web # 看 [bootstrap] 日志确认 admin 是否被创建
# 仅重建 web,不影响 tushare
docker-compose build web && docker-compose up -d web
docker-compose -f docker-compose.trade.yml build web && docker-compose -f docker-compose.trade.yml up -d web
# 本地开发 (后端 + 前端分别起,api 走代理)
cd web/backend && go run ./ # 需要本地 Go 1.25.8;dist/ 目录的占位会被 embed
cd web/frontend && npm install && npm run dev # 默认 5173 端口,/api 代理到 8080
cd web/frontend && npm install && npm run dev # 默认 5173 端口,/api 代理到 4000
```

104
README.md
View File

@@ -13,7 +13,7 @@
### 1. 启动全栈服务
```bash
docker-compose up -d
docker-compose -f docker-compose.trade.yml up -d
```
这会同时启动 PostgreSQL、tushare API 服务(端口 8000)与 Web 浏览端(端口 8080)。
@@ -21,7 +21,7 @@ docker-compose up -d
### 3. 通过 CLI 跑当月主力
```bash
docker-compose run --rm tushare python -m src.main
docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main
```
不传参时,按 `tushare/src/contracts.py``ROLLOVER_RULES` 自动选 FG 玻璃当月主力(例如 2026-05 -> `FG2609.ZCE`),启动后会先打印 `[AUTO] FG 当月主力 -> ...`,然后:
@@ -35,28 +35,28 @@ docker-compose run --rm tushare python -m src.main
```bash
# 触发 FG 打分
curl -X POST http://localhost:8000/api/v1/run -H "Content-Type: application/json" \
curl -X POST http://localhost:4001/api/v1/run -H "Content-Type: application/json" \
-d '{"symbol":"FG"}'
# 查询最新打分
curl "http://localhost:8000/api/v1/scores?limit=5"
curl "http://localhost:4001/api/v1/scores?limit=5"
# 查询合约列表
curl "http://localhost:8000/api/v1/contracts"
curl "http://localhost:4001/api/v1/contracts"
# 查询 K 线数据
curl "http://localhost:8000/api/v1/candles?ts_code=FG2609.ZCE"
curl "http://localhost:4001/api/v1/candles?ts_code=FG2609.ZCE"
```
### 5. 跑其他合约或品种
```bash
# 显式指定合约
docker-compose run --rm tushare python -m src.main RB2510.SHF
docker-compose run --rm tushare python -m src.main I2601.DCE
docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main RB2510.SHF
docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main I2601.DCE
# 按品种代号自动选当月主力(目前只配置了 FG)
docker-compose run --rm tushare python -m src.main --symbol FG
docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main --symbol FG
```
### 6. 玻璃 FG 主力轮换规则
@@ -130,27 +130,27 @@ docker-compose run --rm tushare python -m src.main --symbol FG
```bash
# 查看最新打分
docker-compose exec postgres psql -U trade -d futures -c \
docker-compose -f docker-compose.trade.yml exec postgres psql -U trade -d futures -c \
"SELECT ts_code, trade_date, composite, signal FROM scores ORDER BY trade_date DESC LIMIT 5;"
# 查看合约日线
docker-compose exec postgres psql -U trade -d futures -c \
docker-compose -f docker-compose.trade.yml exec postgres psql -U trade -d futures -c \
"SELECT trade_date, open, high, low, close, vol, oi FROM candles WHERE ts_code='FG2609.ZCE' ORDER BY trade_date DESC LIMIT 10;"
# 或通过 API 查询
curl "http://localhost:8000/api/v1/scores?ts_code=FG2609.ZCE&limit=10"
curl "http://localhost:8000/api/v1/candles?ts_code=FG2609.ZCE"
curl "http://localhost:4001/api/v1/scores?ts_code=FG2609.ZCE&limit=10"
curl "http://localhost:4001/api/v1/candles?ts_code=FG2609.ZCE"
```
## 项目结构
```
trade/
├── docker-compose.yml # Docker Compose 编排(postgres + tushare + web)
├── 使用说明.md # 本文件
├── docker-compose.trade.yml # Docker Compose 编排(postgres + tushare + web)
├── README.md # 本文件
├── CLAUDE.md # Claude Code 项目指引
├── data/ # auth.db 目录(gitignored)
│ └── auth.db # web 自己维护的用户表(SQLite)
├── data/ # 数据目录(gitignored)
│ └── (运行时生成)
├── .gitignore # Git 忽略配置
├── tushare/ # Python 数据服务
│ ├── Dockerfile
@@ -165,17 +165,17 @@ trade/
│ └── main.py # CLI 入口
└── web/ # Web 浏览端
├── .dockerignore
├── backend/ # Go 1.25 后端 (chi + lib/pq + JWT)
├── backend/ # Go 1.25 后端 (chi + lib/pq)
│ ├── Dockerfile # 多阶段:node 构 UI → go 构二进制 → alpine 运行
│ ├── go.mod
│ ├── main.go
│ ├── embed.go # //go:embed all:dist
│ ├── .env.example # ADMIN_USER/ADMIN_PASS/JWT_SECRET 示例
│ ├── go.sum
│ ├── dist/ # 占位,Docker 构建期被 vite 输出覆盖
│ └── internal/
│ ├── config/ # 环境变量加载
│ ├── store/ # PostgreSQL 业务查询 + SQLite auth.db
│ ├── auth/ # JWT + bcrypt + 首启 admin 引导
│ ├── store/ # PostgreSQL 业务查询 + 用户管理
│ ├── auth/ # bcrypt + 首启 admin 引导
│ ├── middleware/ # RequireUser / RequireAdmin / 日志
│ ├── handlers/ # 登录 / 打分 / K线 / 用户管理
│ └── router/ # chi 路由装配
@@ -196,10 +196,10 @@ trade/
## 技术栈
- **Python 3.13** (alpine) + **tushare** + **pandas** + **FastAPI** + **psycopg3** — 数据采集、打分与 API 服务
- **Go 1.25.8** (alpine 3.23) + **chi** + **lib/pq** + **JWT** — Web 后端
- **Go 1.25.8** (alpine 3.23) + **chi** + **lib/pq** — Web 后端
- **Vue 3** + **Vite** + **Element Plus** + **ECharts** — Web 前端
- **PostgreSQL 18.3** (alpine 3.23) — 业务数据存储
- **SQLite** — 鉴权数据存储(auth.db)
- **PostgreSQL** — 业务数据与用户鉴权数据统一存储
- **Docker / Docker Compose** — 容器化部署
## 常见问题
@@ -214,35 +214,23 @@ A: 郑商所用 `.ZCE` 后缀(如 `FG2609.ZCE`),上期所用 `.SHF`,大
**Q: 如何定时自动跑?**
A: 通过宿主机 cron / launchd 等定时器调用 `docker-compose run --rm tushare ...`。也可直接调用 API: `curl -X POST http://localhost:8000/api/v1/run ...`
A: 通过宿主机 cron / launchd 等定时器调用 `docker-compose -f docker-compose.trade.yml run --rm tushare ...`。也可直接调用 API: `curl -X POST http://localhost:4001/api/v1/run ...`
## Web 报表(浏览端)
`./web/` 提供一个图形化的浏览端,展示 tushare 流水线写入 PostgreSQL 的打分与行情数据。后端 Go(`golang:1.25.8-alpine3.23`)读取数据库,前端 Vue 3 + Element Plus + ECharts,通过 docker-compose 一起部署。
### 1. 配置首启凭据
`web/backend/.env` 写入(`.env` 已 gitignored,可参考 `web/backend/.env.example`):
```bash
ADMIN_USER=admin
ADMIN_PASS=请改成强密码
JWT_SECRET=$(openssl rand -hex 32)
```
`ADMIN_USER`/`ADMIN_PASS` 仅在 `auth.db` 中没有任何 admin 时生效,首次启动会以这一对凭据建立管理员;之后即使改这两个变量也不会改密。`JWT_SECRET` 必须 ≥16 字符。
### 2. 启动
### 1. 启动
```bash
# 构建并启动 web 服务,不影响现有 tushare
docker-compose up -d --build web
docker-compose -f docker-compose.trade.yml up -d --build web
# 查看启动日志:首启会出现 [bootstrap] admin 'xxx' created
docker-compose logs -f web
# 查看启动日志:首启会出现 [bootstrap] admin created
docker-compose -f docker-compose.trade.yml logs -f web
```
浏览器访问 `http://localhost:8080`,用上一步的管理员账号登录
浏览器访问 `http://localhost:4000`。首次启动时系统会自动创建默认管理员账号 `admin` / `admin`,首次登录后系统会强制要求修改密码
### 3. 页面说明
@@ -258,45 +246,39 @@ docker-compose logs -f web
1. 用 admin 登录 → 进入 `/admin/users` → 「新建账号」,填写用户名 / 密码(≥6 位) / 角色。
2. 把账号发给同事即可登录;无注册入口。
3. 离职 / 风险事件:用「禁用」临时停用(token 立即失效,前端不能再请求),或「删除」彻底清除。
3. 离职 / 风险事件:用「禁用」临时停用(被禁用的账号将无法登录),或「删除」彻底清除。
### 5. 数据流向与数据库分离
### 5. 数据流向与数据库
```
tushare(写) → PostgreSQL futures 数据库 ←(读)── web 后端
web 后端 ←(读写)→ data/auth.db (SQLite)
tushare(写) → PostgreSQL futures 数据库 ←(读)── web 后端
```
业务数据(`candles` + `scores`)统一存储在 PostgreSQL 中,`auth.db`:
业务数据(`candles` + `scores`)与用户鉴权数据(`users`)统一存储在 PostgreSQL `futures` 数据库中。`users`结构:
```sql
users(id, username UNIQUE, password_hash, role IN ('admin','user'),
disabled, created_at, updated_at)
users(id SERIAL PRIMARY KEY, username TEXT UNIQUE, password_hash TEXT,
role TEXT CHECK(role IN ('admin','user')), disabled BOOLEAN,
force_password_change BOOLEAN, created_at TEXT, updated_at TEXT)
```
`auth.db``./data/` 目录,被 `.gitignore` 覆盖。
### 6. 常见问题
**Q: 忘记管理员密码怎么办?**
```bash
docker-compose stop web
sqlite3 data/auth.db "DELETE FROM users WHERE role='admin';"
# 修改 web/backend/.env 里的 ADMIN_USER/ADMIN_PASS
docker-compose up -d web
docker-compose -f docker-compose.trade.yml stop web
docker-compose -f docker-compose.trade.yml exec postgres psql -U trade -d futures -c \
"DELETE FROM users WHERE role='admin';"
docker-compose -f docker-compose.trade.yml up -d web
```
启动时会重新触发 bootstrap 写入新的 admin
启动时会重新触发 bootstrap 写入新的默认管理员 `admin` / `admin`
**Q: 改了 Go / Vue 代码但页面没变?**
源码不挂载,镜像内是 COPY 进去的。重建:`docker-compose build web && docker-compose up -d web`
**Q: 登录提示 "JWT_SECRET 必须至少 16 个字符"?**
`web/backend/.env` 没设或太短,用 `openssl rand -hex 32` 生成一个 64 字符的十六进制字符串即可。
源码不挂载,镜像内是 COPY 进去的。重建:`docker-compose -f docker-compose.trade.yml build web && docker-compose -f docker-compose.trade.yml up -d web`
**Q: 为什么 tushare 容器启动后没有立即退出?**
因为默认命令改为 `uvicorn src.api:app` 常驻 API 服务。如需执行单次 CLI,用 `docker-compose run --rm tushare python -m src.main ...`
因为默认命令改为 `uvicorn src.api:app` 常驻 API 服务。如需执行单次 CLI,用 `docker-compose -f docker-compose.trade.yml run --rm tushare python -m src.main ...`