Java使用mpxj导入.mpp格式的Project文件(甘特图)

article/2025/9/14 19:48:17

这里写图片描述

最近换工作了,主要的项目都是企业内部为支撑的管理平台,刚入入职没多久,遇到了一个需求,就是导入微软的Project文件,踩过不少坑,所以记录一下,后续还有从数据库导出Project引导文件,也就是xml文件

依赖

<!-- 读取project文件 -->
<dependency><groupId>net.sf.mpxj</groupId><artifactId>mpxj</artifactId><version>7.1.0</version>
</dependency>

代码

博主使用的读取方式是递归读取的,无论Project文件中的任务有多少层,都可以读到,好了,直接看代码,注意看注释:

核心方法

@Transactional@Overridepublic void readMmpFileToDB(File file) {//如果读取的是MultipartFile,那么直接使用获取InputStream即可try{//这个是读取文件的组件MPPReader mppRead = new MPPReader();//注意,如果在这一步出现了读取异常,肯定是版本不兼容,换个版本试试ProjectFile pf = mppRead.read(file);//从文件中获取的任务对象List<Task> tasks = pf.getChildTasks();//这个可以不用,这个list只是我用来装下所有的数据,如果不需要可以不使用List<Project> proList = new LinkedList<>();//这个是用来封装任务的对象,为了便于区别,初始化批次号,然后所有读取的数据都需要加上批次号Project pro = new Project();pro.setBatchNum(StringUtils.UUID());//生成批次号UUID//这个方法是一个递归方法getChildrenTask(tasks.get(0), pro ,proList, 0);}catch (MPXJException e) {logger.error(e.getMessage());throw new RuntimeException();} catch (Exception e) {logger.error(e.getMessage());throw new RuntimeException();}}
 /*** 这个方法是一个递归* 方法的原理:进行读取父任务,如果下一层任务还是父任务,那么继续调用当前方法,如果到了最后一层,调用另外一个读取底层的方法* @param task* @param project* @param list* @param levelNum*/
public void getChildrenTask(Task task, Project project, List<Project> list, int levelNum){if(task.getResourceAssignments().size() == 0){//这个判断是进行是否是最后一层任务的判断==0说明是父任务levelNum ++;//层级号需要增加,这个只是博主用来记录该层的层级数List<Task> tasks = task.getChildTasks();//继续获取子任务for (int i = 0; i < tasks.size(); i++) {//该循环是遍历所有的子任务if(tasks.get(i).getResourceAssignments().size() == 0){//说明还是在父任务层Project pro = new Project();if (project.getProjId() != null){//说明不是第一次读取了,因为如果是第一层,那么还没有进行数据库的添加,没有返回主键Idpro.setParentId(project.getProjId());//将上一级目录的Id赋值给下一级的ParentId}pro.setBatchNum(project.getBatchNum());//批量号pro.setImportTime(new Date());//导入时间pro.setLevel(levelNum);//层级pro.setTaskName(tasks.get(i).getName());//这个是获取文件中的“任务名称”列的数据pro.setDurationDate(tasks.get(i).getDuration().toString());//获取的是文件中的“工期”pro.setStartDate(tasks.get(i).getStart());//获取文件中的 “开始时间”pro.setEndDate(tasks.get(i).getFinish());//获取文件中的 “完成时间”pro.setResource(tasks.get(i).getResourceGroup());//获取文件中的 “资源名称”this.addProjectInfo(pro);//将该条数据添加到数据库,并且会返回主键Id,用做子任务的ParentId,这个需要在mybatis的Mapper中设置getChildrenTask(tasks.get(i), pro,list,levelNum);//继续进行递归,当前保存的只是父任务的信息}else{//继续进行递归getChildrenTask(tasks.get(i), project, list, levelNum);}}}else{//说明已经到了最底层的子任务了,那么就调用进行最底层数据读取的方法if (project.getProjId() != null){getResourceAssignment(task, project, list, levelNum);}}}

public void getResourceAssignment(Task task, Project project, List<Project> proList, int levelNum){List<ResourceAssignment> list = task.getResourceAssignments();//读取最底层的属性ResourceAssignment rs = list.get(0);Project pro = new Project();pro.setTaskName(task.getName());pro.setParentId(project.getProjId());pro.setLevel(levelNum);pro.setImportTime(new Date());pro.setBatchNum(project.getBatchNum());pro.setDurationDate(task.getDuration().toString());pro.setStartDate(rs.getStart());//注意,这个从ResourceAssignment中读取pro.setEndDate(rs.getFinish());//同上String resource = "";if(list.size() > 1){for (int i = 0; i < list.size(); i++) {if (list.get(i).getResource() != null){if(i < list.size() - 1){resource += list.get(i).getResource().getName() + ",";}else{resource += list.get(i).getResource().getName();}}}}else{if(list.size() > 0 && list.get(0).getResource() != null){resource = list.get(0).getResource().getName();}}if(!StringUtils.isEmpty(resource)){pro.setResource(resource);}this.addProjectInfo(pro);//将数据保存在数据库中,同样会返回主键proList.add(pro);}

封装对象


package com.winter.model;import java.util.Date;/*** Project文件封装类* Created By Donghua.Chen on  2018/1/9*/
public class Project {/* 自增主键Id */private Integer projId;/* 上级Id */private Integer parentId;/* 结构层级 */private Integer level;/* 任务名称 */private String taskName;/* 工期 */private String durationDate;/* 开始时间 */private Date startDate;/* 结束时间 */private Date endDate;/* 前置任务ID */private Integer preTask;/* 资源名称 */private String resource;/* 导入时间 */private Date importTime;/* 批次号 */private String batchNum;//省略get、set方法

Mybatis 接口方法

/*** 插入project数据* @param project* @return*/int addProjectSelective(Project project);

Mapper

<sql id="TABLE_PROJECT">PROJECT
</sql><!-- 插入数据之后返回主键 需要这两个配置: useGeneratedKeys="true" keyProperty="projId" -->
<insert id="addProjectSelective" useGeneratedKeys="true" keyProperty="projId" parameterType="com.winter.model.Project">INSERT INTO<include refid="TABLE_PROJECT"/><trim prefix="(" suffix=")" suffixOverrides="," ><if test="parentId != null">parentId,</if><if test="level != null">level,</if><if test="taskName != null">taskName,</if><if test="durationDate != null">durationDate,</if><if test="startDate != null">startDate,</if><if test="endDate != null">endDate,</if><if test="preTask != null">preTask,</if><if test="resource != null">resource,</if><if test="importTime != null">importTime,</if><if test="importTime != null">batchNum,</if></trim><trim prefix="values (" suffix=")" suffixOverrides="," ><if test="parentId != null">#{parentId, jdbcType=INTEGER},</if><if test="level != null">#{level, jdbcType=INTEGER},</if><if test="taskName != null">#{taskName, jdbcType=VARCHAR},</if><if test="durationDate != null">#{durationDate, jdbcType=VARCHAR},</if><if test="startDate != null">#{startDate, jdbcType=DATE},</if><if test="endDate != null">#{endDate, jdbcType=DATE},</if><if test="preTask != null">#{preTask, jdbcType=INTEGER},</if><if test="resource != null">#{resource, jdbcType=VARCHAR},</if><if test="importTime != null">#{importTime, jdbcType=DATE},</if><if test="batchNum != null">#{batchNum, jdbcType=VARCHAR},</if></trim>
</insert>

效果

这里写图片描述

拓展

当然,还有很多自定义的字段读取,如果有需要可以联系我,或者看官方文档
email:1085143002@qq.com

相关资源

  • mpxj官方API文档:http://www.mpxj.org/apidocs/index.html
  • 项目源码:https://github.com/WinterChenS/springboot-mybatis-demo
  • 导入模板在源码的file文件夹中,sql在sql文件夹中

http://chatgpt.dhexx.cn/article/fLVNkcSf.shtml

相关文章

引用型变量的赋值

内存分类 栈内存&#xff1a;空间较小&#xff0c;用来存储变量在堆内存的地址。 堆内存&#xff1a;空间大&#xff0c;用来存储变量的真实数据。 引用型变量数据引用&#xff1a;首先根据变量访问栈内存&#xff0c;再根据栈内存中的地址指向找到在堆内存中的变量数据。 当…

java对象与对象引用变量

Java对象及其引用 先搞清楚什么是堆&#xff0c;什么是栈。 Java开辟了两类存储区域&#xff0c;对比二者的特点 存储区域存储内容优点缺点回收栈基本类型的变量和对象的引用变量存取速度比堆要快&#xff0c;仅次于寄存器&#xff0c;栈数据可以共享存在栈中的数据大小与生…

变量的引用_概念

理解了变量的引用之后, 对于我们理解在python 中的函数的参数传递和以及函数的返回值都是非常由帮助的. 当一个python 程序运行的时候, 变量和数据都是保存在内存中的. 变量的引用这个概念, 就是介绍一下当一个python 程序运行时, 变量和数据是怎样保存的, 并且保存的是什么内…

C++中引用变量详解

目录 一、什么是引用&#xff1f; 二、引用的注意事项 三、引用的本质 四、常量引用 五、引用的使用场景​​​​​​​ 六、引用和指针的区别 一、什么是引用&#xff1f; 引用实际上是给一个变量起别名&#xff0c;编译器不会为引用变量开辟一个新的内存空间&#xff…

C++变量引用

本篇介绍的变量引用与之前介绍的指针不是同一概念&#xff0c;它们有本质的区分&#xff1a; 1&#xff09;不存在空引用。引用必须连接到一块合法的内存。 2&#xff09;一旦引用被初始化为一个对象&#xff0c;就不能被指向到另一个对象。指针可以在任何时候指向到另一个对…

Java_引用变量

目录 1.认识 null ​编辑 2.数组的应用 3.数组作为函数的参数 4.数组作为函数的返回值 5.数组练习 数组转字符串 6.数组拷贝 (1)通过函数Arrays.copyOf()进行拷贝&#xff1a; (2)通过函数System.arraycopy()进行拷贝&#xff1a; (3)拷贝范围Arrays.copyOfRange() (4)比…

C++中的引用变量详解

文章目录 声明及定义代码引用变量的特点图片解释引用变量的本质引用变量的用途int & 和 const int & 的区别引用变量和宏定义&#xff08;#define&#xff09;的区别 声明及定义 [const] int& 变量名 右值 注意&#xff1a;[]内的是可选的。即这里的const限定词是…

引用变量及其作用

首先&#xff0c;先来明白一下什么是引用变量。 一个变量可以声明为一个引用&#xff0c;它起着该变量的别名的作用。对引用进行操作&#xff0c;实际上就是对被引用的变量进行操作。 引用运算符&#xff1a;&&#xff1b; 定义的一般形式&#xff1a;数据类型 &引用变…

变量的引用

引用是C对C的一个重要扩充。 1、引用的概念&#xff1a;变量的引用就是变量的别名。引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间。 从上面图中就可以看出&a…

引用变量

引用就是一个变量的别名&#xff0c;声明&#xff1a; int a 0; int &b a; b就是a的引用&#xff0c;a和b指向的内存时同一个地址&#xff0c;b可以修改变量的值 一。主要的用途是将引用变量作为函数的参数传递&#xff0c;为什么呢&#xff1f; &#xff08;1&…

C++入门基础—— 引用变量

目录 什么是引用&#xff1a; 引用的例子 引用的特性&#xff1a; 常量引用与非常量引用 使用场景 引用与指针的区别 什么是引用&#xff1a; 引用的概念 引用实际上就是取别名&#xff0c;提起这个名字就会让人知道是谁&#xff0c;谈起“鸡哥”就知道是坤坤&#xff0c;…

C++——引用变量

目录 一、创建引用变量 二、将引用作为函数参数 三、引用的属性和特别处 四、临时变量、引用参数和const 五、返回引用 六、何使用引用参数 七、参考书籍 引用变量是C新增的一种复合类型。引用是已定义的变量的别名&#xff08;另一个名字&#xff0c;但两个名字都是表示…

014 变量的引用

目标&#xff1a; 1、变量的引用 2、可变和不可变量类型 3、局部变量和全局变量 一、变量的引用 。变量 和 数据 都是保存在 内存 中的 。在 python 中 函数 的 参数传递 以及 返回值 都是靠 引用 传递的 1.1 引用的概念 在 Python 中 。变量 和 数据 是分开存储的 。数据 保…

C语言动态内存分配函数

目录 1.malloc()2.free()3.calloc()4.realloc()5.小结 在C中我们开辟内存空间有两种方式 :1.静态开辟内存 : 例如: int a; int b[10]; 这种开辟内存空间的特点是 所开辟的内存是在栈中开辟的固定大小的 ,如a是4字节 ,数组b是40字节 ,并且数组在申明时必须指定其长度 , 如果是全…

【C语言】------ 动态内存分配

动态内存开辟详解 动态内存分配什么是动态内存分配&#xff1f; 一、为什么使用动态内存分配呢&#xff1f;二、动态内存函数1.malloc和free2.calloc和realloc 三、常见的动态内存错误1.对NULL指针的解引用操作2.对动态内存开辟的越界访问3.向free传递一个非malloc函数返回的指…

【C语言】为什么存在动态内存分配

文章目录 前言一、动态内存分配定义 二、动态内存分配的意义1.可以控制所开辟的内存大小2.可以多次利用这部分空间 三&#xff0c;动态内存函数的介绍 前言 提示&#xff1a;我们先来看一个在vs编译器下&#xff0c;同学们常犯的错误 注意&#xff1a;在VS编译器下C语言是不支…

静态内存分配与动态内存分配

静态内存分配与动态内存分配 动机 平时看c/c的书籍时&#xff0c;总会看到一种观点&#xff0c;说是C/C语言使用的时候动态内存分配是最重要的&#xff0c;使用malloc等函数分配的内存必须要释放&#xff0c;否则及其容易出现内存泄露。但是自己有时候挺奇怪的&#xff0c;啥…

JVM内存分配机制

Java虚拟机最重要的工作就是如何给对象分配内存空间&#xff0c;以及通过GC如何回收已经不再使用的内存空间。这篇文章主要介绍JVM中的Java对象是创建过程、对象内存的分配机制以及对象内存的回收机制。 一、对象的创建 在前面的文章《JVM类加载机制》中讲过&#xff0c;JVM中…

C语言动态内存分配详解

文章目录 前言一、为什么存在动态内存分配1、已掌握的内存开辟方式2、上述开辟空间方式的特点3、为什么存在动态内存分配 二、动态内存函数的介绍1、malloc2、free3、calloc4、realloc 三、常见的动态内存错误1、对NULL指针的解引用操作2、对动态开辟内存的越界访问3、对非动态…

动态内存管理(内存的分配与回收)详解

** 1. 数据结构之动态内存管理机制 ** 通过前面的学习&#xff0c;介绍很多具体的数据结构的存储以及遍历的方式&#xff0c;过程中只是很表面地介绍了数据的存储&#xff0c;而没有涉及到更底层的有关的存储空间的分配与回收&#xff0c;从本节开始将做更深入地介绍。 在使…