证书数据 Excel 读取与 SQL 生成工具需求文档

证书数据 Excel 读取与 SQL 生成工具需求文档
一、功能概述
开发一个 Java 工具类,用于从 Excel 文件中读取资质证书数据,自动生成数据库操作 SQL 语句和导出 Excel 文件。
二、核心功能需求

  1. Excel 数据读取
    多 Sheet 支持:自动识别并读取 Excel 所有 Sheet 页
    按列解析:每列第 1 个非空数据作为父证书,同列下方数据作为子证书
    层级结构:构建树形证书结构(Sheet 名 → 父证书 → 子证书)
    数据限制:最大读取 100 行×100 列,连续 10 行/列为空则停止
  2. INSERT 语句生成
    主键规则:拼音名称 + 260402(如:renyuanzigezhengshu260402)
    生成范围:仅为每个 Sheet 页的父证书(首行证书)生成 INSERT 语句
    表结构:base_certificate
    字段映射:
    certificate_id:主键(拼音名 +260402)
    certificate_cn_name:证书中文名称
    parent_id:父级证书 ID(有父级时填写,否则 NULL)
    certificate_type:证书类型(固定为 2026002000001)
    have_parent_flag:是否有父级(Y/N)
  3. UPDATE 语句生成
    用途:将子证书名称统一更新为父证书名称
    目标表:RFDEV.TB_BASE_USER_QUALIFY
    生成规则:每个父证书对应一条 UPDATE 语句
    示例:
点击查看代码
UPDATE RFDEV.TB_BASE_USER_QUALIFY 
SET QUALIFY_NAME = '注册计量师证书(二级)' 
WHERE QUALIFY_NAME IN ('注册计量师证', '注册计量证', '二级注册计量师证书');
  1. IN 子句生成
    用途:用于数据库查询验证证书关联情况
    范围:包含所有 Sheet 页的所有父证书名称
    格式:IN ('证书 1', '证书 2', '证书 3')
  2. Excel 导出
    文件名:output_certificates_时间戳.xlsx
    包含内容:所有 Sheet 页的父证书数据(不包含子证书)
    字段顺序:
    Sheet 名称
    层级(1=父证书)
    排序号(从 1 开始递增)
    证书 ID(拼音名 +260402)
    证书名称
    父级 ID
    是否有父级
    三、技术实现要求
  3. 依赖库
    EasyExcel(3.3.2):Excel 读写
    pinyin4j(2.5.1):中文转拼音
    Lombok:简化代码
    Apache POI:获取 Sheet 信息
  4. 关键算法
    拼音转换:使用 pinyin4j 将中文名称转为小写无音调拼音
    树形构建:按列遍历,首行为父,其余为子
    去重处理:同一列中相同名称的子证书只保留一次
  5. 输出格式
    控制台输出:
    INSERT 语句(可注释掉不显示)
    IN 子句
    UPDATE 语句
    文件输出:Excel 文件(项目根目录)
    四、业务规则
  6. 证书层级关系
    一级证书:每列第 1 个非空数据(父证书)
    二级证书:同列第 1 个非空数据下方的数据(子证书)
    三级及以上:暂不支持
  7. 数据处理规则
    空值处理:跳过空单元格
    重复数据处理:同列中相同名称的子证书只保留一次
    Sheet 页命名:使用 Excel 原始 Sheet 名称作为根节点
  8. SQL 生成规则
    先父后子:INSERT 语句先生成父证书,再生成子证书
    外键关联:子证书的 parent_id 指向父证书的 certificate_id
    SQL 转义:自动处理名称中的单引号等特殊字符
    五、使用场景
    场景 1:批量导入证书数据
    使用生成的 INSERT 语句,将 Excel 中的证书分类批量导入数据库。
    场景 2:数据标准化
    使用 UPDATE 语句,将历史遗留的不规范证书名称统一为标准名称。
    场景 3:数据验证
    使用 IN 子句在数据库中查询已关联的证书,验证数据完整性。
    场景 4:数据备份
    导出的 Excel 文件可作为数据备份或迁移的中间文件格式。
点击查看代码
package org.dupl.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.Data;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import org.dupl.entry.BaseCertificate;

import java.util.*;

