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

为什么要这样做?

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

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

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

一、起因与需求

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

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

目标 XML:

RPT-20250509-001

1

/path/设计方案.pdf

PDF

设计方案

2.4MB

2025-05-08 14:22:30

二、模型设计

借助 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 categories; // 多个分类

}

@Data

@XmlAccessorType(XmlAccessType.FIELD)

public static class Category {

@XmlAttribute(name = "title")

private String title; // 分类名称

@XmlElement(name = "Attachment")

private List 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> 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 categories = new ArrayList<>();

for (Map catMap : fileTreeList) {

ReportXml.Category cat = new ReportXml.Category();

cat.setTitle((String) catMap.get("name"));

List files = (List) catMap.get("fileList");

List 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> fileTreeList = new ArrayList<>();

// 分类一:项目评审材料

Map category1 = new HashMap<>();

category1.put("name", "项目评审材料");

List list1 = new ArrayList<>();

list1.add(createInfo("设计方案.pdf", "/data/reports/设计方案.pdf", 1));

list1.add(createInfo("预算表.xlsx", "/data/reports/预算表.xlsx", 2));

category1.put("fileList", list1);

// 分类二:审批文件

Map category2 = new HashMap<>();

category2.put("name", "审批文件");

List 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;

}

}

生成样例

RPT-20250509-001

1

/data/reports/设计方案.pdf

PDF

设计方案

1.5MB

2025-05-09 09:07:53

2

/data/reports/预算表.xlsx

XLSX

预算表

3.0MB

2025-05-09 09:07:53

1

/data/reports/审批意见.docx

DOCX

审批意见

1.5MB

2025-05-09 09:07:53