AML/.claude/skills/aml-local-refresh/SKILL.md

139 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
name: aml-local-refresh
description: Refresh the AML local dev stack - git pull AML_Backend, restore the newest AML-local-dev-*.bak into the mssql container as AbpAML, rebuild the httpapi-host image, and restart the API bypassing db-migrator. Use when asked to 更新/还原/重建/重启本地后端环境, "拉最新代码并还原数据库", "restore the latest bak", or when local portal endpoints 500 after the backend moved on.
---
# AML 本地环境刷新(拉代码 + 还原库 + 重启服务)
一条龙刷新 `/Users/dev/projects/AML/docker-compose-local-dev/` 这套本地栈。栈本身的架构、配置来源(绕过 Nacos)、ABP 授权等背景见同目录 `readme.md`;本 skill 只讲**刷新流程**和**必须踩对的顺序**。
所有命令的工作目录都是:
```bash
cd /Users/dev/projects/AML/docker-compose-local-dev
```
## 铁律(顺序错了必炸)
1. **还原库前先停 host**,否则 `RESTORE` 拿不到独占锁。
2. **永远不要对还原库跑 `db-migrator`**。`.bak` 里**没有** `__EFMigrationsHistory` 表,migrator 会从初始迁移重建表 → `SqlException 2714` → exit 133;而 compose 里 `httpapi-host depends_on db-migrator: service_completed_successfully`,migrator 一挂 host 就起不来。**起 host 必须带 `--no-deps`**。
3. **代码更新了就必须 `build` 重建 host 镜像**。旧二进制配新库 = 列类型对不上(历史事故:`InvalidCastException: Decimal→Int32`,所有门户端点 500)。
4. 还原后 **`seed-root-ou.sql` 必跑**(缺 root OU → 支付后建租户静默失败),`local-schema-catchup.sql` 也跑一遍(幂等)。
5. `seed-agent-plans.sql` **默认不要跑**——先查活跃推薦人关联,有就跳过(见下)。
## 步骤
### 0. 前置检查
```bash
docker info >/dev/null 2>&1 || open -a Docker # 没起就拉起 Docker Desktop,等 docker info 通
```
Docker 内存需 ≥16 GB(mssql 在 Apple Silicon 上是 amd64 模拟,不够会 exit 139)。
### 1. 拉最新后端代码
```bash
cd /Users/dev/projects/AML/AML_Backend && git status -sb | head -5 && git pull
```
- 分支通常是 `dev`。工作区有改动先问用户,别自作主张 stash。
- `failed to get/store: -25308` 是 macOS keychain 的噪音,不影响 pull。
- 拉完看一眼新迁移:`ls src/iCON.Abp.FX.EntityFrameworkCore.DbMigrations/Migrations/*.cs | grep -v Designer | tail -8`,留着第 4 步核对。
### 2. 起基础设施 + 停 host
```bash
cd /Users/dev/projects/AML/docker-compose-local-dev
docker stop aml-httpapi-host
docker compose -f docker-compose.local.yml up -d mssql rabbitmq
# 等健康
until [ "$(docker inspect -f '{{.State.Health.Status}}' aml-mssql 2>/dev/null)" = healthy ]; do /bin/sleep 10; done
```
### 3. 还原最新 .bak
**别写死文件名**,用户会不定期换新的(文件名带日期戳,旧的会删):
```bash
BAK=$(ls -t *.bak | head -1); echo "using: $BAK"
docker cp "$BAK" aml-mssql:/var/opt/mssql/restore.bak
# 坑:docker cp 进去是 root:root 0600,sqlservr 读不到 → "Operating system error 5(Access is denied)"
docker exec -u root aml-mssql bash -lc 'chown mssql:root /var/opt/mssql/restore.bak && chmod 644 /var/opt/mssql/restore.bak'
docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -b -Q "
IF DB_ID('AbpAML') IS NOT NULL ALTER DATABASE [AbpAML] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
RESTORE DATABASE [AbpAML] FROM DISK='/var/opt/mssql/restore.bak'
WITH REPLACE, RECOVERY,
MOVE 'AML-local-dev' TO '/var/opt/mssql/data/AbpAML.mdf',
MOVE 'AML-local-dev_log' TO '/var/opt/mssql/data/AbpAML_log.ldf';
ALTER DATABASE [AbpAML] SET MULTI_USER;"
```
逻辑名恒为 `AML-local-dev` / `AML-local-dev_log`(不确定就先 `RESTORE FILELISTONLY`)。sqlcmd 在容器内路径是 `/opt/mssql-tools18/bin/sqlcmd`,**必须带 `-C`**。
### 4. 补数据 + 对齐 schema
```bash
docker exec -i aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -b -i /dev/stdin < seed-root-ou.sql
docker exec -i aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -b -i /dev/stdin < local-schema-catchup.sql
```
`local-schema-catchup.sql` 输出可能是乱码(中文编码),看 exit code 即可。
然后拿第 1 步列出的最新几条迁移,逐个查库里缺不缺(缺了就往 `local-schema-catchup.sql` 追加幂等 DDL —— 注意 `ALTER TABLE ADD` 与引用该新列的语句不能同批次,回填要包在 `EXEC(N'...')` 里):
```bash
docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -h -1 -W -Q "
SELECT 'SomeTable='+ISNULL(CAST(OBJECT_ID('SomeTable') AS varchar),'MISSING')
+' | SomeCol='+ISNULL(CAST(COL_LENGTH('SomeTable','SomeCol') AS varchar),'MISSING');"
```
> 查表存在性别用 `SELECT COUNT(*) FROM 该表`——表不存在会在编译期报 208。一律 `OBJECT_ID` / `COL_LENGTH` + `ISNULL(CAST(... AS varchar),'MISSING')`。
> Edition 表叫 **`SaasEditions`**(不是 `AbpEditions`)。
推薦人脚本判断:
```bash
docker exec aml-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -h -1 -W \
-Q "SELECT COUNT(*) FROM AMLPortal_AgentUserPlans WHERE IsDeleted=0;"
```
> 0 才考虑 `seed-agent-plans.sql`;非 0(新库自带真实关联)**跳过**——该脚本会先软删 agent_A 的现有关联,且它预期的 agentTest2/3 已不存在(RAISERROR 中止)。
### 5. 重建 host 镜像
```bash
docker compose -f docker-compose.local.yml build httpapi-host
```
- 全量重建约 10–20 分钟(restore 大量 ABP 商业包 + 编译 ~40 个项目)。建议 `run_in_background` 跑,**别用 `| tail` 管道**——那会把失败的 exit code 吃掉,看起来像成功。
- 常见失败 `error NU1301: Unable to load the service index for source https://www.myget.org/...`:sdk 镜像 CA 过期。`Dockerfile.local` 的 `build` 阶段已加 `ca-certificates` 刷新修好;若再复现,同样手法排查(`docker run --rm mcr.microsoft.com/dotnet/sdk:6.0 bash -lc 'curl -sS https://www.myget.org/F/blazorise/api/v3/index.json'`)。
- 编译 OOM(exit 137):确认 Docker 内存 ≥16 GB。
### 6. 起 host(绕过 migrator)
```bash
docker compose -f docker-compose.local.yml up -d --no-build --no-deps httpapi-host
```
### 7. 冒烟验证
```bash
docker compose -f docker-compose.local.yml ps
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:44331/swagger/index.html
# 门户端点是 POST,GET 返 405
curl -s -X POST http://localhost:44331/api/amlPortal/Order/portal/GetEditionList -H 'Content-Type: application/json' -d '{}' | head -c 300
curl -s -X POST http://localhost:44331/api/amlPortal/plan/portal/GetPlanList -H 'Content-Type: application/json' -d '{}' | head -c 300
```
期望 `{"code":0,...}`。
- **一律 `http://localhost:44331`**,`https` 会报 `tlsv1 alert protocol version` / `000`,看着像挂了其实只是协议不对。
- host 起不来先看日志:Release 构建只写文件,`docker logs` 是空的 → `docker cp aml-httpapi-host:/app/Logs ./Logs`。
- exit 214 / `ABP-LIC-0008` = ABP 授权令牌缺失,见 `readme.md` 第 3 节。
## 报告给用户
刷新完说清楚四件事:拉到的 commit、用的 .bak 文件名、库里的数量(表 / Plans / Editions / Tenants)、以及冒烟结果。