/**
 * Excel 证书数据读取工具
 * 按列解析:首行为父级,同列其他行为子级
 * 支持多 Sheet 页,Sheet 页名作为根节点
 * 固定读取最多 100 行×100 列,连续 10 行/10 列无数据则结束
 *
 * @author system
 * @date 2026-04-02
 */
public class CertificateExcelReader {

    // 最大行列数
    private static final int MAX_ROWS = 100;
    private static final int MAX_COLS = 100;
    // 连续空行/空列阈值
    private static final int EMPTY_THRESHOLD = 10;
    // 数据库表名
    private static final String TABLE_NAME = "base_certificate";
    // 主键后缀
    private static final String ID_SUFFIX = "260402";

    /**
     * 读取 Excel 文件并构建树形证书结构
     *
     * @param filePath Excel 文件路径
     * @return 证书树形列表(每个 Sheet 一个根节点)
     */
    public List<BaseCertificate> readCertificateTree(String filePath) {
        List<BaseCertificate> allSheetsCertificates = new ArrayList<>();
        
        // 获取所有 sheet 名称
        List<String> sheetNames = new ArrayList<>();
        try {
            org.apache.poi.ss.usermodel.Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(
                new java.io.File(filePath)
            );
            int numberOfSheets = workbook.getNumberOfSheets();
            for (int i = 0; i < numberOfSheets; i++) {
                sheetNames.add(workbook.getSheetName(i));
            }
            workbook.close();
        } catch (Exception e) {
            System.err.println("获取 Sheet 名称失败:" + e.getMessage());
            // 如果获取失败,至少读取第一个 sheet
            sheetNames.add("Sheet1");
        }
        
        System.out.println("\n========== Excel 信息 ==========");
        System.out.println("共发现 " + sheetNames.size() + " 个 Sheet 页:");
        for (int i = 0; i < sheetNames.size(); i++) {
            System.out.println("  " + (i + 1) + ". " + sheetNames.get(i));
        }
        System.out.println("================================\n");
        
        // 遍历每个 Sheet
        for (int i = 0; i < sheetNames.size(); i++) {
            System.out.println("\n========== 处理第 " + (i + 1) + " 个 Sheet: " + sheetNames.get(i) + " ==========");
            
            CertificateDataListener listener = new CertificateDataListener(sheetNames.get(i));
            
            EasyExcel.read(filePath, CertificateRowData.class, listener)
                    .sheet(i)  // 按索引读取 sheet
                    .headRowNumber(0)  // 不跳过表头,从第 1 行开始读
                    .doRead();
            
            List<BaseCertificate> sheetCertificates = listener.getCertificateTree();
            
            // 如果该 sheet 有证书数据,创建一个以 sheet 名为根的节点
            if (!sheetCertificates.isEmpty()) {
                BaseCertificate sheetRoot = new BaseCertificate();
                sheetRoot.setCertificateId("SHEET_" + i);
                sheetRoot.setCertificateCnName(sheetNames.get(i));
                sheetRoot.setCertificateType("ROOT");
                sheetRoot.setDeleteFlag("0");
                sheetRoot.setHaveParentFlag("N");
                sheetRoot.setChildren(sheetCertificates);
                
                allSheetsCertificates.add(sheetRoot);
                
                System.out.println("Sheet '" + sheetNames.get(i) + "' 处理完成,共 " + sheetCertificates.size() + " 个证书分类");
            }
        }
        
        return allSheetsCertificates;
    }

