regen_derived.py 5.49 KB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""
补齐 `restore_p1.py` 恢复不到的那 16 个文件。

它们不是用 Write 工具写的,因此不在会话 jsonl 的 Write/Edit 记录里:
  * 12 个 Mapper 接口 —— 是当时用一句 bash 函数 `gen()` + heredoc 批量生成的
  * 4 个 apibase 常量/POJO —— 是 `sed` 从老 `zteits-api` 复制改包名生成的

两种生成方式都是确定性的,本脚本按当时**一模一样**的规则重算:
  * Mapper:模板取自会话里那条 `gen()` 命令,`OrgMapper.java`(它是用 Write
    写的、已被 restore_p1.py 恢复)用来交叉验证模板的最终形态(含当时补的
    `sed '/^\*$/d'` 去掉多余星号行)。
  * apibase:直接重放当时那条 `sed -e 's/com.zteits.clouds.api.apibase/…/g'`。

输出写到 tools/recover/out/,与其他恢复产物合并后再一起拷回源码树。
"""

import os
import re

CLOUD = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
LEGACY = os.path.join(os.path.dirname(CLOUD), "zteits-clouds")
STAGE = os.path.join(CLOUD, "tools/recover/out")

SYS = "parking-module-system/src/main/java/com/zteits/parking/module/system"
API = "parking-module-api/src/main/java/com/zteits/parking/module/api/apibase"

# (包/DO 所在子包, 类名前缀, 中文描述, extra) —— 顺序与当时 gen() 的调用完全一致
# extra 是当时生成器的第 4 个参数,三个关联表被传成了 "*" / "'*'",
# 落进 javadoc 就是一行多余的星号,随后由那条 `sed '/^\*$/d'` 删掉。
# 这里如实保留 extra,再在下面用等价的 sed 逻辑清掉,才能和当时的最终文件逐字节一致
# (extra 非空的那三个**没有**空行,extra 为空的那些有——这个差异肉眼看不出来,
#  但对"恢复"来说必须还原)。
MAPPERS = [
    ("user", "User", "用户", ""),
    ("permission", "Role", "角色", ""),
    ("permission", "Menu", "菜单", "*"),
    ("permission", "UserRole", "用户和角色的关联", "'*'"),
    ("permission", "RoleMenu", "角色和菜单的关联", "'*'"),
    ("dict", "DictType", "字典类型", ""),
    ("dict", "DictData", "字典数据", ""),
    ("oauth2", "OAuth2AccessToken", "OAuth2 访问令牌", ""),
    ("oauth2", "OAuth2RefreshToken", "OAuth2 刷新令牌", ""),
    ("oauth2", "OAuth2Client", "OAuth2 客户端", ""),
    ("logger", "LoginLog", "登录日志", ""),
    ("logger", "OperateLog", "操作日志", ""),
]

MAPPER_TPL = """package com.zteits.parking.module.system.dal.mysql.{sub};

import com.zteits.parking.framework.mybatis.core.mapper.BaseMapperX;
import com.zteits.parking.module.system.dal.dataobject.{sub}.{name}DO;
import org.apache.ibatis.annotations.Mapper;

/**
 * {desc} Mapper
 *
 * ⚠️ 每个 Mapper **必须**带 {{@link Mapper}} 注解:
 * `ParkingMybatisAutoConfiguration` 用的是
 * `@MapperScan(value = "${{parking.info.base-package}}", annotationClass = Mapper.class)`,
 * 漏写注解不会被扫描到,报 "Invalid bound statement (not found)"。
{extra}
 * @author ZIEITS
 */
@Mapper
public interface {name}Mapper extends BaseMapperX<{name}DO> {{
}}
"""


def drop_star_lines(text):
    """等价于当时的 `sed -e '/^\\*$/d' -e "/^'\\*'$/d"`:删掉整行只有 * 或 '*' 的行。"""
    return "".join(
        ln for ln in text.splitlines(keepends=True) if ln.rstrip("\n") not in ("*", "'*'")
    )

# sed 的目标:apibase 包名整体改名
APIBASE_FILES = [
    "bean/BizResult.java",
    "bean/ResultBean.java",
    "bean/ErrorCode.java",
    "constants/ErrorType.java",
]
OLD_PKG = "com.zteits.clouds.api.apibase"
NEW_PKG = "com.zteits.parking.module.api.apibase"


def write(rel, text):
    dst = os.path.join(STAGE, rel)
    os.makedirs(os.path.dirname(dst), exist_ok=True)
    # 保持与当时一致的 LF 结尾 + 文件末尾换行
    with open(dst, "w", encoding="utf-8", newline="\n") as f:
        f.write(text)


def main():
    for sub, name, desc, extra in MAPPERS:
        text = MAPPER_TPL.format(sub=sub, name=name, desc=desc, extra=extra)
        write(f"{SYS}/dal/mysql/{sub}/{name}Mapper.java", drop_star_lines(text))
    print(f"✔ 生成 {len(MAPPERS)} 个 Mapper 接口")

    # apibase:老源码存在才做,否则明确报错而不是产出空文件
    src_root = os.path.join(LEGACY, "zteits-api/src/main/java/com/zteits/clouds/api/apibase")
    if not os.path.isdir(src_root):
        raise SystemExit(f"!! 老 apibase 目录不存在:{src_root}")
    for rel in APIBASE_FILES:
        src = os.path.join(src_root, rel)
        if not os.path.isfile(src):
            raise SystemExit(f"!! 老文件不存在:{src}")
        with open(src, encoding="utf-8", errors="surrogateescape") as f:
            text = f.read()
        text = text.replace(OLD_PKG, NEW_PKG)
        write(f"{API}/{rel}", text)
    print(f"✔ 由 {OLD_PKG} 重放改名生成 {len(APIBASE_FILES)} 个 apibase 文件")

    # 自检:新包名生效、老包名无残留
    bad = []
    for rel in APIBASE_FILES:
        text = open(os.path.join(STAGE, f"{API}/{rel}"), encoding="utf-8").read()
        if OLD_PKG in text:
            bad.append(rel)
        if rel == "constants/ErrorType.java":
            # 与当时那条 `grep -c '^    [A-Z_0-9]*('` 等价,用来对齐历史计数 142
            n = sum(1 for ln in text.splitlines() if re.match(r"^    [A-Z_0-9]*\(", ln))
            print(f"   ErrorType 枚举值:{n}(当时记录为 142)")
    if bad:
        raise SystemExit(f"!! 仍有老包名残留:{bad}")


if __name__ == "__main__":
    main()