EasyPoi系列之通用导入接口设计

news/2025/2/22 4:10:35

EasyPoi系列之通用导入接口设计

  • 1 背景
  • 2 分析及设计
    • 2.1 标准导入交互分析
    • 2.2 设计
      • 2.2.1 导入模板生成接口
      • 2.2.2 数据导入接口
  • 3、代码实现
    • 3.1 人员实体-PersonEntity
    • 3.2 定义数据保存通用接口-ExcelImporter
    • 3.3 人员数据保存实现- PersonService
    • 3.4 建立业务与实体及保存实现类之间的关系-EnumImportType
    • 3.5 编写模板导出接口
    • 3.6 通用导入接口
    • 3.7 完整接口-ImporterController
  • 4 接口测试
    • 4.1 导出模板
    • 4.2 数据导入
      • 4.2.1 模板数据填充
      • 4.2.2 执行导入
  • 5 后记

1 背景

在业务管理系统中,经常会涉及到数据导入的功能,针对各个业务场景各自编写相应接口,会发现外部接口除了参数之外,其他编码都一样,显得过于重复,那是否可以通过编写统一的导入接口,各个应用只需要实现各自接收数据后的数据加工存储即可,以达到提升代码的简洁性的效果呢?

2 分析及设计

2.1 标准导入交互分析

在数据导入过程中,尝尝设计到先导入对应的模板,然后根据模板进行相应导入数据填充后,再通过导入接口将填充后的数据进行导入。标准数据导入流程如下图:

  • 标记1:提供模板下载接口,根据业务不同,导出相应的模板
  • 标记2:提供数据导入接口,虽然业务不同,调用接口基本一致,都是Excel文件上传,并解析Excel文件内容
  • 标记3:根据解析后的数据,进行循环解析验证处理,针对验证无误的数据,保存至数据库进行存储
    在这里插入图片描述

2.2 设计

从导入交互分析可得,我们主要需要实现的根据业务不同生成对应的导入模板,以及导入时,根据业务不同,执行相应的解析后入库即可。

2.2.1 导入模板生成接口

通过分析可知,导入模板是根据业务不同,生成不同的模板,故可设计为在前端进行接口调用的时候,增加一个业务类型的参数,后端根据业务类型进行相应的判断,然后执行相应的逻辑,完成导入模板的生成。
通过上文 EasyPoi系列之框架集成及基础使用 可知,EasyPoi通过Entity实体+@Excel注解的方式即可实现Excel数据的导出(如后文代码片段),那基于此,生成导入模板时,可通过EasyPoi导出数据接口来实现,只是数据列表为空而已,所以我们只需要将业务类型参数与后端Entity实体建立联系,即可实现数据导入模板的快速生成。

java">/**
 * 通过实体导出Excel
 * @param response
 */
@RequestMapping("exportByEntity")
public void exportByEntity(HttpServletResponse response) throws Exception {
    ExportParams exportParams = new ExportParams();
    List<ExportEntity> datas = this.buildExportData(10);
    Workbook workbook = null;
    try{
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("测试exportByEntity", "utf-8") + ".xlsx");
        //此处可将datas传入空集合,则导出Excel就只会有表头
        workbook = ExcelExportUtil.exportExcel(exportParams, ExportEntity.class, datas);
        workbook.write(response.getOutputStream());
    }finally {
        IoUtil.close(workbook);
    }
}

2.2.2 数据导入接口

通过分析可得,用户导入填充后的模板,第一步均是调用后端指定接口进行文件上传至服务端,服务端获取到文件流,进行进一步的Excel数据解析为实体集合,然后保存即可。
通过上文 EasyPoi系列之框架集成及基础使用 可知,EasyPoi可快速将文件流读取后转换为对应实体集合(如后文代码片段),此处,我们也可参考模板导出的实现方式

  1. 通过前端传入一个业务参数,然后服务端根据业务参数,转换为对应的实体集合。
  2. 定义通用的保存接口,并定义一个保存方法,需要进行数据保存的业务实现该方法,然后通过业务参数与具体实现了保存方法的类进行关联即可。
java"> /**
  * 数据导入
  * @param response
  */
 @PostMapping("importData")
 @ResponseBody
 public List<ExportEntity> importData(HttpServletResponse response, MultipartFile file) throws Exception{
     ImportParams importParams = new ImportParams();
     //EasyPoi提供的方法,用于将文件流转换为实体集合
     List<ExportEntity> datas = ExcelImportUtil.importExcel(file.getInputStream(), ExportEntity.class, importParams);
     return datas;
 }

3、代码实现

以人员基础信息导入为例

3.1 人员实体-PersonEntity

java">import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;

import java.util.Date;

/**
 * 人员实体
 */
@Data
public class PersonEntity {
    @Excel(name = "人员姓名",width = 50)
    private String personName;

    @Excel(name = "年龄",width = 50)
    private Integer age;

    @Excel(name = "出生日期",width = 50)
    private Date birthday;
    
    @Excel(name = "手机号",width = 50)
    private String phone;
}