    /**
     * Excel 行数据类 - 固定 100 列
     */
    @Data
    public static class CertificateRowData {
        private String col0;
        private String col1;
        private String col2;
        private String col3;
        private String col4;
        private String col5;
        private String col6;
        private String col7;
        private String col8;
        private String col9;
        private String col10;
        private String col11;
        private String col12;
        private String col13;
        private String col14;
        private String col15;
        private String col16;
        private String col17;
        private String col18;
        private String col19;
        private String col20;
        private String col21;
        private String col22;
        private String col23;
        private String col24;
        private String col25;
        private String col26;
        private String col27;
        private String col28;
        private String col29;
        private String col30;
        private String col31;
        private String col32;
        private String col33;
        private String col34;
        private String col35;
        private String col36;
        private String col37;
        private String col38;
        private String col39;
        private String col40;
        private String col41;
        private String col42;
        private String col43;
        private String col44;
        private String col45;
        private String col46;
        private String col47;
        private String col48;
        private String col49;
        private String col50;
        private String col51;
        private String col52;
        private String col53;
        private String col54;
        private String col55;
        private String col56;
        private String col57;
        private String col58;
        private String col59;
        private String col60;
        private String col61;
        private String col62;
        private String col63;
        private String col64;
        private String col65;
        private String col66;
        private String col67;
        private String col68;
        private String col69;
        private String col70;
        private String col71;
        private String col72;
        private String col73;
        private String col74;
        private String col75;
        private String col76;
        private String col77;
        private String col78;
        private String col79;
        private String col80;
        private String col81;
        private String col82;
        private String col83;
        private String col84;
        private String col85;
        private String col86;
        private String col87;
        private String col88;
        private String col89;
        private String col90;
        private String col91;
        private String col92;
        private String col93;
        private String col94;
        private String col95;
        private String col96;
        private String col97;
        private String col98;
        private String col99;
    }

    /**
     * 监听器类 - 处理按列读取的数据
     */
    private static class CertificateDataListener implements ReadListener<CertificateRowData> {
        
        private String sheetName; // Sheet 名称
        
        // 二维数组存储数据 [行][列]
        private String[][] dataGrid = new String[MAX_ROWS][MAX_COLS];
        private int maxRowUsed = 0;
        
        // 最终生成的证书树
        private List<BaseCertificate> certificateTree = new ArrayList<>();

        public CertificateDataListener(String sheetName) {
            this.sheetName = sheetName;
        }

        @Override
        public void invoke(CertificateRowData data, AnalysisContext context) {
            int rowNum = context.readRowHolder().getRowIndex();
            
            if (rowNum >= MAX_ROWS) {
                return; // 超过最大行数
            }
            
            // 提取每一列的数据
            for (int col = 0; col < MAX_COLS; col++) {
                String value = getCellValue(data, col);
                dataGrid[rowNum][col] = value;
            }
            
            maxRowUsed = Math.max(maxRowUsed, rowNum);
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // 构建证书树
            certificateTree = buildTree();
            System.out.println("证书树构建完成,共 " + certificateTree.size() + " 个根证书");
        }

        /**
         * 获取单元格值
         */
        private String getCellValue(CertificateRowData data, int colIndex) {
            switch (colIndex) {
                case 0: return data.getCol0();
                case 1: return data.getCol1();
                case 2: return data.getCol2();
                case 3: return data.getCol3();
                case 4: return data.getCol4();
                case 5: return data.getCol5();
                case 6: return data.getCol6();
                case 7: return data.getCol7();
                case 8: return data.getCol8();
                case 9: return data.getCol9();
                case 10: return data.getCol10();
                case 11: return data.getCol11();
                case 12: return data.getCol12();
                case 13: return data.getCol13();
                case 14: return data.getCol14();
                case 15: return data.getCol15();
                case 16: return data.getCol16();
                case 17: return data.getCol17();
                case 18: return data.getCol18();
                case 19: return data.getCol19();
                case 20: return data.getCol20();
                case 21: return data.getCol21();
                case 22: return data.getCol22();
                case 23: return data.getCol23();
                case 24: return data.getCol24();
                case 25: return data.getCol25();
                case 26: return data.getCol26();
                case 27: return data.getCol27();
                case 28: return data.getCol28();
                case 29: return data.getCol29();
                case 30: return data.getCol30();
                case 31: return data.getCol31();
                case 32: return data.getCol32();
                case 33: return data.getCol33();
                case 34: return data.getCol34();
                case 35: return data.getCol35();
                case 36: return data.getCol36();
                case 37: return data.getCol37();
                case 38: return data.getCol38();
                case 39: return data.getCol39();
                case 40: return data.getCol40();
                case 41: return data.getCol41();
                case 42: return data.getCol42();
                case 43: return data.getCol43();
                case 44: return data.getCol44();
                case 45: return data.getCol45();
                case 46: return data.getCol46();
                case 47: return data.getCol47();
                case 48: return data.getCol48();
                case 49: return data.getCol49();
                case 50: return data.getCol50();
                case 51: return data.getCol51();
                case 52: return data.getCol52();
                case 53: return data.getCol53();
                case 54: return data.getCol54();
                case 55: return data.getCol55();
                case 56: return data.getCol56();
                case 57: return data.getCol57();
                case 58: return data.getCol58();
                case 59: return data.getCol59();
                case 60: return data.getCol60();
                case 61: return data.getCol61();
                case 62: return data.getCol62();
                case 63: return data.getCol63();
                case 64: return data.getCol64();
                case 65: return data.getCol65();
                case 66: return data.getCol66();
                case 67: return data.getCol67();
                case 68: return data.getCol68();
                case 69: return data.getCol69();
                case 70: return data.getCol70();
                case 71: return data.getCol71();
                case 72: return data.getCol72();
                case 73: return data.getCol73();
                case 74: return data.getCol74();
                case 75: return data.getCol75();
                case 76: return data.getCol76();
                case 77: return data.getCol77();
                case 78: return data.getCol78();
                case 79: return data.getCol79();
                case 80: return data.getCol80();
                case 81: return data.getCol81();
                case 82: return data.getCol82();
                case 83: return data.getCol83();
                case 84: return data.getCol84();
                case 85: return data.getCol85();
                case 86: return data.getCol86();
                case 87: return data.getCol87();
                case 88: return data.getCol88();
                case 89: return data.getCol89();
                case 90: return data.getCol90();
                case 91: return data.getCol91();
                case 92: return data.getCol92();
                case 93: return data.getCol93();
                case 94: return data.getCol94();
                case 95: return data.getCol95();
                case 96: return data.getCol96();
                case 97: return data.getCol97();
                case 98: return data.getCol98();
                case 99: return data.getCol99();
                default: return null;
            }
        }

