dms-delete-probe.mjs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /**
  2. * 探测 DMS 删除内容的**正确姿势**(OpenAPI 与 Apifox 对 delContentById 的参数都未文档化)。
  3. *
  4. * 背景:Step-0 用 `DELETE /content/delContentById?id=<uuid>` 得 code=-1,行未删。
  5. * 线索:OpenAPI 里另有 `POST /content/updateAudit`(修改内容状态),
  6. * 而 DMS 的 states 语义是 0草稿 1待审 2审完 3发布 4销毁 5退回
  7. * —— “删除”很可能是把 state 改成 4,而不是物理删除。
  8. *
  9. * 本脚本会:造测试行 → 逐一试候选形态 → 校验行是否真的消失 → 清理残留。
  10. * 用法:node harness/tools/dms-delete-probe.mjs [DMS_TOKEN] # 不传则从 env / .dms-token 取
  11. */
  12. import { readDmsToken, DMS_TOKEN_FILE } from './dms-token.mjs';
  13. const HOST = process.env.DMS_HOST || '121.43.55.7:10081';
  14. const BASE = `http://${HOST}/dms`;
  15. const TOKEN = readDmsToken(process.argv[2]);
  16. const COL = '1887';
  17. const MODEL = '2034';
  18. const TAG = `deltest_${Date.now().toString(36)}`;
  19. if (!TOKEN) {
  20. console.error('用法:node harness/tools/dms-delete-probe.mjs <DMS_TOKEN>');
  21. process.exit(2);
  22. }
  23. const call = async (method, path, form, jsonBody) => {
  24. const init = { method, headers: { token: TOKEN } };
  25. if (jsonBody !== undefined) {
  26. init.headers['Content-Type'] = 'application/json';
  27. init.body = JSON.stringify(jsonBody);
  28. } else if (form) {
  29. init.headers['Content-Type'] = 'application/x-www-form-urlencoded';
  30. init.body = new URLSearchParams(form).toString();
  31. }
  32. try {
  33. const res = await fetch(`${BASE}${path}`, init);
  34. const text = await res.text();
  35. let json = null;
  36. try {
  37. json = JSON.parse(text);
  38. } catch {
  39. /* 非 JSON */
  40. }
  41. return { status: res.status, json, text };
  42. } catch (err) {
  43. return { status: 0, json: null, text: String(err) };
  44. }
  45. };
  46. /** 该 column 下用 c_credit_code 标记的所有行 */
  47. const rowsWithTag = async (columnId, tag) => {
  48. const r = await call('POST', '/content/selectContentList', {
  49. columnId: String(columnId),
  50. page: '0',
  51. pageSize: '100',
  52. search: JSON.stringify([{ field: 'c_credit_code', searchType: 1, content: { value: tag } }]),
  53. });
  54. return r.json?.content?.data || [];
  55. };
  56. const mkRow = async () => {
  57. const sid = `${TAG}_${Math.random().toString(36).slice(2, 8)}`;
  58. const r = await call('POST', '/content/addContent', {
  59. columnId: COL,
  60. modelId: MODEL,
  61. content: JSON.stringify({ c_credit_code: TAG, c_session_id: sid, c_title: 'deltest' }),
  62. });
  63. return typeof r.json?.content === 'string' ? { uuid: r.json.content, sid } : null;
  64. };
  65. const run = async () => {
  66. console.log(`\nDMS 删除姿势探测 ${BASE}\n`);
  67. const candidates = [
  68. ['POST /content/delContentById form{columnId,contentId}', (u) => call('POST', '/content/delContentById', { columnId: COL, contentId: u })],
  69. ['DELETE /content/delContentById json{columnId,contentId}', (u) => call('DELETE', '/content/delContentById', null, { columnId: Number(COL), contentId: u })],
  70. ['DELETE /content/delContentById json{id}', (u) => call('DELETE', '/content/delContentById', null, { id: u })],
  71. ['DELETE form body{columnId,contentId}', (u) => call('DELETE', '/content/delContentById', { columnId: COL, contentId: u })],
  72. ['POST /content/updateAudit form{columnId,contentId,state=4}', (u) => call('POST', '/content/updateAudit', { columnId: COL, contentId: u, state: '4' })],
  73. ['POST /content/updateAudit form{columnId,ids,state=4}', (u) => call('POST', '/content/updateAudit', { columnId: COL, ids: u, state: '4' })],
  74. ['POST /content/updateAudit form{columnId,contentIds,state=4}', (u) => call('POST', '/content/updateAudit', { columnId: COL, contentIds: u, state: '4' })],
  75. ['POST /content/updateAudit form{columnId,id,state=4}', (u) => call('POST', '/content/updateAudit', { columnId: COL, id: u, state: '4' })],
  76. ['POST /content/updateAudit json{columnId,contentIds:[uuid],state:4}', (u) => call('POST', '/content/updateAudit', null, { columnId: Number(COL), contentIds: [u], state: 4 })],
  77. ];
  78. const winners = [];
  79. for (const [label, fn] of candidates) {
  80. const row = await mkRow();
  81. if (!row) {
  82. console.log(` ?? ${label} —— 造行失败`);
  83. continue;
  84. }
  85. let resp;
  86. try {
  87. resp = await fn(row.uuid);
  88. } catch (err) {
  89. console.log(` xx ${label} —— 异常 ${err.message}`);
  90. continue;
  91. }
  92. // 判定:默认查询里还能不能看到这一行
  93. const still = (await rowsWithTag(COL, TAG)).some((r) => r.id === row.uuid);
  94. const code = resp.json?.code ?? resp.status;
  95. const ok = !still && code === 200;
  96. console.log(` ${ok ? 'ok ' : 'xx '} ${label}\n → code=${code}${still ? '(行仍在)' : '(行已消失)'} ${String(resp.text).slice(0, 120)}`);
  97. if (ok) winners.push({ label, fn });
  98. }
  99. console.log(`\n可用姿势:${winners.length ? winners.map((w) => w.label).join(' / ') : '(都不行)'}`);
  100. // ── 清理:用可用的姿势删掉所有 deltest_* 与 step0_* 残留 ──────────────
  101. console.log('\n【清理残留】');
  102. const targets = [
  103. ...(await rowsWithTag(COL, TAG)),
  104. ...(await rowsWithTag('1889', TAG)),
  105. ...(await rowsWithTag(COL, 'step0_credit_code')),
  106. ...(await rowsWithTag('1889', 'step0_credit_code')),
  107. ...(await rowsWithTag(COL, 'probe_cc')),
  108. ...(await rowsWithTag('1889', 'probe_cc')),
  109. ];
  110. console.log(` 待清理 ${targets.length} 行`);
  111. if (!winners.length) {
  112. console.log(' ⚠️ 没有可用姿势,残留无法自动清理(需人工在 DMS 后台处理)');
  113. console.log(' 残留 id:', targets.map((t) => `${t.id}(col ${t.column_id})`).join(', '));
  114. return;
  115. }
  116. for (const row of targets) {
  117. for (const w of winners) {
  118. try {
  119. await w.fn(row.id);
  120. if (!(await rowsWithTag(String(row.column_id), row.c_credit_code)).some((r) => r.id === row.id)) break;
  121. } catch {
  122. /* 换下一种 */
  123. }
  124. }
  125. }
  126. const left = [
  127. ...(await rowsWithTag(COL, TAG)),
  128. ...(await rowsWithTag('1889', TAG)),
  129. ...(await rowsWithTag(COL, 'step0_credit_code')),
  130. ...(await rowsWithTag('1889', 'step0_credit_code')),
  131. ...(await rowsWithTag(COL, 'probe_cc')),
  132. ...(await rowsWithTag('1889', 'probe_cc')),
  133. ];
  134. console.log(` 清理后剩余 ${left.length} 行${left.length ? '(' + left.map((r) => r.id).join(', ') + ')' : ' ✅'}`);
  135. };
  136. run().catch((e) => {
  137. console.error('脚本异常:', e);
  138. process.exit(1);
  139. });