Files
citywalk-stamp/.claude/commands/deploy-stamp.md
YANG JIANKUAN dbe8ea5460 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>
2026-04-19 18:14:41 +08:00

147 lines
5.6 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.

---
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 永远返回 falsewhile 循环会立即退出,接着的 `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