From 6a1541ad9c4d3998d59855eac6cc09cd7982df08 Mon Sep 17 00:00:00 2001 From: fish Date: Sun, 3 May 2026 22:16:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=96=87=E6=A1=A3=E4=B8=8E?= =?UTF-8?q?=E5=AE=9E=E9=99=85=E4=BB=A3=E7=A0=81=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 42 +++++++++++----------- README.md | 104 ++++++++++++++++++++++-------------------------------- 2 files changed, 65 insertions(+), 81 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index edf5f8a..a918d70 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 ``` diff --git a/README.md b/README.md index 15edf7c..e348c12 100644 --- a/README.md +++ b/README.md @@ -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 ...`。