网站逆向重构 · 逆出API契约重建前端

SKILL开发
开发 未标注权限 许可证不明确
安装这个技能

逆向已上线的 Web 系统(Vue/React/Angular 等 SPA),逆出真实 API 契约后重建前端并对接真后端,同时按需求改进交互体验。核心是四步闭环——批量解包产物拿接口清单、契约探测定 method/参数位置/返回结构、单变量对照实验定位不给原因的报错、Playwright 抓真实流量兜底。内置四个实战检验过的脚本(unpack_spa / probe_contract / ab_probe / login_diag),沉淀了 Shiro 会话、加密 Hex-vs-Base64、info 结构不统一、字段语义反转(auditing)等高频坑。当用户说"这个网站/系统太难用了想改造""复刻这个前端""扒下它的接口自己实现一套""照着它做一个自己的""前端重写但要能连真实后端""逆向它的接口"时使用。不适用:只想把网站抓成静态离线副本(用 website-offline-mirror / any-site-to-offline)、只想看页面正文(用 WebFetch)、要爬公开数据做分析(用 requests+解析)。

一句话选型结论

按资料字段整理

网站逆向重构 · 逆出API契约重建前端是开发类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类开发 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_0c7f1934/spa-contract-rebuild
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

开发
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_0c7f1934仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量24.8 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
写代码与改 bug

按需求补代码、定位并修掉报错。

读仓库与生成改动

让模型读懂项目结构,再给出可落地的改动。

跑测试与排查故障

按现象排查问题,补测试用例并复跑。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在开发场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
网站逆向重构 · 逆出API契约重建前端当前 未标注权限 未标注 未标注 未标注 有
技能审查员 浏览器控制 ClawHub 市场 877.9 万 OpenClaw 有
Vercel React 最佳实践 浏览器控制 GitHub 仓库 849.1 万 Codex、Claude、Cursor 有
GitHub 代码协作 联网访问 ClawHub 市场 763.3 万 OpenClaw 有
网页设计指南 联网访问 GitHub 仓库 668.6 万 Codex、Claude、Cursor 有
本体论 只读 ClawHub 市场 663.7 万 OpenClaw 有

SKILL.md 全文

24.8 KB
模型实际读到的说明书原文,来自技能仓库

网站逆向重构 · 复刻优化网页页面

把一个已上线的 Web 系统逆向出真实契约,重建一套能连真后端、并按需求改过交互的前端。

与已有技能的边界

技能 目的 产物
website-offline-mirror / any-site-to-offline 把网站抓下来存本地 静态副本,断网可看,源码原样
本技能 把系统逆向出来重做 活的前端,连真后端,可改交互

判据:用户想「离线看」→ 用 mirror;想「自己维护、能提交数据、能改好用」→ 用本技能。


铁律(违反其中任何一条都会浪费数小时)

1. 不测完不交付,每个结论必须能追溯到出处

「接口返回 200」不等于「契约摸对了」。必须问:返回结构是数组还是 {list,total}?参数走 query 还是 body?哪些字段真必填?

2. 后端报错不给原因时,做单变量对照实验,不要猜

code:202 + info:null 是最恶心的错误形态——它什么都告诉你。不要逐个猜字段,要用 ab_probe.js 固定其他条件、一次只改一个变量,看哪个改动让它从失败变成功。

> 实战:某个 add 接口的 202 查了 3 小时(先怀疑加密、再怀疑权限、又卡在 taskId)。做完对照实验 10 分钟定位 —— 根因是漏传 auditing: 0,导致数据被后端默认判成「已审核」,且已审核记录不可删除,产生了死数据。

3. 抓不到的地方,用 Playwright 抓真实流量

穷举 header / 参数组合猜不出来时,直接让浏览器自己发一次给你看。请求头、Cookie、body 格式一次全拿到。

const { chromium } = require('playwright-core');
const browser = await chromium.launch({
  executablePath: 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', // 免下载浏览器
  headless: true,
});
page.on('request', r => console.log(r.method(), decodeURIComponent(r.url())));
page.on('response', async r => console.log(await r.text()));

4. 登录接口有失败次数限制,测试前先算次数预算

反复试密码会锁账户(实测 5 次锁定 5 分钟,且计数持久化不按会话重置)。

  • 优先用免登录接口验证大部分逻辑,只在最后用真实登录验一次
  • 一次登录把要验的全验完,不要每修一个 bug 就重登一次
  • 拿不准密码时,反解后端存储的密文拿权威值(见下方坑 1)

