Skip to content

介绍

暂时只提供快捷excel操作,底层基于 fastexcel 实现 Excel 的读写,其核心代码来源于 RuoYi-Vue-Plus excel代码,在此基础上进行微调和二次封装。

依赖库

名称描述
zebra-common-util
fastexcel
poi-tl
minio
thumbnailator

快速开始

引入

xml
<dependency>
		<groupId>io.github.zhanghongbin</groupId>
		<artifactId>zebra-spring-boot-starter-file</artifactId>
</dependency>

导入excel

excel操作提供两种方式分别为注解方式及api方式

  1. 注解方式
    在Controller方法中,使用 @ImportExcel 注解来标记参数,用来接收表格数据,参数类型必须为List<T> t为带有easyexcel注解的对象, 带有@Validated表示是否进行validate验证
java
@RestController
@RequestMapping("/api")
public class UserController {
		@PostMapping("/import")
		public boolean importUser(@ImportExcel @Validated List<SysUser> sysUser) {
				//sysUser 变量为excel数据
				System.out.println(sysUser);
				return true;
		}
}

idea 测试

text

POST http://localhost:8080/iam/api/import
Content-Type: application/x-www-form-urlencodedContent-Disposition: form-data; name="file"; filename="xxxx_abf36d986bff4d36b509b7189b648fb6.xlsx"
Content-Type: text/plain

< C:/Users/Administrator/Desktop/xxxx_abf36d986bff4d36b509b7189b648fb6.xlsx

@ImportExcel

属性类型是否必须默认值描述
fileNameStringfile指定文件名
readListenerClass<? extends ExcelListener>DefaultExcelListener.classexcel读取监听器

readListener 值必须为 ExcelListener子类,必须实现 public void invoke(T data, AnalysisContext context) 方法,具体参见 easyexcel AnalysisEventListener使用及参考 DefaultExcelListener 实现。

  1. 使用 SimpleExcel api 方式 SimpleExcel 提供以下导入静态方法
名称参数描述
<T> <> importExcel(InputStream is, Class<T> clazz)is为文件流,clazz为对象class类型,返回结果为List同步导入(适用于小数据量)
<T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate)is为文件流,clazz为对象class类型,isValidate 是否进行参数校验,ExcelResult 返回结果对象使用校验监听器 异步导入 同步返回
<T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener)is为文件流,clazz为对象class类型,listenedr 结果监听器使用自定义监听器 异步导入 自定义返回

使用示例:

java

/**
 * 导入数据
 *
 * @param file          导入文件
 * @param updateSupport 是否更新已存在数据
 */
@Log(title = "导入操作", module = "用户管理", businessType = BusinessType.IMPORT)
@SaCheckPermission("system:user:import")
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String importData(@RequestPart("file") MultipartFile file, boolean updateSupport) {
	ExcelResult<SysUserImportDto> result = simpleExcel.importExcel(
			file.getInputStream(), SysUserImportDto.class, new SysUserImportListener(updateSupport));
	return result.getAnalysis();
}

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.extra.spring.SpringUtil;
import cn.idev.excel.context.AnalysisContext;
import lombok.extern.slf4j.Slf4j;
import org.zebra.admin.sys.domain.dto.SysUserDto;
import org.zebra.admin.sys.domain.dto.SysUserImportDto;
import org.zebra.admin.sys.domain.vo.SysUserQuery;
import org.zebra.admin.sys.service.ISysConfigService;
import org.zebra.admin.sys.service.ISysUserService;
import org.zebra.common.core.ValidatorUtil;
import org.zebra.file.excel.core.ExcelListener;
import org.zebra.file.excel.core.ExcelResult;
import org.zebra.satoken.LoginHelper;

import java.util.List;

/**
 * 系统用户自定义导入
 *
 * @author Lion Li
 */
@Slf4j
public class SysUserImportListener extends ExcelListener<SysUserImportDto> {

    private final ISysUserService userService;

    private final String password;

    private final Boolean isUpdateSupport;

    private final String operUserId;

    private int successNum = 0;
    private int failureNum = 0;
    private final StringBuilder successMsg = new StringBuilder();
    private final StringBuilder failureMsg = new StringBuilder();

