Files
gitlab-instance-0a899031_cn…/README.md
2026-06-07 23:18:53 +08:00

619 lines
19 KiB
Markdown
Raw 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
NomadCNA 是面向数字游民的生活操作系统,用一个站点承载城市决策、会员加入、线上/线下活动、视频会议、Web 群聊、内容资源、任务赏金、支付和媒体存储等能力。
生产站点:
- 主站https://nomadro.cn
- APIhttps://api.nomadro.cn
- PocketBasehttps://pocketbase.nomadro.cn
- MinIOhttps://minio.nomadro.cn
- MiroTalk P2P 视频房https://mirotalk.nomadro.cn
- Web 群聊https://lounge.nomadro.cn
- IRC 服务名https://ergo.nomadro.cn
## 业务介绍
NomadCNA 的目标用户是正在中国或亚洲范围内长期旅居、远程工作、寻找社群和机会的数字游民。业务上分为以下几条主线:
| 模块 | 说明 |
| --- | --- |
| 城市探索 | 展示中国数字游民城市数据,包括生活成本、网络、温度、社群密度、安全、文化等指标。 |
| 下一站决策 | 根据预算、网络、气候、标签偏好生成城市推荐。 |
| 线上/线下活动 | 支持公开、登录成员、VIP、主持人等权限层级线上和混合活动可进入 MiroTalk 视频房。 |
| Web 群聊 | 使用 The Lounge + Ergo IRC Server 提供数字游民 Web 群组聊天,活动参与人可进入群聊。 |
| 会员加入与支付 | 支持加入表单、支付订单、支付状态轮询、VIP/会员写入。 |
| Digital 子站 | `/digital` 是内容专题站,包含数字游民指南、课程、电子书、资源、工作机会、个人资料等。 |
| 媒体与内容 | 支持图片/视频上传、远程媒体代理缓存、内容投稿、博客、电子书、视频等内容页面。 |
| 社区与增长 | 包含讨论、交友匹配、任务赏金、反馈、通知、站内消息、服务线索。 |
## 技术栈
| 层级 | 技术 |
| --- | --- |
| Web 前端 | Next.js 16 App Router、React 19、TypeScript、Tailwind CSS 4 |
| API | Next.js Route Handlers、FastAPI、PocketBase SDK/HTTP |
| 数据 | PocketBase、静态数据、MinIO 对象存储 |
| 支付 | ZPay / XorPay 适配层 |
| 视频会议 | MiroTalk P2PNginx HTTPS 反代 |
| Web 群聊 | The Lounge 4.5.0 + Ergo IRC Server 2.18.0 |
| 部署 | systemd、Nginx、Certbot、Docker |
| 包管理 | pnpm 10.25.0 |
生产服务器当前 Node.js 版本为 `v22.13.1`
## 源码解析
### 顶层结构
```text
.
├── app/ # Next.js App Router 主目录
│ ├── [locale]/ # 中文/英文路由,默认 zh
│ ├── api/ # Next.js Route Handlers
│ ├── components/ # 主站通用组件
│ ├── context/ # Locale、Theme 等上下文
│ ├── data/ # 城市、地图、mock 数据
│ ├── digital/ # Digital 子站组件、配置、数据和工具
│ └── lib/ # API、支付、媒体、活动、PocketBase 等工具
├── backend/ # FastAPI 后端
├── config/ # 主站和 Digital 子站配置
├── i18n/ # 路由和国际化导航封装
├── messages/ # zh/en 文案
├── public/ # 静态资源
├── cnomadcna.sh # 生产 Next.js 启动脚本
├── next.config.ts # Next.js 配置与 API rewrites
├── package.json # 前端依赖与脚本
└── tsconfig.json # TypeScript 配置
```
### Next.js 页面结构
主站所有面向用户的页面位于 `app/[locale]`,路由强制带语言前缀:
- `/zh``/en`:首页与城市探索
- `/zh/dashboard`:下一站决策仪表盘
- `/zh/meetups`:线上/线下活动,含 MiroTalk 视频房和 Web 群聊入口
- `/zh/meetups/host`:活动发布页
- `/zh/dating`:社区交友/匹配
- `/zh/discuss`:讨论
- `/zh/gigs`:任务赏金
- `/zh/tools`:工具箱
- `/zh/report``/zh/report/data`:统计与年报
- `/zh/digital`Digital 子站入口
通用布局与组件:
- `app/layout.tsx`:根布局
- `app/[locale]/layout.tsx`:语言布局
- `app/components/Navbar.tsx``Footer.tsx``Sidebar.tsx`:主站导航与外壳
- `app/context/LocaleContext.tsx`:运行时文案读取
- `app/context/ThemeContext.tsx`:亮色/暗色主题
### Digital 子站
Digital 子站代码集中在 `app/digital``app/[locale]/digital`
- `app/[locale]/digital/page.tsx`:专题首页
- `app/[locale]/digital/course`:课程页
- `app/[locale]/digital/course/[moduleIndex]/[lessonIndex]`:课程章节
- `app/[locale]/digital/ebook`:电子书
- `app/[locale]/digital/jobs`:远程工作
- `app/[locale]/digital/join`:加入/申请
- `app/[locale]/digital/profile`:用户资料
配置在 `config/digital`
- `site.config.ts`:专题主题、功能开关、课程/电子书/加入等能力
- `services.config.ts`PocketBase、支付、MinIO 配置读取
- `domain.config.ts`:域名和服务地址推导
- `course.ts``lessons.ts`:课程数据
### API 分层
本项目有两层 API
1. Next.js 内置 API`app/api/*`
2. FastAPI 后端:`backend/main.py`
`next.config.ts` 将大多数 `/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
### Next.js API
| 路径 | 作用 |
| --- | --- |
| `app/api/digital/auth/*` | Digital 子站登录、退出、当前用户、注册、跨站同步 |
| `app/api/digital/join` | Digital 加入/申请表单写入 PocketBase |
| `app/api/digital/pay/*` | Digital 支付创建、确认、状态查询 |
| `app/api/digital/upload-media` | 媒体上传到 MinIO |
| `app/api/digital/vip/check` | VIP 状态检查 |
| `app/api/media/proxy` | 远程媒体抓取、校验、缓存到 MinIO 后跳转公开 URL |
### FastAPI 后端
FastAPI 入口为 `backend/main.py`,配置读取在 `backend/settings.py`。核心能力:
| API | 作用 |
| --- | --- |
| `/api/health` | 健康检查 |
| `/api/auth/email``/api/auth/google``/api/auth/me` | 主站认证 |
| `/api/join``/api/join/status` | 加入表单与状态 |
| `/api/upload-media` | 后端媒体上传 |
| `/api/pay``/api/pay/status` | 支付创建与查询 |
| `/cnomadcna/*_notify` | 支付网关回调 |
| `/api/cities``/api/cities/{slug}` | 城市数据 |
| `/api/content/*` | 首页内容、内容投稿、内容详情 |
| `/api/notifications/*` | 通知、回执、偏好 |
| `/api/recommendations/next-stop` | 下一站推荐 |
| `/api/meetups` | 活动列表与活动创建 |
| `/api/discussions` | 讨论列表与创建 |
| `/api/gigs` | 任务赏金列表、创建、申请 |
| `/api/profiles``/api/matches/swipes` | 资料与匹配 |
| `/api/feedback` | 用户反馈 |
| `/api/stats/*` | 成员地图、概览、城市排行 |
| `/api/services``/api/services/leads` | 增值服务与线索 |
| `/api/ai/assistant` | AI 助手占位接口 |
后端辅助模块:
- `backend/pb_client.py`PocketBase HTTP 客户端
- `backend/payment.py`ZPay / XorPay 支付适配
- `backend/minio_storage.py`MinIO 上传
- `backend/init_db.py``seed_data.py``enhanced_seed.py`:初始化与种子数据
- `backend/settings.py`:环境变量读取
### 活动、视频与群聊源码
活动相关逻辑主要在:
- `app/[locale]/meetups/page.tsx`
- `app/[locale]/meetups/host/page.tsx`
- `app/lib/meetups.ts`
`app/lib/meetups.ts` 负责:
- MiroTalk 默认地址:`https://mirotalk.nomadro.cn`
- MiroTalk 房间名规范化
- MiroTalk 入会链接生成
- Lounge 默认地址:`https://lounge.nomadro.cn`
- Web 群聊服务标签:`The Lounge + Ergo IRC`
线上/混合活动详情页会展示:
- MiroTalk P2P 视频入口
- 页面内 iframe 打开视频房
- The Lounge Web 群聊入口
- 信令/中继服务器和群聊入口域名
### 配置与国际化
| 文件 | 作用 |
| --- | --- |
| `config/site.config.ts` | 主站 SEO 与站点基础信息 |
| `config/nav.config.ts` | 顶部导航 |
| `config/footer.config.ts` | 页脚 |
| `config/services.config.ts` | 主站支付配置 |
| `config/runtime-env.ts` | 运行环境判断 |
| `config/digital/*` | Digital 子站主题、服务、课程、域名配置 |
| `messages/zh.json``messages/en.json` | 中英文文案 |
| `i18n/routing.ts` | 语言列表与默认语言 |
| `i18n/navigation.ts` | 语言路由 Link / Router 封装 |
## 本地开发
### 环境要求
- Node.js 20+,生产当前使用 Node.js 22.13.1
- pnpm 10+
- Python 3.12+
- PocketBase
- 可选MinIO、ZPay/XorPay 测试配置
### 安装依赖
```bash
pnpm install
```
### 前端开发
```bash
pnpm dev
```
默认启动在:
```text
http://localhost:3001
```
### FastAPI 后端开发
```bash
python3 -m venv backend/.venv
source backend/.venv/bin/activate
pip install -r backend/requirements.txt
uvicorn backend.main:app --host 127.0.0.1 --port 8000 --reload
```
### 构建与启动
```bash
pnpm build
pnpm start
```
### 常用检查
```bash
pnpm lint
pnpm exec tsc --noEmit --pretty false
APP_ENV=production NEXT_PUBLIC_APP_ENV=production NODE_ENV=production pnpm build
```
## 环境变量
不要把真实密钥提交到 Git。`.env.example` 只放示例值,生产值放 `.env.local``backend/.env` 或 systemd Environment。
### 前端公开变量
| 变量 | 说明 |
| --- | --- |
| `APP_ENV` | `development` / `production` |
| `NEXT_PUBLIC_APP_ENV` | 浏览器可见运行环境 |
| `NEXT_PUBLIC_SITE_URL` | 站点根地址 |
| `NEXT_PUBLIC_SITE_ID` | 站点 ID用于会员/VIP 隔离 |
| `NEXT_PUBLIC_API_BASE_URL` | API 基础地址,生产通常为 `/api` |
| `NEXT_PUBLIC_POCKETBASE_URL` | 浏览器可访问 PocketBase 地址 |
| `NEXT_PUBLIC_MIROTALK_URL` | MiroTalk P2P 服务地址 |
| `NEXT_PUBLIC_LOUNGE_URL` | The Lounge Web 群聊地址 |
| `NEXT_PUBLIC_MINIO_PUBLIC_URL` | MinIO 文件公开地址 |
### 后端和服务端变量
| 变量 | 说明 |
| --- | --- |
| `POCKETBASE_URL` | 服务端访问 PocketBase 的内网地址 |
| `POCKETBASE_EMAIL` | PocketBase 管理员邮箱 |
| `POCKETBASE_PASSWORD` | PocketBase 管理员密码 |
| `PAYMENT_API_URL` | 支付 API 地址 |
| `PAYMENT_PROVIDER` | `zpay``xorpay` |
| `PAYMENT_DEFAULT_AMOUNT` | 默认支付金额 |
| `PAYMENT_JOIN_AMOUNT` | 加入场景金额 |
| `PAYMENT_JOIN_TYPE` | 加入场景订单类型 |
| `ZPAY_PID``ZPAY_KEY` | ZPay 商户配置 |
| `XORPAY_AID``XORPAY_SECRET` | XorPay 商户配置 |
| `MINIO_ENDPOINT` | MinIO 内网端点 |
| `MINIO_PORT` | MinIO 端口 |
| `MINIO_USE_SSL` | 是否对 MinIO 内网连接启用 SSL |
| `MINIO_BUCKET` | 存储桶 |
| `MINIO_ACCESS_KEY` | MinIO Access Key |
| `MINIO_SECRET_KEY` | MinIO Secret Key |
| `MINIO_REGION` | MinIO 区域 |
| `MINIO_UPLOAD_PREFIX` | 上传路径前缀 |
| `FRONTEND_ORIGINS` | FastAPI CORS 白名单 |
| `UPLOAD_DIR` | 后端本地上传目录 |
## 生产服务器支持
生产代码目录:
```text
/home/ubuntu/gitlab-instance-0a899031_cnomadcna
```
### 域名与服务映射
| 域名 | 上游 | 说明 |
| --- | --- | --- |
| `nomadro.cn` / `www.nomadro.cn` | `127.0.0.1:4002` | Next.js 主站,`cnomadcna.service` |
| `api.nomadro.cn` | `127.0.0.1:8000` | FastAPI |
| `pocketbase.nomadro.cn` | `127.0.0.1:8090` | PocketBase |
| `minio.nomadro.cn` | `127.0.0.1:9100` / `9101` | MinIO API / Console |
| `mirotalk.nomadro.cn` | `127.0.0.1:9300` | MiroTalk P2P |
| `lounge.nomadro.cn` | `127.0.0.1:9000` | The Lounge Web IRC |
| `ergo.nomadro.cn` | HTTPS redirect | 跳转到 `lounge.nomadro.cn`IRC 后端只内网监听 |
| `ntfy.nomadro.cn` | `127.0.0.1:9180` | ntfy 通知服务 |
### systemd 服务
| 服务 | 作用 | 当前策略 |
| --- | --- | --- |
| `cnomadcna.service` | Next.js 生产服务 | enabled监听 `0.0.0.0:4002` |
| `mirotalk-p2p.service` | MiroTalk P2P | enabled监听 `127.0.0.1:9300` |
| `ergo.service` | Ergo IRC Server | enabled监听 `127.0.0.1:6667``[::1]:6667` |
| `thelounge.service` | The Lounge Web IRC | enabled监听 `127.0.0.1:9000` |
常用命令:
```bash
systemctl status cnomadcna.service
systemctl status mirotalk-p2p.service
systemctl status ergo.service
systemctl status thelounge.service
journalctl -u cnomadcna.service -n 100 --no-pager
journalctl -u mirotalk-p2p.service -n 100 --no-pager
journalctl -u ergo.service -n 100 --no-pager
journalctl -u thelounge.service -n 100 --no-pager
```
### Docker 服务
当前生产容器:
| 容器 | 镜像 | 端口 |
| --- | --- | --- |
| `nomadro-minio` | `minio/minio:latest` | `127.0.0.1:9100->9000``127.0.0.1:9101->9001` |
| `nomadro-ntfy` | `binwiederhier/ntfy:latest` | `127.0.0.1:9180->80` |
检查:
```bash
docker ps
docker logs nomadro-minio --tail 100
docker logs nomadro-ntfy --tail 100
```
### Nginx
Nginx vhost 目录:
```text
/www/server/panel/vhost/nginx
```
关键配置:
- `www.nomadro.cn.conf`:主站
- `api.nomadro.cn.conf`FastAPI
- `mirotalk.nomadro.cn.conf`MiroTalk
- `lounge.nomadro.cn.conf`The Lounge + Ergo redirect
- `minio.nomadro.cn.conf`MinIO
- `pocketbase.nomadro.cn.conf`PocketBase
常用命令:
```bash
nginx -t
nginx -s reload
```
### HTTPS 证书
证书由 Certbot 管理。`lounge.nomadro.cn` 证书包含两个域名:
```text
lounge.nomadro.cn
ergo.nomadro.cn
```
当前证书路径:
```text
/etc/letsencrypt/live/lounge.nomadro.cn/fullchain.pem
/etc/letsencrypt/live/lounge.nomadro.cn/privkey.pem
```
检查:
```bash
certbot certificates
```
### MiroTalk P2P
安装目录:
```text
/opt/nomadro-services/mirotalk
```
运行方式:
- systemd`mirotalk-p2p.service`
- 内网端口:`127.0.0.1:9300`
- 公网入口:`https://mirotalk.nomadro.cn`
- 活动页自动生成链接:`https://mirotalk.nomadro.cn/join?room=...&name=...`
健康检查:
```bash
curl -I https://mirotalk.nomadro.cn/join?room=nomadcna-healthcheck
```
### Ergo IRC Server
安装目录:
```text
/opt/ergo
```
配置:
```text
/etc/ergo/ircd.yaml
/etc/ergo/ergo.motd
/var/lib/ergo/ircd.db
```
当前策略:
- 网络名:`NomadCNA`
- 服务名:`ergo.nomadro.cn`
- 只监听本机 `127.0.0.1:6667``[::1]:6667`
- 不开放公网 IRC TLS `6697`
- `ergo.nomadro.cn` 的 HTTPS 请求跳转到 The Lounge
本机 IRC 握手检查:
```bash
{ printf 'NICK health\r\nUSER health 0 * :NomadCNA Health\r\nJOIN #nomadcna\r\nQUIT :healthcheck\r\n'; sleep 1; } \
| timeout 5s nc 127.0.0.1 6667
```
### The Lounge Web 群聊
安装目录:
```text
/opt/nomadro-services/thelounge
```
配置:
```text
/var/lib/thelounge/config.js
```
当前策略:
- public mode
- 只监听 `127.0.0.1:9000`
- Nginx 通过 `https://lounge.nomadro.cn` 反代
- 默认网络锁定到本机 Ergo`127.0.0.1:6667`
- 默认加入频道:`#nomadcna,#online-events`
- 文件上传关闭
- The Lounge 自身消息持久化关闭Ergo 保留内存历史
健康检查:
```bash
curl -I https://lounge.nomadro.cn/
curl 'https://lounge.nomadro.cn/socket.io/?EIO=4&transport=polling&t=health'
```
## 上线流程
### 前端上线
```bash
cd /home/ubuntu/gitlab-instance-0a899031_cnomadcna
APP_ENV=production NEXT_PUBLIC_APP_ENV=production NODE_ENV=production pnpm build
systemctl restart cnomadcna.service
systemctl status cnomadcna.service --no-pager
```
### 后端上线
FastAPI 当前运行在 `127.0.0.1:8000`。更新后需重启对应 uvicorn 进程或其 systemd 服务。如果后续为 FastAPI 增加 systemd 单元,建议命名为:
```text
cnomadcna-api.service
```
### Nginx 配置变更
```bash
nginx -t
nginx -s reload
```
### 服务健康检查
```bash
curl -I https://nomadro.cn/zh/meetups
curl -I https://api.nomadro.cn/api/health
curl -I https://mirotalk.nomadro.cn/join?room=nomadcna-healthcheck
curl -I https://lounge.nomadro.cn/
curl -I https://ergo.nomadro.cn/
```
## 数据与备份
建议纳入备份的内容:
| 路径/服务 | 说明 |
| --- | --- |
| `.env.local``backend/.env` | 生产环境变量和密钥 |
| PocketBase 数据目录 | 用户、会员、订单、内容、活动等业务数据 |
| MinIO 数据卷 | 上传媒体、远程媒体缓存 |
| `/var/lib/ergo` | Ergo IRC 数据库 |
| `/var/lib/thelounge` | The Lounge 配置、VAPID key、包配置 |
| `/opt/nomadro-services/mirotalk` | MiroTalk 源码部署目录 |
| `/www/server/panel/vhost/nginx/*.conf` | Nginx vhost 配置 |
| `/etc/systemd/system/*.service` | systemd 单元 |
## 安全注意事项
- README 不应包含真实密钥、管理员密码、支付密钥或 MinIO Secret。
- `.env.local``backend/.env` 不应提交到 Git。
- The Lounge 当前为 public mode适合活动公开群聊如需长期身份和历史可切换 private mode 并创建用户账号。
- Ergo 原生 IRC 公网端口当前不开放,这是有意设计;需要原生 IRC 客户端时再开放 `6697` 并配置 TLS。
- 内部服务优先绑定 `127.0.0.1`,统一通过 Nginx HTTPS 暴露。
- 修改 Nginx 前必须先 `nginx -t`
- 更新证书后确认 SAN 包含对应域名。
## 开发约定
- 新页面优先放入 `app/[locale]`,并补充 `messages/zh.json``messages/en.json`
- 需要语言感知链接时使用 `i18n/navigation.ts` 导出的 `Link`
- 调用后端 API 使用 `app/lib/api-client.ts`
- 主站通用配置放 `config/*`Digital 子站配置放 `config/digital/*`
- 媒体上传和远程媒体代理优先走 MinIO。
- 支付相关逻辑优先复用 `app/lib/payment``backend/payment.py``config/*/services.config.ts`
- 线上活动相关逻辑优先复用 `app/lib/meetups.ts`
## 常见问题
### 为什么 `ergo.nomadro.cn` 不直接打开 IRC
`ergo.nomadro.cn` 是 IRC 服务名,但原生 IRC 端口没有对公网开放。用户从浏览器进入 `lounge.nomadro.cn`The Lounge 在服务器本机连接 Ergo。这样能减少公网攻击面同时满足 Web 群聊需求。
### 为什么 `/api/digital/*` 没有转发到 FastAPI
`next.config.ts` 明确排除了 `digital``media` 两类 API它们由 Next.js Route Handlers 直接处理。其他 `/api/*` 才会转发到 FastAPI。
### 活动视频房如何生成?
`app/lib/meetups.ts` 会根据活动城市、日期、时间和 ID 生成 MiroTalk room slug。如果活动有自定义 `meetingUrl`,则优先使用自定义链接。
### 群聊频道如何进入?
The Lounge public mode 的默认网络已锁定到 Ergo并默认加入 `#nomadcna``#online-events`。活动详情页的“进入群聊”按钮指向 `https://lounge.nomadro.cn`
## 维护命令速查
```bash
# 构建并重启主站
APP_ENV=production NEXT_PUBLIC_APP_ENV=production NODE_ENV=production pnpm build
systemctl restart cnomadcna.service
# 查看服务
systemctl status cnomadcna.service --no-pager
systemctl status mirotalk-p2p.service --no-pager
systemctl status ergo.service --no-pager
systemctl status thelounge.service --no-pager
# 查看日志
journalctl -u cnomadcna.service -n 100 --no-pager
journalctl -u mirotalk-p2p.service -n 100 --no-pager
journalctl -u ergo.service -n 100 --no-pager
journalctl -u thelounge.service -n 100 --no-pager
# Nginx
nginx -t
nginx -s reload
# 端口
ss -ltnp | rg ':4002|:8000|:8090|:9000|:9300|:9100|:9101|:6667|:6697'
# Docker
docker ps
# HTTPS
certbot certificates
```