// 一次性脚本:把档案管理模块(yudao-module-archive)并入 config_export_all_20260929.sql 的阶段一
|
// 用法:node _merge_archive.js
|
const fs = require('node:fs');
|
const path = require('node:path');
|
const os = require('node:os');
|
|
const DIR = __dirname;
|
const TARGET = path.join(DIR, 'config_export_all_20260929.sql');
|
const MODULE = path.join(DIR, 'archive_module.sql');
|
const BPM = path.join(DIR, '_archive_bpm_export.sql');
|
const TMP = path.join(os.homedir(), 'AppData', 'Local', 'Temp');
|
|
const raw = fs.readFileSync(TARGET, 'utf8');
|
if (!raw.includes('\r\n')) throw new Error('目标文件不是 CRLF');
|
let doc = raw.replace(/\r\n/g, '\n');
|
|
const norm = (s) => s.replace(/\r\n/g, '\n');
|
const moduleLines = norm(fs.readFileSync(MODULE, 'utf8')).split('\n');
|
const bpmLines = norm(fs.readFileSync(BPM, 'utf8')).split('\n');
|
|
/** 断言锚点唯一 */
|
function anchor(text) {
|
const n = doc.split(text).length - 1;
|
if (n !== 1) throw new Error(`锚点出现 ${n} 次,期望 1 次:${text.slice(0, 90)}`);
|
}
|
/** 在锚点前插入(插入内容与锚点之间留一个空行) */
|
function insertBefore(text, before) {
|
anchor(text);
|
doc = doc.replace(text, before + '\n\n' + text);
|
}
|
/** 替换一次 */
|
function replaceOnce(from, to) {
|
anchor(from);
|
doc = doc.replace(from, to);
|
}
|
|
// ---------------------------------------------------------------- 数据行风格归一
|
/** 按顶层逗号切分 VALUES 元组(尊重单引号与反斜杠转义) */
|
function splitTuple(s) {
|
const out = [];
|
let cur = '';
|
let inS = false;
|
for (let i = 0; i < s.length; i++) {
|
const c = s[i];
|
if (inS) {
|
cur += c;
|
if (c === '\\' && i + 1 < s.length) {
|
cur += s[++i];
|
} else if (c === "'") {
|
if (s[i + 1] === "'") cur += s[++i];
|
else inS = false;
|
}
|
} else if (c === "'") {
|
inS = true;
|
cur += c;
|
} else if (c === ',') {
|
out.push(cur.trim());
|
cur = '';
|
} else {
|
cur += c;
|
}
|
}
|
if (cur.trim()) out.push(cur.trim());
|
return out;
|
}
|
|
/**
|
* 把从库 JDBC 导出的 INSERT 行改写成本文件的既有风格:
|
* 字符串/数字/日期一律加单引号,bit 列 deleted 出 X'00',NULL 保持 NULL。
|
*/
|
function restyle(line) {
|
const m = line.match(/^INSERT INTO `([^`]+)` \(([^)]+)\) VALUES \((.*)\);$/);
|
if (!m) throw new Error(`无法解析数据行:${line.slice(0, 90)}`);
|
const [, table, colText, valsText] = m;
|
const cols = colText.split(',').map((c) => c.trim().replace(/`/g, ''));
|
const vals = splitTuple(valsText);
|
if (cols.length !== vals.length) {
|
throw new Error(`${table} 列数 ${cols.length} 与值数 ${vals.length} 不符`);
|
}
|
const out = vals.map((v, i) => {
|
if (v === 'NULL') return 'NULL';
|
if (cols[i] === 'deleted') {
|
if (v !== '0') throw new Error(`${table}.deleted 期望 0,实际 ${v}`);
|
return "X'00'";
|
}
|
return v.startsWith("'") ? v : `'${v}'`;
|
});
|
return `INSERT INTO \`${table}\` (${cols.map((c) => '`' + c + '`').join(', ')}) VALUES (${out.join(', ')});`;
|
}
|
|
function readRows(file) {
|
const text = norm(fs.readFileSync(path.join(TMP, file), 'utf8'));
|
const rows = text
|
.split('\n')
|
.map((l) => l.trim())
|
.filter((l) => l.startsWith('INSERT INTO '))
|
.map(restyle);
|
return rows;
|
}
|
|
const CATEGORY_ROWS = readRows('archive_category_rows.sql');
|
const DICT_TYPE_ROWS = readRows('archive_dict_type_rows.sql');
|
const DICT_DATA_ROWS = readRows('archive_dict_data_rows.sql');
|
const BPM_CATEGORY_ROWS = readRows('archive_bpm_category_row.sql');
|
if (CATEGORY_ROWS.length !== 6) throw new Error(`分类数据 ${CATEGORY_ROWS.length} 行,期望 6`);
|
if (DICT_TYPE_ROWS.length !== 6) throw new Error(`字典类型 ${DICT_TYPE_ROWS.length} 行,期望 6`);
|
if (DICT_DATA_ROWS.length !== 19) throw new Error(`字典数据 ${DICT_DATA_ROWS.length} 行,期望 19`);
|
if (BPM_CATEGORY_ROWS.length !== 1) throw new Error(`BPM 分类 ${BPM_CATEGORY_ROWS.length} 行,期望 1`);
|
|
// ================================================================ 1. 表数量 427 → 430
|
replaceOnce(
|
'-- 范围:全库 BASE TABLE 共 427 张(0918 的 419 张 + 2 张 finance_* + 2 张 ums_* + 4 张 research_*),全部输出 CREATE TABLE;',
|
'-- 范围:全库 BASE TABLE 共 430 张(0918 的 419 张 + 2 张 finance_* + 2 张 ums_* + 4 张 research_* + 3 张 archive_*),全部输出 CREATE TABLE;',
|
);
|
|
// ============================================= 2. 带数据表清单补 ⑦(档案管理模块)
|
insertBefore(
|
'-- ⑥ AI 配置(仅模型+key)+ 基础设施配置管理:ai_model、ai_api_key、infra_config、infra_job、infra_file_config、infra_data_source_config、infra_codegen_table、infra_codegen_column',
|
'-- ⑦ 档案管理模块(1 张带初始数据 + 2 张仅结构):archive_category(初始 6 条分类)、archive_document、archive_borrow',
|
);
|
|
// ============================================================== 3. 补丁登记块
|
const PREAMBLE = [
|
'-- 补丁内容(2026-09-29,档案管理模块 yudao-module-archive):',
|
'-- ① 补 3 张 archive_* 表结构(档案分类 / 档案主表 / 借阅单),其中 archive_category 带初始 6 条分类数据:',
|
'-- archive_category、archive_document、archive_borrow',
|
'-- ② 补 6 个字典类型 + 19 条字典数据(并入阶段一的 system_dict_type / system_dict_data):',
|
'-- archive_category_status(启用/停用)',
|
'-- archive_document_status(在库/借出中)',
|
'-- archive_secret_level(公开/内部/保密)',
|
'-- archive_carrier_type(电子/实体/电子+实体)',
|
'-- archive_source_type(手工录入/课题归档/会员归档/财务归档)',
|
'-- archive_borrow_status(草稿/审批中/借出中/已归还/已驳回)',
|
'-- ③ 补 1 条 BPM 分类(并入阶段一的 bpm_category):',
|
'-- id=8003 \'档案借阅审批\' / code=\'archive_borrow_approve\'',
|
'-- ④ 补 1 条 BPM 流程定义(档案借阅两级审批:档案管理员审核→分管领导审批),',
|
'-- 分别落 act_re_deployment / act_re_procdef / act_ge_bytearray(3 条) / act_re_model /',
|
'-- bpm_process_definition_info 五张表,流程 key=\'archive_borrow_approve\'。',
|
'-- bpm_process_definition_info.form_type=20(自定义表单),',
|
'-- form_custom_create_path=/archive/borrow/index、',
|
'-- form_custom_view_path=/archive/borrow/detail/approve/index.vue。',
|
'-- ⚠ 该流程的 BPMN 为手写两节点,**不是** bpm_deploy_flow_from_template 生成的单节点流程。',
|
'-- ⑤ 补 3 张来源表各 1 列 archive_document_id(bigint NULL,归档联动回写档案 ID):',
|
'-- research_application(课题申报单)、ums_member(会员)、finance_reimbursement(报销单)。',
|
'-- 列可空、不影响既有数据;「是否已归档」以该列为硬标记(另有 archive_document 侧',
|
'-- source_type+source_id 幂等兜底)。',
|
'-- ⑥ 补 24 条档案管理菜单:1075600「档案管理」(**顶级目录**,parent_id=0,path=/archive)',
|
'-- + 1075601「档案分类」/ 1075602「档案管理」/ 1075603「借阅登记」/ 1075604「档案统计」',
|
'-- + 1075610~1075613(分类查询/创建/更新/删除)、',
|
'-- 1075620~1075625(档案查询/创建/更新/删除/导出/保密档案查看)、',
|
'-- 1075630~1075635(借阅查询/登记/修改/删除/提交审批/归还登记)、',
|
'-- 1075640(档案统计查询)、',
|
'-- 1075536(课题申报归档,挂 1075512 课题申报下)、',
|
'-- 1075504(会员归档,挂 1075492 会员列表下)。',
|
'-- 财务归档复用既有 finance:reimbursement:archive(1075424),不新增权限点。',
|
'-- **菜单段本文件无需为此改动**:阶段② 的写法是 `INSERT INTO system_menu SELECT *',
|
'-- FROM \\`mom-jssm\\`.system_menu WHERE deleted=b\'0\'`,是执行期跨库全量拷贝,',
|
'-- 只要来源库 mom-jssm 有这几行就会被带过去 —— 与阶段一的字面量 INSERT 不同。',
|
'-- 在此登记只为说明「这版文件确实覆盖了该变更」,避免后来者以为漏了。',
|
'-- ⚠ 档案模块的 BPMN 部署产物(④)为**先部署后导出**:新库初始化时依赖本文件的字面量 INSERT,',
|
'-- 若重跑导出请勿遗漏五张表(含 act_ge_bytearray 的 3 条:bpmn / png / source)。',
|
].join('\n');
|
insertBefore('-- 历史补丁内容(2026-09-18):', PREAMBLE);
|
|
// ======================================== 4. 3 张 archive_* 表 DDL + 初始分类数据
|
function ddlBlock(fromLine, toLine, tableName, rows) {
|
const body = moduleLines.slice(fromLine - 1, toLine).join('\n');
|
const titles = rows ? `导出 ${rows.length} 行` : '仅 DDL,无数据';
|
return [`-- ============ 表:${tableName}(${titles}) ============`, body, ...(rows || []), ''].join('\n');
|
}
|
const ARCHIVE_DDL = [
|
'-- ***********************************************************************',
|
'-- ⑦ 档案管理模块(yudao-module-archive:分类树 + 档案主表 + 借阅单)',
|
'-- ***********************************************************************',
|
'',
|
ddlBlock(9, 28, 'archive_category', CATEGORY_ROWS),
|
ddlBlock(31, 67, 'archive_document', null),
|
ddlBlock(70, 104, 'archive_borrow', null),
|
].join('\n');
|
insertBefore(
|
['-- ***********************************************************************', '-- 其余 382 张表(仅表结构,无数据)', '-- ***********************************************************************'].join('\n'),
|
ARCHIVE_DDL,
|
);
|
|
// ===================================== 5. 三张来源表各补 1 列 archive_document_id
|
const ARCHIVE_COL =
|
" `archive_document_id` bigint DEFAULT NULL COMMENT '归档生成的档案 ID(archive_document.id,归档联动回写)',";
|
// 5.1 research_application(放在 notice_id 之后)
|
replaceOnce(
|
" `notice_id` bigint DEFAULT NULL COMMENT '已发布的立项通知公告 ID',",
|
" `notice_id` bigint DEFAULT NULL COMMENT '已发布的立项通知公告 ID',\n" + ARCHIVE_COL,
|
);
|
// 5.2 ums_member(放在 status 之后;两行锚点以消歧)
|
replaceOnce(
|
" `expire_date` date DEFAULT NULL COMMENT '有效期至',\n" +
|
" `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-正常 0-暂停',",
|
" `expire_date` date DEFAULT NULL COMMENT '有效期至',\n" +
|
" `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-正常 0-暂停',\n" +
|
ARCHIVE_COL,
|
);
|
// 5.3 finance_reimbursement(放在 archive_time 之后;两行锚点以消歧)
|
replaceOnce(
|
" `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'BPM流程实例ID',\n" +
|
" `archive_time` datetime DEFAULT NULL COMMENT '归档时间',",
|
" `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'BPM流程实例ID',\n" +
|
" `archive_time` datetime DEFAULT NULL COMMENT '归档时间',\n" +
|
ARCHIVE_COL,
|
);
|
|
// "其余 382 张表" → 384(⑦ 已插入 3 张,其中 2 张无数据故不进带数据清单)
|
replaceOnce(
|
['-- ***********************************************************************', '-- 其余 382 张表(仅表结构,无数据)', '-- ***********************************************************************'].join('\n'),
|
['-- ***********************************************************************', '-- 其余 384 张表(仅表结构,无数据)', '-- ***********************************************************************'].join('\n'),
|
);
|
|
// ===================================================== 6. BPM 部署产物(7 条 INSERT)
|
const bpmInserts = bpmLines.filter((l) => l.startsWith('INSERT INTO '));
|
if (bpmInserts.length !== 7) throw new Error(`BPM INSERT 条数 ${bpmInserts.length},期望 7`);
|
const pick = (table) => bpmInserts.filter((l) => l.startsWith(`INSERT INTO \`${table}\``));
|
const insProcdef = pick('act_re_procdef');
|
const insDeployment = pick('act_re_deployment');
|
const insBytearray = pick('act_ge_bytearray');
|
const insModel = pick('act_re_model');
|
const insProcInfo = pick('bpm_process_definition_info');
|
if (
|
insProcdef.length !== 1 ||
|
insDeployment.length !== 1 ||
|
insBytearray.length !== 3 ||
|
insModel.length !== 1 ||
|
insProcInfo.length !== 1
|
) {
|
throw new Error('BPM INSERT 分类数量不符');
|
}
|
|
// 6.1 act_re_procdef(23 → 24 行)
|
insertBefore('-- ============ 表:act_re_deployment(导出 23 行) ============', insProcdef.join('\n'));
|
replaceOnce(
|
'-- ============ 表:act_re_procdef(导出 23 行) ============',
|
'-- ============ 表:act_re_procdef(导出 24 行) ============',
|
);
|
// 6.2 act_re_deployment(23 → 24 行)
|
insertBefore('-- ============ 表:act_ge_bytearray(导出 28 行) ============', insDeployment.join('\n'));
|
replaceOnce(
|
'-- ============ 表:act_re_deployment(导出 23 行) ============',
|
'-- ============ 表:act_re_deployment(导出 24 行) ============',
|
);
|
// 6.3 act_ge_bytearray(28 → 31 行,bpmn / png / source 三条)
|
insertBefore('-- ============ 表:act_re_model(导出 23 行) ============', insBytearray.join('\n'));
|
replaceOnce(
|
'-- ============ 表:act_ge_bytearray(导出 28 行) ============',
|
'-- ============ 表:act_ge_bytearray(导出 31 行) ============',
|
);
|
// 6.4 act_re_model(23 → 24 行)
|
insertBefore('-- ============ 表:bpm_process_definition_info(导出 23 行) ============', insModel.join('\n'));
|
replaceOnce(
|
'-- ============ 表:act_re_model(导出 23 行) ============',
|
'-- ============ 表:act_re_model(导出 24 行) ============',
|
);
|
// 6.5 bpm_process_definition_info(23 → 24 行,插在 ② 分组横幅之前)
|
insertBefore(
|
['-- ***********************************************************************', '-- ② 协同(业务附件配置 + 分类 + 表单 + 扩展配置)(DDL + 数据)', '-- ***********************************************************************'].join(
|
'\n',
|
),
|
insProcInfo.join('\n'),
|
);
|
replaceOnce(
|
'-- ============ 表:bpm_process_definition_info(导出 23 行) ============',
|
'-- ============ 表:bpm_process_definition_info(导出 24 行) ============',
|
);
|
|
// ===================================================== 7. bpm_category 补 8003(25 → 26 行)
|
insertBefore('-- ============ 表:bpm_form(导出 4 行) ============', BPM_CATEGORY_ROWS.join('\n'));
|
replaceOnce(
|
'-- ============ 表:bpm_category(导出 25 行) ============',
|
'-- ============ 表:bpm_category(导出 26 行) ============',
|
);
|
|
// ================================================ 8. 字典类型 6 条(254 → 260 行)
|
insertBefore('-- ============ 表:system_dict_data(导出 1205 行) ============', DICT_TYPE_ROWS.join('\n'));
|
replaceOnce(
|
'-- ============ 表:system_dict_type(导出 254 行) ============',
|
'-- ============ 表:system_dict_type(导出 260 行) ============',
|
);
|
// ================================================ 9. 字典数据 19 条(1205 → 1224 行)
|
insertBefore('-- ============ 表:system_tenant(导出 3 行) ============', DICT_DATA_ROWS.join('\n'));
|
replaceOnce(
|
'-- ============ 表:system_dict_data(导出 1205 行) ============',
|
'-- ============ 表:system_dict_data(导出 1224 行) ============',
|
);
|
|
// ---------------------------------------------------------------- 写出
|
doc = doc.replace(/\n/g, '\r\n');
|
fs.writeFileSync(TARGET, doc, 'utf8');
|
console.log(`合并完成 → ${path.basename(TARGET)}(${doc.split('\r\n').length} 行)`);
|