feat: 新增静态文章模块并支持 NFC 链接分发
- 新增 Article 数据模型 + 迁移(title/subtitle/body/coverImage/caption) - 后端:公共 /api/articles 查询接口 + 管理端 CRUD/上传/二维码 - 前端:移动端 /article/:id 阅读页(Playfair + 纸张肌理 + 首行缩进) - Admin:新增文章管理三页(列表/表单/二维码)与侧栏入口 - 导入 6 篇点位解说词:朝天宫/七家湾/运渎/打钉巷/绒庄街/熙南里 - Admin 二维码页增加「复制链接(写入 NFC)」按钮 - 落地页步骤文案从扫码改为 NFC 触碰 - Dockerfile + entrypoint 增加 articles 图片回灌 - 修复 deploy-stamp skill 构建轮询卡住(pgrep 模式错误) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
146
.claude/commands/deploy-stamp.md
Normal file
146
.claude/commands/deploy-stamp.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
description: 推送本地变更到 njcq 服务器并重新部署 Docker Compose
|
||||
argument-hint: "[可选 --no-build 仅 rsync 不重启 | --seed 部署后执行 db:seed(危险:会清空用户数据)]"
|
||||
---
|
||||
|
||||
# 一键部署 citywalk-stamp 到 njcq 服务器
|
||||
|
||||
## 部署目标
|
||||
- 主机:SSH 别名 `njcq`
|
||||
- 目录:`/opt/1panel/apps/citywalk-stamp`
|
||||
- 容器:`citywalk-stamp`(端口 `127.0.0.1:3001` ← 宿主 3000 被 1Panel Gitea 占用)
|
||||
- 持久化:`./data/`(SQLite)、`./uploads/`(图章图片)、`.env`(密钥)均在服务器保留,本次部署不会覆盖
|
||||
|
||||
## 参数
|
||||
|
||||
用户可能通过 `$ARGUMENTS` 传入:
|
||||
- `--no-build`:跳过 compose up,仅同步代码
|
||||
- `--seed`:部署后执行 `pnpm db:seed`(⚠️ 会清空 Stamp 表并级联删除用户 Collection 数据,仅首次部署或彻底重置时使用)
|
||||
- 无参数:默认推送代码 + 重新 build 并重启容器
|
||||
|
||||
## 步骤
|
||||
|
||||
### 1. 预检
|
||||
|
||||
确认当前在项目根:
|
||||
```bash
|
||||
test -f Dockerfile && test -f docker-compose.yml && test -f pnpm-workspace.yaml
|
||||
```
|
||||
如缺文件立即中止。
|
||||
|
||||
### 2. rsync 推送代码(sudo 身份写 /opt)
|
||||
|
||||
```bash
|
||||
rsync -avz --delete --rsync-path="sudo rsync" \
|
||||
--exclude='.git/' \
|
||||
--exclude='node_modules/' \
|
||||
--exclude='**/node_modules/' \
|
||||
--exclude='**/dist/' \
|
||||
--exclude='.env' \
|
||||
--exclude='.env.backup' \
|
||||
--exclude='.env.local' \
|
||||
--exclude='.claude/' \
|
||||
--exclude='prisma/dev.db*' \
|
||||
--exclude='/data/' \
|
||||
--exclude='/uploads/' \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='build.log' \
|
||||
./ njcq:/opt/1panel/apps/citywalk-stamp/
|
||||
```
|
||||
|
||||
**关键陷阱(必须保持以下写法)**:
|
||||
- `--rsync-path="sudo rsync"` 解决 ubuntu 用户对 `/opt` 无写权限(否则 rsync 会静默 status 23,目录仍为空)
|
||||
- `/data/` 和 `/uploads/` **带前导斜杠**——只排除项目根下这两个目录(它们是 docker bind mount 产生的运行时数据),**不要**写成 `data/` 或 `uploads/`,否则会把 `packages/server/uploads/stamps/` 也一并排除
|
||||
- `.env` 被排除,远端 JWT_SECRET / ADMIN_API_KEY / SITE_URL 不受影响
|
||||
- `--delete` 会删除远端多余文件,但 exclude 的目录保留
|
||||
|
||||
### 3. 如果是 `--no-build`,到这里就结束
|
||||
|
||||
只同步代码而不重启服务,只在改了纯文档或静态资源、容器内存里的代码用不上时才用。
|
||||
|
||||
### 4. 远端后台启动 compose(避免 SSH 断开导致构建中断)
|
||||
|
||||
```bash
|
||||
ssh njcq 'sudo bash -c "cd /opt/1panel/apps/citywalk-stamp && rm -f build.log && nohup docker compose up -d --build > build.log 2>&1 &" && sleep 2 && echo started'
|
||||
```
|
||||
|
||||
`nohup ... &` 让构建脱离 SSH 进程。ssh 立即返回,构建在远端持续进行。
|
||||
|
||||
### 5. 轮询等待构建完成(可能 3-5 分钟)
|
||||
|
||||
**不要**靠 `pgrep` 判断构建是否结束——服务器用的是 docker v2(`docker compose`),没有名为 `docker-compose compose up` 的进程,pgrep 永远返回 false,while 循环会立即退出,接着的 `docker compose ps` 会在 daemon 忙时阻塞,表现为"卡住"。直接读 `build.log` 的末尾特征字符串才准:
|
||||
|
||||
```bash
|
||||
ssh njcq '
|
||||
cd /opt/1panel/apps/citywalk-stamp
|
||||
for i in $(seq 1 40); do # 最多等 10 分钟
|
||||
tail_out=$(sudo tail -n 30 build.log 2>/dev/null)
|
||||
# 成功标记:docker compose v2 finish line
|
||||
if echo "$tail_out" | grep -qE "Container .* (Started|Running|Healthy)"; then
|
||||
echo "=== build finished ==="
|
||||
break
|
||||
fi
|
||||
# 失败标记
|
||||
if echo "$tail_out" | grep -qE "(failed to solve|Error response from daemon|ERROR: )"; then
|
||||
echo "=== build FAILED ==="
|
||||
echo "$tail_out" | tail -15
|
||||
exit 1
|
||||
fi
|
||||
sleep 15
|
||||
echo "... building $(date +%H:%M:%S)"
|
||||
echo "$tail_out" | tail -2 | sed "s/^/ /"
|
||||
done
|
||||
echo "---"
|
||||
sudo docker compose ps
|
||||
echo "=== container logs (last 20 lines) ==="
|
||||
sudo docker compose logs --tail=20 app
|
||||
'
|
||||
```
|
||||
|
||||
用 **Bash `run_in_background: true`** 启动这条命令,然后用 TaskOutput 等待完成——避免 SSH 命令卡住主 Claude 流程。
|
||||
|
||||
### 6. 健康检查
|
||||
|
||||
```bash
|
||||
ssh njcq '
|
||||
echo "--- /api/health ---"
|
||||
curl -sS http://127.0.0.1:3001/api/health && echo
|
||||
echo "--- /api/stamps count ---"
|
||||
curl -sS http://127.0.0.1:3001/api/stamps | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d[\"data\"]))"
|
||||
echo "--- /uploads sample ---"
|
||||
curl -sI http://127.0.0.1:3001/uploads/stamps/stamp-01-color.jpg | head -1
|
||||
echo "--- /album (SPA) ---"
|
||||
curl -s -o /dev/null -w "http %{http_code}\n" http://127.0.0.1:3001/album
|
||||
'
|
||||
```
|
||||
|
||||
期望输出:
|
||||
- health: `{"success":true,...}`
|
||||
- stamps count: `16`(如果是首次部署或数据库被清空,可能是 `0`,此时需要 `--seed`)
|
||||
- stamps 首图: `HTTP/1.1 200 OK`
|
||||
- SPA: `http 200`
|
||||
|
||||
### 7. 如传入 `--seed`,执行首次 seed
|
||||
|
||||
**仅在首次部署或主动重置时使用**。会触发 `seed.ts` 里的 `prisma.stamp.deleteMany()` 级联删除所有 Collection:
|
||||
|
||||
```bash
|
||||
ssh njcq 'cd /opt/1panel/apps/citywalk-stamp && sudo docker compose exec -T app pnpm db:seed' | tail -10
|
||||
```
|
||||
|
||||
## 完成后给用户的反馈
|
||||
|
||||
一段简短摘要,包含:
|
||||
- 镜像是否重建、容器是否在运行
|
||||
- `/api/stamps` 返回的图章数量
|
||||
- 访问入口:`https://njcitywalk.njcqit.com`(外部 Nginx 反代到 127.0.0.1:3001)
|
||||
- 如果有异常(stamps=0、health 失败、容器重启循环),提示如何查看日志:
|
||||
`ssh njcq 'cd /opt/1panel/apps/citywalk-stamp && sudo docker compose logs -f app'`
|
||||
|
||||
## 不做的事(防止破坏生产数据)
|
||||
|
||||
- ❌ 不删除远端 `data/` 或 `uploads/`(已排除)
|
||||
- ❌ 不覆盖 `.env`(已排除)
|
||||
- ❌ 不自动 `pnpm db:seed`(必须显式 `--seed`)
|
||||
- ❌ 不修改 Nginx 配置或证书
|
||||
- ❌ 不在远端执行 `git pull`(没 git 仓,只走 rsync)
|
||||
Reference in New Issue
Block a user