fix: resolve legal/about pages in production, update deploy docs

- Fix markdown file path resolution for SSR production builds
- Try multiple paths (public/legal, client/legal) for dev and prod
- Update deploy/README.md with frontend deployment instructions
- Document PM2, Nginx configuration, and port mapping
This commit is contained in:
zl-q
2026-05-11 19:09:35 +08:00
parent dda8014428
commit 91c089a894
3 changed files with 154 additions and 10 deletions
+122
View File
@@ -14,6 +14,8 @@
- Docker - Docker
- Docker Compose v2 - Docker Compose v2
- AWS CLI v2 - AWS CLI v2
- Node.js 22+ (前端 SSR 服务)
- PM2 (前端进程管理)
确认命令: 确认命令:
@@ -21,6 +23,31 @@
docker --version docker --version
docker compose version docker compose version
aws --version aws --version
node --version # 需要 >= 22.12.0
pm2 --version
```
## 服务架构
生产环境运行以下服务:
| 服务 | 端口 | 说明 |
|------|------|------|
| Nginx | 80, 443 | 反向代理,SSL 终结 |
| 前端 SSR | 4322 | Node.js + Astro SSR |
| 后端 API | 5775 | Docker 容器 |
| Redis | 6379 | Docker 容器 |
| Worker Agent | - | Docker 容器 |
### 端口映射
```
Internet → Nginx (443)
├── /_astro/* → 静态文件 (client 目录)
├── /images/* → 静态文件 (client 目录)
└── /* → 反向代理到 localhost:4322 (前端 SSR)
前端 SSR (4322) → /api/* → 反向代理到 localhost:5775 (后端 API)
``` ```
## 环境变量 ## 环境变量
@@ -199,3 +226,98 @@ docker compose --env-file ./.env -f docker-compose.prod.yml --profile workers do
``` ```
谨慎使用 `down -v`,它会删除 Redis 持久化数据。 谨慎使用 `down -v`,它会删除 Redis 持久化数据。
## 前端部署
### 构建前端
在本地开发机器上:
```bash
cd web
PUBLIC_API_URL=https://api.meeyao.com npm run build
```
构建产物在 `web/dist/` 目录:
- `dist/client/` - 静态资源 (CSS, JS, 图片)
- `dist/server/` - SSR 服务端代码
### 上传到服务器
```bash
# 上传构建产物
rsync -avz -e "ssh -i xunmee.pem" web/dist/ ubuntu@18.218.38.213:/home/ubuntu/deploy/web/
# 上传依赖配置
rsync -avz -e "ssh -i xunmee.pem" web/package.json web/package-lock.json ubuntu@18.218.38.213:/home/ubuntu/deploy/web/
# 安装生产依赖
ssh -i xunmee.pem ubuntu@18.218.38.213 "cd /home/ubuntu/deploy/web && npm install --omit=dev"
```
### PM2 管理
```bash
# 启动服务
pm2 start server/entry.mjs --name meeyao-web
# 设置端口 (默认 4321,需要改为 4322)
export HOST=0.0.0.0
export PORT=4322
pm2 restart meeyao-web
# 保存进程列表 (开机自启)
pm2 save
# 查看状态
pm2 status
# 查看日志
pm2 logs meeyao-web
```
### Nginx 配置
前端 SSR 服务监听 `localhost:4322`Nginx 配置要点:
```nginx
server {
listen 443 ssl http2;
server_name meeyao.com;
# 静态资源直接从 client 目录提供
location /_astro {
root /home/ubuntu/deploy/web/client;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /images {
root /home/ubuntu/deploy/web/client;
expires 30d;
}
# 其他请求代理到 Node.js SSR
location / {
proxy_pass http://127.0.0.1:4322;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### 前端更新流程
```bash
# 1. 本地构建
cd web && PUBLIC_API_URL=https://api.meeyao.com npm run build
# 2. 上传
rsync -avz -e "ssh -i xunmee.pem" dist/ ubuntu@18.218.38.213:/home/ubuntu/deploy/web/
# 3. 重启服务
ssh -i xunmee.pem ubuntu@18.218.38.213 "pm2 restart meeyao-web"
```
+16 -5
View File
@@ -17,12 +17,23 @@ const titleMap: Record<Locale, string> = {
en: 'About Us', en: 'About Us',
}; };
const filePath = path.resolve('public/legal', locale, 'about_us.md'); // Try multiple paths for dev and production environments
const possiblePaths = [
path.resolve('public/legal', locale, 'about_us.md'),
path.resolve('client/legal', locale, 'about_us.md'),
path.resolve('../client/legal', locale, 'about_us.md'),
];
let raw = ''; let raw = '';
try { for (const filePath of possiblePaths) {
raw = fs.readFileSync(filePath, 'utf-8'); try {
raw = raw.replace(/^#\s+.+\n*/m, ''); raw = fs.readFileSync(filePath, 'utf-8');
} catch { raw = raw.replace(/^#\s+.+\n*/m, '');
break;
} catch {
// Try next path
}
}
if (!raw) {
raw = `Content not available.`; raw = `Content not available.`;
} }
const content = await marked(raw); const content = await marked(raw);
+16 -5
View File
@@ -46,12 +46,23 @@ const subtitle = subtitleMap[docType]?.[locale] ?? '';
const warning = warningMap[locale]; const warning = warningMap[locale];
const legalTitle = legalTitleMap[locale]; const legalTitle = legalTitleMap[locale];
const filePath = path.resolve('public/legal', locale, `${docType}.md`); // Try multiple paths for dev and production environments
const possiblePaths = [
path.resolve('public/legal', locale, `${docType}.md`),
path.resolve('client/legal', locale, `${docType}.md`),
path.resolve('../client/legal', locale, `${docType}.md`),
];
let raw = ''; let raw = '';
try { for (const filePath of possiblePaths) {
raw = fs.readFileSync(filePath, 'utf-8'); try {
raw = raw.replace(/^#\s+.+\n*/m, ''); raw = fs.readFileSync(filePath, 'utf-8');
} catch { raw = raw.replace(/^#\s+.+\n*/m, '');
break;
} catch {
// Try next path
}
}
if (!raw) {
raw = `Content not available.`; raw = `Content not available.`;
} }
const content = await marked(raw); const content = await marked(raw);