自选股全栈小系统(任务正文 v1)
一个可运行的全栈自选股系统:React 18 前端 + Go Gin/GORM 后端 + PostgreSQL。 支持 A 股 / 港股 / 美股切换,自选股的新增、删除、修改、查询,列表直接展示基础行情; 后端提供跨域、参数校验、统一错误结构与请求 / 应答日志,前端打印每一次通信的请求与应答, 后端不可用时页面给出明确告警并自动重试。
本项目是全栈应用,没有可公开访问的静态预览地址,因此归档中不登记
external。 源码与运行方式都在本目录,按下面的步骤即可在本地跑起来。
目录
docker-compose.yml 一条命令起 PostgreSQL
backend/ Go 服务
main.go 启动、优雅退出、超时配置
.env.example 全部可配置项
migrations/001_init.sql 参考 DDL(服务启动时用 AutoMigrate 自动建表)
internal/
config/ 环境变量读取
database/ 连接池、AutoMigrate、种子数据
models/ 实体与出入参结构
repository/ 自选股与基础数据访问
service/ 行情数据源(接口 + 模拟实现)
handler/ HTTP 接口
middleware/ CORS、请求日志、panic 恢复、请求 ID
apperr/ 统一错误类型
frontend/ React 应用
src/api.js 接口封装 + 通信日志
src/App.jsx 页面编排、轮询、告警与重试
src/components/ 表格、弹窗、通信日志面板
本地运行
# 1. 数据库
docker compose up -d # 或者用本机已有的 PostgreSQL
# 2. 后端
cd backend
cp .env.example .env # 按需修改
go run . # 默认监听 :8080
# 3. 前端(另开一个终端)
cd frontend
npm ci
npm run dev # http://localhost:5173
前端开发服务器把 /api 代理到 http://127.0.0.1:8080,因此本地不需要额外处理跨域;
但后端仍然实现了完整的 CORS 中间件,直接跨域访问时把前端地址加进 CORS_ORIGINS 即可。
首次启动时后端会自动建表,并写入 A 股 / 港股 / 美股三个市场与 15 个示例代码。
接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/health |
健康检查。数据库不通时返回 503 + status: degraded |
| GET | /api/v1/markets |
市场列表 |
| GET | /api/v1/markets/:marketCode/symbols |
某市场下的代码列表 |
| GET | /api/v1/watchlist?market=&q= |
查询自选股(可按市场过滤、按关键字搜索) |
| POST | /api/v1/watchlist |
新增,重复添加返回 409 |
| PUT | /api/v1/watchlist/:id |
修改备注 / 提醒价 / 排序(部分更新) |
| DELETE | /api/v1/watchlist/:id |
删除 |
| GET | /api/v1/quotes?market=&codes= |
批量行情 |
用户维度由请求头 X-User-Key 区分,缺省为 demo。
统一响应结构:
{ "requestId": "…", "data": { } }
{ "requestId": "…", "error": { "code": "INVALID_INPUT", "message": "请求参数不合法", "fields": { "note": "备注不能超过 200 字" } } }
关键实现
跨域
middleware.CORS 接受白名单来源列表,回显 Access-Control-Allow-Origin,
放行 Content-Type / Authorization / X-User-Key / X-Request-Id,
OPTIONS 预检直接返回 204 并带 10 分钟 Max-Age。
校验与错误处理
- 请求体用结构体 tag 做必填校验,业务规则(备注长度、提醒价非负、排序非负、id 为正整数)在 handler 里显式校验, 错误信息带字段名返回,前端可以直接标红对应输入框。
- 数据库层的「记录不存在」统一映射为 404,重复添加映射为 409。
middleware.Recovery兜住 panic,返回同一套错误结构并打印堆栈。- 包级
ErrXxx是只读模板,WithField/WithCause返回副本,避免并发请求串字段。
日志
- 后端:
middleware.RequestID生成或透传X-Request-Id;middleware.Logger打印[req] 方法 路径 来源IP query 请求体摘要与[resp] 状态码 字节数 耗时,5xx 额外记录底层原因。 请求体读取后会被放回,handler 仍能正常解析。 - 前端:
src/api.js在每次请求前后打一条日志,同时输出到浏览器控制台和页面底部的 「通信日志」折叠面板(可按全部 / 失败 / 成功过滤)。
后端不可用
三层保护:
- 数据库连不上时后端不退出,以只读模式继续监听,数据库接口统一返回 503;
- 前端把「代理层 5xx(不含后端 error.code)」和「fetch 直接失败」都判定为后端不可用, 停止轮询,顶部出现两条告警(一条说明原因与排查方式,一条说明数据为缓存且 8 秒后自动重试);
- 恢复后自动重新拉取健康检查并恢复行情刷新。
行情数据源
service.QuoteProvider 是接口,当前实现 SimulatedProvider 按「代码 + 15 秒时间桶」确定性生成
价格、昨收、今开、最高、最低、成交量与涨跌幅:同一时刻重复请求结果一致,随时间自然变化,
响应里 source: "simulated" 标明来源。接真实行情时新增一个实现并切换 QUOTE_MODE 即可,
handler 与前端都不需要改。
验证记录
go build ./...、go vet ./...、gofmt均通过。go run .实际启动后逐项核对:跨域响应头、预检 204、X-Request-Id回传、 备注超长 / 缺少必填 / id 非法三种校验错误、数据库缺失时的 503、以及[req][resp]两条日志。- 前端
npm run build通过;用真实浏览器打开开发服务器,确认后端宕机时出现「后端不可用」告警与通信日志面板。 - 未覆盖:测试环境没有可用的 PostgreSQL 实例,带数据库的自选股读写链路(自动建表、种子数据、 实际落库的增删改查)没有在真实数据库上跑通,只经过编译与接口契约检查。
已知边界
- 没有鉴权与登录,
X-User-Key只是用来区分数据归属的占位做法。 - 行情为模拟数据,不接任何真实行情供应商。
- 前端没有做分页,自选股数量大时一次性返回。
- 没有写测试用例(Go 单测与前端测试均未包含)。
当前归档信息(自动生成)
- 实验 ID:
stock-watching--minimax-m3-1-flash-preview-max-r01 - 模型:MiniMax M3.1 Flash Preview;推理档位:max
- 类型:fullstack;预览:none;网络:required
- 完整原始输入快照 · 主题与其他版本
- 输入记录:用户指令、覆盖范围确认与任务正文 v1 原文均已完整留存,无图片、附件或历史上下文。任务正文提到「基于项目原型图」,但该原型图未随本轮输入提供,本轮按文字描述自行设计信息架构。
- 工具:Claude Code(当前会话)。模型 MiniMax M3.1 Flash Preview,推理档位 max,单会话直接执行,未委派子代理。已验证:go build / go vet / gofmt 通过,go run 启动后 CORS 头、请求 ID、参数校验(备注超长、缺少必填、id 非法)、统一错误结构、预检请求、数据库缺失时的 503 与前后端日志均按预期输出;前端 vite build 通过,并用真实浏览器验证了「后端不可用」告警与通信日志面板。测试环境没有可用的 PostgreSQL 实例,因此带数据库的读写链路(自动建表、种子数据、自选股 CRUD 的实际落库)未在真实库上跑通,这部分只经过编译与接口契约检查。
- 运行方式:无静态预览,参见上方历史说明与源码。
部署适配记录
- 行情数据:任务正文未指定行情来源,这里抽象出 service.QuoteProvider 接口并提供 SimulatedProvider 实现(按「代码 + 15 秒时间桶」确定性生成价格,同一时刻多次请求一致、随时间自然变化),响应中带 source 字段标明来源。接真实行情只需新增一个实现并切换 QUOTE_MODE,handler 与前端不改。
- 后端不可用时的行为:数据库连不上时服务不退出,仍监听端口并以只读模式启动,数据库相关接口统一返回 503 与 {code, message, fields} 结构;/api/v1/health 返回 status=degraded,前端据此展示「后端降级」。
- 错误对象改为不可变:包级 ErrXxx 作为只读模板,WithField / WithCause 返回副本,避免并发请求之间互相污染 fields(初版直接改共享对象,实测出现跨请求串字段)。
- 前端补充网关错误识别:Vite dev proxy 与反向代理在后端宕机时返回的是代理自己的 5xx(不含后端的 error.code),前端据此判定为「后端不可用」而不是普通业务错误。
- 归档来源:F:\dev\ai-coding-demo-test(model-test-base 分支)中的 demos/stock-watching/runs/minimax-m3-1-flash-preview-max-r01;保留该测试轮号。
- 维护者于 2026-10-22 按 v3 核对原始输入及无预览采集记录,保留 unavailable;未生成截图或数值评分。旧 v2 评分见 catalog/score-reviews/2026-10-21-v3-source.json,未重跑模型、启动历史后端或改写实验源码。