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

15 KiB
Raw Blame History

name description
aml-local-refresh Refresh the AML local dev stack - git pull AML_Backend (and AML_Frontend), 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. Also covers running AML_Frontend against that local backend (npm run start:local, login admin, on the Mac or on FG-W11-ICON-DEV via a reverse SSH tunnel). Use when asked to 更新/还原/重建/重启本地后端环境, "拉最新代码并还原数据库", "restore the latest bak", 前端连本地后端/本地登录, 在 Windows 那台跑 AML 前端, 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 只讲刷新流程和必须踩对的顺序。

所有命令的工作目录都是:

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. 前置检查

docker info >/dev/null 2>&1 || open -a Docker    # 没起就拉起 Docker Desktop,等 docker info 通

Docker 内存需 ≥16 GB(mssql 在 Apple Silicon 上是 amd64 模拟,不够会 exit 139)。

1. 拉最新后端代码

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 步核对。

1b. 同步拉前端代码(AML_Frontend)

后端功能(批量操作、电话区号之类)通常前后端成对提交,只更后端会让前端对不上。前端不在 docker 栈里,这一步只拉代码:

cd /Users/dev/projects/AML/AML_Frontend && git status -sb | head -5
OLD=$(git rev-parse HEAD) && git pull --ff-only
git log -1 --format='%h %s'
git diff --name-only $OLD HEAD -- package.json     # 有输出 = 依赖变了
  • 分支是 main(不是 dev)。origin/dev 的内容平时会合进 main;拿不准就跑 git rev-list --count origin/main..origin/dev,是 0 就说明 main 已经是最新。工作区有改动同样先问用户。

  • package-lock.json 被 gitignore 了,所以只能看 package.json 有没有变。

  • 依赖变了就装一次。系统 Node 是 v24,Angular 11 不支持,所以前端一律通过 fnm 用 Node 14(x64,跑在 Rosetta 上):AML_Frontend/.node-version 写的是 14(只在本地,已加进 .git/info/exclude)。非交互 shell 里不会自动切版本,要显式写:

    fnm exec --using 14 -- npm install --legacy-peer-deps
    fnm exec --using 14 -- npm run build:dev      # 可选验证,约 2 分钟
    

    watchpack-chokidar2/fsevents 的 gyp ERR! 是可选依赖编译失败,npm 会跳过,exit 0 就算成功。

2. 起基础设施 + 停 host

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

别写死文件名,用户会不定期换新的(文件名带日期戳,旧的会删):

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

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'...') 里):

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)。

推薦人脚本判断:

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 镜像

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)

docker compose -f docker-compose.local.yml up -d --no-build --no-deps httpapi-host

7. 冒烟验证

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 节。

前端联调(AML_Frontend 连本地后端)

登录账号:宿主层管理员 admin,租户留空(Host)。

  • 还原 .bak 的库(本 skill 的正常流程):密码 1qaz@WSX。
  • 用 db-migrator 种子新建的库:密码 1q2w3E*(ABP 默认,readme 里也记了这个)。

库里还有几十个租户下也叫 admin 的账号,那些是真实客户的,别拿来试。

关键约束:issuer 必须是 http://localhost:44331

  • 后端的 AuthServer__Authority / App__SelfUrl 固定是 http://localhost:44331。IdentityServer 的 issuer 跟着请求的主机名走:用 IP 访问时,发现文档里的 issuer 会变成 http://192.168.x.x:44331,签出来的 token 会被 API 拒掉(401)。所以无论前端跑在哪台机器上,浏览器都必须通过 localhost:44331 访问后端。
  • 前端默认的 environment.ts 写的是 https://localhost:44331,而本地 host 只提供 http,所以用默认配置连本地后端是登录不了的。要用 npm run start:local:angular.json 里的 local 配置会把它换成 src/environments/environment.local.ts,全部指向 http。
  • 后端这边已经就绪,不用改:CORS 放行了 http://localhost:4200,FX_App client 允许 password 授权、不需要 secret、回调地址里有 http://localhost:4200。前端的 loginMode = 'password' 用的就是密码模式。
  • 排查顺序:先看 http://localhost:44331/.well-known/openid-configuration 里的 issuer,再用 Origin: http://localhost:4200 对 /connect/token 发一个 OPTIONS 预检(期望 204),最后看浏览器 Network 里 /connect/token 的响应。

在 Mac 上跑

cd /Users/dev/projects/AML/AML_Frontend
fnm exec --using 14 -- npm run start:local     # 编译约 2 分钟,浏览器打开 http://localhost:4200

验证配置是否生效:curl -s http://localhost:4200/main.js | grep -o "issuer: *'[^']*'",应该输出 http://localhost:44331。

在 FG-W11-ICON-DEV(Windows)上跑

