# -*- coding: utf-8 -*- """重新生成 docs/sql/config_export_all_20260930.sql(全量重生成,替换 0929 版)。 阶段一:当前源库 ruoyi-vue-pro 全库 CREATE TABLE + 6 类配置数据(mysqldump 字面量)。 阶段二:沿用 0929 文件的跨库身份/菜单/OAuth2 补导段(逐字复用)。 """ import re import subprocess import sys MYSQL_BIN = r"D:\MySQL\mysql-8.0.31-winx64\bin" DB = "ruoyi-vue-pro" USER = "root" PWD = "1234" BASE_DIR = r"D:\develop\XingRuan\MOM-LXWT\mom-pro2-after\docs\sql" SRC_PREV = BASE_DIR + r"\config_export_all_20260929.sql" OUT = BASE_DIR + r"\config_export_all_20260930.sql" MYSQL = MYSQL_BIN + r"\mysql.exe" MYSQLDUMP = MYSQL_BIN + r"\mysqldump.exe" COMMON = ["--default-character-set=utf8mb4", "--no-tablespaces", "--skip-comments", "--skip-add-locks", "--skip-disable-keys", "--skip-triggers", "--column-statistics=0", "--set-gtid-purged=OFF", "--single-transaction"] # ---------------- 6 类配置表(表名, 过滤条件 None=全表) ---------------- SECTIONS = [ ("① 协同审批流程(引擎元数据+定义,仅激活流程,剔历史/运行/身份)", [ ("act_ge_property", None), ("act_id_property", None), ("act_re_procdef", "SUSPENSION_STATE_=1"), ("act_re_deployment", "ID_ IN (SELECT DEPLOYMENT_ID_ FROM act_re_procdef WHERE SUSPENSION_STATE_=1)"), ("act_ge_bytearray", "DEPLOYMENT_ID_ IN (SELECT DEPLOYMENT_ID_ FROM act_re_procdef WHERE SUSPENSION_STATE_=1) " "OR NAME_ IN ('source','source-extra')"), ("act_re_model", None), ("bpm_process_definition_info", "deleted=0"), ]), ("② 协同(业务附件配置 + 分类 + 表单 + 扩展配置)", [ ("bpm_business_attach", "deleted=0"), ("bpm_category", "deleted=0"), ("bpm_form", "deleted=0"), ("bpm_user_group", "deleted=0"), ("bpm_process_listener", "deleted=0"), ("bpm_process_expression", "deleted=0"), ]), ("③ BI 决策大屏 / go-view", [ ("bi_dashboard", "deleted=0"), ("bi_chart_config", "deleted=0"), ("bi_data_source_config", "deleted=0"), ("bi_warehouse_coordinate", "deleted=0"), ("report_go_view_project", "deleted=0"), ]), ("④ MES 编码规则(剔流水记录)", [ ("mes_md_auto_code_rule", "deleted=0"), ("mes_md_auto_code_part", "deleted=0"), ]), ("⑤ 系统配置主数据(字典/租户/公司配置;剔身份/菜单/日志)", [ ("system_dict_type", "deleted=0"), ("system_dict_data", "deleted=0"), ("system_tenant", "deleted=0"), ("system_tenant_package", "deleted=0"), ("system_company_config", "deleted=0"), ]), ("⑥ AI 配置(仅模型+key)+ 基础设施配置管理", [ ("ai_model", "deleted=0"), ("ai_api_key", "deleted=0"), ("infra_config", "deleted=0"), ("infra_job", "deleted=0"), ("infra_file_config", "deleted=0"), ("infra_data_source_config", "deleted=0"), ("infra_codegen_table", "deleted=0"), ("infra_codegen_column", "deleted=0"), ]), ] def run(cmd): p = subprocess.run(cmd, capture_output=True) if p.returncode != 0: sys.stderr.write("FAILED: %s\n%s\n" % (" ".join(cmd), p.stderr.decode("utf-8", "replace"))) sys.exit(1) return p.stdout.decode("utf-8") def strip_mysqldump_noise(text): out = [] for line in text.splitlines(): if line.startswith("/*!") or line.startswith("--"): continue out.append(line) return out def fetch_tables(): sql = ("SELECT table_name FROM information_schema.tables " "WHERE table_schema='%s' AND table_type='BASE TABLE' ORDER BY table_name" % DB) out = run([MYSQL, "-u" + USER, "-p" + PWD, "--default-character-set=utf8mb4", "-N", "-B", "-e", sql]) return [l.strip() for l in out.splitlines() if l.strip()] def fetch_view_count(): sql = ("SELECT COUNT(*) FROM information_schema.views WHERE table_schema='%s'" % DB) out = run([MYSQL, "-u" + USER, "-p" + PWD, "--default-character-set=utf8mb4", "-N", "-B", "-e", sql]) return int(out.strip()) def dump_ddl(tables): """一次导出全部表结构,切分成 {表名: ddl块}。""" out = run([MYSQLDUMP, "-u" + USER, "-p" + PWD] + COMMON + ["--no-data", DB] + tables) lines = strip_mysqldump_noise(out) blocks = {} cur = None buf = [] for line in lines: m = re.match(r"^DROP TABLE IF EXISTS `([^`]+)`;", line) if m: if cur: blocks[cur] = "\n".join(buf).rstrip() cur = m.group(1) buf = [line] elif cur is not None: buf.append(line) if cur: blocks[cur] = "\n".join(buf).rstrip() return blocks def dump_data(tables_filters): """tables_filters: [(table, where)] 相同 where 的分组调用;返回 {表名: insert语句}。""" groups = {} for t, w in tables_filters: if w is None: groups.setdefault(None, []).append(t) else: groups.setdefault(w, []).append(t) data = {} for where, tables in groups.items(): cmd = [MYSQLDUMP, "-u" + USER, "-p" + PWD] + COMMON + \ ["--no-create-info", "--complete-insert", "--hex-blob", "--order-by-primary"] if where is not None: cmd.append("--where=" + where) cmd.append(DB) cmd += tables out = run(cmd) lines = strip_mysqldump_noise(out) stmt_lines = [] for line in lines: if not line.strip(): continue if line.startswith("INSERT INTO "): stmt_lines = [line] elif stmt_lines: stmt_lines.append(line) else: sys.stderr.write("UNEXPECTED line: %s\n" % line[:120]) continue stmt = "\n".join(stmt_lines) if stmt.rstrip().endswith(";"): m = re.match(r"^INSERT INTO `([^`]+)`", stmt) data[m.group(1)] = stmt.rstrip() stmt_lines = [] return data def fetch_counts(tables_filters): parts = [] for t, w in tables_filters: parts.append("SELECT '%s' AS t, COUNT(*) AS c FROM `%s`%s" % (t, t, (" WHERE " + w) if w else "")) sql = " UNION ALL ".join(parts) out = run([MYSQL, "-u" + USER, "-p" + PWD, "--default-character-set=utf8mb4", "-N", "-B", DB, "-e", sql]) counts = {} for line in out.splitlines(): if not line.strip(): continue k, v = line.split("\t") counts[k] = int(v) return counts def load_stage2_block(): with open(SRC_PREV, encoding="utf-8") as f: lines = f.read().splitlines() for i, line in enumerate(lines): if "阶段二:补导身份" in line: start = i - 1 # 上方 =============== 横线 return "\n".join(lines[start:]).rstrip() + "\n" raise RuntimeError("0929 文件未找到阶段二段落") HEADER = """-- ======================================================================= -- 文件:config_export_all_20260930.sql -- 用途:ruoyi-vue-pro「全库表结构 + 6 类配置数据」导出,供同结构全新库初始化 -- 阶段一:全库 {n_tables} 张表 CREATE TABLE + 6 类配置数据(字面量,自包含,不依赖源库在线) -- 阶段二:跨库补导 菜单/角色/用户/OAuth2 等身份数据(执行期从源库拷贝) -- 来源库:ruoyi-vue-pro -- 基线:2026-09-29 版(417 张表)→ 本次为**全量重生成**(不再基于旧文件打补丁): -- 直接按当前源库最新状态重导,一次性覆盖 0929 之后全部结构与配置漂移。 -- 本次变更(相对 0929,2026-09-30): -- ① 表数量不变({n_tables} 张),无新增/删除表。 -- ② 5 张出库单明细行增加 3 列(出库单行支持「主单位 + 辅单位(结合型号换算)」录入): -- mes_wm_misc_issue_line(杂项出库)、mes_wm_outsource_issue_line(外协发料)、 -- mes_wm_product_issue_line(生产领料)、mes_wm_product_sales_line(销售出库)、 -- mes_wm_return_vendor_line(采购退货): -- item_model_id(物料型号编号)、enter_unit_measure_id(录入单位编号)、 -- enter_quantity(录入数量,按录入单位计); -- quantity 保持为「物料主单位口径的出库数量」,库存与库存流水口径不变。 -- ③ hrm_attendance_holiday 增加 2 列(考勤节假日配置由「单日」改为「时间段」录入): -- start_date(开始日期)、end_date(结束日期);原 date 列保留不动, -- 后端保存时仍写入开始日期,仅作存量兼容,业务判断改用 start_date/end_date。 -- ④ 配置数据以当前源库为准重导: -- - system_dict_type {n_dicttype}、system_dict_data {n_dictdata} -- - 协同流程定义按源库当前**激活**集合重导({n_procdef} 个流程定义 + 对应部署/字节资源 + 设计态模型) -- ⑤ 菜单/权限节点由阶段二跨库全量拷贝自动携带,无需改动本文件数据段。 -- 本次未新增菜单/权限点(节假日配置菜单及其按钮权限此前已入库,随阶段二自动携带)。 -- 验证:{verify} -- 范围:全库 BASE TABLE 共 {n_tables} 张,全部输出 CREATE TABLE; -- 仅以下配置类表带数据(INSERT),其余业务/系统/运行历史表只保留空表结构: -- ① 协同审批流程(引擎元数据+定义,仅激活流程,剔历史/运行/身份):act_ge_property、act_id_property、act_re_procdef、act_re_deployment、act_ge_bytearray、act_re_model、bpm_process_definition_info -- ② 协同(业务附件配置 + 分类 + 表单 + 扩展配置):bpm_business_attach、bpm_category、bpm_form、bpm_user_group、bpm_process_listener、bpm_process_expression -- ③ BI 决策大屏 / go-view:bi_dashboard、bi_chart_config、bi_data_source_config、bi_warehouse_coordinate、report_go_view_project -- ④ MES 编码规则(剔流水记录):mes_md_auto_code_rule、mes_md_auto_code_part -- ⑤ 系统配置主数据(字典/租户/公司配置;剔身份/菜单/日志):system_dict_type、system_dict_data、system_tenant、system_tenant_package、system_company_config -- ⚠ 身份/权限类表(system_users/role/menu/role_menu/user_role/dept/post/oauth2_client)此处仅建空表结构,数据由文件末尾"阶段二"跨库补导。 -- ⑥ AI 配置(仅模型+key)+ 基础设施配置管理:ai_model、ai_api_key、infra_config、infra_job、infra_file_config、infra_data_source_config、infra_codegen_table、infra_codegen_column -- 过滤:协同流程仅保留激活(SUSPENSION_STATE_=1)定义及其部署资源(bpmn/png 字节); -- 设计态模型 act_re_model 与模型源码字节(NAME_=source/source-extra)按源库全量导出(含个别已删除流程残留的模型, -- 源库实际存在即保留,供流程设计器打开);运行期变量字节(var-*/hist.var-*)一律不导出; -- 其余配置表仅导 deleted=0;编码规则剔除流水记录 mes_md_auto_code_record。 -- ⚠ 脚本含全库 DROP TABLE IF EXISTS,会把除本脚本建表外的所有表删除重建, -- 业务表数据将全部清空,仅可在全新/可覆盖的空库执行! -- ======================================================================= """ VERIFY_PLACEHOLDER = "(待空库整份执行验证后回填;见文件末尾生成备注)" def main(): tables = fetch_tables() views = fetch_view_count() if views: print("WARNING: 源库存在 %d 个视图,本生成器未处理" % views) ddl = dump_ddl(tables) missing = [t for t in tables if t not in ddl] extra = [t for t in ddl if t not in tables] if missing or extra: print("DDL 集合不一致 missing=%s extra=%s" % (missing, extra)) sys.exit(1) print("tables=%d ddl_blocks=%d" % (len(tables), len(ddl))) all_cfg = [tf for _, tfs in SECTIONS for tf in tfs] cfg_names = [t for t, _ in all_cfg] counts = fetch_counts(all_cfg) data = dump_data(all_cfg) for t in cfg_names: if counts.get(t, 0) > 0 and t not in data: print("MISSING data for %s (count=%d)" % (t, counts[t])) sys.exit(1) print("config tables=%d, with data=%d" % (len(cfg_names), len(data))) rest = [t for t in tables if t not in set(cfg_names)] print("rest tables=%d" % len(rest)) parts = [] header = HEADER.format( n_tables=len(tables), n_dicttype=counts["system_dict_type"], n_dictdata=counts["system_dict_data"], n_procdef=counts["act_re_procdef"], verify=VERIFY_PLACEHOLDER, ) parts.append(header) parts.append("SET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n" "SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';\n\n") for title, tfs in SECTIONS: parts.append("\n-- ***********************************************************************\n") parts.append("-- %s(DDL + 数据)\n" % title) parts.append("-- ***********************************************************************\n\n") for t, w in tfs: n = counts.get(t, 0) parts.append("-- ============ 表:%s(导出 %d 行) ============\n" % (t, n)) parts.append(ddl[t] + "\n") if n > 0: parts.append(data[t] + "\n") parts.append("\n") parts.append("\n-- ***********************************************************************\n") parts.append("-- 其余 %d 张表(仅表结构,无数据)\n" % len(rest)) parts.append("-- ***********************************************************************\n\n") for t in rest: parts.append("-- ============ 表:%s(仅 DDL,无数据) ============\n" % t) parts.append(ddl[t] + "\n\n") parts.append("\nSET FOREIGN_KEY_CHECKS = 1;\nSET SQL_MODE = @OLD_SQL_MODE;\n\n\n") parts.append(load_stage2_block()) text = "".join(parts) with open(OUT, "w", encoding="utf-8", newline="\n") as f: f.write(text) print("WROTE %s bytes=%d lines=%d" % (OUT, len(text.encode("utf-8")), text.count("\n"))) if __name__ == "__main__": main()