快一年没写推文了,感觉肚子里也积了点东西,今天就搞一搞个人觉得还是挺酷的东西--结合反射机制搞一个通用Excel导入,通用导出功能。
想了解下反射api的使用可以看下JAVASchool的说明:.html
以前写POI导入Excel的时候,基本都是来一个模板,就写一个解析读取接口,就下面像这样:
一列一列的取,取完再赋值给定义的实体类,这样来一个模板写一段代码去解析读取呢,其实也没啥问题,在需要读取的Excel很少的时候,写也很快。但是当需要读取的Excel越来越多的时候,每来一个模板就写一个Excel,来一个写一个的话,那就很难受了,效率也很低!像之前我搞的一个项目,需要读取解析的Excel模板四十多个的,一个一个写的话,那我要写到吐了。
1:根据不同的Excel文档模型,在数据库中定义一个解析模板
2:接口调用的时候把Excel文件与相应的Excel模板类型名传入,后台根据模板类型名找相应的解析模板去解析Excel
3:解析读取数据
4:拿到数据进行后续的处理
详细过程:
1:先在数据库中定义一个模板解析表(用来保存每个Excel的解析模板),sql如下:
CREATE TABLE `template_data` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(100) NOT NULL DEFAULT '' COMMENT '模板类型',
`start_row` int(11) NOT NULL DEFAULT '0' COMMENT '开始行',
`field` json DEFAULT NULL COMMENT '字段',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模板数据表';
提示一下,filed字段是json类型的,mysql 5.7 以上的版本才支持json类型,低于5.7的版本sql会执行失败,需要升级mysql。
表字段说明:
type:用来存模板类型,也可以说是这个模板的名称
start_row:表示从第几行开始读取,一般的Excel表前几行都会是描述或者表头,所以需要定义好从第几行开始读取数据,像我用来做测试用的张大炮的Excel样例,就是从第三行开始读取
field:用来存每一列的数据的类型,对应的java类属性名(关键点,决定在利用反射的时候赋值给java类中的哪个属性),是否允许为空等。张大炮Excel文档的一条模板解析sql如下:
INSERT INTO `template_data`(`id`, `type`, `start_row`, `field`) VALUES (1, 'ZhangDaPao', 2, '[{"fieldLong": 50, "fieldName": "name", "fieldType": "String", "isNotNull": "是", "templateColumn": 0}, {"fieldLong": 11, "fieldName": "age", "fieldType": "Integer", "isNotNull": "是", "templateColumn": 1}, {"fieldLong": 10, "fieldName": "sex", "fieldType": "String", "isNotNull": "是", "templateColumn": 2}, {"fieldLong": 255, "fieldName": "address", "fieldType": "String", "isNotNull": "是", "templateColumn": 3}, {"fieldLong": 1024, "fieldName": "remark", "fieldType": "String", "isNotNull": "是", "templateColumn": 4}]');
filed字段里面的json数据说明:
fieldLong-用来限制Excel列的数据长度
filedName-对应的类属性名
fieldType-属性类型
isNotNull-是否不能为空
templateColumn-数据在Excel行中的第几列,下标从0开始。
之后每新增一个Excel模板,就在数据库中配置多一条sql就好了,接下来就是具体的代码实现了。
项目结构如下:
pom文件依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- POI依赖 --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version></dependency><!-- lombok依赖 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency><!-- 阿里数据源依赖 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.0</version></dependency><!-- mysql依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>javax.persistence</groupId><artifactId>javax.persistence-api</artifactId><version>2.2</version></dependency><dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib</artifactId><version>2.4</version><classifier>jdk15</classifier></dependency><dependency><groupId>org.apachemons</groupId><artifactId>commons-lang3</artifactId></dependency><!-- mybatis-plus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.p</version></dependency><!-- json--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.49</version></dependency><dependency><groupId>javax</groupId><artifactId>javaee-web-api</artifactId><version>7.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies>
实体类准备:
TemplateData:模板数据类
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;/*** 模板数据*/
@Data
public class TemplateData {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;/**实体对象*/private String type;/**开始行*/private int startRow;/**字段*/private String field;
}
FieldTemplate:JSON字段实体类
import lombok.Data;/*** 字段模板*/
@Data
public class FieldTemplate {/**字段名*/private String fieldName;/**模板列*/private int templateColumn;/**字段类型*/private String fieldType;/**是否非空 (是 否)*/private String isNotNull;/**字段长度限制*/private int fieldLong;
}
ZhangDaPao:张大炮实体类
import lombok.Data;@Data
public class ZhangDaPao {private String name;private Integer age;private String sex;private String address;private String remark;
}
Mybatis映射类:
TemplateDataMapper:模板数据映射类
ity.TemplateData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;@Mapper
@Repository
public interface TemplateDataMapper {@Select("select * from template_data where type = #{type}")TemplateData findByType(@Param("type") String type);}
业务层:ReportService,写了很多注释,希望对理解逻辑有帮助
主要逻辑:
1-获取解析模板
2-匹配模板字段与实体类的成员变量
3-校验数据列数据与赋值给成员变量
4-返回数据
import com.alibaba.fastjson.JSON;
ity.FieldTemplate;
ity.TemplateData;
ity.ZhangDaPao;
plate.mapper.TemplateDataMapper;
slf4j.Slf4j;
import net.sf.json.JSONArray;
import org.apachemons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import flect.Field;
import java.util.ArrayList;
import java.util.List;@Service
@Slf4j
public class ReportService {@Autowiredprivate TemplateDataMapper templateDataMapper;/*** @param file:需要解析的Excel* @param type:文件模板名* @return*/public Object dataUpload(MultipartFile file, String type) {if (file == null || file.isEmpty()) {("上报文件为空");return "上报文件为空";}Workbook workbook = null;try {workbook = InputStream());} catch (Exception e) {e.printStackTrace();}if (workbook == null) {("workbook == null");return "workbook == null";}return execute(workbook, type);}/*** 上报处理*/public Object execute(Workbook hssfWorkbook, String type) {TemplateData templateData = null;switch (type) {case "ZhangDaPao"://根据模板类型去数据库取相应的解析模板templateData = templateDataMapper.findByType("ZhangDaPao");return addData(templateData, hssfWorkbook,new ZhangDaPao());("没有找到对应的模板...");}return null;}/*** @param templateData* @param hssfWorkbook* @param t 与Excel中数据对应的实体类* @param <T>* @return*/public <T> List<T> addData(TemplateData templateData, Workbook hssfWorkbook, T t) {JSONArray jsonArray = JSONArray.fromObject(new ArrayList<>());//读第一个Excel表Sheet hssfSheet = SheetAt(0);//拿解析模板中的field字段的json数据列,转为字段模板列表List<FieldTemplate> fieldTemplateList = JSON.Field(), FieldTemplate.class);//从模板中定义的开始行开始遍历读取行for (int rowNum = StartRow(); rowNum <= LastRowNum(); rowNum++) {Row hssfRow = Row(rowNum);if (hssfRow != null) {T data = t;//遍历field字段中的json数据列for (FieldTemplate sqlFt : fieldTemplateList) {//获取泛型的class,再用getDeclaredFields()获取类的成员变量//遍历成员变量for (Field field : t.getClass().getDeclaredFields()) {//如果成员变量跟数据库中定义的属性名匹配,进入数据读取解析//也就是sql中,field定义的 "fieldName" 与实体ZhangDaPao中的属性名相同,进入数据读取if (Name().FieldName())) {Cell cell = TemplateColumn());/*Excel数据列的校验,读取,赋值操作* cell:数据列* sqlFt:数据库中field字段中的json模板* field:反射中的成员变量* rowNum:读取的Excel是哪一行,用来抛异常的时候记录是第几行有问题* data:与Excel数据对应的实体类* */verifyType(cell, sqlFt, field, rowNum, data);}}}//把读取成功的data加到json数组中,用于后续泛型转换jsonArray.add(data);}}//转换为相应的实体列表返回return (List<T>) Collection(jsonArray, t.getClass());}/*** 校验,读取* @param cell:数据列** @param sqlFt:数据库中field字段中的json模板,格式是下面这样的* {"fieldLong": 50, "fieldName": "name", "fieldType": "String", "isNotNull": "是", "templateColumn": 0}** @param field:反射中获取到的成员变量* @param rowNum:读取的Excel是哪一行,用来抛异常的时候记录是第几行有问题* @param data:与Excel数据对应的实体类*/private <T> T verifyType(Cell cell, FieldTemplate sqlFt, Field field, int rowNum, T data) {// 校验非空if (IsNotNull().equals("是")) {if (cell == null || StringUtils.String())) {("数据格式错误:行:" + (rowNum + 1) + ";列:" + (TemplateColumn() + 1) + ",必要数据不能为空");}}// 校验长度if (cell != null && String().length() >= FieldLong()) {("数据格式错误:行:" + (rowNum + 1) + ";列:" + (TemplateColumn() + 1) + ",长度超出限制");}try {//打破封装//一般成员变量我们都是定义为private的,在反射机制中,需要设置该属性允许被操作field.setAccessible(true);//根据数据库中json数据中定义的该列是什么数据类型,进行相应的数据处理,这里只弄了String跟Integer的if (FieldType().equals("String")) {//String类型,直接toString赋值field.set(data, String());} else if (FieldType().equals("Integer")) {//Integer类型,读取到的格式是这样的 30.0 先转 double再转为int类型field.set(data, new String()).intValue());}} catch (Exception e) {("数据格式错误:行:" + (rowNum + 1) + ";列:" + (TemplateColumn() + 1));}return data;}
}
控制类:
plate.service.ReportService;
slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;@RestController
@Slf4j
public class TemplateUploadController {@Autowiredprivate ReportService reportService;@RequestMapping(value = "/upload", method = RequestMethod.POST)public void illegalBuildingUpload(MultipartFile file, @RequestParam String type) {log.info("文件上传---------");reportService.dataUpload(file, type);}
}
postman调用测试:调用成功,返回了张大炮等人的信息
接下来我们增加一个Excel模板,如下
新增Teacher实体类如下:
import lombok.Data;@Data
public class Teacher {private String name;private String school;private String course;
}
新增一条模板数据到数据库
INSERT INTO `template_data`(`id`, `type`, `start_row`, `field`) VALUES (2, 'Teacher', 2, '[{"fieldLong": 50, "fieldName": "name", "fieldType": "String", "isNotNull": "是", "templateColumn": 0}, {"fieldLong": 50, "fieldName": "school", "fieldType": "String", "isNotNull": "是", "templateColumn": 1}, {"fieldLong": 50, "fieldName": "course", "fieldType": "String", "isNotNull": "是", "templateColumn": 2}]');
在ReportService的execute中加入Teacher类型的捕获,如下:
具体代码:
case "Teacher":templateData = templateDataMapper.findByType("Teacher");return addData(templateData, hssfWorkbook,new Teacher());
postman调用:
获取数据成功,后续继续新增Excel模板的时候,只需要在数据库中配置新的模板,再建一个实体类就可以了。
通用的Excel导入就写到这,哪天心血来潮了再继续写通用导出的。
程序员-就得搞搞偷懒式的代码
本文发布于:2024-02-05 00:35:20,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170720007861297.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |