1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# -*- coding: utf-8 -*-
"""重新生成 docs/sql/config_export_all_20260928.sql(全量重生成,替换 0918 版)。
 
阶段一:当前源库 ruoyi-vue-pro 全库 416 张表 CREATE TABLE + 6 类配置数据(mysqldump 字面量)。
阶段二:沿用 0918 文件的跨库身份/菜单/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\mom-pro2-after\docs\sql"
SRC_0918 = BASE_DIR + r"\config_export_all_20260918.sql"
OUT = BASE_DIR + r"\config_export_all_20260928.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_0918, 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("0918 文件未找到阶段二段落")
 
 
HEADER = """-- =======================================================================
-- 文件:config_export_all_20260928.sql
-- 用途:ruoyi-vue-pro「全库表结构 + 6 类配置数据」导出,供同结构全新库初始化
--       阶段一:全库 {n_tables} 张表 CREATE TABLE + 6 类配置数据(字面量,自包含,不依赖源库在线)
--       阶段二:跨库补导 菜单/角色/用户/OAuth2 等身份数据(执行期从源库拷贝)
-- 来源库:ruoyi-vue-pro
-- 基线:2026-09-18 版(419 张表)→ 本次为**全量重生成**(不再基于旧文件打补丁):
--       直接按当前源库最新状态重导,一次性覆盖 0918 之后全部结构与配置漂移。
-- 本次变更(相对 0918,2026-09-28):
--   ① 表结构:419 → {n_tables} 张
--      - 删除 6 张历史备份表(bpm_bak_20260904_bytearray/category/deployment/model/procdef/procinfo,源库已无)
--      + 新增 3 张表(after_sale_problem、after_sale_visit、bpm_process_auto_approve_config,仅结构,源库无数据)
--      + 10 张表 DDL 与 0918 不同,以当前源库为准(after_sale_ticket、mes_qc_indicator、mes_qc_indicator_result_detail、
--        mes_qc_ipqc、mes_qc_iqc、mes_qc_oqc、mes_qc_rqc、mes_qc_template、mes_qc_template_indicator、mes_wm_return_issue_line)
--   ② 配置数据以当前源库为准重导(下列为 0918 声明值 → 本次导出值):
--      - system_dict_type 241→{n_dicttype}、system_dict_data 1157→{n_dictdata}(含 hrm_holiday_type 节假日类型等新增字典)
--      - system_company_config 0→1、bi_chart_config 36→37、bi_warehouse_coordinate 1→2、ai_api_key 1→3、bpm_user_group 3→4
--      - bpm_business_attach 11→4、bpm_category 22→15、bpm_form 4→3、bpm_process_listener 1→0(源库已删除的行不再导出)
--      - 协同流程定义按源库当前**激活**集合重导({n_procdef} 个流程定义 + 对应部署/字节资源 + 设计态模型)
--   ③ 菜单/权限节点(节假日配置 6014/60141-60145、各模块导入按钮 1075311/1075417-1075420 等)由阶段二
--      跨库全量拷贝自动携带,无需改动本文件数据段;在此登记以说明本版文件确实覆盖这些变更。
-- 验证:{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()