3.2 定义数据保存通用接口-ExcelImporter

java">import java.util.List;
/**
 * 定义通用导入接口
 * @param <T>
 */
public interface ExcelImporter<T> {
    /**
     * 数据保存方法<br/>
     * 需要执行导入的业务,均实现
     * @param data
     */
    void save(List<T> data);
}

3.3 人员数据保存实现- PersonService

参考企业级实现方式,先定义一个IPersonService接口:

java">/**
 * 人员服务,由于人员涉及到导入,故接口继承ExcelImporter
 */
public interface IPersonService extends ExcelImporter<PersonEntity>{
    //TODO 人员标准业务,其他业务处理方法
}

具体的PersonService 实现类:

java">import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * 人员服务实现类,通过多继承,实现对应的导入方法
 */
@Service
public class PersonService implements IPersonService{
    @Override
    public void save(List<PersonEntity> data) {
        //TODO 实现保存方法
        System.out.println(JSON.toJSONString(data));
    }
}

3.4 建立业务与实体及保存实现类之间的关系-EnumImportType

java">import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 导入类型关系枚举
 */
@AllArgsConstructor
@Getter
public  enum EnumImportType {
	//定义person业务,其关联的实体为PersonEntity,关联的保存服务为PersonService
	person("人员信息", PersonEntity.class, IPersonService.class)
	;
	/**
	 * 描述
	 */
	private String title;

	/**
	 * 实体类
	 */
	private  Class<?> clazz;

	/**
	 * 导入数据
	 */
	private Class<? extends ExcelImporter<?>> importer;
}

3.5 编写模板导出接口

java">/**
 * 通用数据导出
 * @param importType 导入类型
 * @param response
 */
@GetMapping("/exportTemplate")
public void exportDataTemplate(HttpServletRequest request, String importType, HttpServletResponse response) throws Exception {
    //根据导入类型,匹配对应枚举的关联关系
    EnumImportType enumExportType = EnumUtil.fromStringQuietly(EnumImportType.class, importType);
    if (null == enumExportType) {
        throw new RuntimeException("未知模板类型");
    }
    ExportParams exportParams = new ExportParams();
    Workbook workbook = null;
    try{
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(enumExportType.getTitle(), "utf-8") + ".xlsx");
        //通过匹配的关联关系,填充对应的实体,其中数据列表传入一个空列表
        workbook = ExcelExportUtil.exportExcel(exportParams, enumExportType.getClazz(), new ArrayList<>());
        workbook.write(response.getOutputStream());
    }finally {
        IoUtil.close(workbook);
    }
}

3.6 通用导入接口

java">/**
 * 通用数据导入
 * @param importType 模版类型
 */
@PostMapping("/importData")
@ResponseBody
public Map importData(MultipartFile file, String importType) throws Exception {
    Map<String,Object> result = new HashMap<>();
    EnumImportType enumExportType = EnumUtil.fromStringQuietly(EnumImportType.class, importType);
    if (null == enumExportType) {
        throw new RuntimeException("未知导入类型:"+importType);
    }
    ImportParams importParams = new ImportParams();
    List<ExportEntity> datas = ExcelImportUtil.importExcel(file.getInputStream(), enumExportType.getClazz(), importParams);
    if(null != datas && !datas.isEmpty()){
        //通过SpringUtil 获取当前匹配的导入实现类
        ExcelImporter importer = SpringUtil.getBean(enumExportType.getImporter());
        //调用保存方法
        importer.save(datas);
    }
    result.put("code",200);
    result.put("msg","导入成功");
    return result;
}

3.7 完整接口-ImporterController

java">/**
 * 数据导入接口
 */
@Controller
@RequestMapping("/importer")
public class ImporterController {

    /**
     * 通用数据导出
     * @param importType 导入类型
     * @param response
     */
    @GetMapping("/exportTemplate")
    public void exportDataTemplate(HttpServletRequest request, String importType, HttpServletResponse response) throws Exception {
        //根据导入类型,匹配对应枚举的关联关系
        EnumImportType enumExportType = EnumUtil.fromStringQuietly(EnumImportType.class, importType);
        if (null == enumExportType) {
            throw new RuntimeException("未知模板类型");
        }
        ExportParams exportParams = new ExportParams();
        Workbook workbook = null;
        try{
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(enumExportType.getTitle(), "utf-8") + ".xlsx");
            //通过匹配的关联关系,填充对应的实体,其中数据列表传入一个空列表
            workbook = ExcelExportUtil.exportExcel(exportParams, enumExportType.getClazz(), new ArrayList<>());
            workbook.write(response.getOutputStream());
        }finally {
            IoUtil.close(workbook);
        }
    }

    /**
     * 通用数据导入
     * @param importType 模版类型
     */
    @PostMapping("/importData")
    @ResponseBody
    public Map importData(MultipartFile file, String importType) throws Exception {
        Map<String,Object> result = new HashMap<>();
        EnumImportType enumExportType = EnumUtil.fromStringQuietly(EnumImportType.class, importType);
        if (null == enumExportType) {
            throw new RuntimeException("未知导入类型:"+importType);
        }
        ImportParams importParams = new ImportParams();
        List<ExportEntity> datas = ExcelImportUtil.importExcel(file.getInputStream(), enumExportType.getClazz(), importParams);
        if(null != datas && !datas.isEmpty()){
            //通过SpringUtil 获取当前匹配的导入实现类
            ExcelImporter importer = SpringUtil.getBean(enumExportType.getImporter());
            //调用保存方法
            importer.save(datas);
        }
        result.put("code",200);
        result.put("msg","导入成功");
        return result;
    }
}