    public SysUserImportListener(Boolean isUpdateSupport) {
        super(false);
        String initPassword = SpringUtil.getBean(ISysConfigService.class).selectConfigByKey("sys.user.initPassword");
        this.userService = SpringUtil.getBean(ISysUserService.class);
        this.password = BCrypt.hashpw(initPassword);
        this.isUpdateSupport = isUpdateSupport;
        this.operUserId = LoginHelper.getUserId();
    }

    @Override
    public void invoke(SysUserImportDto userVo, AnalysisContext context) {
        SysUserDto sysUser = this.userService.selectUserByUserName(userVo.getUserName());
        try {
            // 验证是否存在这个用户
            if (ObjectUtil.isNull(sysUser)) {
                SysUserQuery user = BeanUtil.toBean(userVo, SysUserQuery.class);
                ValidatorUtil.validate(user);
                user.setPassword(password);
                user.setCreateBy(operUserId);
                userService.insertUser(user);
                successNum++;
                successMsg
                        .append("<br/>")
                        .append(successNum)
                        .append("、账号 ")
                        .append(user.getUserName())
                        .append(" 导入成功");
            } else if (isUpdateSupport) {
                String userId = sysUser.getUserId();
                SysUserQuery user = BeanUtil.toBean(userVo, SysUserQuery.class);
                user.setUserId(userId);
                ValidatorUtil.validate(user);
                userService.checkUserAllowed(user.getUserId());
                userService.checkUserDataScope(user.getUserId());
                user.setUpdateBy(operUserId);
                userService.updateUser(user);
                successNum++;
                successMsg
                        .append("<br/>")
                        .append(successNum)
                        .append("、账号 ")
                        .append(user.getUserName())
                        .append(" 更新成功");
            } else {
                failureNum++;
                failureMsg
                        .append("<br/>")
                        .append(failureNum)
                        .append("、账号 ")
                        .append(sysUser.getUserName())
                        .append(" 已存在");
            }
        } catch (Exception e) {
            failureNum++;
            String msg = "<br/>" + failureNum + "、账号 " + userVo.getUserName() + " 导入失败:";
            failureMsg.append(msg).append(e.getMessage());
            log.error(msg, e);
        }
    }

    @Override
    public ExcelResult<SysUserImportDto> getExcelResult() {
        return new ExcelResult<>() {

            @Override
            public String getAnalysis() {
                if (failureNum > 0) {
                    failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
                    throw ExceptionUtil.wrapRuntime(failureMsg.toString());
                } else {
                    successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
                }
                return successMsg.toString();
            }

            @Override
            public List<SysUserImportDto> getList() {
                return null;
            }

            @Override
            public List<String> getErrorList() {
                return null;
            }
        };
    }
}

导出excel

在方法上使用@ExportExcel注解,返回值必须为Collection类型

java

@RestController
@RequestMapping("/api")
public class UserController {
		@GetMapping("/export")
		@ExportExcel(name = "xxxx")
		public List<SysUser> export() {
				List<SysUser> list = new ArrayList<>();
				return list;
		}
}

@ExportExcel

属性类型是否必须默认值描述
nameStringfile下载文件名
templateString模版地址 ,当存储类型为minio时,桶名:文件夹/文件名 或 文件夹/文件名
templateStorageTypeTemplateStorageTypeCLASSPATH模版存储类型,有类路径和minio
  1. 使用 SimpleExcel api 方式 SimpleExcel 提供以下导入静态方法
名称参数描述
<T> void exportExcel(Collection<T> collection, String sheetName, Class<T> clazz, HttpServletResponse response)collection 导出数据集合,sheetName 工作表的名称,clazz 实体类,response导出excel
exportTemplate( Collection<Object> data,String filename,String templatePath,TemplateStorageType templateStorageType,HttpServletResponse response)data 数据,filename 文件名,templatePath 模版路径,templateStorageType 模版存储类型,response 响应体基于模板数据导出

使用示例:

java

/**
 * 导出岗位列表
 */
@Log(title = "导出操作", module = "岗位管理", businessType = BusinessType.EXPORT)
@SaCheckPermission("system:post:export")
@PostMapping("/export")
public void export(SysPostQuery post, HttpServletResponse response) {
	List<SysPostDto> list = postService.selectPostList(post);
	simpleExcel.exportExcel(list, "岗位数据", SysPostDto.class, response);
}

