| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- /**
- * 验证「附件上传到 DMS 文件服务」这条链路(`POST {DMS}/file/uploadFile`)。
- *
- * 背景(用户 2026-09-23):「图片和文件调用接口存储…然后文件存放的相对路径放到对应的 URL 里」——
- * 即上传走 DMS,返回的**相对路径**随聊天请求体的 `pictures` / `files` 发给后端。
- * 此前附件直传厂商(metamaker)OSS,给的是绝对地址。
- *
- * **实测契约(2026-09-23,打真实服务)**:
- * - `multipart/form-data`,**五个字段都必填**:file / type / columnId / contentId / paraName
- * (少一个 → `{"code":-1,"message":"缺少必传字段"}`)
- * - `type` 决定存储目录:0 图 → `/static/photo/…`;1音/2视/3 文件 → `/static/file/…`
- * - 成功返回 `{"code":200,"content":"/static/photo/1889-c_question-<uuid>.png"}`
- *
- * 怎么跑(需要 dev server 起着,走同源代理;node 侧不校验 TLS):
- * 1) npm run dev
- * 2) npx esbuild harness/tools/_entry-dms.ts --bundle --format=esm \
- * --outfile=harness/tools/_dms.mjs --alias:@=./src --define:import.meta.env='{}'
- * 3) node harness/tools/verify-dms-file-upload.mjs
- *
- * ⚠️ 「真上传」那一段会往文件服务写一个**极小的测试文件**(名字带 verify_upload_ 前缀,
- * 体积几十字节);接口没有提供删除能力,所以它会留在那儿 —— 可接受但要知情。
- */
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
- const base = process.argv[2] || 'https://localhost:8083/dms-api';
- globalThis.VITE_DMS_API = base;
- const realFetch = globalThis.fetch;
- let captured = [];
- const stubFetch = (responder) => {
- globalThis.fetch = async (url, init = {}) => {
- captured.push({ url: String(url), body: init.body });
- return responder(String(url), init);
- };
- };
- const {
- uploadDmsAttachment,
- buildDmsFileDisplayUrl,
- DMS_ATTACHMENT_COLUMN_ID,
- DMS_ATTACHMENT_CONTENT_ID,
- DMS_ATTACHMENT_PARA_NAME,
- DMS_ATTACHMENT_TYPE_IMAGE,
- DMS_ATTACHMENT_TYPE_FILE,
- } = await import('./_dms.mjs');
- let pass = 0;
- let fail = 0;
- const check = (name, cond, extra = '') => {
- if (cond) {
- pass++;
- console.log(' ok ' + name);
- } else {
- fail++;
- console.log(' FAIL ' + name + ' ' + extra);
- }
- };
- /** node 里构造一个假的 File(Node 20+ 有全局 File) */
- const makeFile = (name, type, content = 'verify upload probe') =>
- new File([content], name, { type });
- console.log(`\nDMS 附件上传验证\n 代理地址: ${base}\n`);
- console.log('【1】请求形态:五个字段都得带上');
- {
- captured = [];
- stubFetch(() =>
- new Response(JSON.stringify({ code: 200, message: '上传成功!', content: '/static/photo/x.png' }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' },
- })
- );
- const path = await uploadDmsAttachment(makeFile('a.png', 'image/png'), { isImage: true });
- globalThis.fetch = realFetch;
- const req = captured[0];
- check('只发一次请求', captured.length === 1, String(captured.length));
- check('打的是 /file/uploadFile', req?.url.endsWith('/file/uploadFile'), req?.url);
- check('返回相对路径', path === '/static/photo/x.png', path);
- const form = req?.body;
- check('body 是 FormData', form instanceof FormData, String(form?.constructor?.name));
- const keys = form ? [...form.keys()].sort() : [];
- check(
- '**五个字段齐全**(少一个后端就返回"缺少必传字段")',
- JSON.stringify(keys) === JSON.stringify(['columnId', 'contentId', 'file', 'paraName', 'type']),
- JSON.stringify(keys)
- );
- check('type:图片 = 0', form?.get('type') === DMS_ATTACHMENT_TYPE_IMAGE, String(form?.get('type')));
- check(`columnId = ${DMS_ATTACHMENT_COLUMN_ID}`, form?.get('columnId') === String(DMS_ATTACHMENT_COLUMN_ID));
- check(`contentId = ${DMS_ATTACHMENT_CONTENT_ID}`, form?.get('contentId') === String(DMS_ATTACHMENT_CONTENT_ID));
- check(`paraName = ${DMS_ATTACHMENT_PARA_NAME}`, form?.get('paraName') === DMS_ATTACHMENT_PARA_NAME);
- check('file 就是那个文件', form?.get('file')?.name === 'a.png', String(form?.get('file')?.name));
- }
- console.log('\n【2】非图片走 type=3');
- {
- captured = [];
- stubFetch(() => new Response(JSON.stringify({ code: 200, content: '/static/file/y.pdf' }), { status: 200 }));
- await uploadDmsAttachment(makeFile('b.pdf', 'application/pdf'), { isImage: false });
- globalThis.fetch = realFetch;
- check('type:非图片 = 3', captured[0]?.body?.get('type') === DMS_ATTACHMENT_TYPE_FILE, String(captured[0]?.body?.get('type')));
- }
- console.log('\n【3】失败要抛错(调用方据此把上传项标成 error)');
- {
- stubFetch(() => new Response(JSON.stringify({ code: -1, message: '缺少必传字段' }), { status: 200 }));
- let msg = '';
- try {
- await uploadDmsAttachment(makeFile('c.png', 'image/png'), { isImage: true });
- } catch (err) {
- msg = err.message;
- }
- globalThis.fetch = realFetch;
- check('code != 200 → 抛错并带上后端说法', msg === '缺少必传字段', msg);
- }
- {
- stubFetch(() => new Response('boom', { status: 500 }));
- let msg = '';
- try {
- await uploadDmsAttachment(makeFile('d.png', 'image/png'), { isImage: true });
- } catch (err) {
- msg = err.message;
- }
- globalThis.fetch = realFetch;
- check('HTTP 非 2xx → 抛错', msg.includes('500'), msg);
- }
- console.log('\n【4】真上传一次(走代理打真实文件服务)');
- {
- try {
- const imgPath = await uploadDmsAttachment(makeFile(`verify_upload_${Date.now()}.png`, 'image/png'), {
- isImage: true,
- });
- check('图片:返回 200 且拿到相对路径', /^\/static\/photo\/1889-c_question-[\w-]+\.png$/.test(imgPath), imgPath);
- const filePath = await uploadDmsAttachment(makeFile(`verify_upload_${Date.now()}.txt`, 'text/plain'), {
- isImage: false,
- });
- check('文件:落在 /static/file/', /^\/static\/file\/1889-c_question-[\w-]+\.txt$/.test(filePath), filePath);
- check(
- '路径里带上了 columnId 与 paraName(后端按这三个字段定位)',
- imgPath.includes('1889-c_question-') && filePath.includes('1889-c_question-'),
- `${imgPath} | ${filePath}`
- );
- console.log(' (测试文件已写入文件服务,名字前缀 verify_upload_,体积几十字节)');
- // 关键:相对路径**取不到**,但拼上代理前缀能取到 —— 缩略图预览靠的就是它
- const direct = await realFetch(`http://121.43.55.7:10081${imgPath}`).catch(() => null);
- check('相对路径直接取**不到**(所以不能当 img src)', !direct || direct.status === 404, `HTTP ${direct?.status}`);
- const displayUrl = buildDmsFileDisplayUrl(imgPath);
- check('展示地址 = 代理前缀 + 相对路径', displayUrl === `${base}${imgPath}`, displayUrl);
- const served = await realFetch(displayUrl).catch(() => null);
- check('**展示地址能取到**(缩略图刷新后仍在)', served?.status === 200, `HTTP ${served?.status}`);
- check('空路径返回空串(不拼出半个地址)', buildDmsFileDisplayUrl('') === '', buildDmsFileDisplayUrl(''));
- check('已是绝对地址则原样返回', buildDmsFileDisplayUrl('https://a.cn/x.png') === 'https://a.cn/x.png');
- } catch (err) {
- check('真上传成功', false, err?.message || String(err));
- console.log(' ⚠️ dev server 没起、或代理不通会走到这里');
- }
- }
- console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
- process.exit(fail ? 1 : 0);
|