生成XML文件-这样子用更明了

本文以“项目报告与附件 XML 生成工具”为主题,分享如何在生产环境中使用 JAXBLombok 快速构建 Java 对象模型,并将后台业务数据转换成层次分明、易于扩展的 XML 文档。

为什么要这样做?

  • 可读性:层次分明,任何人一眼就能找到“报告编号”“附件列表”“每个附件的属性”。

  • 可扩展性:未来要加“上传人”“审核状态”……只需在模型里多写几个字段即可。

  • 兼容性:XML 是标准,大部分系统都能轻松解析与交互。

一、起因与需求

在大型后端系统中,往往存在“报表+附件”或“档案+文档”这样的场景,需要把数据库或服务返回的文件树结构导出成标准 XML,供上游系统或第三方存储解析。

  • 业务侧数据:List<Map<String,Object>> fileTreeList,每条 Map 包含分类名称 (name) 与该分类下若干 FileInfo 列表。

  • 目标 XML:

<report title="项目报告与附件">
  <ReportID title="报告编号">RPT-20250509-001</ReportID>
  <Attachments title="所有附件">
    <Category title="项目评审材料">
      <Attachment title="设计方案.pdf">
        <SEQ   title="序号">1</SEQ>
        <PATH  title="存储路径">/path/设计方案.pdf</PATH>
        <FORMAT title="格式">PDF</FORMAT>
        <NAME  title="文件名">设计方案</NAME>
        <SIZE  title="文件大小">2.4MB</SIZE>
        <CREATED title="创建时间">2025-05-08 14:22:30</CREATED>
      </Attachment>
      <!-- …更多附件… -->
    </Category>
    <!-- …更多分类… -->
  </Attachments>
</report>

二、模型设计

借助 JAXB(Java XML Binding)进行序列化,利用 Lombok 省略样板代码,使模型定义简洁明了。

XML对象:ReportXml 

@XmlRootElement(name = "report")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public class ReportXml {

    /** 报表标题,对应根节点的 title 属性 */
    @XmlAttribute(name = "title")
    private String title;

    /** 报告编号节点 */
    @XmlElement(name = "ReportID")
    private ReportID reportID;

    /** 附件容器节点 */
    @XmlElement(name = "Attachments")
    private Attachments attachments;

    @Data
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class ReportID {
        @XmlAttribute(name = "title")
        private String title;  // 如 "报告编号"
        @XmlValue
        private String value;  // 编号内容
    }

    @Data
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Attachments {
        @XmlAttribute(name = "title")
        private String title;              // 如 "所有附件"
        @XmlElement(name = "Category")
        private List<Category> categories; // 多个分类
    }

    @Data
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Category {
        @XmlAttribute(name = "title")
        private String title;                   // 分类名称
        @XmlElement(name = "Attachment")
        private List<Attachment> attachments;   // 多个附件
    }

    @Data
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Attachment {
        @XmlAttribute(name = "title")
        private String title;      // 文件名(带扩展名)

        @XmlElement(name = "SEQ")
        private TagWithTitle seq;  // 序号

        @XmlElement(name = "PATH")
        private TagWithTitle path; // 存储路径

        @XmlElement(name = "FORMAT")
        private TagWithTitle format; // 格式

        @XmlElement(name = "NAME")
        private TagWithTitle name;   // 文件名(不含扩展名)

        @XmlElement(name = "SIZE")
        private TagWithTitle size;   // 文件大小

        @XmlElement(name = "CREATED")
        private TagWithTitle created; // 创建时间
    }

    @Data
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class TagWithTitle {
        @XmlAttribute(name = "title")
        private String title;  // 节点描述,如“序号”“存储路径”
        @XmlValue
        private String value;  // 节点值
    }
}

 文件对象:FileInfo

/**
 * 业务层通用文件信息对象
 */
@Data
public class FileInfo {

    /**
     * 序号
     */
    private int pieceNo;

    /**
     * 文件在磁盘或存储服务上的完整路径
     */
    private String filePath;

    /**
     * 文件格式(如 "PDF"、"JPG" 等)
     */
    private String format;

    /**
     * 计算机端不带扩展名的文件名
     */
    private String computerFileName;

    /**
     * 带单位的文件大小字符串(如 "2.4MB")
     */
    private String size;

    /**
     * 文件创建或生成时间字符串(格式 "yyyy-MM-dd HH:mm:ss")
     */
    private String createTime;

    /**
     * 原始文件名(带扩展名)
     */
    private String originalFilename;
}

