/** * Step-0:切换前端到 DMS 之前的**小样本实测**。 * * 为什么需要它:DMS 的 content 读写接口在文档里全是「📄 未实测」 * (参数来自 SKILL.md/Apifox,OpenAPI 里是 @RequestBody 抓不到), * 而前端的 upsert 依赖三件没被证实的事: * ① addContent 是否要求 c_id(模型里 must=true)→ 若强校验,整个方案要改 * ② addContent 返回体里有没有记录 uuid(有则省掉一次反查) * ③ timestamp 写入格式、states 默认值、长文本往返 * * 怎么跑(在项目根目录;token 不落盘,从参数读): * node harness/tools/dms-step0-verify.mjs * * 脚本会自行清理它写入的测试数据(session_id/record_id 以 step0_ 开头)。 */ const HOST = process.env.DMS_HOST || '121.43.55.7:10081'; const BASE = `http://${HOST}/dms`; const TOKEN = process.argv[2] || process.env.DMS_TOKEN || ''; const COL_SESSION = 1887; const MODEL_SESSION = 2034; const COL_RECORD = 1889; const MODEL_RECORD = 2038; const SUFFIX = Date.now().toString(36); const SESSION_ID = `step0_sess_${SUFFIX}`; const RECORD_ID = `step0_rec_${SUFFIX}`; const CREDIT = 'step0_credit_code'; if (!TOKEN) { console.error('缺 token。用法:node harness/tools/dms-step0-verify.mjs '); process.exit(2); } const results = []; const note = (name, ok, detail) => { results.push({ name, ok, detail }); console.log(` ${ok === null ? '? ' : ok ? 'ok ' : 'FAIL'} ${name}${detail ? ' → ' + detail : ''}`); }; async function call(method, path, form) { const init = { method, headers: { token: TOKEN } }; if (form) { init.headers['Content-Type'] = 'application/x-www-form-urlencoded'; init.body = new URLSearchParams(form).toString(); } const res = await fetch(`${BASE}${path}`, init); const text = await res.text(); let json = null; try { json = JSON.parse(text); } catch { /* 非 JSON 原样返回 */ } return { status: res.status, json, text }; } const search = (columnId, field, value, extra = {}) => call('POST', '/content/selectContentList', { columnId: String(columnId), page: '0', pageSize: '10', search: JSON.stringify([{ field, searchType: 1, content: { value } }]), ...extra, }); const cleanup = async (columnId, field, value) => { const r = await search(columnId, field, value); const rows = r.json?.content?.data || []; for (const row of rows) { if (row?.id) await call('DELETE', `/content/delContentById?id=${encodeURIComponent(row.id)}`); } return rows.length; }; const run = async () => { console.log(`\nDMS Step-0 实测 ${BASE} 标记=${SUFFIX}\n`); // ── 1. addContent:不传 c_id 会不会被拒(最高优先级)──────────────────── console.log('【1】addContent(1887 会话)不传 c_id'); const addSession = await call('POST', '/content/addContent', { columnId: String(COL_SESSION), modelId: String(MODEL_SESSION), content: JSON.stringify({ c_credit_code: CREDIT, c_session_id: SESSION_ID, c_title: 'step0 会话标题', c_source: 'zhaoshang', }), }); console.log(' 原始响应:', addSession.text.slice(0, 400)); const addOk = addSession.json?.code === 200; note('addContent 不传 c_id 被接受', addOk, addOk ? '' : `code=${addSession.json?.code}`); const returnedUuid = addSession.json?.content?.id || addSession.json?.content?.uuid || null; note('addContent 响应回传记录 uuid', !!returnedUuid, returnedUuid ? String(returnedUuid) : '(未回传,需反查)'); // ── 2. 反查:states 默认值 / c_id 是否自动生成 ────────────────────────── console.log('\n【2】反查(不带 states)'); const found = await search(COL_SESSION, 'c_session_id', SESSION_ID); const rows = found.json?.content?.data || []; note('写后能查回(不传 states)', rows.length > 0, `命中 ${rows.length} 行, code=${found.json?.code}`); const row = rows[0] || {}; console.log(' 行内容:', JSON.stringify(row).slice(0, 500)); note('c_id 由 DMS 自动生成', row.c_id !== undefined && row.c_id !== null && row.c_id !== '', `c_id=${row.c_id}`); note('记录 uuid 存在行里(反查可取)', !!row.id, String(row.id || '')); const uuid = row.id || returnedUuid; // ── 3. timestamp 形态 ────────────────────────────────────────────────── console.log('\n【3】timestamp'); note('c_created_at 未传时是否自动填充', row.c_created_at !== undefined && row.c_created_at !== null, `读到 ${JSON.stringify(row.c_created_at)}`); const tsFormats = ['2026-09-17 21:30:00', '2026-09-17T21:30:00+08:00']; for (const ts of tsFormats) { const r = await call('POST', '/content/updateContent', { columnId: String(COL_SESSION), modelId: String(MODEL_SESSION), content: JSON.stringify({ id: uuid, c_updated_at: ts }), }); const back = await search(COL_SESSION, 'c_session_id', SESSION_ID); const got = back.json?.content?.data?.[0]?.c_updated_at; note(`写入 "${ts}"`, r.json?.code === 200 && got != null, `code=${r.json?.code}, 读回=${JSON.stringify(got)}`); } // ── 4. updateContent 改标题 ─────────────────────────────────────────── console.log('\n【4】updateContent(带 id=uuid)'); const upd = await call('POST', '/content/updateContent', { columnId: String(COL_SESSION), modelId: String(MODEL_SESSION), content: JSON.stringify({ id: uuid, c_title: 'step0 改后的标题' }), }); const afterUpd = await search(COL_SESSION, 'c_session_id', SESSION_ID); const newTitle = afterUpd.json?.content?.data?.[0]?.c_title; note('改标题生效', upd.json?.code === 200 && newTitle === 'step0 改后的标题', `code=${upd.json?.code}, c_title=${JSON.stringify(newTitle)}`); // ── 5. 长文本 + 中文 + 协议标记往返 ──────────────────────────────────── console.log('\n【5】1889 长文本往返(含协议标记)'); const longAnswer = [ '', '\n', '第一段中文回答。'.repeat(120), '{"kind":"company_need"}', '结尾行', ].join('\n'); const addRecord = await call('POST', '/content/addContent', { columnId: String(COL_RECORD), modelId: String(MODEL_RECORD), content: JSON.stringify({ c_credit_code: CREDIT, c_session_id: SESSION_ID, c_record_id: RECORD_ID, c_question: 'step0 问题:高新技术企业奖励是多少?', c_answer: longAnswer, c_source: 'zhaoshang', }), }); note('1889 addContent 成功', addRecord.json?.code === 200, `code=${addRecord.json?.code}`); const recRows = (await search(COL_RECORD, 'c_record_id', RECORD_ID)).json?.content?.data || []; const rec = recRows[0] || {}; note('长文本逐字节一致', rec.c_answer === longAnswer, `原长 ${longAnswer.length} / 读回 ${String(rec.c_answer || '').length}`); note('中文问题往返一致', rec.c_question === 'step0 问题:高新技术企业奖励是多少?', JSON.stringify(rec.c_question)); // ── 6. 反馈字段 update ──────────────────────────────────────────────── console.log('\n【6】1889 反馈字段'); const fb = await call('POST', '/content/updateContent', { columnId: String(COL_RECORD), modelId: String(MODEL_RECORD), content: JSON.stringify({ id: rec.id, c_feedback_status: 2, c_feedback_option: '没有回答我的问题', c_feedback_remark: 'step0 备注', c_feedback_at: '2026-09-17 21:31:00', }), }); const recAfter = (await search(COL_RECORD, 'c_record_id', RECORD_ID)).json?.content?.data?.[0] || {}; note( '反馈字段写入生效', fb.json?.code === 200 && Number(recAfter.c_feedback_status) === 2 && recAfter.c_feedback_option === '没有回答我的问题', `code=${fb.json?.code}, status=${recAfter.c_feedback_status}, option=${JSON.stringify(recAfter.c_feedback_option)}, at=${JSON.stringify(recAfter.c_feedback_at)}` ); // ── 7. 中文 search 精确匹配(访客 访客_xxx 依赖)────────────────────── console.log('\n【7】中文精确 search'); const cn = await search(COL_SESSION, 'c_credit_code', CREDIT); note('中文/下划线 credit_code 精确命中', (cn.json?.content?.data || []).length > 0, `命中 ${(cn.json?.content?.data || []).length} 行`); // ── 8. pageSize 上限 ───────────────────────────────────────────────── console.log('\n【8】pageSize'); const big = await call('POST', '/content/selectContentList', { columnId: String(COL_RECORD), page: '0', pageSize: '200', search: JSON.stringify([{ field: 'c_session_id', searchType: 1, content: { value: SESSION_ID } }]), }); note('pageSize=200 被接受', big.json?.code === 200 || big.json?.code === 202, `code=${big.json?.code}, 返回 ${(big.json?.content?.data || []).length} 行`); // ── 9. delContentById 参数形态 ─────────────────────────────────────── console.log('\n【9】delContentById 参数形态'); const probe = await call('DELETE', `/content/delContentById?id=${encodeURIComponent(rec.id)}`); const stillThere = ((await search(COL_RECORD, 'c_record_id', RECORD_ID)).json?.content?.data || []).length; note('DELETE ?id= 生效', stillThere === 0, `code=${probe.json?.code}, 剩余 ${stillThere} 行`); // ── 10. 清理 1887 测试行 ───────────────────────────────────────────── console.log('\n【10】清理'); const removed = await cleanup(COL_SESSION, 'c_session_id', SESSION_ID); const left = await cleanup(COL_RECORD, 'c_record_id', RECORD_ID); note('测试数据已清理', true, `1887 删 ${removed} 行 / 1889 删 ${left} 行`); // ── 汇总 ───────────────────────────────────────────────────────────── const failed = results.filter((r) => r.ok === false); console.log(`\n===== 汇总:${results.length - failed.length}/${results.length} 通过 =====`); if (failed.length) { console.log('未通过项:'); failed.forEach((f) => console.log(' - ' + f.name + (f.detail ? ' ' + f.detail : ''))); } console.log('\n关键结论(回填到计划/记录里):'); console.log(' addContent 是否需 c_id :', addOk ? '不需要(自动生成)' : '被拒 → 需 DMS 侧调整模型'); console.log(' addContent 是否回 uuid :', returnedUuid ? '回传,可省一次反查' : '不回传,需 search 反查'); console.log(' 时间戳 : 见【3】读回值'); }; run().catch((err) => { console.error('\n脚本异常:', err); process.exit(1); });