介绍
暂时只提供快捷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方式
- 注解方式
在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
属性 | 类型 | 是否必须 | 默认值 | 描述 |
---|---|---|---|---|
fileName | String | 否 | file | 指定文件名 |
readListener | Class<? extends ExcelListener> | 否 | DefaultExcelListener.class | excel读取监听器 |
readListener 值必须为 ExcelListener子类,必须实现 public void invoke(T data, AnalysisContext context) 方法,具体参见 easyexcel AnalysisEventListener使用及参考 DefaultExcelListener 实现。
- 使用 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
属性 | 类型 | 是否必须 | 默认值 | 描述 |
---|---|---|---|---|
name | String | 否 | file | 下载文件名 |
template | String | 否 | 空 | 模版地址 ,当存储类型为minio时,桶名:文件夹/文件名 或 文件夹/文件名 |
templateStorageType | TemplateStorageType | 否 | CLASSPATH | 模版存储类型,有类路径和minio |
- 使用 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));
}
}