Article
2026-06-27-襷の系譜-v0.1-技术方案
襷の系譜 V0.1 技术方案
日期:2026-06-27
关联文档:2026-06-27-襷の系譜-v0.1-PRD.md
更新记录:
- 2026-06-28:补充 V0.1 P0 数据范围、所属履历时间线模型、四层赛事模型、审计模型和 V0.2 边界。
- 2026-06-28:明确 V0.1 实际部署方案:先与个人站点共存在 ubuntu-3,通过目录、服务、数据库、反向代理和备份隔离,保留后续迁移到 ubuntu-4 的路径。
- 2026-06-28:新增独立部署方案文档,明确后续采用与 personal-site 一致的 GitHub Actions 镜像归档部署,并单独讨论数据同步策略。
- 2026-06-29:调整真实数据更新路线,明确 V0.1 采用单库直连更新,
seed退出真实数据日常维护主链路。 - 2026-06-29:补充姓名标准化规则、来源抽样要求和存量重复治理原则。
1. 技术目标
V0.1 的技术目标是支撑一个小而可信的选手资料站:
- 以选手档案为核心页面
- 支持日文主体验,中文和英文保留界面框架
- 支持青山学院大学、國學院大學、中央大学的代表样本数据录入
- 支持 P0 数据:基础资料、所属履历、当前 PB、箱根记录、来源、确认状态
- 支持从选手跳转到同高中、同大学、同当前所属的相关选手
- 支持后续扩展到更多大学、高中、实业团、赛事和成绩库
V0.1 不追求复杂架构。优先级是:
- 数据模型清楚
- 数据可维护
- 页面可读
- 来源可追溯
- 后续可演进
2. 技术栈选择
2.1 应用框架
采用:
- Next.js
- React
- TypeScript
- App Router
理由:
- 前后端一体,适合个人项目快速推进
- SSR/SSG 能支持资料站和 SEO
- API Route / Server Action 足够支撑后台管理和数据查询
- TypeScript 有利于长期维护数据结构
- 后续部署到 Vercel、自有服务器或 Docker 都比较顺滑
2.2 样式与组件
采用:
- Tailwind CSS
- shadcn/ui
- lucide-react
理由:
- 适合快速搭建克制、清爽、资料馆风格的界面
- 表格、表单、弹窗、标签、Tabs 等基础组件容易复用
- lucide-react 能满足搜索、筛选、外链、语言切换、来源等图标需求
视觉方向:
Archive, not Dashboard.
界面应像现代化选手名鉴和资料馆,不做新闻站,也不做复杂数据大屏。
2.3 数据库
采用:
- PostgreSQL
- Prisma ORM
理由:
- 项目早期最重要的是实体、关系、来源、状态和约束
- PostgreSQL 适合做主业务库
- Prisma 能提供清晰 schema、迁移、类型生成和开发体验
- 后续可以平滑增加全文搜索、物化视图、分析同步等能力
明确不在 V0.1 使用:
- Neo4j
- ClickHouse
- BigQuery
- 独立数据仓库
- 独立图数据库
关系图和关系跳转在早期完全可以由 PostgreSQL 查询支撑。
2.4 版本范围决策
V0.1 只做“选手核心档案”,不做完整比赛库。
P0 数据范围:
- 姓名
- 读音
- 罗马字
- 生年月日
- 出身地
- 高中
- 大学
- 当前所属,若已毕业或转入实业团
- 5000m PB
- 10000m PB
- 半马 PB
- 马拉松 PB,若有
- 箱根出场记录
- 数据来源
- 数据确认状态
V0.1 不做:
- PB 历史
- 2nd best
- 全量比赛成绩
- 出雲駅伝、全日本大学駅伝系统收录
- 普通田径比赛成绩库
- SNS、趣味、座右铭、Q&A 等粉丝向字段
- 复杂关系图
V0.2 再开始建设“比赛结果库”,收录每场比赛数据,并为关系图提供更细粒度的比赛关系。
3. 架构概览
V0.1 架构:
Next.js App
├─ Public Pages
│ ├─ Home
│ ├─ Player List
│ ├─ Player Detail
│ └─ Search
│
├─ Admin Pages
│ ├─ Player Management
│ ├─ Organization Management
│ ├─ Record Management
│ └─ Source Management
│
├─ Server Actions / API Routes
│ ├─ Query public data
│ ├─ Mutate admin data
│ └─ Maintain seed/sample data
│
└─ PostgreSQL
├─ Person
├─ Organization
├─ Membership
├─ Competition
├─ CompetitionEdition
├─ Race
├─ RaceResult
├─ PersonalBest
├─ Source
├─ AuditLog
└─ Translation / Alias
部署早期可以采用单体应用:
Next.js + PostgreSQL
无需拆分独立后端服务。
4. 数据建模原则
4.1 宽收集,窄展示
V0.1 页面展示可以很克制,但数据收集应尽量完整。
原因:
- 大学官网、箱根官方、NTV、月陸、粉丝站提供的字段各不相同
- 部分字段当前不用,后续可能成为关系探索、筛选或专题内容的基础
- 对个人项目来说,早期字段略多的成本低于后期反复补历史数据
原则:
- 能稳定确认的公开事实可以收
- 不确定字段可以收,但必须标注状态
- 粉丝向字段可以收,但默认不作为核心展示
- 每条重要数据尽量关联来源
4.2 关系优先于页面
数据模型应优先表达实体之间的关系,而不是只服务当前页面。
重点关系:
- 选手属于大学
- 选手出身高中
- 选手曾经属于某组织
- 选手参加某届赛事
- 选手在某区间有成绩
- 选手拥有某项目 PB
- 数据来自某个来源
V0.1 的页面很少,但模型要能支撑:
- 同大学
- 同高中
- 同当前所属
- 所属履历时间线
- 高中到大学流向
- 大学到实业团流向
- 赛事出场历史
核心约定:
Person表示人物/选手。Organization统一表示高中、大学、实业团、俱乐部、公司、联盟等组织。Membership表示“所属履历/生涯所属”,即某人在某时间段与某组织的关系。- 高中、大学、实业团不直接写死在
Person上,而是通过Membership串联。 - 当前所属通过
Membership.endDate = null或endDate >= today判断。
4.3 来源和确认状态是一等数据
数据站的可信度来自来源和校验,而不是字段数量。
所有关键事实都应支持:
- 来源 URL
- 来源名称
- 来源类型
- 抓取或录入日期
- 确认状态
- 备注
建议确认状态:
verified:已确认pending:待确认conflicting:存在冲突missing:缺失
4.4 多语言以原文为核心
日文是主语言和事实原文。
多语言策略:
- 日文名称作为主名称
- 中文和英文作为翻译或辅助展示
- 罗马字、读音、旧字体、简称、媒体常见写法作为 name variant 管理
- 不要求 V0.1 翻译所有数据
<<<<<<< HEAD
4.5 审计优先于事件流
=======
4.5 姓名标准化规则
人物姓名相关字段需要明确分工,避免继续出现“同一人因罗马字顺序不同被创建两次”的问题。
统一规则:
displayNameJa:以日文原文为主,采用日文常规顺序,保存为“姓 名”,姓名中间统一使用单个半角空格。displayNameRoman:统一采用GivenName FamilyName。slug:一旦创建即冻结,不再根据新来源中的罗马字顺序重新生成。- 来源页面看到的原始姓名顺序,不直接覆盖内部主身份字段,而是作为来源证据保留。
落地原则:
- 日文名是主事实字段。
- 罗马字是辅助展示和辅助检索字段,不作为主身份键。
slug是内部稳定标识,不要求与任一来源页面的罗马字顺序完全一致。- 若后续同名、同罗马字、同读音仍无法区分,再通过后缀方式生成新 slug,例如
_2,但这只能作为最后手段。
站点访问规则:
- 每访问一个新的来源类型,先抽样 2 到 3 个已知选手,判断该站点的罗马字顺序是
Family Given还是Given Family。 - 抽样结果写入来源策略或本地 skill。
- 未完成抽样前,不批量导入该站点的人名。
4.6 审计优先于事件流
4d3e3d7 (Add featured article and sync latest posts)
V0.1 建设 AuditLog,暂不建设 domain_event。
原因:
- 真实世界事实已经可以由业务表表达,例如
Membership、PersonalBest、RaceResult。 - 单独维护事件表会导致重复录入和同步问题。
- 当前阶段还没有“选手动态流”“新闻事件”“用户时间线”等明确产品需求。
当前原则:
- 业务表记录事实。
AuditLog记录数据维护行为。- 未来如需选手生涯时间线,优先从业务表聚合生成。
5. 核心数据域
5.1 Person:选手/人物
用于表示选手、教练、监督、主务等人。
V0.1 主要使用选手,后续可扩展到教练和工作人员。
建议收集字段类型:
- 姓名
- 读音
- 罗马字
- 中文译名
- 英文名
- 生年月日
- 年龄
- 出身地
- 国籍
- 登録陸協
- 身高
- 体重
- 人物类型:选手、监督、教练、主务等
- 备注
- 数据状态
5.2 Organization:组织
用于表示高中、大学、实业团、公司、俱乐部、赛事组织方。
建议收集字段类型:
- 正式名称
- 简称
- 读音
- 罗马字
- 中文名
- 英文名
- 组织类型:高中、大学、实业团、公司、俱乐部、联盟等
- 所在地
- 都道府县
- 官方网站
- 备注
- 数据状态
注意:
- 大学和大学长距离队伍可以先视为同一组织,后续需要时再拆分
- 实业团队伍和母公司可先用组织类型区分,后续再细分关系
5.3 Membership:所属关系
中文含义:所属履历 / 生涯所属。
用于表示人物在某时间段与某组织的关系。它是未来关系图和选手生涯时间线的核心表。
示例:
黒田朝日 - 玉野光南高校 - 2019-04-01 ~ 2022-03-31
黒田朝日 - 青山学院大学 - 2022-04-01 ~ 2026-03-31
黒田朝日 - GMOインターネットグループ - 2026-04-01 ~ null
建议收集字段类型:
- 人物
- 组织
- 关系类型
- 开始日期
- 结束日期
- 开始年份
- 结束年份
- 年级,作为来源快照,不作为关系核心
- 学部
- 学科
- 期/代
- 队内角色:主将、主务等
- 来源
- 数据状态
V0.1 用它表达:
- 高中履历
- 大学履历
- 当前实业团或当前所属
- 学部・学科
- 队内角色,例如主将
关系类型约定:
- 高中:
Organization.type = high_school,Membership.type = enrolled - 大学:
Organization.type = university,Membership.type = enrolled - 实业团:
Organization.type = corporate_team,Membership.type = affiliated origin暂时保留,但 V0.1 不作为出身高中主表达方式
时间约定:
- V0.1 使用
startDate / endDate,不增加时间精度字段。 - 日本高中/大学入学默认使用
04-01。 - 日本高中/大学毕业默认使用
03-31。 - 实业团入社默认使用
04-01。 - 不确定性写入备注。
startYear / endYear可保留,方便查询。grade只表示某来源中的年级快照,不作为长期事实。
5.4 Personal Best:个人最好成绩
用于表示选手在某项目上的当前 PB 快照。
V0.1 不做 PB 历史,不做 2nd best,也不从全量比赛成绩推导 PB。
规则:
- 每个选手每个项目只保留当前最好成绩。
- PB 可以记录创造日期、赛事名、地点和来源。
- 旧 PB 暂不录入。
- V0.2 建设比赛结果库后,再考虑用比赛成绩推导或同步 PB。
建议收集项目:
- 1500m
- 3000m
- 3000mSC
- 5000m
- 10000m
- 10マイル
- ハーフマラソン
- マラソン
建议收集字段类型:
- 人物
- 项目
- 记录
- 创造日期
- 创造赛事
- 创造地点
- 当时所属
- 当时年级或阶段
- 是否高校 PB
- 是否大学 PB
- 来源
- 数据状态
V0.1 页面优先展示:
- 5000m
- 10000m
- ハーフマラソン
- マラソン,若有
其他项目可先录入但不重点展示。
5.5 Competition:赛事
用于表示赛事系列。
示例:
- 箱根駅伝
- 出雲駅伝
- 全日本大学駅伝
- 全国高校駅伝
- ニューイヤー駅伝
建议收集字段类型:
- 赛事名称
- 赛事类型
- 主办方
- 地区
- 官方网站
- 备注
V0.1 只将箱根駅伝作为核心赛事系列样本。出雲駅伝、全日本大学駅伝、路跑和田径赛事进入 V0.2 比赛结果库阶段。
5.6 Competition Edition:赛事届次
用于表示某一年或某一届赛事。
示例:
- 第102回東京箱根間往復大学駅伝競走
建议收集字段类型:
- 赛事
- 届次
- 年份
- 日期
- 官方名称
- 简称
- 备注
- 来源
5.7 Race:比赛单元
用于表示某一届赛事下面的具体比赛、区间、项目或组别。
这是 V0.1 采用的四层赛事模型中的第三层:
Competition -> CompetitionEdition -> Race -> RaceResult
示例:
Competition: 箱根駅伝
CompetitionEdition: 第102回箱根駅伝
Race: 5区
RaceResult: 黒田朝日 / 1:07:16 / 区間1位
Competition: MARCH対抗戦
CompetitionEdition: MARCH対抗戦2025
Race: 男子10000m 4組
RaceResult: 黒田朝日 / 27:37.62 / 1位
建议收集字段类型:
- 赛事届次
- 比赛单元名称
- 项目
- 区间号,駅伝使用
- 轮次
- 组别
- 距离
- 开始时间
- 来源
- 数据状态
- 备注
V0.1 主要用于箱根区间,例如:
- 第100回箱根駅伝 2区
- 第101回箱根駅伝 2区
- 第102回箱根駅伝 5区
5.8 Race Result:比赛成绩
用于表示选手在某个 Race 中的出场和成绩。
建议收集字段类型:
- 人物
- 所属大学
- 比赛单元
- 是否报名
- 是否实际出场
- 是否当日変更
- 成绩
- 名次
- 综合排名相关信息
- 备注
- 来源
- 数据状态
V0.1 优先用于箱根出场记录。
箱根不做特殊化:
- 不新增箱根专用表。
- 不新增
isSectionPrize/isSectionRecord等箱根专用字段。 - 区間賞、区間新等标签 V0.1 先写入
notes。 - V0.2 比赛结果库阶段,再考虑
ResultTag或Achievement。
5.9 Source:数据来源
用于记录数据来自哪里。
建议来源类型:
- 大学官网
- 箱根官方
- NTV 箱根页面
- 日本陸連
- 月陸
- 数据站
- 粉丝站
- World Athletics
- 当前所属队伍官网
- 人工整理
建议收集字段类型:
- 来源名称
- 来源 URL
- 来源类型
- 发布日期,若有
- 访问日期
- 备注
- 可信等级
5.10 AuditLog:审计日志
用于记录数据维护行为。
它不表示现实世界事件,只表示我们的数据库发生了什么维护动作。
适合记录:
- 新增选手
- 修改基础资料
- 新增或修正 PB
- 新增箱根记录
- 修正所属履历
- 修改确认状态
- 合并或拆分实体
- 批量导入导致的变更
建议收集字段类型:
- 实体类型
- 实体 ID
- 动作:create / update / delete / verify / merge / split
- 字段名
- 旧值
- 新值
- 变更原因
- 变更说明
- 来源
- 操作者类型:admin / import_script / system
- 批次 ID
- 创建时间
V0.1 暂不要求所有操作自动写入审计,但模型先建立。
5.11 Name Variant:名称变体
用于管理日文、中文、英文、罗马字、简称、旧字体等。
适用对象:
- 人物
- 组织
- 赛事
建议收集字段类型:
- 目标对象
- 名称
- 语言
- 名称类型:正式名、简称、读音、罗马字、中文译名、英文译名、旧称、媒体写法
- 是否主名称
- 来源
6. V0.1 页面设计
6.1 首页
首页第一屏应包含:
- 站点名称:襷の系譜
- 副标题
- 搜索框
- 三所大学入口
- 最近更新选手
- 语言切换
首页不是营销页,而是资料入口。
6.2 选手列表页
列表支持:
- 按当前所属筛选
- 按大学筛选,基于所属履历
- 按出身高中筛选,若数据足够
- 搜索姓名
展示字段:
- 姓名
- 读音或罗马字
- 当前所属
- 大学履历摘要
- 出身高中
- 主要 PB 摘要
- 数据状态
6.3 选手详情页
页面结构:
- 基本资料
- 当前所属
- 所属履历
- 主要 PB
- 箱根记录
- 相关选手
- 数据来源
相关选手模块:
- 同当前所属
- 同大学
- 同高中
V0.1 不做图谱,只做清晰跳转。
6.4 管理后台
V0.1 需要一个轻量后台,因为数据维护比公开页面更重要。
后台功能:
- 新增/编辑选手
- 新增/编辑组织
- 新增/编辑所属关系
- 新增/编辑 PB
- 新增/编辑箱根记录
- 新增/编辑来源
- 数据状态标记
- 审计记录查看,后置也可以
V0.1 不急于做完整 CSV 导入。真实数据导入管线会在模型经过单选手深样本验证后再设计。
后台可以先很朴素,但必须好用。
7. 数据导入策略
7.1 初始数据策略
V0.1 不急于大规模导入三校完整名单。
推荐先做单选手深样本,以黒田朝日为代表,验证模型和页面:
- 基础资料
- 高中 -> 大学 -> 实业团所属履历
- 当前 PB
- 箱根记录
- 来源优先级
- 来源冲突
- 审计记录
单选手深样本稳定后,再扩展到三校代表选手,最后再考虑 CSV/批量导入。
7.2 初始数据来源
V0.1 初始数据优先来自:
- 青山学院大学长距离成员页
- 國學院大學成员页
- 中央大学长距离成员页
- 箱根官方报名 PDF
- NTV 箱根页面
- 月陸 Online 名鑑
- 当前所属实业团官网,若选手已毕业
- World Athletics,主要用于 PB 与国际记录参考
- JAAF / 赛事官方结果,若用于确认 PB
这些来源覆盖:
- 当前名单
- 姓名读音
- 罗马字
- 学部・学科
- 出身高中
- 出身地
- PB
- 箱根出场记录
- 队伍角色
7.3 数据来源优先级
人工规则先写入文档和维护流程,不急于写死到系统逻辑。
初步规则:
- 选手当前所属:当前所属队伍官网优先。
- 高中/大学履历:大学官网、队伍官网、箱根/KGRR 报名资料优先。
- 箱根成绩:箱根官方历史记录优先,其次 KGRR / NTV。
- PB:赛事官方结果 / JAAF / World Athletics / 当前所属队伍官网交叉确认。
- 媒体名鑑:字段很全,但主要作为补全来源。
- 粉丝站/数据站:适合发现线索和交叉检查,关键事实需要二次确认。
7.4 导入方式
V0.1 不做自动爬虫,也不优先做批量 CSV 导入。
推荐流程:
- 在本地抓取和整理来源数据
- 使用本地脚本完成抽取、归一化和匹配
- 直接连接正式在线数据库写入业务表
- 同时写入来源引用和审计记录
- 通过前台页面和数据库查询立即验证结果
原因:
- 各站结构差异很大
- 数据量早期不大
- 早期重点是确认模型、匹配和去重,而不是离线环境隔离
这里的“本地”只表示脚本运行位置,不再表示一套独立的离线数据库环境。
从当前阶段开始,默认只有一套主业务库:
- 开发时连接它
- 导入真实数据时连接它
- 前台页面展示它
- 审计和来源也写在它上面
7.5 seed 的角色调整
seed 仍然保留,但职责收缩。
保留用途:
- 初始化最小开发环境
- 准备演示样本
- 在本地快速重建基础站点骨架
不再承担:
- 持续录入真实数据
- 批量导入真实比赛结果
- 线上数据库的日常更新
- 来源校验后的正式发布
原因:
seed把代码和真实数据混在一起,随着数据量增长会迅速失控。seed更适合“重建初始状态”,不适合“增量维护真实世界事实”。- 真实数据更新需要更强的去重、审计、来源引用和失败回滚能力。
7.6 单库直连更新方案
V0.1 的真实数据更新采用:
本地脚本 + 在线数据库直连
即:
- 抓取、抽取、归一化、校验在本地完成
- 最终写入直接连接正式 PostgreSQL
- 不再设计“线下库整理完再同步线上”的双阶段方案
这一方案的前提判断是:
- 当前项目规模小
- 当前维护者极少
- 当前优先级是速度和一致性
- 当前不优先处理复杂权限和安全治理
因此,V0.1 更关心:
- 数据写入入口统一
- 原始来源可回溯
- 实体匹配可解释
- 重复写入可阻断
- 变更可审计
7.7 数据更新链路
推荐链路分为四步:
-
fetch- 访问来源页面、JSON、PDF 或结果页
- 将原始内容保存到本地工作目录
-
normalize- 抽取选手、组织、赛事、成绩、PB 等结构化信息
- 形成统一输入格式
-
reconcile- 匹配已有
Person、Organization、Race - 检查重名、倒序罗马字、同组织同届冲突
- 生成待确认项或阻断项
- 匹配已有
-
publish- 通过统一导入脚本写入正式数据库
- 同时写入
Source、来源引用和AuditLog
V0.1 的重点是先把 publish 收口,而不是先追求全自动抓取。
7.8 正式导入入口
后续不允许长期并行使用多种随意入口更新真实数据。
真实数据更新应尽量收口到少数几类命令:
import:personimport:raceimport:organizationreconcile:personverify:import
这些命令的具体脚本名可以后定,但职责需要稳定。
不推荐长期保留以下方式作为主链路:
- 手工改
seed.ts - 临时 SQL 直接写表
- 没有审计的 ad hoc 脚本
- 一次性脚本写完即丢
7.9 原始来源落盘策略
虽然正式写库直接连在线数据库,但原始来源仍建议保留在本地文件系统中。
原因:
- 方便复查抽取逻辑
- 方便处理来源冲突
- 方便后续抽取器复用
- 方便为本地 skill 累积来源攻略
建议至少保留:
- 原始 URL
- 抓取时间
- 原始 HTML / JSON / PDF 路径
- 关键页面说明
- 当前抽取规则备注
这部分是“本地证据层”,不是离线数据库。
7.10 重复实体防线
重复选手问题说明,V0.1 不能继续使用“看到一个新罗马字就生成一个新 slug”的方式。
后续导入必须遵守:
slug一旦确定,不再因新来源的罗马字词序变化而重新生成。 <<<<<<< HEADdisplayNameRoman只能作为辅助匹配字段,不能作为主身份键。- 优先使用来源映射或既有实体匹配,而不是字符串猜测。
- 在正式写库前,必须执行重复检查。 =======
displayNameRoman统一采用GivenName FamilyName,并且只能作为辅助匹配字段,不能作为主身份键。- 优先使用来源映射或既有实体匹配,而不是字符串猜测。
- 在正式写库前,必须执行重复检查。
- 新来源首次接入前,先做姓名顺序抽样,再决定解析规则。
4d3e3d7 (Add featured article and sync latest posts)
至少检查:
- 规范化日文名重复
- 罗马字正反序重复
- 同组织同年级高相似重复
- 同一来源重复指向不同人物
命中后应进入待确认或阻断,而不是自动写入。
7.11 来源映射表
为了避免重复导入,V0.1 后续应增加来源映射机制。
核心思想是:
某个外部来源中的人物、组织、比赛页面,应能稳定映射到本站内部实体。
例如人物至少需要记录:
- 来源站点
- 来源页面 URL
- 来源侧标识或可稳定定位的 key
- 对应的内部
personId
这样下次再次访问同来源时,系统优先复用既有映射,而不是重新猜 slug。
7.12 数据校验
基础校验包括:
- 同名选手提示
- 规范化日文名重复提示
- 罗马字正反序重复提示
- 同一时间段所属履历冲突提示
- 同一项目重复 PB 提示
- PB 时间格式校验
- 成绩项目枚举校验
- 来源缺失提示
- 出身高中不存在时提示创建组织
- 当前所属缺失提示
- 数据来源冲突提示
7.13 审计要求
既然 V0.1 直接写正式数据库,就更不能省略审计。
每次正式导入至少应能回答:
- 改了什么
- 为什么改
- 来源是什么
- 是新增、修正还是覆盖
因此正式导入脚本默认应写入:
AuditLog- 导入批次标识
- 来源引用
这部分先服务于站主自己,不以用户前台展示为目标。
8. 搜索策略
V0.1 使用 PostgreSQL 基础查询即可。
支持:
- 日文姓名搜索
- 罗马字搜索
- 大学筛选
- 高中筛选
后续再引入:
- Meilisearch
- Typesense
- Elasticsearch
搜索服务不应该成为 V0.1 的复杂度来源。
9. 多语言实现策略
URL 建议:
/ja
/zh
/en
主语言:
/ja
V0.1 翻译范围:
- 导航
- 页面标题
- 字段标签
- 站点说明
- 数据状态
- 来源说明
不要求翻译:
- 所有选手简介
- 所有学校名称
- 所有备注内容
人名和学校名优先展示日文原文,必要时追加罗马字、中文名或英文名。
实现策略:
- 第一层 i18n 只翻译界面文案,使用 JSON dictionary。
- 实体翻译仍保留在 DB 字段或名称变体中,但 V0.1 不要求完整维护。
- 路由使用
/ja、/zh、/en。 - 语言切换需要在首页、列表页、详情页都可用,并尽量保留当前路径。
10. 权限与后台安全
V0.1 后台只面向站主使用。
建议:
- 单管理员账号
- 登录后访问
/admin - 不开放注册
- 不开放社区投稿
- 不开放评论
认证可以使用:
- Auth.js
或者更简单的:
- 单管理员密码 + session
如果项目会部署到公开网络,至少需要:
- 后台登录保护
- 管理接口鉴权
- 基础 CSRF 防护
- 环境变量管理密钥
11. 部署与域名方案
V0.1 采用自有服务器部署,先与个人站点共存在 ubuntu-3。
部署细节、GitHub Actions 方案和数据同步策略单独维护在:
当前不新开 ubuntu-4。原因:
- V0.1 仍处在验证期,访问量和数据量都很小
- 与个人站点共用机器可以减少早期运维成本
- 只要从一开始做好隔离,后续迁移成本可控
- 项目需要稳定迭代数据模型和页面功能,部署层先保持简单
11.1 部署原则
tasuki-keifu 与个人站点只共享同一台服务器,不共享应用进程、数据库、环境变量或反向代理配置。
隔离原则:
- 独立代码目录
- 独立运行端口
- 独立 systemd service
- 独立 PostgreSQL database
- 独立 PostgreSQL user
- 独立
.env - 独立 Nginx server block
- 独立日志和备份路径
这样后续迁移到 ubuntu-4 时,可以整体搬迁 tasuki-keifu,不会和个人站点互相缠绕。
11.2 ubuntu-3 上的目标形态
建议目录:
/srv/tasuki-keifu
├─ app
├─ releases
├─ shared
│ └─ .env
└─ backups
建议运行方式:
Nginx
└─ tasuki-keifu 子域名
└─ proxy_pass http://127.0.0.1:3020
systemd
└─ tasuki-keifu.service
└─ pnpm start / next start
PostgreSQL
├─ database: tasuki_keifu
└─ user: tasuki_keifu_user
端口约定:
- 个人站点继续使用原有端口
tasuki-keifu使用独立端口,例如3020- 本地开发端口仍可使用
3000
环境变量:
DATABASE_URLNEXT_PUBLIC_SITE_URLSESSION_SECRET,上线管理后台前需要- 未来导入脚本需要的外部 API key 或管理密钥
11.3 域名策略
V0.1 先使用已有主域名下的子域名。
建议:
tasuki-keifu.gelatoni.uk
短期不急于购买独立域名。等以下条件满足后,再考虑独立品牌域名:
- 数据覆盖超过样例阶段
- 日文页面具备基本公开浏览价值
- 数据来源和确认状态足够清楚
- 有持续维护计划
如果后续购买独立域名,可以通过 Nginx 新增 server block 或直接替换现有子域名配置,不影响应用代码。
当前部署状态:
tasuki-keifu已部署到ubuntu-3- 应用目录:
/opt/apps/tasuki-keifu - Docker Compose 文件:
/opt/apps/tasuki-keifu/docker-compose.prod.yml - Nginx 配置:
/etc/nginx/sites-available/tasuki-keifu - 本机应用地址:
http://127.0.0.1:3020 - 目标域名:
tasuki-keifu.gelatoni.uk - Cloudflare DNS 已指向
13.230.244.67 - HTTP 反向代理已在服务器侧验证通过
- HTTPS 证书已通过 certbot 签发
- 公网访问
https://tasuki-keifu.gelatoni.uk/ja已验证 200
11.4 数据库与备份
V0.1 使用 PostgreSQL 作为主业务库。
生产数据库建议:
- 与个人站点不同库
- 与个人站点不同用户
- 用户只授予
tasuki_keifu所需权限
备份策略:
- 初期使用
pg_dump - 每日一次逻辑备份
- 保留最近 7-14 天
- 备份文件放在独立目录,例如
/srv/tasuki-keifu/backups - 重要数据录入前后可以手动执行一次备份
后续如果数据量扩大,再考虑:
- 异地备份
- S3 / Lightsail object storage
- 定期恢复演练
- 数据导入前自动生成快照
11.5 发布流程
当前已完成一次手动部署:
本地代码 -> 同步到 Ubuntu-3 -> Docker Compose 构建镜像 -> Prisma migrate deploy -> 重启 app
下一步发布流程应切换为 GitHub Actions:
- push 到 GitHub
main - GitHub Actions 安装依赖、类型检查、构建应用
- GitHub Actions 构建 Docker 镜像并保存为归档
- 通过 SSH/SCP 上传镜像归档和部署文件到
Ubuntu-3 - 服务器
docker load - 执行
prisma migrate deploy docker compose up -d app- 检查健康页面和核心页面
常规部署不执行 seed。pnpm db:seed 只用于本地开发或明确的初始化动作,不能成为生产发布步骤。
上线前至少检查:
/ja/zh/en/ja/players- 一个选手详情页
- 数据库连接
- Nginx HTTPS
11.6 后台上线约束
公开部署后,如果没有管理后台,公共页面可以先上线。
如果上线 /admin 或任何写操作接口,必须先完成:
- 登录保护
- session secret
- 管理接口鉴权
- CSRF 或等价保护
- 服务端校验
- 审计日志写入
V0.1 早期可以先不开放管理后台,但真实数据维护应通过统一的本地导入脚本直连正式数据库完成,而不是继续依赖 seed 作为主入口。
11.7 迁移到 ubuntu-4 的路径
如果后续访问量、数据导入任务或运维边界要求提高,可以迁移到 ubuntu-4。
迁移步骤:
- 在
ubuntu-4安装 Node.js、pnpm、PostgreSQL、Nginx - 创建
tasuki_keifudatabase 和独立用户 - 在
ubuntu-3执行pg_dump - 在
ubuntu-4执行 restore - 将代码部署到
/srv/tasuki-keifu - 复制并调整
.env - 创建
tasuki-keifu.service - 配置 Nginx 和 HTTPS
- 用临时域名或 hosts 验证页面
- 切换 DNS 到
ubuntu-4 - 确认稳定后,停止
ubuntu-3上的tasuki-keifu.service
因为应用目录、数据库、服务和 Nginx 配置从一开始就是独立的,迁移时不需要拆个人站点。
11.8 备选方案
如果后续希望减少服务器运维,也可以改用:
- Vercel
- Neon / Supabase Postgres
这条路线适合快速公开验证,但不是当前 V0.1 的默认方案。
12. 后续演进
12.1 V0.2
- 比赛结果库第一版
- 开始收录每场比赛数据
- 支持箱根以外的比赛:出雲駅伝、全日本大学駅伝、主要田径赛、路跑/马拉松
- 完善
Race和RaceResult的字段 - 引入结果标签或成就模型,例如区間賞、区間新、学生新、大会新
- 建立更完整的数据来源审计
- 设计 CSV/批量导入管线
12.2 V0.3
- 关系图与可视化第一版
- 高中 -> 大学 -> 实业团流向
- 同场比赛、同区间、同队参赛关系
- 大学页
- 高中页
- 实业团队伍页
- 选手对比和筛选
12.3 V0.4
- 搜索索引服务
- 更完善的中文/英文体验
- 解释日本长跑体系的内容页
- SEO 优化
12.4 V1.0 以后
- 自动化数据导入
- 数据变更监控
- 社区纠错
- 投稿审核
- 图谱可视化
- 数据仓库或分析库
- API / 数据下载
13. 不做事项
V0.1 不做:
- 独立后端服务
- 微服务
- 数据仓库
- 图数据库
- 复杂爬虫
- 实时比赛更新
- 用户系统
- 社区互动
- 复杂图谱可视化
- 大规模 BI 分析
- 完整比赛结果库
- PB 历史
- SNS 和粉丝向 Q&A 字段
domain_event事件流
这些都不是第一版风险点。
14. 关键风险
14.1 数据来源分散
不同来源字段不一致,且可能更新频率不同。
应对:
- 数据模型保留来源和状态
- 先人工整理
- 不急于自动化
14.2 同名和名称变体
日文姓名、旧字体、罗马字、中文译名可能不一致。
应对:
- 建立 name variant
- 不把翻译名作为主键
- 所有关系使用内部 ID
14.3 字段过多导致后台难用
宽收集可能让后台表单变重。
应对:
- 后台按模块编辑
- 选手基础资料、PB、所属关系、来源分开维护
- V0.1 前台只展示核心字段
14.4 所属关系缺少时间线会影响关系图
如果只记录当前大学和年级,数据很快会过时,也无法表达高中 -> 大学 -> 实业团的生涯路径。
应对:
- 所属履历使用
startDate / endDate - 高中、大学、实业团统一走
Organization + Membership - 当前所属由时间段推导
- 年级只作为来源快照
14.5 赛事模型过早特殊化
如果针对箱根单独建特殊字段,后续接入出雲、全日本、田径赛、马拉松会变困难。
应对:
- 采用四层赛事模型:
Competition -> CompetitionEdition -> Race -> RaceResult - 箱根区间只是
Race的一种 - 区間賞/区間新 V0.1 先放
notes - V0.2 再结构化结果标签
14.6 版权和引用边界
媒体名鑑和图片存在版权风险。
应对:
- 只收公开事实数据
- 不复制文章正文
- 不直接搬运图片作为核心资产
- 页面保留来源链接
15. 实施顺序
建议按以下顺序实施:
- 初始化 Next.js 项目
- 接入 PostgreSQL 和 Prisma
- 建立核心 schema:Person / Organization / Membership / PersonalBest / Competition / CompetitionEdition / Race / RaceResult / Source / AuditLog / NameVariant
- 建立
Membership.startDate / endDate时间线模型 - 建立四层赛事模型
- 实现基础 seed 数据
- 实现首页、选手列表页、选手详情页
- 补充多语言界面第一层:JSON dictionary +
/ja、/zh、/en - 以黒田朝日为单选手深样本,补齐 P0 数据
- 校验页面和数据关系
- 根据样本调整页面展示:当前所属、所属履历、PB、箱根记录、来源
- 实现基础搜索
- 实现轻量后台
- 做视觉打磨
- 准备 V0.1 内部预览
CSV/批量导入不作为当前最近一步。它应在模型经过单选手深样本验证后再设计。