5. 逆向要区分「契约」和「缺陷」

业务契约(接口/字段/加密算法)必须一模一样;前端的校验 bug、UI 缺陷要修正,不要照抄。

> 实战:原站登录正则漏了 . 且缺 $,导致 <姓名拼音>@<公司域名> 这类带点邮箱在原站都登不进。照抄等于把 bug 复制一份。

6. 写接口永远不要盲信 200,必须回读确认落库

code === 200 只说明「请求被接受」,不等于「数据写对了、写得进、写得进你能查到的那张表」。

三个高频陷阱(详见坑 9):
- userId 为 null → 后端照收不误写无归属记录 → 原站按 userId 查永远查不到
- 接口是 upsert → 重复提交不新增而是累加 → 用户以为写了新记录,其实并进了旧的
- 读副本延迟 → 回读校验必须轮询,只查一次会把成功提交误报成失败

// ❌ 前端只信 200 → 假成功
if (res.code === 200) { showSuccess(); }

// ✅ 回读确认才报成功
if (res.code === 200) {
  await reload()
  if (await waitConfirmed(date, payload)) { clearDraft(); showSuccess() }
  else { keepDraft(); showError('后端未确认写入,草稿已保留') }
}

加防御性校验前,先想清楚它会不会误报——我自己第一版只查一次回读,
结果把所有成功提交都判成失败,比原 bug 更糟。

7. 前端状态恢复是独立环节,登录时有的不代表刷新后还在

Pinia/Vuex 里凡是「登录时拉一次」的数据(用户信息、权限、字典),
必须在 App 启动时也拉一次。否则「登录后用着好好的,刷新一下就提交失败」这类玄学问题。

自检:搜一遍 store 的 actions,找出只在 login() 里被调用的方法。


工作流

Step 1 · 批量解包,拿接口清单

node scripts/unpack_spa.js http://目标站点:端口 ./recon