三、核心代码

    /**
     * 生成报告 XML 文件
     *
     * @param fileTreeList 项目文件树列表
     * @param outFilePath  输出文件路径
     * @param reportId     报告编号
     * @return 文件路径
    */
    private static String createReportXmlFile(List<Map<String, Object>> fileTreeList, String outFilePath, String reportId) {

        // 1. 根与编号
        ReportXml xml = new ReportXml();
        xml.setTitle("项目报告与附件");
        ReportXml.ReportID rid = new ReportXml.ReportID();
        rid.setTitle("报告编号");
        rid.setValue(reportId);
        xml.setReportID(rid);

        // 2. 附件容器
        ReportXml.Attachments atts = new ReportXml.Attachments();
        atts.setTitle("所有附件");

        // 3. 分类和附件
        List<ReportXml.Category> categories = new ArrayList<>();
        for (Map<String, Object> catMap : fileTreeList) {
            ReportXml.Category cat = new ReportXml.Category();
            cat.setTitle((String) catMap.get("name"));

            List<FileInfo> files = (List<FileInfo>) catMap.get("fileList");
            List<ReportXml.Attachment> atList = new ArrayList<>();
            int seq = 1;
            for (FileInfo info : files) {
                ReportXml.Attachment att = new ReportXml.Attachment();
                att.setTitle(info.getOriginalFilename());
                att.setSeq(newTag("序号", String.valueOf(seq++)));
                att.setPath(newTag("存储路径", info.getFilePath()));
                String fmt = info.getFilePath() .substring( info.getFilePath().lastIndexOf('.') + 1);
                att.setFormat(newTag("格式", fmt.toUpperCase()));
                String nameOnly = info.getOriginalFilename() .replace("." + fmt, "");
                att.setName(newTag("文件名", nameOnly));
                att.setSize(newTag("文件大小", info.getSize()));
                att.setCreated(newTag("创建时间", info.getCreateTime()));

                atList.add(att);
            }
            cat.setAttachments(atList);
            categories.add(cat);
        }
        atts.setCategories(categories);
        xml.setAttachments(atts);

        // 4. 写文件
        try {
            JAXBContext ctx = JAXBContext.newInstance(ReportXml.class);
            Marshaller m = ctx.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            File out = new File(outFilePath);
            if (out.getParentFile() != null) out.getParentFile().mkdirs();
            m.marshal(xml, out);
            return out.getAbsolutePath();
        } catch (Exception e) {
            throw new RuntimeException("生成报告 XML 失败", e);
        }
    }
    /**
     * 快速构造带 title 的节点
     *
     * @param title 节点标题
     * @param value 节点值
     * @return null
    */
    private static ReportXml.TagWithTitle newTag(String title, String value) {
        ReportXml.TagWithTitle tag = new ReportXml.TagWithTitle();
        tag.setTitle(title);
        tag.setValue(value);
        return tag;
    }

四、测试与运行案例

XmlUtil工具类

public class XmlUtil {
    public static void main(String[] args) {
        // 1. 准备测试数据:两类分类,每类下若干附件
        List<Map<String,Object>> fileTreeList = new ArrayList<>();

        // 分类一:项目评审材料
        Map<String,Object> category1 = new HashMap<>();
        category1.put("name", "项目评审材料");
        List<FileInfo> list1 = new ArrayList<>();
        list1.add(createInfo("设计方案.pdf", "/data/reports/设计方案.pdf", 1));
        list1.add(createInfo("预算表.xlsx", "/data/reports/预算表.xlsx", 2));
        category1.put("fileList", list1);

        // 分类二:审批文件
        Map<String,Object> category2 = new HashMap<>();
        category2.put("name", "审批文件");
        List<FileInfo> list2 = new ArrayList<>();
        list2.add(createInfo("审批意见.docx", "/data/reports/审批意见.docx", 1));
        category2.put("fileList", list2);

        fileTreeList.add(category1);
        fileTreeList.add(category2);

        // 2. 指定输出路径和报告编号
        String outPath = "output/report_with_attachments.xml";
        String reportId = "RPT-20250509-001";

        // 3. 调用生成方法
        String generated = createReportXmlFile(fileTreeList, outPath, reportId);
        System.out.println("XML 已生成,路径:" + generated);
    }

    /**
     * 快速创建FileInfo 对象
     */
    private static FileInfo createInfo(String originalFilename, String path, int seq) {
        FileInfo info = new FileInfo();
        info.setOriginalFilename(originalFilename);
        info.setFilePath(path);
        // 自动根据路径提取格式
        String fmt = path.substring(path.lastIndexOf('.') + 1).toUpperCase();
        info.setFormat(fmt);
        // 文件名(无扩展名)
        info.setComputerFileName(originalFilename.replace("." + fmt, ""));
        // 模拟大小
        info.setSize(seq * 1.5 + "MB");
        // 当前时间
        String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(new Date());
        info.setCreateTime(now);
        info.setPieceNo(seq);
        return info;
    }
}

生成样例

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<report title="项目报告与附件">
    <ReportID title="报告编号">RPT-20250509-001</ReportID>
    <Attachments title="所有附件">
        <Category title="项目评审材料">
            <Attachment title="设计方案.pdf">
                <SEQ title="序号">1</SEQ>
                <PATH title="存储路径">/data/reports/设计方案.pdf</PATH>
                <FORMAT title="格式">PDF</FORMAT>
                <NAME title="文件名">设计方案</NAME>
                <SIZE title="文件大小">1.5MB</SIZE>
                <CREATED title="创建时间">2025-05-09 09:07:53</CREATED>
            </Attachment>
            <Attachment title="预算表.xlsx">
                <SEQ title="序号">2</SEQ>
                <PATH title="存储路径">/data/reports/预算表.xlsx</PATH>
                <FORMAT title="格式">XLSX</FORMAT>
                <NAME title="文件名">预算表</NAME>
                <SIZE title="文件大小">3.0MB</SIZE>
                <CREATED title="创建时间">2025-05-09 09:07:53</CREATED>
            </Attachment>
        </Category>
        <Category title="审批文件">
            <Attachment title="审批意见.docx">
                <SEQ title="序号">1</SEQ>
                <PATH title="存储路径">/data/reports/审批意见.docx</PATH>
                <FORMAT title="格式">DOCX</FORMAT>
                <NAME title="文件名">审批意见</NAME>
                <SIZE title="文件大小">1.5MB</SIZE>
                <CREATED title="创建时间">2025-05-09 09:07:53</CREATED>
            </Attachment>
        </Category>
    </Attachments>
</report>

 

posted @ 2025-05-09 09:59  ~落辰~  阅读(120)  评论(0)    收藏  举报