@ExcelDictFormat 注解

有些情况下在导出excel文件某些字段,如性别 存储为 0 未知 1 男 2 女,而在 excel 字段中需要显示中文,在导入时 excel 字段是中文,保存到数据库里应该是数字,这时候就需要用到 @ExcelDictFormat 注解。
对于简单字典转换只需要设置 @ExcelDictFormat readConverterExp 属性即可,复杂需要实现 DictConverter 接口并且注解 @Service 或 @Component
DictConverter 接口介绍

名称参数描述
String getDictLabel(String dictType, String dictValue, String separator)dictType 就是 readConverterExp值,dictValue 字段值,separator 分隔符获取显示名称
String getDictValue(String dictType, String dictLabel, String separator)dictType 就是 readConverterExp值,dictLabel 字段值,separator 分隔符获取实际值
Map<String, String> getAllDictByDictType(String dictType)dictType 就是 readConverterExp值获取字典下所有的字典值与标签

使用示例:

java

@ExcelProperty(value = "业务类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=登录,8=退出")
/**
 * 业务类型(0其它 1新增 2修改3删除 4授权 5导出 6导入 7登录 8退出登录)
 */
private Integer businessType;


/**
 * 帐号状态(0正常 1停用)
 */
@ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictConvertClass = SysDictTypeServiceImpl.class, readConverterExp = "sys_normal_disable")
private String status;

@RequiredArgsConstructor
@Service
public class SysDictTypeServiceImpl 
        implements  DictConverter {

    private final ISysDictDataService sysDictDataService;
    /**
     * 根据所有字典类型
     *
     * @return 字典类型集合信息
     */
    @Override
    public List<SysDictTypeDto> selectDictTypeAll() {
        return this.selectVoList();
    }

    /**
     * 根据字典类型和字典值获取字典标签
     *
     * @param dictType  字典类型
     * @param dictValue 字典值
     * @param separator 分隔符
     * @return 字典标签
     */
    @Override
    public String getDictLabel(String dictType, String dictValue, String separator) {
        // 必须使用 pringUtil.getBean(this.getClass()) 否则缓存不生效
        List<SysDictDataDto> datas = SpringUtil.getBean(this.getClass()).selectDictDataByType(dictType);
        if (datas.isEmpty()) return "";
        Map<String, String> map = datas.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(SysDictDataDto::getDictValue, SysDictDataDto::getDictLabel, (l, r) -> l));
        if (StringUtils.containsAny(dictValue, separator)) {
            return Arrays.stream(dictValue.split(separator))
                    .map(v -> map.getOrDefault(v, StringUtils.EMPTY))
                    .collect(Collectors.joining(separator));
        } else {
            return map.getOrDefault(dictValue, StringUtils.EMPTY);
        }
    }

    /**
     * 根据字典类型和字典标签获取字典值
     *
     * @param dictType  字典类型
     * @param dictLabel 字典标签
     * @param separator 分隔符
     * @return 字典值
     */
    @Override
    public String getDictValue(String dictType, String dictLabel, String separator) {
        // 必须使用 springUtil.getBean(this.getClass()) 否则缓存不生效
        List<SysDictDataDto> datas = SpringUtil.getBean(this.getClass()).selectDictDataByType(dictType);
        if (datas.isEmpty()) return "";
        Map<String, String> map = datas.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(SysDictDataDto::getDictLabel, SysDictDataDto::getDictValue, (l, r) -> l));
        if (StringUtils.containsAny(dictLabel, separator)) {
            return Arrays.stream(dictLabel.split(separator))
                    .map(l -> map.getOrDefault(l, StringUtils.EMPTY))
                    .collect(Collectors.joining(separator));
        } else {
            return map.getOrDefault(dictLabel, StringUtils.EMPTY);
        }
    }

    @Override
    public Map<String, String> getAllDictByDictType(String dictType) {
        // 必须使用 pringUtil.getBean(this.getClass()) 否则缓存不生效
        List<SysDictDataDto> list = SpringUtil.getBean(this.getClass()).selectDictDataByType(dictType);
        if (list.isEmpty()) Collections.emptyMap();
        return list.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(SysDictDataDto::getDictValue, SysDictDataDto::getDictLabel, (l, r) -> l));
    }
}