产出 recon/index.html、recon/appConfigs.js(运行时配置,常含 API 基址)、recon/js/*.js(全部懒加载 chunk)。

现代前端主包通常只有几十 KB,所有页面和接口常量都在懒加载 chunk 里,不解包等于什么都看不到。

手工检索入口:

grep -oE '"/api/[^"]+"' recon/js/*.js | sort -u                    # 全部接口
grep -oE 'const \w+=\{login:"[^"]+"' recon/js/*.js                # API 常量表(含 method 线索)
grep -o 'import("./[^"]+\.js")' recon/js/index-*.js               # 懒加载入口

Step 2 · 探明认证方式

从常量表里找到登录接口,逆出加密逻辑(密钥常是硬编码常量,藏在登录页 chunk)。

node scripts/login_diag.js --key <密钥> --cipher <后端存储的密文> [--pwd "你手打的密码"]

四层诊断:反解权威密码 → 逐字节对比你手打的 → 跨库加密自检 →(可选)远程验证。默认不联网。

Step 3 · 探测接口契约

node scripts/probe_contract.js --base http://站点 --auth <token> \
  [--cookie "TMS_SESSION_ID=xxx; rememberMe=yyy"] [--only "/api/a,/api/b"]

输出每个接口的:HTTP 状态 / 错误语义 / 返回结构 / 是否真需要鉴权(自动做有无 token 对照)。

Step 4 · 重建前端 + 改造交互

按探测结果写 API 层,然后重写页面。改造前先问清楚:用户当前最痛的是什么。

Step 5 · 端到端验证(必须真跑一次)

用 Playwright 登录真实站点,走完关键路径,确认数据真的落库。


高频坑速查

坑 1 · 密码手打错,且肉眼看不出

两个字符串可能只差一个字母顺序:

错误:<拼音A>-<顺序写错>  →  6a 69 6e 71 75 69 75 40 31 32 33  (11 字节)
正确:<拼音B>    →  6a 69 6e 71 69 75 40 31 32 33  (10 字节)

肉眼完全一样,但登录就是失败。解法:拿后端 user/getById 返回的 password 字段(存储密文),用同一密钥反解出权威明文,别靠肉眼比。

坑 2 · 加密编码:Hex 还是 Base64

// crypto-js:.ciphertext 是 WordArray,toString() 未指定 formatter 时默认输出 Hex
CryptoJS.AES.encrypt(k, key, {mode:ECB, padding:Pkcs7}).ciphertext.toString()  // Hex
// 误用 Base64 发送 → 后端解密得到乱码 → 报「密码错误」

⚠️ user/getById 返回的 password 字段是后端二次加密后的存储值,不等于前端加密结果,别拿它当基准。

坑 3 · 后端靠会话(Shiro),不靠 token 头

实测特征:登录响应下发 TMS_SESSION_ID + rememberMe 两个 Cookie;
project/list 只发 Authorization 头一律返回 code:500 请登录,
但 dailyReport/list 却正常——因为后者免登录就能读,不能拿它当鉴权通过的证据。

浏览器同源请求自动带 Cookie,前端代码不需处理;但 Postman / curl / Node fetch 必须自己维护 cookie jar 并回传,否则会误判成"鉴权方式不对"。

坑 4 · info 返回结构不统一(页面空白的元凶)

info 是数组 info 是 {list,total}
department/list、projectTask/listByProjectId dailyReport/list、project/list、user/list

用错的表现是:接口 200 有数据,但页面一片空白。因为 res.info?.list 取到 undefined。

在 API 层统一归一化,页面永远拿 res.info.list:

async function listRequest(config) {
  const res = await request(config);
  if (res && res.code === 200) {
    res.info = Array.isArray(res.info)
      ? { list: res.info, total: res.info.length }
      : res.info;
  }
  return res;
}

坑 5 · 字段语义反转,漏传就出事

auditing(0=待审核 / 1=已审核)漏传 → 后端按 null 处理 → 新提交直接变已审核 →
而已审核记录不可修改删除 → 产生既不能审也不能删的死数据。

抄字段名容易,抄字段的赋值才关键。去原站代码里看它提交时实际带了什么。

坑 6 · Git Bash 会把 /api/xxx 转成 Windows 路径

$ node probe.js --only "/api/user/list"
→ 实际收到 D:/workbudy/WorkBuddy/resources/vendor/PortableGit/api/user/list

脚本里已内置 unMsysPath() 还原(从最后一个 /api/ 截断)。自己写脚本时注意。

坑 7 · 前端内部状态结构 ≠ 接口接收结构

原站前端内部用 { workDate, projects: [...] } 嵌套,但后端 add 收的是扁平数组:

// ✅ 接口要的
[{ workDate, userId, auditing:0, projectId, taskId, workDuration, workOvertime, workMatters }]
// ❌ 前端内部用的
[{ workDate, projects: [{...}] }]   → 202

别照抄前端的数据结构当成接口契约。用 ab_probe.js --shapes array,object,flatten 试。

坑 8 · 外键字段必须真实存在

taskId: ''(空字符串)→ 后端 202;taskId: 234(真实 id)→ 200。
前端没让用户选时,要自动取一个默认值,否则整批提交静默失败。

🔴 坑 9 · 「接口返回 200」不等于「业务成功」—— 假成功三连坑

> 来源:<甲方>工时系统,用户报「弹了提交成功,原站却查不到」,排查出 5 个叠加原因。

这是复刻写接口时最高频、也最难自证的一类 bug。前端永远不能盲信 200。

(a) 前端 store 的状态恢复是独立环节

user.js 之类的 store 里,fetchUserInfo() 常常只在 login() 里被调用。
于是:走登录页登录 → 一切正常;刷新页面 / 直接访问路由 / 从书签进入 → userId 是 null。

后果链条最致命:userId: null 提交 → 后端照收不误写入无归属记录 → 接口返回 200 →
前端弹「提交成功」→ 原站按 userId=X 查询永远查不到。

// ✅ App.vue 启动时恢复(不能只在 login 里做)
onMounted(async () =&gt; {
  const token = tokenStore.get()
  if (!token || userStore.userInfos.id) return
  const me = await userStore.fetchUserInfo(token, true)
  if (!me) tokenStore.clear()   // 会话失效
})
// ✅ 提交前再拦一道:拿不到 userId 宁可不提交
if (!userStore.userInfos.id) return { ok: false, reason: &#x27;未获取到登录用户&#x27; }

顺带检查 store 里的缓存判断有没有写错维度,典型错误:
String(cached.id) === String(token) —— 拿用户 id 和登录 token 比,两个不同维度的东西。

(b) 写接口可能是 upsert,重复提交不新增而是累加

实测:user + date + 外键A + 外键B 已存在时,后端不插入新行,
而是累加数值字段、拼接文本字段(&quot;A&quot;/1h → &quot;A,A&quot;/2h),仍返回 200。

提交第几次 返回 库里实际
第 1 次(1h, "A") 200 id=X, 1h, &quot;A&quot;
第 2 次(1h, "A") 200 仍是 id=X,2h, &quot;A,A&quot;

用户以为新增了一条 → 原站看还是一条 → 「没提交成功」。
修复:重复提交前弹确认框,明说这是累加更新。

(c) 读副本延迟:回读校验必须轮询,不能只查一次

抓包时间线(实测):add 返回 200 → 紧接着第 1 次 list 返回提交前的旧数据 →
第 2 次才拿到新记录(total: 6 → 7)。

// ✅ 轮询确认:只有回读到记录才清草稿、才弹成功
async function waitConfirmed(date, payload, retries = 4, gap = 1200) {
  for (let i = 0; i &lt;= retries; i++) {
    if (confirmed(date, payload)) return true
    if (i === retries) break
    await sleep(gap)
    await reload()
  }
  return confirmed(date, payload)
}

> ⚠️ 自己踩过的坑:第一版只查一次就下结论,结果加上校验后所有提交都误报「后端未确认写入」,
> 比原 bug 更严重。加防御性校验前,先想清楚它会不会误报。

(d) 查询参数不一定稳定生效

同一 token 下,有时 userId 过滤生效(返回我的 7 条),有时被忽略返回全量 51 条(会话刚建立时)。
不自己按 record.userId 再兜一层,就会把同事的记录混进「我的列表」,校验结论完全错乱。

(e) 原站与复刻站的查询口径可能不同

抓原站真实流量发现,原站用单日查询 { workDate, userId },
复刻站用月查询 { yearMonth, userId }。两者应一致,但竞态时会错位成
「原站看得到、复刻站回显不出来」。提交后按两种口径都查一次补漏。

🔴 坑 10 · 自写的「限流 / 并发控制」可能根本没生效

// ❌ 看似限了并发,实际每轮都发全部请求
for (let i = 0; i &lt; targets.length; i += CONCURRENCY) {
  await Promise.all(targets.map(...))   // 遍历的是整个 targets,不是当前批次
}

// ✅
const batch = targets.slice(i, i + CONCURRENCY)
await Promise.all(batch.map(...))

症状极其隐蔽:浏览器对同域并发有上限,超出的请求被排队长期不返回
(实测某接口响应体为 null 且挂起),表现是「偶发点了没反应 / 按钮一直转圈」,
极难归因——因为后端单测该接口完全正常(999ms 返回)。

顺带:凡是用 Promise.all 依赖「一定会返回」的接口,都加超时兜底:

const tasks = await Promise.race([
  taskApi(pid),
  new Promise((r) =&gt; setTimeout(() =&gt; r(null), 12000)),
])
if (tasks === null) return []   // 超时:不写缓存,让下次还能重试

💡 方法 · 用「定义了却没人调用」挖功能缺口

复刻完不是终点。主动 grep 一下接口定义文件里哪些方法从没被页面调用过,
这些就是「后端有能力、但前端没做」的功能缺口,成本极低:

# 列出 api 定义的所有方法名,逐个查是否在 views/ 里出现过
grep -oP &quot;^\s+\w+:\s*\(&quot; src/api/index.js | tr -d &#x27; :(&#x27; \
  | while read m; do
      n=$(grep -rl &quot;$m&quot; src/views/ 2&gt;/dev/null | wc -l)
      [ &quot;$n&quot; -eq 0 ] &amp;&amp; echo &quot;⚠️ 定义了但没人用: $m&quot;
    done

实战:某工时系统的 dailyReport/update 定义了但 0 处调用,
用户反馈「填错了只能删掉重填」——顺手实测确认接口可用(改 aud0 生效、aud1 拒绝),
就补出了「已提交记录可编辑」这个高价值功能。

关键动作是先实测再实现:接口定义存在 ≠ 接口可用 ≠ 权限允许。
实测只需提交一次 + 回查确认,成本几分钟,但能避免做完才发现被拒。

💡 方法 · 「提示太晚」是独立的一类缺陷

很多 UX 问题不是「没提示」,而是提示的时机错了——用户已经白填了一遍才被告知。

典型:某写接口对重复提交是 upsert(累加而非新增)。
如果在「提交时」才弹警告,用户已经把内容填完了,此时只能取消重来。
正确做法是把警告前移到动作发生的时刻:

// ❌ 只在提交时提示 —— 太晚
async function submitOne(date) { /* 这里才检查重复 */ }

