Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d283ee3196 | ||
|
|
9a965fab14 |
@@ -0,0 +1,488 @@
|
|||||||
|
# 市场综合概览 TDX 直连实施计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` to implement this plan task-by-task with review checkpoints.
|
||||||
|
|
||||||
|
**Goal:** 使用 Nest API 直接读取本机 TDX 隧道,在 Vue 3 首页提供可独立降级的市场综合概览。
|
||||||
|
|
||||||
|
**Architecture:** API 服务通过固定的 `127.0.0.1:17710` TDX HTTP 客户端读取只读行情,将 TDX 原始字段映射为版本化市场概览 DTO。首页按摘要、指数、宽度、板块和资金拆分请求,通过 Axios API 文件消费 Nest 接口;浏览器不直接连接 TDX。
|
||||||
|
|
||||||
|
**Tech Stack:** Vue 3、Vue Router、Pinia、Naive UI、Dart Sass、NestJS 11、TypeScript 5.9、Node.js 22.x 内置 `fetch`、OpenAPI/Swagger、Vitest、Playwright。
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Node 使用 `22.x`,MySQL 兼容 `5.7.x`,字符集排序规则为 `utf8mb4_general_ci`。
|
||||||
|
- TDX 只读访问固定到服务端 `http://127.0.0.1:17710/`,不得由请求参数传入任意 URL。
|
||||||
|
- TDX 不可用时显示 `unavailable`,不得使用离线样本或伪造实时行情回退。
|
||||||
|
- 前端业务请求只能位于 `web/src/api/<业务域>/index.ts`,统一调用 `web/src/utils/request.ts` 的 Axios 封装。
|
||||||
|
- 业务代码必须提供准确中文注释;不以原子化为目的过度拆分文件。
|
||||||
|
- 本功能不启用 MySQL、Redis、RocketMQ、AI、用户体系、交易指令或公网采集链路。
|
||||||
|
- 不提交 SSH 地址、Tailscale 地址、密钥、密码、`.env`、真实原始 TDX 载荷或生产数据。
|
||||||
|
- 修改符号前必须完成 GitNexus 影响分析;提交前必须执行 GitNexus 变更检测。
|
||||||
|
- 每个提交使用中文 Conventional Commit,并立即推送当前分支到项目 Gitea。
|
||||||
|
|
||||||
|
## 文件地图
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- Modify: `server/libs/config/src/runtime-config.ts`,增加 API 专属 TDX 地址和超时配置及严格校验。
|
||||||
|
- Modify: `deploy/env/development/api.env.example`,增加不含凭据的 `TDX_BASE_URL` 与 `TDX_TIMEOUT_MS` 模板。
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.types.ts`,记录已验证的 TDX 请求、响应和错误类型。
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.client.ts`,封装固定地址、超时、响应大小和脱敏错误。
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.mapper.ts`,转换 TDX 字段和数据口径。
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.dto.ts`,定义 OpenAPI/HTTP 响应 DTO。
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.service.ts`,并行获取模块数据并生成统一快照元数据。
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.controller.ts`,提供五个只读接口。
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.module.ts`,装配 TDX 客户端、服务和控制器。
|
||||||
|
- Modify: `server/apps/api/src/api.module.ts`,注册市场概览模块并注入 API 配置。
|
||||||
|
- Modify: `server/tools/export-openapi.ts`,让 OpenAPI 生成器包含市场概览控制器及其测试替身。
|
||||||
|
- Modify: `server/openapi/openapi.json`,提交生成后的契约。
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.client.test.ts`,测试 TDX 请求、超时、网络错误和响应上限。
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.mapper.test.ts`,测试字段映射、单位和缺失字段。
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.service.test.ts`,测试并行接口、快照时间和模块级降级。
|
||||||
|
- Create: `server/apps/api/test/market-overview.e2e-spec.ts`,测试 HTTP 状态、响应结构和错误映射。
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- Modify: `web/src/api/generated/schema.ts`,由 OpenAPI 生成命令更新。
|
||||||
|
- Create: `web/src/types/market-overview.ts`,定义页面消费的类型别名和显示状态。
|
||||||
|
- Create: `web/src/api/market-overview/index.ts`,集中封装五个 Axios 请求。
|
||||||
|
- Create: `web/src/views/market-overview/useMarketOverview.ts`,管理并行加载、刷新、模块状态和分析偏好本地存储。
|
||||||
|
- Create: `web/src/views/market-overview/MarketOverviewPage.vue`,组织首屏布局和页面级状态。
|
||||||
|
- Create: `web/src/views/market-overview/components/MarketConclusion.vue`,显示结论、交易状态、更新时间和来源。
|
||||||
|
- Create: `web/src/views/market-overview/components/MarketDrivers.vue`,显示驱动与风险证据。
|
||||||
|
- Create: `web/src/views/market-overview/components/IndexOverview.vue`,显示可点击核心指数。
|
||||||
|
- Create: `web/src/views/market-overview/components/MarketBreadth.vue`,显示涨跌分布、宽度和量能。
|
||||||
|
- Create: `web/src/views/market-overview/components/SectorOverview.vue`,显示可点击板块排行。
|
||||||
|
- Create: `web/src/views/market-overview/components/FundTrend.vue`,显示资金趋势和不可用状态。
|
||||||
|
- Modify: `web/src/app/router.ts`,将 `/` 指向市场概览页面。
|
||||||
|
- Modify: `web/src/app/App.vue` 与 `web/src/styles/index.scss`,更新应用壳和响应式页面基础样式。
|
||||||
|
- Create: `web/src/api/market-overview/index.test.ts`,测试 API 路径、方法、参数和错误脱敏。
|
||||||
|
- Create: `web/src/views/market-overview/useMarketOverview.test.ts`,测试并行加载、刷新和偏好保存。
|
||||||
|
- Modify: `web/src/app/App.test.ts`,改为验证市场概览首屏语义和状态入口。
|
||||||
|
- Modify: `web/tests/smoke.spec.ts`,增加桌面、窄屏、模块降级和无水平溢出验证。
|
||||||
|
|
||||||
|
### 功能档案
|
||||||
|
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/requirements.md`,同步 TDX 直连和分模块接口范围。
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/design.md`,链接本设计并移除旧的样本默认表述。
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/implementation-plan.md`,更新为已执行任务索引。
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/test-plan.md`,加入 TDX 联调、模块降级、安全和性能证据。
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/development-summary.md`,记录原始需求、实际完成、缺陷、延期项、提交和验证证据。
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/review/code-review.md`,记录代码 Review 结论和残余风险。
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/review/standards-review.md`,记录开发规范符合性 Review。
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/tdx-smoke-report.md`,只保存脱敏的真实 TDX 字段完整性、耗时和错误分类。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: 开发门禁、工作区和 TDX 协议基线
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/gitnexus/impact-plan.md`
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/gitnexus/implementation-baseline.md`
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/tdx-smoke-report.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: 已确认设计文档、现有 Tailscale SSH 隧道、开发环境 API 配置。
|
||||||
|
- Produces: 新分支和工作区、GitNexus 影响证据、TDX 只读请求方法及脱敏字段基线。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建隔离工作区并确认远程状态**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch origin main
|
||||||
|
git worktree add .worktrees/feat-mo-001-tdx-overview -b codex/feat-mo-001-tdx-overview origin/main
|
||||||
|
git -C .worktrees/feat-mo-001-tdx-overview status --short --branch
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:新工作区基于已推送的 `9a965fa`,不携带主工作区的 `AGENTS.md` 未提交修改。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 执行开发前门禁并记录结果**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm schema:baseline
|
||||||
|
pnpm schema:verify
|
||||||
|
npx gitnexus analyze
|
||||||
|
npx gitnexus impact --repo /Users/citrons/Documents/Codex/2026-08-12/wo-x/a-share-analysis --scope upstream "ApiModule.register"
|
||||||
|
npx gitnexus impact --repo /Users/citrons/Documents/Codex/2026-08-12/wo-x/a-share-analysis --scope upstream "createAppRouter"
|
||||||
|
npx gitnexus impact --repo /Users/citrons/Documents/Codex/2026-08-12/wo-x/a-share-analysis --scope upstream "createRequestClient"
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:Schema 命令只读取开发环境;若本机缺少 MySQL 5.7.x 客户端,沿用已确认的 FEAT-MO-001 一次性 waiver 记录失败,不伪造基线。任何 HIGH/CRITICAL 影响先记录并暂停代码修改。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 用当前 TDX 隧道执行最小只读协议请求**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --fail-with-body --silent --show-error \
|
||||||
|
--connect-timeout 3 --max-time 10 \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-X POST 'http://127.0.0.1:17710/' \
|
||||||
|
--data '{"id":1,"method":"get_market_data","params":{"stock_list":["600519.SH"],"count":5,"dividend_type":"none","period":"1d"}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:记录实际 HTTP 状态、业务错误码、响应耗时、字段名称和字段完整性;报告不得保存原始载荷、地址、账户或密钥。若方法不可用,按 TDX 返回的公开错误信息调整为仓库文档支持的只读方法,并把最终方法名写入 `tdx.types.ts` 和冒烟报告。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 提交门禁和协议基线**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add docs/features/FEAT-MO-001-market-overview/gitnexus docs/features/FEAT-MO-001-market-overview/tdx-smoke-report.md
|
||||||
|
git commit -m "docs(FEAT-MO-001): 建立TDX概览开发基线"
|
||||||
|
git push -u origin codex/feat-mo-001-tdx-overview
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:提交只包含治理和脱敏报告,不包含真实 TDX 原始 JSON。
|
||||||
|
|
||||||
|
### Task 2: 配置和 TDX 客户端
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `server/libs/config/src/runtime-config.ts`
|
||||||
|
- Modify: `deploy/env/development/api.env.example`
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.types.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.client.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.client.test.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 1 的已验证只读请求方法和字段基线。
|
||||||
|
- Produces: `ApiRuntimeConfig.tdx`、`TdxClient.request<TResponse>()`、`TdxClientError`。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 先写配置和客户端失败测试**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('拒绝非本机 TDX 地址', () => {
|
||||||
|
expect(() => loadApiConfig({ ...validEnv, TDX_BASE_URL: 'http://example.test' })).toThrow(
|
||||||
|
'TDX_BASE_URL 必须指向本机回环地址',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('将 TDX 超时转换为脱敏错误', async () => {
|
||||||
|
const client = createTdxClient({ baseUrl: 'http://127.0.0.1:17710', timeoutMs: 50 }, timeoutFetch);
|
||||||
|
await expect(client.request('get_market_data', { stock_list: ['600519.SH'] })).rejects.toMatchObject({
|
||||||
|
kind: 'timeout',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行定向测试确认失败**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/server exec vitest run apps/api/src/market-overview/tdx/tdx.client.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:因配置字段、客户端工厂和错误类型不存在而失败。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 实现最小配置和客户端**
|
||||||
|
|
||||||
|
`ApiRuntimeConfig` 增加:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
tdx: Readonly<{
|
||||||
|
baseUrl: 'http://127.0.0.1:17710';
|
||||||
|
timeoutMs: number;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
`TdxClient` 暴露:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface TdxClient {
|
||||||
|
request<TResponse>(method: string, params: Record<string, unknown>): Promise<TResponse>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
客户端使用 Node 22 内置 `fetch`,固定 POST `/`、JSON-RPC 请求体、连接/读取超时和 1 MiB 响应上限;只抛出 `timeout`、`network`、`protocol`、`unavailable` 四类错误,禁止把 URL、请求头或原始正文放入错误对象。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 运行定向测试确认通过**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/server exec vitest run apps/api/src/market-overview/tdx/tdx.client.test.ts
|
||||||
|
pnpm --filter @a-share-analysis/server typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交并推送**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/libs/config/src/runtime-config.ts deploy/env/development/api.env.example server/apps/api/src/market-overview/tdx
|
||||||
|
git commit -m "feat(FEAT-MO-001): 增加TDX直连客户端"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 3: 市场概览 DTO、映射和分模块 API
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.mapper.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/tdx/tdx.mapper.test.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.dto.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.service.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.service.test.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.controller.ts`
|
||||||
|
- Create: `server/apps/api/src/market-overview/market-overview.module.ts`
|
||||||
|
- Modify: `server/apps/api/src/api.module.ts`
|
||||||
|
- Create: `server/apps/api/test/market-overview.e2e-spec.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `TdxClient.request<TResponse>()` 和 `ApiRuntimeConfig.tdx`。
|
||||||
|
- Produces: `GET /api/v1/market-overview/{summary,indices,breadth,sectors,funds}`,统一响应元数据和模块数据。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 写 DTO 和映射失败测试**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('将涨跌字段转换为 A 股红涨绿跌口径并保留百分比', () => {
|
||||||
|
expect(mapIndexQuote({ code: '000001', name: '上证指数', price: 3380.12, change: 28.4 })).toEqual({
|
||||||
|
code: '000001', name: '上证指数', price: 3380.12, changePercent: 0.86, direction: 'up',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('缺少板块数据时返回 partial 而不是抛出整页错误', async () => {
|
||||||
|
const response = await service.getSectors();
|
||||||
|
expect(response.status).toBe('partial');
|
||||||
|
expect(response.warnings).toHaveLength(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行定向测试确认失败**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/server exec vitest run apps/api/src/market-overview/tdx/tdx.mapper.test.ts apps/api/src/market-overview/market-overview.service.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 实现 DTO 和五个服务方法**
|
||||||
|
|
||||||
|
服务公开以下方法:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getSummary(): Promise<MarketOverviewResponse<SummaryData>>;
|
||||||
|
getIndices(): Promise<MarketOverviewResponse<IndexData>>;
|
||||||
|
getBreadth(): Promise<MarketOverviewResponse<BreadthData>>;
|
||||||
|
getSectors(): Promise<MarketOverviewResponse<SectorData>>;
|
||||||
|
getFunds(): Promise<MarketOverviewResponse<FundData>>;
|
||||||
|
```
|
||||||
|
|
||||||
|
每次请求生成 `snapshotId` 和 `asOf`,调用已验证的 TDX 只读方法,映射后只返回页面需要字段。单模块错误映射为 `partial` 或 `unavailable`,不将 TDX 原始错误透传。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 注册控制器并完成 HTTP 测试**
|
||||||
|
|
||||||
|
控制器只注入 `MarketOverviewService`,每个 `@Get` 方法仅调用对应服务方法并声明 Swagger 成功/不可用响应;模块在 `ApiModule.register(config)` 中装配 `TdxClient` 的配置工厂。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/server exec vitest run apps/api/test/market-overview.e2e-spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:五个接口返回 200 或结构化 503,响应不包含 TDX 地址、请求参数或原始载荷。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交并推送**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/apps/api/src/market-overview server/apps/api/src/api.module.ts server/apps/api/test/market-overview.e2e-spec.ts
|
||||||
|
git commit -m "feat(FEAT-MO-001): 增加市场概览分模块接口"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 4: OpenAPI 契约和前端 API 状态层
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `server/tools/export-openapi.ts`
|
||||||
|
- Modify: `server/openapi/openapi.json`
|
||||||
|
- Modify: `web/src/api/generated/schema.ts`
|
||||||
|
- Create: `web/src/types/market-overview.ts`
|
||||||
|
- Create: `web/src/api/market-overview/index.ts`
|
||||||
|
- Create: `web/src/api/market-overview/index.test.ts`
|
||||||
|
- Create: `web/src/views/market-overview/useMarketOverview.ts`
|
||||||
|
- Create: `web/src/views/market-overview/useMarketOverview.test.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: 后端五个 OpenAPI 路径和 `web/src/utils/request.ts`。
|
||||||
|
- Produces: `marketOverviewApi`、`useMarketOverview()`、统一模块加载状态和本地分析偏好。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 先写前端 API 和状态层失败测试**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('使用独立路径请求指数接口', async () => {
|
||||||
|
const request = vi.fn().mockResolvedValue({ data: { status: 'fresh' } });
|
||||||
|
await createMarketOverviewApi(request).getIndices();
|
||||||
|
expect(request).toHaveBeenCalledWith({ method: 'GET', url: '/api/v1/market-overview/indices' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('一个模块失败时其他模块仍保留结果', async () => {
|
||||||
|
const state = await useMarketOverview({ summary: okSummary, indices: failed, breadth: okBreadth, sectors: okSectors, funds: failed });
|
||||||
|
expect(state.summary.status).toBe('ready');
|
||||||
|
expect(state.indices.status).toBe('error');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行定向测试确认失败**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/web exec vitest run src/api/market-overview/index.test.ts src/views/market-overview/useMarketOverview.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 生成并校验 OpenAPI 类型**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm check:contracts
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:生成的 JSON 和 TypeScript 契约只包含五个市场概览路径,且 `verify-generated-contracts` 通过。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 实现 API 文件和 composable**
|
||||||
|
|
||||||
|
API 文件只调用请求封装:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export function createMarketOverviewApi(requestClient = request) {
|
||||||
|
return {
|
||||||
|
getSummary: () => requestClient<SummaryResponse>({ method: 'GET', url: '/api/v1/market-overview/summary' }),
|
||||||
|
getIndices: () => requestClient<IndicesResponse>({ method: 'GET', url: '/api/v1/market-overview/indices' }),
|
||||||
|
getBreadth: () => requestClient<BreadthResponse>({ method: 'GET', url: '/api/v1/market-overview/breadth' }),
|
||||||
|
getSectors: () => requestClient<SectorsResponse>({ method: 'GET', url: '/api/v1/market-overview/sectors' }),
|
||||||
|
getFunds: () => requestClient<FundsResponse>({ method: 'GET', url: '/api/v1/market-overview/funds' }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`useMarketOverview()` 使用 `Promise.allSettled` 并行加载,暴露 `refresh()`、每个模块的 `loading/ready/error` 状态,以及浏览器 `localStorage` 中的分析周期和分析引擎偏好;不得存储行情原始载荷。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 运行定向测试和类型检查**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/web exec vitest run src/api/market-overview/index.test.ts src/views/market-overview/useMarketOverview.test.ts
|
||||||
|
pnpm --filter @a-share-analysis/web typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: 提交并推送**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/tools/export-openapi.ts server/openapi/openapi.json web/src/api/generated/schema.ts web/src/types/market-overview.ts web/src/api/market-overview web/src/views/market-overview/useMarketOverview.ts web/src/views/market-overview/useMarketOverview.test.ts
|
||||||
|
git commit -m "feat(FEAT-MO-001): 同步市场概览前端契约"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 5: Vue 首屏和交互状态
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `web/src/views/market-overview/MarketOverviewPage.vue`
|
||||||
|
- Create: `web/src/views/market-overview/components/MarketConclusion.vue`
|
||||||
|
- Create: `web/src/views/market-overview/components/MarketDrivers.vue`
|
||||||
|
- Create: `web/src/views/market-overview/components/IndexOverview.vue`
|
||||||
|
- Create: `web/src/views/market-overview/components/MarketBreadth.vue`
|
||||||
|
- Create: `web/src/views/market-overview/components/SectorOverview.vue`
|
||||||
|
- Create: `web/src/views/market-overview/components/FundTrend.vue`
|
||||||
|
- Modify: `web/src/app/router.ts`
|
||||||
|
- Modify: `web/src/app/App.vue`
|
||||||
|
- Modify: `web/src/styles/index.scss`
|
||||||
|
- Modify: `web/src/app/App.test.ts`
|
||||||
|
- Modify: `web/tests/smoke.spec.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `useMarketOverview()`、市场概览类型和 `marketOverviewApi`。
|
||||||
|
- Produces: `/` 可访问的市场综合概览首屏,包含数据来源、时间、模块状态、依据入口和分析偏好入口。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 先写页面失败测试**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('首屏显示市场结论、更新时间和 TDX 来源', async () => {
|
||||||
|
const wrapper = mountMarketOverview({ state: freshState });
|
||||||
|
expect(wrapper.getByRole('heading', { name: '市场综合概览' })).toBeTruthy();
|
||||||
|
expect(wrapper.getByText('数据来源:TDX')).toBeTruthy();
|
||||||
|
expect(wrapper.getByText('更新时间')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TDX 不可用时显示错误状态而不是实时数据', async () => {
|
||||||
|
const wrapper = mountMarketOverview({ state: unavailableState });
|
||||||
|
expect(wrapper.getByText('TDX 数据不可用')).toBeTruthy();
|
||||||
|
expect(wrapper.queryByText('实时行情')).toBeNull();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行前端测试确认失败**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/web exec vitest run src/app/App.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 实现首屏和组件状态**
|
||||||
|
|
||||||
|
页面保持结论优先顺序;指数、板块使用按钮/链接样式保留未来详情页入口;加载、空、部分、过期、错误状态均有明确文案和稳定尺寸。分析周期和分析引擎使用 Naive UI 的选择控件,偏好保存在本地,不触发 TDX 请求参数变化。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 接入路由和应用壳**
|
||||||
|
|
||||||
|
`createAppRouter()` 的 `/` 路由渲染 `MarketOverviewPage`;应用壳保留可访问 header、main 和 `NMessageProvider`,不新增无业务意义的导航菜单。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 运行组件、构建和浏览器冒烟**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @a-share-analysis/web exec vitest run src/app/App.test.ts src/views/market-overview
|
||||||
|
pnpm --filter @a-share-analysis/web build
|
||||||
|
pnpm verify:e2e -- --project=chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:桌面和窄屏无横向溢出,TDX 连接不可用时页面仍可读但明确标记不可用。
|
||||||
|
|
||||||
|
- [ ] **Step 6: 提交并推送**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add web/src/views/market-overview web/src/app/router.ts web/src/app/App.vue web/src/styles/index.scss web/src/app/App.test.ts web/tests/smoke.spec.ts
|
||||||
|
git commit -m "feat(FEAT-MO-001): 完成TDX市场概览首屏"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 6: 真实 TDX 联调、文档追踪和 Review
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/requirements.md`
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/design.md`
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/implementation-plan.md`
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/test-plan.md`
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/development-summary.md`
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/review/code-review.md`
|
||||||
|
- Create: `docs/features/FEAT-MO-001-market-overview/review/standards-review.md`
|
||||||
|
- Modify: `docs/features/FEAT-MO-001-market-overview/manifest.yaml`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: 已完成的 API、前端页面、脱敏 TDX 基线和测试结果。
|
||||||
|
- Produces: 完整功能档案、开发总结、安全/性能证据、代码 Review 和规范 Review。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 外出 Tailscale 隧道执行真实只读联调**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -S "$HOME/.ssh/tdx-windows-tailnet-control" -O check tdx-windows-tailnet
|
||||||
|
lsof -nP -iTCP:17710 -sTCP:LISTEN
|
||||||
|
pnpm --filter @a-share-analysis/server exec vitest run apps/api/test/market-overview.e2e-spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
记录接口耗时、字段完整性、模块状态和 TDX 错误分类;报告不保存地址、密钥、完整请求或完整响应。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 更新功能档案**
|
||||||
|
|
||||||
|
开发总结必须明确:原始需求、范围变化(由分层混合改为 TDX 单路径)、实际完成、修复缺陷、延期项、关键提交、测试/构建/视觉证据和残余风险。需求、设计、测试计划和 manifest 的状态必须一致。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 执行代码 Review 和规范 Review**
|
||||||
|
|
||||||
|
Review 至少检查:TDX 地址白名单、错误脱敏、模块级降级、请求超时、响应大小、DTO 不透传原始字段、Axios API 边界、中文注释、性能目标、无凭据提交和移动端溢出。问题及处置写入两个 Review 文档。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 完成全量验证和 GitNexus 变更检测**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx gitnexus analyze
|
||||||
|
npx gitnexus detect-changes --repo /Users/citrons/Documents/Codex/2026-08-12/wo-x/a-share-analysis --scope all
|
||||||
|
pnpm verify
|
||||||
|
pnpm verify:source
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:GitNexus 只显示市场概览、配置、OpenAPI、前端路由和测试相关变更;任何未预期跨模块影响必须先处置。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 执行治理收尾并推送标签(仅在用户批准发布时)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm governance:finish -- FEAT-MO-001 market-overview
|
||||||
|
git add docs/features/FEAT-MO-001-market-overview
|
||||||
|
git commit -m "docs(FEAT-MO-001): 完成市场概览开发收尾"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
未获发布批准前不创建或推送版本标签;所有提交推送失败时不得标记功能完成。
|
||||||
|
|
||||||
|
## 计划自审
|
||||||
|
|
||||||
|
- 设计中的五个分模块接口均在 Task 3、Task 4、Task 5 有对应实现和测试。
|
||||||
|
- TDX 单一路径、无样本回退、安全、性能、分钟级刷新、错误状态和脱敏报告均有明确任务。
|
||||||
|
- OpenAPI、前端 Axios API 文件、Nest 分层、中文注释和 GitNexus 门禁均有明确文件及命令。
|
||||||
|
- 没有 `TODO`、`TBD` 或未定义的后续函数;TDX 方法名在 Task 1 以实际只读协议验证结果为准并写入类型,避免臆测字段。
|
||||||
|
- MySQL/Redis/RocketMQ/AI/用户体系未被实现任务隐式引入。
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# 市场综合概览 TDX 直连设计
|
||||||
|
|
||||||
|
- 功能编号:FEAT-MO-001
|
||||||
|
- 设计版本:0.2
|
||||||
|
- 状态:已获用户确认,待实现计划
|
||||||
|
- 日期:2026-08-18
|
||||||
|
|
||||||
|
## 1. 设计目标
|
||||||
|
|
||||||
|
首页市场概览直接使用 TDX 行情数据,向用户提供结论优先的综合盘面视图。数据链路不保留离线样本 Provider 或运行时数据源切换,TDX 不可用时明确展示不可用状态,不伪造实时行情。
|
||||||
|
|
||||||
|
首屏支持综合、短线和中长线视角,以及规则、AI、混合和对照模式的状态入口;本次 TDX 首屏只提供统一的市场快照和规则事实展示,不在本轮实现付费 AI 结论。
|
||||||
|
|
||||||
|
## 2. 系统边界
|
||||||
|
|
||||||
|
浏览器不得直接连接 TDX。Nest API 在服务端调用本机 TDX 隧道端点,Vue 通过既有 Axios 封装访问 Nest API。
|
||||||
|
|
||||||
|
```text
|
||||||
|
Vue 市场概览
|
||||||
|
-> Axios request 封装
|
||||||
|
-> Nest 市场概览 API
|
||||||
|
-> TDX HTTP 客户端
|
||||||
|
-> 127.0.0.1:17710
|
||||||
|
-> SSH / Tailscale 隧道
|
||||||
|
-> Windows 127.0.0.1:17709
|
||||||
|
-> TDX
|
||||||
|
```
|
||||||
|
|
||||||
|
家庭局域网和外出 Tailscale 只改变 SSH 别名和网络路径,业务服务始终访问本机 `127.0.0.1:17710`。真实地址、用户、密钥和控制套接字只保存在开发机,不进入仓库。
|
||||||
|
|
||||||
|
## 3. 后端模块设计
|
||||||
|
|
||||||
|
后端按市场概览业务模块组织,保持连续可读的职责边界:
|
||||||
|
|
||||||
|
```text
|
||||||
|
server/apps/api/src/market-overview/
|
||||||
|
├── market-overview.module.ts
|
||||||
|
├── market-overview.controller.ts
|
||||||
|
├── market-overview.service.ts
|
||||||
|
├── market-overview.dto.ts
|
||||||
|
└── tdx/
|
||||||
|
├── tdx.client.ts
|
||||||
|
├── tdx.mapper.ts
|
||||||
|
└── tdx.types.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
- `controller`:提供按业务模块拆分的只读 HTTP 接口。
|
||||||
|
- `service`:组织 TDX 数据、统一快照时间并生成规则事实摘要。
|
||||||
|
- `dto`:定义 HTTP 响应契约,禁止把 TDX 原始字段直接暴露给前端。
|
||||||
|
- `tdx.client`:只负责请求本机 TDX 端点、超时和协议级错误。
|
||||||
|
- `tdx.mapper`:把 TDX 原始字段转换为领域字段和展示口径。
|
||||||
|
- `tdx.types`:描述经过确认的 TDX 原始响应结构。
|
||||||
|
|
||||||
|
首版接口:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/market-overview/summary
|
||||||
|
GET /api/market-overview/indices
|
||||||
|
GET /api/market-overview/breadth
|
||||||
|
GET /api/market-overview/sectors
|
||||||
|
GET /api/market-overview/funds
|
||||||
|
```
|
||||||
|
|
||||||
|
接口按模块拆分,页面进入时可并行请求;每个接口失败不会使其他模块整体失败。未来详情页可以复用指数和板块的业务边界,不通过新增巨型首页接口解决。
|
||||||
|
|
||||||
|
## 4. 前端模块设计
|
||||||
|
|
||||||
|
```text
|
||||||
|
web/src/
|
||||||
|
├── api/market-overview/index.ts
|
||||||
|
├── views/market-overview/
|
||||||
|
│ ├── MarketOverviewPage.vue
|
||||||
|
│ ├── useMarketOverview.ts
|
||||||
|
│ └── components/
|
||||||
|
└── types/market-overview.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
业务请求只能通过 `web/src/api/market-overview/index.ts` 调用 `web/src/utils/request.ts`。页面组件不直接使用 Axios、不拼接 TDX 地址,也不读取服务端环境变量。
|
||||||
|
|
||||||
|
首屏顺序:市场结论、驱动与风险、核心指数、涨跌分布与市场宽度、成交量与成交额、板块强弱、资金趋势、更新时间和 TDX 连接状态。指数和板块保留进入未来详情页的可识别入口。
|
||||||
|
|
||||||
|
## 5. 响应契约与状态
|
||||||
|
|
||||||
|
每个业务接口采用统一外层结构:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"snapshotId": "20260818-101500",
|
||||||
|
"asOf": "2026-08-18T10:15:00+08:00",
|
||||||
|
"source": "tdx",
|
||||||
|
"status": "fresh",
|
||||||
|
"data": {},
|
||||||
|
"warnings": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`status` 取值为 `fresh`、`delayed`、`partial` 或 `unavailable`。`snapshotId` 和 `asOf` 用于让并行接口在同一次市场快照下可对照;数据日期、更新时间和来源状态必须在页面可见。
|
||||||
|
|
||||||
|
- TDX 单个模块失败:只显示对应模块错误。
|
||||||
|
- TDX 返回部分字段:保留可用字段并标记 `partial`。
|
||||||
|
- TDX 超时或完全不可用:显示连接状态和重试入口,不显示虚假行情。
|
||||||
|
- 后端日志只记录脱敏错误摘要,不保存完整原始行情载荷。
|
||||||
|
|
||||||
|
## 6. 安全策略
|
||||||
|
|
||||||
|
- TDX 地址固定来自服务端配置,禁止由 HTTP 请求参数传入任意 URL,避免 SSRF。
|
||||||
|
- TDX 仅通过本机回环端点访问;Windows TDX 继续只监听 `127.0.0.1:17709`。
|
||||||
|
- API 只读,不提供交易指令、买卖建议、目标价或仓位建议。
|
||||||
|
- SSH、Tailscale、TDX 账号、密钥、密码、本地环境文件和生产地址不提交 Git。
|
||||||
|
- 真实行情只用于只读联调,日志、测试报告和截图不得保存家庭网络信息或完整原始载荷。
|
||||||
|
|
||||||
|
## 7. 性能策略
|
||||||
|
|
||||||
|
- 页面并行请求各业务接口,避免串行阻塞整页。
|
||||||
|
- TDX 客户端设置连接超时和读取超时,并记录请求耗时。
|
||||||
|
- DTO 只返回页面需要字段,不透传完整 TDX 原始响应。
|
||||||
|
- 首版采用分钟级刷新,不承诺秒级实时;暂不启用 Redis 缓存,待数据口径和刷新频率稳定后评估。
|
||||||
|
- 前端使用模块级加载、空、部分数据和错误状态,避免一个模块阻断首屏。
|
||||||
|
|
||||||
|
## 8. 测试与验证
|
||||||
|
|
||||||
|
- TDX 类型转换和展示口径的单元测试。
|
||||||
|
- 每个接口的成功、超时、空数据、部分字段和不可用测试。
|
||||||
|
- Nest API 响应契约和错误状态测试。
|
||||||
|
- Vue API 模块、页面状态和模块级失败降级测试。
|
||||||
|
- 家庭局域网 SSH 路径和外出 Tailscale SSH 路径的端到端只读联调。
|
||||||
|
- 桌面与窄屏视觉验证,确认结论、更新时间和错误状态不重叠。
|
||||||
|
- 开发前和提交前执行 GitNexus 影响分析与变更检测;开发完成执行代码 Review、规范 Review、测试、构建和 `governance:finish`。
|
||||||
|
|
||||||
|
## 9. 明确不在本次实现范围
|
||||||
|
|
||||||
|
- 离线样本 Provider、运行时数据源切换和自动回退。
|
||||||
|
- MySQL、Redis、RocketMQ 的正式行情存储或采集链路。
|
||||||
|
- 付费 AI 请求和 AI 结论生成。
|
||||||
|
- 用户、邀请、会员、订阅、社区、自选股、笔记、计划和交易功能。
|
||||||
|
- 指数或板块详情页的完整实现;本次只保留入口和接口边界。
|
||||||
|
|
||||||
|
## 10. 回退方案
|
||||||
|
|
||||||
|
若 TDX 协议字段无法稳定确认、首屏核心模块无法独立降级或隧道联调影响本地构建,则停止 TDX 直连功能提交,回退到上一个本地标签;不得以样本数据冒充 TDX 实时结果。回退只撤销本功能提交,不删除开发机 SSH、Tailscale 或 TDX 配置。
|
||||||
Reference in New Issue
Block a user