        /**
         * 构建树形结构
         * 每一列的第 1 个数据是父级,其他数据是子级
         */
        private List<BaseCertificate> buildTree() {
            List<BaseCertificate> tree = new ArrayList<>();
            int certificateIdCounter = 1;
            
            // 遍历每一列
            int emptyCols = 0; // 连续空列计数
            for (int col = 0; col < MAX_COLS && emptyCols < EMPTY_THRESHOLD; col++) {
                List<String> columnValues = new ArrayList<>();
                
                // 收集该列的数据(最多 MAX_ROWS 行)
                // 注意:不跳过空值,保留原始位置
                for (int row = 0; row <= maxRowUsed; row++) {
                    String value = dataGrid[row][col];
                    columnValues.add(value); // 保留空值占位
                }
                
                // 检查该列是否全为空
                boolean allEmpty = true;
                for (String val : columnValues) {
                    if (val != null && !val.trim().isEmpty()) {
                        allEmpty = false;
                        break;
                    }
                }
                
                if (allEmpty) {
                    emptyCols++;
                    continue;
                } else {
                    emptyCols = 0; // 重置空列计数
                }
                
                // 第一个非空数据作为父级证书
                String parentValue = null;
                int parentRowIndex = -1;
                for (int i = 0; i < columnValues.size(); i++) {
                    if (columnValues.get(i) != null && !columnValues.get(i).trim().isEmpty()) {
                        parentValue = columnValues.get(i).trim();
                        parentRowIndex = i;
                        break;
                    }
                }
                
                if (parentValue == null) {
                    continue;
                }
                
                BaseCertificate parentCert = new BaseCertificate();
                parentCert.setCertificateId("CERT_" + (certificateIdCounter++));
                parentCert.setCertificateCnName(parentValue);
                parentCert.setCertificateType("2026002000001"); // 人员资质证书
                parentCert.setDeleteFlag("0");
                parentCert.setHaveParentFlag("N");
                parentCert.setChildren(new ArrayList<>());
                
                // 收集该列其他非空数据作为子证书
                Set<String> addedChildren = new HashSet<>(); // 去重
                for (int i = parentRowIndex + 1; i < columnValues.size(); i++) {
                    String childValue = columnValues.get(i);
                    if (childValue != null && !childValue.trim().isEmpty() && !addedChildren.contains(childValue.trim())) {
                        BaseCertificate childCert = new BaseCertificate();
                        childCert.setCertificateId("CERT_" + (certificateIdCounter++));
                        childCert.setCertificateCnName(childValue.trim());
                        childCert.setParentId(parentCert.getCertificateId());
                        childCert.setCertificateType("2026002000001");
                        childCert.setDeleteFlag("0");
                        childCert.setHaveParentFlag("Y");
                        
                        parentCert.getChildren().add(childCert);
                        addedChildren.add(childValue.trim());
                    }
                }
                
                tree.add(parentCert);
            }
            
            // 打印证书树
            printTree(tree);
            
            return tree;
        }