// ✅ 复制来的那一刻就预判并提示
async function copyPrevDay(date) {
  const clash = src.filter((r) =&gt;
    already.some((a) =&gt; String(a.projectId) === String(r.projectId)
                      &amp;&amp; String(a.taskId) === String(r.taskId)))
  if (clash.length) { /* 逐条列出冲突项:项目 X 3h(已提交那条是 2h) */ }
}

自查方法:把每个 ElMessage 调用点问一遍「这个提示应该在更早的动作里出现吗?」

💡 方法 · 页面数字要能和后端汇总接口对账

前后端各算一遍合计时,很容易出现「页面上是 109h,原站只有 105h」,
用户会怀疑数据丢了,而且很难自证清白。

做法:让页面的口径显式拆开,和后端接口逐项对齐。

// 后端 getWorkTimeNum 只统计已提交
const monthSubmitted = computed(() =&gt; round2(days.reduce((s, d) =&gt; s + d.subTotal, 0)))
const monthDraft     = computed(() =&gt; round2(days.reduce((s, d) =&gt; s + d.draftTotal, 0)))
// 分别显示,而不是只给一个「合计」

同时定期对账:本地求和 vs 后端汇总接口返回值,差值必须是 0。

🔴 坑 11 · 用户说「太素、看不清」时,先查结构而不是先加深颜色