4 接口测试

4.1 导出模板

浏览器访问 http://ip:port/importer/exportTemplate?importType=person 链接(ip与port根据实际情况进行替换),导出的模板内容如下:
在这里插入图片描述

4.2 数据导入

4.2.1 模板数据填充

针对4.1中导出的模板,填写一些样例数据如下:
在这里插入图片描述

4.2.2 执行导入

在ApiFox中按如下方式进行接口配置,并选择编写好的导入数据文件,点击发送:
在这里插入图片描述
后端断点效果如下,可看到Excel总的3条数据已读入到实体集合中,若需要保存至数据库,只需要调用存储接口即可:
在这里插入图片描述

5 后记

后续需要新增导入业务类,只需要在EnumImportType新增一个关联映射关系枚举,并参考Person用户导入方式,编写对应的类即可,无需再重复编写模板导出与数据导入的接口。
提示:前端也可以进行组件封装,通过传入业务类型,实现组件的重复利用(如下图)
组件使用参考:

<common-import ref="commonImport" 
	:title="'用户信息导入'"  	<!--弹框标题-->
	import-type="person" 	<!--导入类型-->
	@importSuccess="importSuccess"> <!--导入成功后的处理,如刷新列表-->
</data-import>

在这里插入图片描述


http://www.niftyadmin.cn/n/5861582.html

相关文章

数据结构系列一:初识集合框架+复杂度

前言 数据结构——是相互之间存在一种或多种特定关系的数据元素的集合。数据结构是计算机专业的基础课程&#xff0c;但也是一门不太容易学好的课&#xff0c;它当中有很多费脑子的东西&#xff0c;之后在学习时&#xff0c;你若碰到了困惑或不解的地方 都是很正常的反应&…

petalinux-build ERROR

最近编译Xilinx的固件的时候报了一个错&#xff0c;看的我云里雾里&#xff0c;一度认为ubuntu的版本跟petalinux的版本不匹配&#xff0c;想要重新安装操作系统和编译环境&#xff0c;想想都头大。 petalinux-create -t project --template zynqMP -n petalinux-config --ge…

C#基础:使用Linq进行简单去重处理(DinstinctBy/反射)

目录 一、示例代码 二、示例输出 三、注意雷点 四、全字段去重封装方法 1.封装 2.示例 一、示例代码 using System; using System.Collections.Generic; using System.Linq;public class Program {public static void Main(){// 创建一些示例实体对象var people new Li…

Rust编程语言入门教程 (七)函数与控制流

Rust 系列 &#x1f380;Rust编程语言入门教程&#xff08;一&#xff09;安装Rust&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;二&#xff09;hello_world&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;三&#xff09; Hello Cargo&#x1f…

数据结构——字符串匹配KMP

首先明确几个概念&#xff1a; s[ ]: 主串 p[ ]: 模式串(用于匹配) next[ j ]&#xff1a;以p[ j ]结尾的p字符串的前后缀最大匹配值,也是当p[ j1 ]与s[ i ]不匹配时,j指针移动的下一位置。(需要预处理出来) AcWing - 算法基础课 代码如下&#xff1a; #include<iostre…

BS架构网络安全 网络安全架构分析

文章目录 Web架构安全分析 一、web工作机制 1. 简述用户访问一个网站的完整路径2. web系统结构 二、url 1. 概述2. 完整格式3. url编码 三、HTTP 1. request请求报文2. http请求方法3. response响应报文 三、同源策略 1. 概述2. 同源策略的条件3. 非同源受到的限制4. …

vue2和vue3的按需引入的详细对比通俗易懂

以下是 Vue2 与 Vue3 按需引入的对比详解&#xff0c;用最简单的语言和场景说明差异&#xff1a; 一、按需引入的本质 目标&#xff1a;只打包项目中实际用到的代码&#xff08;组件、API&#xff09;&#xff0c;减少最终文件体积。类比&#xff1a;去餐厅点餐&#xff0c;只…

从面试中的“漏掉步骤”谈自我表达与思维方式的转变

在今天的面试中&#xff0c;我遇到了一个让我深刻反思自己思维方式的问题。当面试官问到如何应对用户量和请求量逐渐增加时&#xff0c;我的回答遗漏了一些基础步骤&#xff0c;导致我给出了“我暂时想不出更好的反思”的回答。这一经历让我意识到&#xff0c;在面对问题时&…