2026-06-07 21:02:57 -05:00
s
2026-06-07 15:57:47 +08:00
s
2026-06-08 10:02:16 +08:00
s
2026-06-08 10:02:16 +08:00
s
2026-06-08 10:02:16 +08:00
s
2026-06-07 18:06:52 +08:00
2026-03-08 23:20:36 -05:00
s
2026-06-08 10:02:16 +08:00
's'
2026-03-07 12:33:14 -06:00
2026-06-07 23:18:53 +08:00
2026-06-07 23:18:53 +08:00
s
2026-06-06 23:05:17 +08:00
s
2026-06-06 23:05:17 +08:00
ss
2026-06-07 19:18:24 +08:00
s
2026-06-06 23:05:17 +08:00
's'
2026-06-07 21:02:57 -05:00
's'
2026-03-12 19:54:58 -05:00
's'
2026-03-07 12:33:14 -06:00
's'
2026-03-07 12:33:14 -06:00
2026-06-07 23:18:53 +08:00
\s
2026-06-06 23:25:39 +08:00
's'
2026-06-07 01:17:46 -05:00
's'
2026-06-07 01:17:46 -05:00
's'
2026-03-07 12:33:14 -06:00
's'
2026-03-07 12:33:14 -06:00
s''
2026-06-06 04:29:46 -05:00
's'
2026-03-11 23:10:18 -05:00
2026-06-07 23:18:53 +08:00
's'
2026-03-07 12:33:14 -06:00

游牧中国 NomadCNA

NomadCNA 是面向数字游民的生活操作系统,用一个站点承载城市决策、会员加入、线上/线下活动、视频会议、Web 群聊、内容资源、任务赏金、支付和媒体存储等能力。

生产站点:

业务介绍

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

源码解析

顶层结构

.
├── 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/digitalDigital 子站入口

通用布局与组件:

  • app/layout.tsx:根布局
  • app/[locale]/layout.tsx:语言布局
  • app/components/Navbar.tsxFooter.tsxSidebar.tsx:主站导航与外壳
  • app/context/LocaleContext.tsx:运行时文案读取
  • app/context/ThemeContext.tsx:亮色/暗色主题

Digital 子站

Digital 子站代码集中在 app/digitalapp/[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.tsPocketBase、支付、MinIO 配置读取
  • domain.config.ts:域名和服务地址推导
  • course.tslessons.ts:课程数据

API 分层

本项目有两层 API

  1. Next.js 内置 APIapp/api/*
  2. FastAPI 后端:backend/main.py

next.config.ts 将大多数 /api/* 转发到 FastAPI

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.pyPocketBase HTTP 客户端
  • backend/payment.pyZPay / XorPay 支付适配
  • backend/minio_storage.pyMinIO 上传
  • backend/init_db.pyseed_data.pyenhanced_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.jsonmessages/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 测试配置

安装依赖

pnpm install

前端开发

pnpm dev

默认启动在:

http://localhost:3001

FastAPI 后端开发

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

构建与启动

pnpm build
pnpm start

常用检查

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.localbackend/.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 zpayxorpay
PAYMENT_DEFAULT_AMOUNT 默认支付金额
PAYMENT_JOIN_AMOUNT 加入场景金额
PAYMENT_JOIN_TYPE 加入场景订单类型
ZPAY_PIDZPAY_KEY ZPay 商户配置
XORPAY_AIDXORPAY_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 后端本地上传目录

生产服务器支持

生产代码目录:

/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.cnIRC 后端只内网监听
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

常用命令:

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->9000127.0.0.1:9101->9001
nomadro-ntfy binwiederhier/ntfy:latest 127.0.0.1:9180->80

检查:

docker ps
docker logs nomadro-minio --tail 100
docker logs nomadro-ntfy --tail 100

Nginx

Nginx vhost 目录:

/www/server/panel/vhost/nginx

关键配置:

  • www.nomadro.cn.conf:主站
  • api.nomadro.cn.confFastAPI
  • mirotalk.nomadro.cn.confMiroTalk
  • lounge.nomadro.cn.confThe Lounge + Ergo redirect
  • minio.nomadro.cn.confMinIO
  • pocketbase.nomadro.cn.confPocketBase

常用命令:

nginx -t
nginx -s reload

HTTPS 证书

证书由 Certbot 管理。lounge.nomadro.cn 证书包含两个域名:

lounge.nomadro.cn
ergo.nomadro.cn

当前证书路径:

/etc/letsencrypt/live/lounge.nomadro.cn/fullchain.pem
/etc/letsencrypt/live/lounge.nomadro.cn/privkey.pem

检查:

certbot certificates

MiroTalk P2P

安装目录:

/opt/nomadro-services/mirotalk

运行方式:

  • systemdmirotalk-p2p.service
  • 内网端口:127.0.0.1:9300
  • 公网入口:https://mirotalk.nomadro.cn
  • 活动页自动生成链接:https://mirotalk.nomadro.cn/join?room=...&name=...

健康检查:

curl -I https://mirotalk.nomadro.cn/join?room=nomadcna-healthcheck

Ergo IRC Server

安装目录:

/opt/ergo

配置:

/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 握手检查:

{ 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 群聊

安装目录:

/opt/nomadro-services/thelounge

配置:

/var/lib/thelounge/config.js

当前策略:

  • public mode
  • 只监听 127.0.0.1:9000
  • Nginx 通过 https://lounge.nomadro.cn 反代
  • 默认网络锁定到本机 Ergo127.0.0.1:6667
  • 默认加入频道:#nomadcna,#online-events
  • 文件上传关闭
  • The Lounge 自身消息持久化关闭Ergo 保留内存历史

健康检查:

curl -I https://lounge.nomadro.cn/
curl 'https://lounge.nomadro.cn/socket.io/?EIO=4&transport=polling&t=health'

上线流程

前端上线

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 单元,建议命名为:

cnomadcna-api.service

Nginx 配置变更

nginx -t
nginx -s reload

服务健康检查

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.localbackend/.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.localbackend/.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.jsonmessages/en.json
  • 需要语言感知链接时使用 i18n/navigation.ts 导出的 Link
  • 调用后端 API 使用 app/lib/api-client.ts
  • 主站通用配置放 config/*Digital 子站配置放 config/digital/*
  • 媒体上传和远程媒体代理优先走 MinIO。
  • 支付相关逻辑优先复用 app/lib/paymentbackend/payment.pyconfig/*/services.config.ts
  • 线上活动相关逻辑优先复用 app/lib/meetups.ts

常见问题

为什么 ergo.nomadro.cn 不直接打开 IRC

ergo.nomadro.cn 是 IRC 服务名,但原生 IRC 端口没有对公网开放。用户从浏览器进入 lounge.nomadro.cnThe Lounge 在服务器本机连接 Ergo。这样能减少公网攻击面同时满足 Web 群聊需求。

为什么 /api/digital/* 没有转发到 FastAPI

next.config.ts 明确排除了 digitalmedia 两类 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

维护命令速查

# 构建并重启主站
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
Description
No description provided
Readme 3 MiB
Languages
TypeScript 49.2%
HTML 48.3%
CSS 2.1%
JavaScript 0.3%