Files
2026-06-07 23:57:52 -05:00

454 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# NomadCNA — Cursor 项目上下文报告
> 更新日期2026-06-08
> 用途:为 Cursor AI 助手提供项目全貌、架构约定与开发指引。
> 生产站点https://nomadro.cn
---
## 1. 项目简介
**NomadCNA游牧中国** 是面向数字游民的生活操作系统,单仓 Monorepo 包含:
- **主站**:城市探索、下一站决策、活动、社区匹配、支付、统计、访谈视频等
- **Digital 子站**`/digital`):指南、课程、电子书、远程工作、个人资料
- **FastAPI 后端**:认证、业务 API、支付回调、PocketBase 集成
- **VPS 自建开源服务**MiroTalk P2P视频、The Lounge + Ergo IRCWeb 群聊)
| 模块 | 说明 |
|------|------|
| 城市探索 | 18 城静态数据 + API生活成本/网速/安全等指标 |
| 下一站决策 | `/dashboard`,预算/气候/标签推荐 |
| 活动 | 线上/线下/混合;**统一活动间** `/meetups/[id]/live` 深度融合 MiroTalk + Lounge |
| 访谈视频 | `/videos` 列表 + `/videos/[slug]` 详情 |
| 社区匹配 | `/dating` 滑动匹配、意图/城市筛选、互赞、额度、撤销 |
| 会员与支付 | ZPay / XorPay加入表单 + VIP 状态 |
| Digital 子站 | 课程章节、电子书 Markdown、资源导航 |
| 媒体 | MinIO 上传 + `app/lib/media.ts` + `/api/media/proxy` |
---
## 2. 技术栈
| 层级 | 技术 | 版本 |
|------|------|------|
| 前端框架 | Next.js App Router | 16.1.6 |
| UI | React + TypeScript | 19.2.3 / TS 5 |
| 样式 | Tailwind CSS | 4 |
| 图表 | ECharts | 6 |
| Markdown | marked, react-markdown, highlight.js | — |
| 后端 | FastAPI + uvicorn | ≥0.115 |
| 数据库 | PocketBase | SDK 0.27 |
| 对象存储 | MinIO | Node + Python SDK |
| 视频会议 | MiroTalk P2P | VPS :9300 |
| Web 群聊 | The Lounge 4.5 + Ergo IRC 2.18 | VPS :9000 / :6667 |
| 支付 | ZPay / XorPay 适配层 | — |
| 包管理 | pnpm | 10.25.0 |
| Node 生产版本 | — | v22.13.1 |
路径别名:`@/*` → 项目根目录(见 `tsconfig.json`)。
---
## 3. 架构总览
```mermaid
flowchart TB
subgraph Client["浏览器"]
Next["Next.js 16<br/>:3001 dev / :4002 prod"]
end
subgraph APIs["API 层"]
NextAPI["Next Route Handlers<br/>/api/digital/*<br/>/api/media/*"]
FastAPI["FastAPI<br/>:8000<br/>/api/*"]
end
subgraph Data["数据与服务"]
PB["PocketBase :8090"]
MinIO["MinIO :9100"]
MiroTalk["MiroTalk P2P :9300"]
Lounge["The Lounge :9000"]
Ergo["Ergo IRC :6667"]
end
Client --> Next
Next -->|"rewrite 排除 digital/media"| FastAPI
Next --> NextAPI
NextAPI --> PB
NextAPI --> MinIO
FastAPI --> PB
FastAPI --> MinIO
Next -->|"活动间 iframe"| MiroTalk
Next -->|"活动间 iframe"| Lounge
Lounge --> Ergo
```
### API 路由分流(关键)
`next.config.ts`**除 `digital` 和 `media` 外** 的所有 `/api/*` 转发到 FastAPI
```ts
source: "/api/:path((?!(?:digital|media)(?:$|/.*)).*)"
destination: "http://127.0.0.1:8000/api/:path*"
```
| 路径前缀 | 处理方 |
|----------|--------|
| `/api/digital/*` | Next.js Route Handlers |
| `/api/media/*` | Next.js Route Handlers |
| 其他 `/api/*` | FastAPI经 rewrite 代理) |
前端统一通过 `app/lib/api-client.ts``apiFetch()` / `apiUrl()` 调用后端。
---
## 4. 目录结构
```text
.
├── app/
│ ├── [locale]/ # 主站多语言页面(~54 个 page.tsx
│ │ ├── dating/ # 匹配页 + components/
│ │ ├── meetups/ # 活动列表、host、[id]/live 活动间
│ │ ├── videos/ # 访谈列表 + [slug] 详情
│ │ └── digital/ # Digital 子站页面路由
│ ├── api/
│ │ ├── digital/ # Digital 认证、支付、上传、VIP
│ │ └── media/proxy/ # 远程媒体代理
│ ├── components/
│ │ └── meetups/EventLiveRoom.tsx # 活动间分屏 UI
│ ├── context/ # LocaleContext、ThemeContext
│ ├── data/ # cities.ts、avatars.ts、mock.ts
│ ├── digital/ # Digital 子站组件、lib、context
│ └── lib/ # api-client、meetups、media、payment、pocketbase
├── backend/
│ ├── main.py # FastAPI 入口(~70 个端点)
│ ├── meetup_live.py # 活动间 URL / 权限
│ ├── avatar_assets.py # 头像池(与前端 avatars.ts 同步)
│ ├── settings.py # 环境变量
│ ├── pb_client.py # PocketBase HTTP 客户端
│ ├── payment.py # ZPay / XorPay
│ ├── minio_storage.py # MinIO 上传
│ ├── init_db.py # 集合 schema + seed
│ └── seed_data.py # 种子数据
├── config/
│ ├── remote-services.ts # 线上服务地址、Windows 远程开发
│ ├── site.config.ts # 主站 SEO
│ ├── nav.config.ts # 顶部导航
│ └── digital/ # Digital 子站专属配置
├── scripts/
│ ├── dev-tunnel.py # SSH 隧道MinIO → localhost:9100
│ └── dev-windows.ps1 # Windows 一键开发
├── i18n/ # routing.ts、navigation.ts
├── messages/ # zh.json、en.json 主站文案
└── next.config.ts
```
### 主站 vs Digital 子站代码边界
| 区域 | 路径 | 说明 |
|------|------|------|
| 主站页面 | `app/[locale]/*` | 城市、活动、匹配、视频等 |
| 主站组件 | `app/components/` | Navbar、EventLiveRoom 等 |
| 主站工具 | `app/lib/` | api-client、meetups、media、payment |
| Digital 页面 | `app/[locale]/digital/*` | 课程、电子书、工作等 |
| Digital 组件 | `app/digital/components/` | 独立 Header、Hero、课程区块 |
| Digital 工具 | `app/digital/lib/` | 平行的 payment、pocketbase、learning |
| Digital 配置 | `config/digital/*` | 课程、域名、视觉主题 |
两套代码 **有意平行**(非完全共享),修改时注意对应目录。
---
## 5. 路由与国际化
- 语言:`zh`(默认)、`en`,路由 **始终带前缀**`/zh/...``/en/...`
- 配置:`i18n/routing.ts`
- 文案:`messages/zh.json``messages/en.json`
- 语言感知导航:**必须**使用 `i18n/navigation.ts``Link``useRouter``useTranslation`
```tsx
import { Link, useRouter, useTranslation } from "@/i18n/navigation";
```
新页面:在 `app/[locale]/``page.tsx` → 补 `messages/*.json` → 必要时改 `config/nav.config.ts`
---
## 6. API 端点速查
### 6.1 Next.js Route Handlers
| 路径 | 作用 |
|------|------|
| `app/api/digital/auth/*` | Digital 认证 |
| `app/api/digital/join``pay/*``vip/check` | 加入、支付、VIP |
| `app/api/digital/upload-media` | 媒体上传 MinIO |
| `app/api/media/proxy` | 远程媒体抓取/缓存 |
### 6.2 FastAPI`backend/main.py`
| 分类 | 端点 |
|------|------|
| 健康/认证 | `GET /api/health``POST /api/auth/email|google``GET /api/auth/me` |
| 加入/支付 | `POST /api/join``POST /api/pay``GET /api/pay/status` |
| 城市/内容 | `GET /api/cities``GET /api/content/home``GET /api/content/{slug}` |
| **视频** | `GET /api/videos` |
| **活动** | `GET/POST /api/meetups``GET /api/meetups/{id}`**`GET /api/meetups/{id}/session`** |
| **匹配** | `GET /api/matches/candidates|likes|mutual|quota|profile/me``POST /api/matches/swipes``POST /api/matches/swipes/undo` |
| 社区 | `GET/POST /api/discussions``/api/gigs``GET /api/profiles` |
| 通知/统计 | `/api/notifications/*``/api/stats/*` |
| 其他 | `POST /api/feedback``POST /api/recommendations/next-stop` |
| 支付回调 | `/cnomadcna/zpay_order_status``/cnomadcna/xorpay_notify` |
**注意**`/api/videos` 必须独立路由,不能走 `/api/content/{slug}`slug=`videos` 会 404
---
## 7. 核心业务模块指引
### 7.1 城市探索
- 静态:`app/data/cities.ts`18 城)
- 首页:`app/[locale]/page.tsx` + FilterBar、CityCard、CityModal
- API`GET /api/cities``/api/cities/{slug}`
### 7.2 活动 / 视频 / 群聊(深度融合)
| 层级 | 文件 |
|------|------|
| 列表/弹窗 | `app/[locale]/meetups/page.tsx` |
| **活动间** | `app/[locale]/meetups/[id]/live/page.tsx` |
| UI 组件 | `app/components/meetups/EventLiveRoom.tsx` |
| 前端逻辑 | `app/lib/meetups.ts` |
| 后端逻辑 | `backend/meetup_live.py` |
| 会话 API | `GET /api/meetups/{id}/session` |
流程:
1. 用户打开活动详情 → **「进入活动间」**
2. Live 页请求 `/api/meetups/{id}/session`(带 Cookie
3. 返回 `videoUrl`MiroTalk + `name=`)、`chatUrl`Lounge channel deep link`canJoin`
4. `EventLiveRoom` 分屏 iframe 嵌入;支持只看视频/只看群聊
PocketBase `meetups` 关键字段:`mode``accessLevel``mirotalkRoom``loungeChannel``features`
### 7.3 访谈视频
- 列表:`app/[locale]/videos/page.tsx``GET /api/videos`
- 详情:`app/[locale]/videos/[slug]/page.tsx`
- 首页 video 卡片 `targetUrl` 固定 `/videos`(见 `app/[locale]/page.tsx``normalizeHomeContentItem`
### 7.4 社区匹配Dating
- 页面:`app/[locale]/dating/page.tsx`
- 组件目录:`app/[locale]/dating/components/`
- `IntentTabs``CityFilter``SwipeStack``SwipeCard`
- `LikesPanel``MatchModal``AdvancedFilters``SwipeQuota`
- 常量/类型:`constants.ts``types.ts``utils.ts`
- 头像:`app/data/avatars.ts``backend/avatar_assets.py`
- Schema`profiles`lookingFor、gender、citySlug`swipes`intent`match_connections`
### 7.5 支付
- 前端:`app/lib/payment/`
- Digital`app/digital/lib/payment/`
- 后端:`backend/payment.py`
- 配置:`config/services.config.ts``config/digital/services.config.ts`
### 7.6 媒体
- `app/lib/media.ts`URL 规范化、Unsplash 直连、MinIO 公开路径
- `app/api/media/proxy/route.ts`:代理失败时 passthrough 原图
### 7.7 认证
- Cookie`app/lib/auth-cookie.ts`
- PocketBase`app/lib/pocketbase/auth.ts`
- FastAPI 会话:`backend/pb_client.py` + `current_user()`
---
## 8. 配置系统
| 文件 | 用途 |
|------|------|
| `config/site.config.ts` | 主站 SEO |
| `config/nav.config.ts` | 顶部导航 |
| `config/home.config.ts` | 首页静态块 |
| `config/remote-services.ts` | nomadro.cn 各服务 URL、MinIO/PB 解析、`isRemoteDevMode()` |
| `config/services.config.ts` | 主站 PocketBase/支付/MinIO |
| `config/runtime-env.ts` | `APP_ENV` 判断 |
| `config/digital/*` | Digital 子站 |
---
## 9. 开发命令
```bash
pnpm install
pnpm dev # http://localhost:3001
# 后端Windows
python -m venv backend/.venv
backend\.venv\Scripts\pip install -r backend/requirements.txt
backend\.venv\Scripts\uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reload
# Windows 连线上 MinIO
python scripts/dev-tunnel.py
# 初始化 PocketBase 集合 + 种子
python -m backend.init_db
# 质量检查(上线前)
pnpm lint
pnpm exec tsc --noEmit --pretty false
```
**PowerShell 不支持 `&&`**,命令分步执行。
本地需 **Next.js (:3001) + FastAPI (:8000)** 同时运行。8000 端口若被多个旧 uvicorn 占用,新路由不会生效——先杀干净再启一个实例。
---
## 10. 环境变量要点
> ⚠️ 勿提交 `.env.local`、`backend/.env` 或真实密钥。
### 前端(`.env.local`
| 变量 | 说明 |
|------|------|
| `NEXT_PUBLIC_SITE_URL` | 本地 `http://localhost:3001` |
| `NEXT_PUBLIC_API_BASE_URL` | 生产 `/api`;本地可留空走 rewrite |
| `FASTAPI_PROXY_TARGET` | rewrite 目标,默认 `http://127.0.0.1:8000` |
| `NEXT_PUBLIC_POCKETBASE_URL` | PocketBase |
| `NEXT_PUBLIC_MIROTALK_URL` | MiroTalk |
| `NEXT_PUBLIC_LOUNGE_URL` | The Lounge |
| `NEXT_PUBLIC_MINIO_PUBLIC_URL` | MinIO 公开读 |
| `DEV_SSH_*` | dev-tunnel 用(勿提交密码) |
### 后端(`backend/.env`
| 变量 | 说明 |
|------|------|
| `POCKETBASE_URL` | 内网或公网 PB |
| `MIROTALK_URL` / `LOUNGE_URL` | 活动间 URL 生成 |
| `MINIO_*` | 对象存储 |
| `DEV_AUTO_PAY` | 开发自动完成支付 |
完整列表见 `.env.example``README.md`
---
## 11. AI 编码约定
### 必须遵守
1. **最小改动**:只改任务相关文件
2. **匹配现有风格**`@/` import、组件结构一致
3. **国际化**UI 文案走 `messages/*.json` + `t()`
4. **路由**`app/[locale]/` + `@/i18n/navigation``Link`
5. **API**:主站用 `app/lib/api-client.ts``apiFetch`
6. **活动**:复用 `app/lib/meetups.ts` + `backend/meetup_live.py`
7. **媒体**:复用 `app/lib/media.ts`
8. **支付**:复用 `app/lib/payment` + `backend/payment.py`
9. **不提交密钥**
### 文件选择决策树
```
新功能属于 Digital
├── 是 → app/[locale]/digital/ + app/digital/ + config/digital/
└── 否 → app/[locale]/ + app/components/ + config/
需要新 API
├── Digital → app/api/digital/
├── 媒体代理 → app/api/media/
└── 主站业务 → backend/main.py
线上活动?
├── 列表/弹窗 → meetups/page.tsx
├── 活动间 → meetups/[id]/live + EventLiveRoom.tsx
└── URL/权限 → meetups.ts + meetup_live.py + /session API
```
### Python 约定
- 请求体用 Pydantic `BaseModel`
- PocketBase 走 `pb_client.py`
- 新端点加 `main.py`,配置加 `settings.py`
---
## 12. 生产部署架构
| 域名 | 上游 | 服务 |
|------|------|------|
| nomadro.cn | :4002 | Next.jscnomadcna.service |
| api.nomadro.cn | :8000 | FastAPI |
| pocketbase.nomadro.cn | :8090 | PocketBase |
| minio.nomadro.cn | :9100/9101 | MinIO |
| mirotalk.nomadro.cn | :9300 | MiroTalk P2P |
| lounge.nomadro.cn | :9000 | The Lounge |
| ergo.nomadro.cn | HTTPS 重定向 | → loungeIRC 仅本机 :6667 |
VPS`82.157.112.245`,代码目录 `/home/ubuntu/gitlab-instance-0a899031_cnomadcna`
上线前端:
```bash
APP_ENV=production NEXT_PUBLIC_APP_ENV=production NODE_ENV=production pnpm build
systemctl restart cnomadcna.service
```
活动间相关改动需同步部署 **Next.js + FastAPI**VPS 上 MiroTalk/Lounge 配置变更需 `systemctl restart`
---
## 13. 常见陷阱
| 问题 | 说明 |
|------|------|
| dev 端口 | `pnpm dev`**3001**,不是 3000 |
| API 分流 | `/api/digital/*``/api/media/*` 不走 FastAPI |
| `/api/videos` | 独立路由;`/api/content/videos` 会 404 |
| 8000 多进程 | 多个 uvicorn 占端口时 session 等新路由可能不生效 |
| PowerShell | 不支持 `&&`,分步执行 |
| 双套 lib | `app/lib/` vs `app/digital/lib/` 各自独立 |
| MiroTalk embed | `ALLOWED_EMBED_ORIGINS` 需含 nomadro.cn 与 localhost:3001 |
| Ergo 公网 | 不开放 IRC 6697仅 The Lounge 内网连 Ergo |
| 密钥 | `.env.example` 仅示例,生产用独立密钥 |
---
## 14. 关键文件索引
| 场景 | 文件 |
|------|------|
| 新增主站页面 | `app/[locale]/``messages/*.json` |
| 活动间 | `meetups/[id]/live/page.tsx``EventLiveRoom.tsx``meetup_live.py` |
| 活动 URL/权限 | `app/lib/meetups.ts` |
| 访谈视频 | `videos/page.tsx``GET /api/videos` |
| 社区匹配 | `dating/page.tsx``dating/components/*` |
| 调用后端 | `app/lib/api-client.ts` |
| 封面/媒体 URL | `app/lib/media.ts` |
| 头像池 | `app/data/avatars.ts``backend/avatar_assets.py` |
| 远程开发 | `config/remote-services.ts``scripts/dev-tunnel.py` |
| API 代理 | `next.config.ts` |
| 后端入口 | `backend/main.py` |
| DB 初始化 | `backend/init_db.py``seed_data.py` |
| 完整运维 | `README.md` |
---
## 15. 相关文档
- `README.md` — 业务介绍、部署、MiroTalk/Lounge 运维、Windows 本地开发
- `.env.example` — 环境变量模板
- `app/lib/charts/README.md` — 图表组件
---
*本文件供 Cursor AI 编辑 NomadCNA 时快速建立上下文。重大架构变更后请同步更新。*