        /**
         * 打印证书树(精简版)
         */
        private void printTree(List<BaseCertificate> tree) {
            System.out.println("\n【" + sheetName + "】共 " + tree.size() + " 个证书分类:");
            for (BaseCertificate parent : tree) {
                System.out.println("  ├─ " + parent.getCertificateCnName() + " (" + parent.getChildren().size() + " 个子证书)");
                if (parent.getChildren() != null && !parent.getChildren().isEmpty()) {
                    for (BaseCertificate child : parent.getChildren()) {
                        System.out.println("  │   └─ " + child.getCertificateCnName());
                    }
                }
            }
        }

        public List<BaseCertificate> getCertificateTree() {
            return certificateTree;
        }
    }

    /**
     * 主方法 - 测试
     */
    public static void main(String[] args) {
        String filePath = "src/main/resources/人员资质(4).xlsx";
        
        CertificateExcelReader reader = new CertificateExcelReader();
        List<BaseCertificate> certificates = reader.readCertificateTree(filePath);
        
        System.out.println("\n\n========== 最终汇总 ==========");
        System.out.println("读取完成!共 " + certificates.size() + " 个 Sheet 页");
        
        // 打印汇总信息
        for (BaseCertificate sheetCert : certificates) {
            System.out.println("\n📋 " + sheetCert.getCertificateCnName() + " (共 " + sheetCert.getChildren().size() + " 个证书分类)");
        }
        
        System.out.println("\n====================================\n");
        
        // 生成 INSERT 语句
        System.out.println("\n========== INSERT 语句 ==========");
        String insertSql = generateInsertStatements(certificates);
//        System.out.println(insertSql);
        System.out.println("\n====================================\n");
        
        // 生成 IN 子句
        System.out.println("\n========== IN 子句 ==========");
        String inClause = generateInClause(certificates);
        System.out.println(inClause);
        System.out.println("\n====================================\n");
        
        // 生成 UPDATE 语句
        System.out.println("\n========== UPDATE 语句 ==========");
        String updateSql = generateUpdateStatements(certificates);
        System.out.println(updateSql);
        System.out.println("\n====================================\n");
        
        // 输出 Excel 文件
        String outputFilePath = "output_certificates_" + System.currentTimeMillis() + ".xlsx";
        exportToExcel(certificates, outputFilePath);
        System.out.println("Excel 文件已生成:" + outputFilePath);
        
        // 可以进一步处理,比如保存到数据库
        // saveToDatabase(certificates);
    }

    /**
     * 将中文转换为拼音(小写,无音调)
     */
    private static String toPinyin(String chinese) {
        if (chinese == null || chinese.trim().isEmpty()) {
            return "";
        }
        
        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        format.setVCharType(HanyuPinyinVCharType.WITH_V);
        
        StringBuilder pinyin = new StringBuilder();
        char[] chars = chinese.trim().toCharArray();
        
        try {
            for (char c : chars) {
                if (c >= 0x4e00 && c <= 0x9fa5) {  // 中文字符
                    String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c, format);
                    if (pinyins != null && pinyins.length > 0) {
                        pinyin.append(pinyins[0]);
                    } else {
                        pinyin.append(c);
                    }
                } else if (Character.isLetterOrDigit(c)) {  // 字母数字保留
                    pinyin.append(c);
                }
                // 其他字符(空格、标点等)跳过
            }
        } catch (Exception e) {
            System.err.println("拼音转换失败:" + e.getMessage());
            return chinese.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
        }
        