用户有些测试要在那台机器上做。操作远程机器走 remote-dev-box skill(先 boxes.sh locate -H FG-W11-ICON-DEV,IP 会变)。现状:

项 值
代码 C:/Projects/AML_Frontend,不是 git 仓库,是从 Mac 推过去的快照
Node 便携版 C:/Tools/node-v14.21.3-win-x64(系统里的 Node 22 不动)
启动脚本 C:/Tools/start-aml-frontend.cmd(设好了 PATH 和 4 GB 堆上限,用户双击就能起)
日志 C:/Tools/aml-frontend-serve.log

1. 后端:由 Mac 起反向隧道,让 Windows 上的 localhost:44331 通到 Mac 的 docker host:

ssh -fNT -o ControlPath=none -o ExitOnForwardFailure=yes -R 44331:127.0.0.1:44331 FG-W11-ICON-DEV
  • 这条 auto mode 不让 Claude 跑,要请用户自己在提示符里用 ! 运行,并提醒他们整行粘贴:之前在 -R 后面断行,结果报了 option requires an argument -- R。
  • ControlPath=none 不能省:~/.ssh/config 里给这台机器开了 ControlMaster auto + ControlPersist 10m,不关的话,转发会挂在 run.sh 的复用连接上,10 分钟就断。
  • Mac 睡眠、断网或重启后隧道会断,重跑即可。关闭:pkill -f "R 44331"。
  • 验证(在 Windows 上):Invoke-WebRequest -UseBasicParsing http://localhost:44331/.well-known/openid-configuration,issuer 应该是 http://localhost:44331。

2. 更新代码(Mac 上的前端有新提交,或 start:local 相关文件改了时):把 git 跟踪的文件和未跟踪的新文件一起打包推过去,不带 node_modules:

cd /Users/dev/projects/AML/AML_Frontend
git ls-files -co --exclude-standard -z | COPYFILE_DISABLE=1 tar --null -T - -czf "$SCRATCH/aml-frontend-src.tgz"
$S/cp.sh push -H FG-W11-ICON-DEV "$SCRATCH/aml-frontend-src.tgz" 'C:/Tools/aml-frontend-src.tgz'
# 远端:tar -xzf C:/Tools/aml-frontend-src.tgz -C C:/Projects/AML_Frontend ,然后删掉 tgz

覆盖解压不会删除文件。如果上游删了文件,要先清掉旧目录(node_modules 可以留着)。

3. 装依赖(首次安装,或 package.json 变了):

$env:Path = 'C:/Tools/node-v14.21.3-win-x64;' + $env:Path
Set-Location C:/Projects/AML_Frontend
npm install --legacy-peer-deps --no-audit --no-fund --prefer-offline --maxsockets=6 --fetch-retries=5 *> C:/Tools/aml-npm-install.log
  • 要用 run_in_background 跑,首次安装约 17 分钟。
  • npm 6 在这台机器上会假死:进程还在、和 registry 的连接也还在,但 npm 缓存大小、CPU 时间、日志行数几分钟都不动。所以跑的过程中要隔几分钟查一次这三样,卡住就 Stop-Process 掉重跑。已经下载的包都在缓存里,重跑会接着用。看到 node_modules/.staging 里的目录数在涨,说明正在解压。

4. 启动:SSH 起的进程在 session 0,用户看不到,所以要用一次性计划任务在桌面会话里起:

schtasks /Create /TN TmpAmlFrontend /TR '"C:\Tools\start-aml-frontend.cmd"' /SC ONCE /ST 23:59 /IT /F
schtasks /Run    /TN TmpAmlFrontend
Start-Sleep 8; schtasks /Delete /TN TmpAmlFrontend /F
  • NODE_OPTIONS=--max_old_space_size=4096 必须设(已经写在启动脚本里)。Node 14 默认堆上限约 2 GB,首次编译完会紧接着再构建一轮,这时会 JavaScript heap out of memory,ng serve 直接退出。
  • 首次编译约 4–5 分钟。dev server 构建期间会挂住请求,Invoke-WebRequest 看起来像超时,不是端口问题。日志最后一行如果还是 Generating browser application bundles...,就继续等。
  • 验证:日志里有 Compiled successfully,且之后没有 FATAL / npm ERR;http://127.0.0.1:4200/ 返回 200;main.js 里的 issuer 是 http://localhost:44331。起来一分钟后再查一次,前面那次 OOM 就是在第二轮构建时才崩的。
  • 用户在那台机器的浏览器里打开 http://localhost:4200 登录。关掉桌面上那个「AML Frontend (start:local)」窗口就停了。

报告给用户

刷新完说清楚五件事:后端拉到的 commit、前端拉到的 commit(及 package.json 是否变了)、用的 .bak 文件名、库里的数量(表 / Plans / Editions / Tenants)、以及冒烟结果。