> 来源:工时系统反馈「页面太素了,不容易看清,能不能对比更强烈,但不要太花哨」。

我第一反应是加深颜色和加重边框。做完后才意识到真正的问题不是颜色淡,是缺结构引导——
表格有 6 列内容却没有列头,用户根本不知道哪列是工时、哪列是加班。
「看不清」往往是「不知道该看哪」的另一说法。

优先级排序(工具型界面,从上到下):

优先级 手段 说明
1 加列头 / 结构分组 表格没列头 = 用户不知道每列是什么,这是根因
2 字重分级 标题 700 / 关键数字 700 / 正文 400,靠字重分层最稳且零噪音
3 关键信息色块化 「合计 8h」给底色、状态给实心标签,不靠眼睛在白底上找
4 边框加重 淡边框在投影屏和笔记本上几乎看不见

别做的事(用户说了"这是工具不是展示页"):
大面积渐变、阴影花边、动画、彩色背景、装饰性图标。

&lt;!-- ❌ Element Plus 的 plain 标签几乎是白的,等于没有状态 --&gt;
&lt;el-tag effect=&quot;plain&quot;&gt;待审核&lt;/el-tag&gt;
&lt;!-- ✅ --&gt;
&lt;el-tag effect=&quot;dark&quot;&gt;待审核&lt;/el-tag&gt;

🎯 把「够不够清楚」变成可测量的

「我觉得清楚了」是主观的,对比度是算得出来的。用 Playwright 在浏览器里实测 WCAG 比值,
比反复凭感觉调 CSS 快得多,而且能防止下次改版又淡回去:

// 注入页面直接跑,采样关键文本
const lum = (c) =&gt; {
  const [r, g, b] = c.match(/\d+/g).map(Number).map((v) =&gt; {
    const s = v / 255
    return s &lt;= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4)
  })
  return 0.2126 * r + 0.7152 * g + 0.0722 * b
}
const ratio = (fg, bg) =&gt; {
  const l1 = lum(fg), l2 = lum(bg)
  return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05)
}
// 背景要往上找,直到找到不透明的
let p = el
while (p) {
  const b = getComputedStyle(p).backgroundColor
  if (b &amp;&amp; b !== &#x27;rgba(0, 0, 0, 0)&#x27; &amp;&amp; b !== &#x27;transparent&#x27;) { bg = b; break }
  p = p.parentElement
}

验收线:正文 ≥ 4.5:1(AA),大字号/次要文字 ≥ 3:1。
实测把 EP 默认的 --el-text-color-regular: #606266、--el-border-color: #dcdfe6
压深到 #374151 / #c9ced6 后,9 处关键文本从 3.x 拉到 4.6~17.7,全数达标。

改完一定要跑一遍原有 E2E——视觉改版容易把选择器可见性搞坏
(例:列表变长后 waitForSelector 等到的是隐藏项,加 :visible 才稳)。


脚本

脚本 用途 联网
scripts/unpack_spa.js 批量下载打包产物 + 提取接口清单 是
scripts/login_diag.js 登录失败四层诊断(默认不联网) 默认否
scripts/probe_contract.js 探测接口 method/结构/鉴权要求 是
scripts/ab_probe.js 单变量对照实验 是

ab_probe.js 用法

# 字段开关实验:找出哪个字段一加就成功
node ab_probe.js --base http://x --path /api/xxx --auth 103 \
  --template &#x27;{&quot;workDate&quot;:&quot;2026-10-07&quot;,&quot;userId&quot;:103,&quot;projectId&quot;:56}&#x27; \
  --toggle &#x27;{&quot;auditing&quot;:[0,1],&quot;taskId&quot;:[234,270]}&#x27;

# 结构实验:接口到底收数组还是对象
node ab_probe.js --base http://x --path /api/xxx --auth 103 \
  --template &#x27;{...}&#x27; --shapes array,object,flatten

交付检查清单

  • [ ] 每个接口的 method / 参数位置(query vs body)/ 返回结构都实测过,不是照抄前端代码
  • [ ] 登录能跑通,且没把账户锁了
  • [ ] 写接口验证过数据真的落库(再查一次 list 确认),且轮询确认过(不是只查一次)
  • [ ] 返回结构差异已归一化,不会出现「接口通但页面空」
  • [ ] 布尔/状态字段的语义已确认(0/1 哪个是哪个),且提交时显式传值
  • [ ] 自动化测试跑得通(不只是 npm run build 过)
  • [ ] 刷新页面后功能仍正常(store 状态恢复、userId 等关键字段非空)
  • [ ] 在原站交叉验证过:复刻站提交的记录,原站能查到;反之亦然
  • [ ] 查询接口返回的列表自己再按归属过滤一遍,不假设后端的过滤参数稳定生效
  • [ ] 所有 Promise.all 依赖的接口都有超时兜底,并发控制验证过真的生效(不是写完就信)
  • [ ] 表格类界面有列头,关键数字有权重或色块,能一眼定位
  • [ ] 关键文本实测过 WCAG 对比度 ≥ 4.5:1(不是凭感觉),工具型界面不堆装饰
  • [ ] 视觉改版后原有 E2E 全部重跑(选择器可见性最容易受影响)
  • [ ] grep 一遍接口定义:没有「定义了却没人调用」的方法(= 未开发的功能缺口)
  • [ ] 每个警告提示的时机正确(在动作发生时就提示,不是事后)
  • [ ] 页面合计数字能与后端汇总接口对账,口径已显式拆开

  • [ ] 改造点是真解决了用户痛点,不是换皮

  • [ ] 测试写入生产的数据已清理,且清理不了的已列明(谁、哪条、为什么)
  • [ ] 踩过的坑写进交付文档(尤其是不给原因的报错)

反模式

别做 改做
照抄原站前端的校验 bug 契约照抄,缺陷修正
把免登录接口通当成鉴权 OK 用真正需要鉴权的接口验证
报错就猜字段 单变量对照实验
反复试密码 反解存储密文拿权威值
猜不出就手动穷举 header Playwright 抓真实流量
npm run build 通过就交付 端到端跑一次,确认落库
在生产库写测试数据不清理 清理,或列明需管理员处理
code === 200 就弹「提交成功」 回读确认落库才报成功;轮询以避开读副本延迟
「登录时拉到用户信息」就以为刷新后还在 App 启动时也拉一次;提交前校验 userId 非空
自写的并发/限流写完就信 验证真的生效;给 Promise.all 加超时兜底
在外部猜组件内部状态 挂临时调试钩子直接读 ref,验完即删
用户说「看不清」就一味加深颜色 先查结构(有没有列头/分组),再调对比度
「我觉得够清楚了」 用 Playwright 算 WCAG 对比度,≥4.5:1 才算过
el-tag effect=&quot;plain&quot; 当状态用 plain 几乎是白的,用 dark 或自定义色块

参考资料

  • references/实战案例-工时填报系统.md —— 完整实战:契约明细、踩坑时间线、
    以及「弹了提交成功原站却查不到」的 5 原因叠加排查全过程
  • references/排查决策树.md —— 遇到症状按图索骥

常见问题

5 问
答案由本站依据条目字段整理
网站逆向重构 · 逆出API契约重建前端怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_0c7f1934/spa-contract-rebuild。安装方式为未标注。装完刷新技能列表或重开客户端。

网站逆向重构 · 逆出API契约重建前端支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

网站逆向重构 · 逆出API契约重建前端权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

网站逆向重构 · 逆出API契约重建前端收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

网站逆向重构 · 逆出API契约重建前端用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。