        return pinyin.toString();
    }

    /**
     * 生成主键 ID:拼音 + 260402
     */
    private static String generateId(String name) {
        String pinyin = toPinyin(name);
        // 限制长度,避免过长
        if (pinyin.length() > 50) {
            pinyin = pinyin.substring(0, 50);
        }
        return pinyin + ID_SUFFIX;
    }

    /**
     * 生成 INSERT 语句(只生成父证书,不生成子证书)
     */
    private static String generateInsertStatements(List<BaseCertificate> certificates) {
        StringBuilder sqlBuilder = new StringBuilder();
        // 用于存储证书名称到 ID 的映射
        Map<String, String> certNameToIdMap = new HashMap<>();
        
        for (BaseCertificate sheetRoot : certificates) {
            // 为该 Sheet 下的所有证书生成 INSERT(只收集父证书)
            if (sheetRoot.getChildren() != null) {
                for (BaseCertificate cert : sheetRoot.getChildren()) {
                    collectParentCertificates(cert, certNameToIdMap);
                }
            }
        }
        
        // 生成 INSERT 语句(只生成父证书)
        for (BaseCertificate sheetRoot : certificates) {
            if (sheetRoot.getChildren() != null) {
                for (BaseCertificate cert : sheetRoot.getChildren()) {
                    sqlBuilder.append(generateInsertStatement(cert, certNameToIdMap)).append("\n");
                }
            }
        }
        
        return sqlBuilder.toString();
    }

    /**
     * 递归收集所有证书及其 ID 映射(只收集父级证书)
     */
    private static void collectParentCertificates(BaseCertificate cert, Map<String, String> certNameToIdMap) {
        String id = generateId(cert.getCertificateCnName());
        certNameToIdMap.put(cert.getCertificateCnName(), id);
        cert.setCertificateId(id); // 设置实际的 ID
        
        // 递归处理子证书(但不为子证书生成 INSERT)
        if (cert.getChildren() != null && !cert.getChildren().isEmpty()) {
            for (BaseCertificate child : cert.getChildren()) {
                collectParentCertificates(child, certNameToIdMap);
            }
        }
    }

    /**
     * 递归收集所有证书及其 ID 映射
     */
    private static void collectCertificates(BaseCertificate cert, Map<String, String> certNameToIdMap) {
        String id = generateId(cert.getCertificateCnName());
        certNameToIdMap.put(cert.getCertificateCnName(), id);
        cert.setCertificateId(id); // 设置实际的 ID
        
        if (cert.getChildren() != null && !cert.getChildren().isEmpty()) {
            for (BaseCertificate child : cert.getChildren()) {
                // 为子证书设置正确的父级 ID
                child.setParentId(id);
                collectCertificates(child, certNameToIdMap);
            }
        }
    }

    /**
     * 生成单条 INSERT 语句
     */
    private static String generateInsertStatement(BaseCertificate cert, Map<String, String> certNameToIdMap) {
        String id = cert.getCertificateId(); // 使用已生成的 ID
        String parentId = cert.getParentId(); // 已经是正确的父级 ID
        
        return String.format(
            "INSERT INTO %s (certificate_id, certificate_code, certificate_cn_name, certificate_en_name, parent_id, certificate_type, history_certificate_names, delete_flag, have_parent_flag) " +
            "VALUES ('%s', NULL, '%s', NULL, %s, '%s', NULL, '%s', '%s');",
            TABLE_NAME,
            id,
            escapeSql(cert.getCertificateCnName()),
            parentId != null ? "'" + parentId + "'" : "NULL",
            cert.getCertificateType(),
            cert.getDeleteFlag(),
            cert.getHaveParentFlag()
        );
    }

    /**
     * SQL 转义:处理单引号等特殊字符
     */
    private static String escapeSql(String str) {
        if (str == null) {
            return "";
        }
        return str.replace("'", "''");
    }

    /**
     * 生成 UPDATE 语句(将子证书名称更新为带层级的名称)
     */
    private static String generateUpdateStatements(List<BaseCertificate> certificates) {
        StringBuilder sqlBuilder = new StringBuilder();
        Map<String, Set<String>> parentToChildrenMap = new HashMap<>();
        
        // 收集父子关系
        for (BaseCertificate sheetRoot : certificates) {
            if (sheetRoot.getChildren() != null) {
                for (BaseCertificate parentCert : sheetRoot.getChildren()) {
                    if (parentCert.getChildren() != null && !parentCert.getChildren().isEmpty()) {
                        String parentName = parentCert.getCertificateCnName();
                        Set<String> childNames = parentToChildrenMap.computeIfAbsent(parentName, k -> new HashSet<>());
                        
                        for (BaseCertificate childCert : parentCert.getChildren()) {
                            childNames.add(childCert.getCertificateCnName());
                        }
                    }
                }
            }
        }
        
        // 为每个父证书生成 UPDATE 语句
        for (Map.Entry<String, Set<String>> entry : parentToChildrenMap.entrySet()) {
            String parentName = entry.getKey();
            Set<String> childNames = entry.getValue();
            
            StringBuilder inClause = new StringBuilder("IN (");
            boolean first = true;
            for (String childName : childNames) {
                if (!first) {
                    inClause.append(", ");
                }
                inClause.append("'").append(escapeSql(childName)).append("'");
                first = false;
            }
            inClause.append(")");
            
            String updateSql = String.format(
                "UPDATE RFDEV.TB_BASE_USER_QUALIFY \n" +
                "SET QUALIFY_NAME = '%s' \n" +
                "WHERE QUALIFY_NAME %s;\n",
                escapeSql(parentName),
                inClause.toString()
            );
            
            sqlBuilder.append(updateSql);
        }
        
        return sqlBuilder.toString();
    }
    private static void exportToExcel(List<BaseCertificate> certificates, String filePath) {
        List<CertificateExportData> dataList = new ArrayList<>();
        int sortOrder = 1;
        
        for (BaseCertificate sheetRoot : certificates) {
            System.out.println("处理 Sheet: " + sheetRoot.getCertificateCnName());
            if (sheetRoot.getChildren() != null) {
                for (BaseCertificate parentCert : sheetRoot.getChildren()) {
                    // 添加父证书
                    CertificateExportData parentData = new CertificateExportData();
                    parentData.setSheetName(sheetRoot.getCertificateCnName());
                    parentData.setLevel(1);
                    parentData.setSortOrder(sortOrder++);
                    parentData.setCertificateId(parentCert.getCertificateId());
                    parentData.setCertificateName(parentCert.getCertificateCnName());
                    parentData.setParentId(parentCert.getParentId());
                    parentData.setHaveParentFlag(parentCert.getHaveParentFlag());
                    dataList.add(parentData);
                    // 不添加子证书
                }
            }
        }
        
        // 写入 Excel
        EasyExcel.write(filePath, CertificateExportData.class)
                .sheet("证书数据")
                .doWrite(dataList);
    }

    /**
     * 生成 IN 子句(只包含父证书,包含所有 Sheet 页)
     */
    private static String generateInClause(List<BaseCertificate> certificates) {
        Set<String> certNames = new LinkedHashSet<>();
        
        for (BaseCertificate sheetRoot : certificates) {
            if (sheetRoot.getChildren() != null) {
                for (BaseCertificate parentCert : sheetRoot.getChildren()) {
                    certNames.add(parentCert.getCertificateCnName());
                    // 不包含子证书
                }
            }
        }
        
        StringBuilder inClause = new StringBuilder("IN (");
        boolean first = true;
        for (String name : certNames) {
            if (!first) {
                inClause.append(", ");
            }
            inClause.append("'").append(escapeSql(name)).append("'");
            first = false;
        }
        inClause.append(")");
        
        return inClause.toString();
    }

    /**
     * Excel 导出数据类
     */
    @Data
    public static class CertificateExportData {
        @com.alibaba.excel.annotation.ExcelProperty(value = "Sheet 名称", index = 0)
        private String sheetName;
        
        @com.alibaba.excel.annotation.ExcelProperty(value = "层级", index = 1)
        private Integer level;
        
        @com.alibaba.excel.annotation.ExcelProperty(value = "排序", index = 2)
        private Integer sortOrder;
        
        @com.alibaba.excel.annotation.ExcelProperty(value = "证书 ID", index = 3)
        private String certificateId;
        
        @com.alibaba.excel.annotation.ExcelProperty(value = "证书名称", index = 4)
        private String certificateName;
        
        @com.alibaba.excel.annotation.ExcelProperty(value = "父级 ID", index = 5)
        private String parentId;
        
        @com.alibaba.excel.annotation.ExcelProperty(value = "是否有父级", index = 6)
        private String haveParentFlag;
    }
}

posted @ 2026-04-03 10:39  严重焦虑症  阅读(15)  评论(0)    收藏  举报