Excel读写操作
使用Apache POI提供了程序对微软Office文档读写的操作
1.导入相关的jar包
2.读写Excel表,并插入几个数据,将表导入到项目根目录中
3.通过代码解析Excel和生成Excel
//Excel文件---HSSWorkbook(针对xls格式文件)工作薄对象----sheet----rows
HSSFWorkbook hssfWorkbook = new HSSFWorkbook(new FileInputStream("info.xls"));//获取工作簿对象
HSSFSheet sheet = hssfWorkbook.getSheet("Sheet1");//通过名称获取Sheet
HSSFSheet sheet = hssfWorkbook.getAt(0);//通过下标获取Sheet
//得到sheet,解析每一行
for(Row row:sheet){
for(Cell cell:row){
cell.getStringCellValue();//获取每个单元格的值,如果Excel单元格使用的是数字格式,不能使用GetStringCellValue
cell.getNumberCellValue();//读取数字
/*
实际开发中采用判断单元格格式的方式
*/
if(cell.getCellType()==Cell.CELL_TYPE_STRING){
cell.getStringCellValue();
}else if(cell.getCellType()==Cell.CELL_CELL_TYPE_NUMERIC){
cell.getNumbericCellValue();
}
}
}
鉴于以上的判断,所以可以在Excel单元格中右键以文本的格式进行存储
//打印指定单元格
row.getCell(1).getStringValue();//每行第二个单元格内容
生成Excel文件
//向C盘根目录生成一个Excel文件
HSSFWorkbook hssfWorkbook = new HSSFWorkbook(); //1.创建空工作簿
HSSFSheet sheet = hssfWorkbook.createSheet("数据信息"); //2.创建sheet
HSSFRow row = sheet.createRow(0); //3.创建行
row.createCell(0).setCellValue("产品"); //4.创建单元格,并填充数据
row.createCell(1).setCellValue("价格");
//将数据写入到磁盘上
hssfWorkbook.write(new FileOutputStream(C:/text.xls));
================================================================
导出
实体类
@Data
public class DictEeVo{
@ExcelProperty(value="id",index = 0)
private Long id;
@ExcelProperty(value="上级id",index=1)
private Long parantId;
@ExcelProperty(value="名称",index=2)
private String name;
@ExcelProperty(value="值",index=3)
private String value;
@ExcelProperty(value="编码",index=4)
private String dictCode;
}
Controller
//Controller 导出数据字典
@GetMapping("exportData")
public void exportDict(HttpServletResponse response){
dictService.exportDictData(response);
}
//Service实现类
public void exportDictData(HttpServletResponse response){
//设置下载信息
response.setContentType("application/vnd.ms-excel");
response.setharacterEncoding("utf-8");
String fileName = URLEncoder.encode("数据字典","UTF-8");
response.setHeader("Content-disposition","attachment;filename="+fileName+".xlsx");
//查询数据库
List<Dict> dictList = baseMapper.selectList(null);
//将Dict->DictEeVo
List<DictEeVo> dictVoList = new ArrayList<>();
for(Dict dict:dictList){
DictEeVo dictEeVo = new DictEeVo();
BeanUtils.copyProperties(dict,dictEeVo);
dictVoList.add(dictEeVo);
}
//调用EasyExcel方法进行写操作
try{
EasyExcel.write(response.getOutputStream(),DictEeVo.class).sheet("dict")
.doWrite(dictVoList);
}catch( IOException e){
e.printStackTrace();
}
}
前端list.vue
<div class="el-toolbar">
<div class="el-toolbar-body" style="justify-content:flex-start;">
<el-button type="text" @click="exportData">
<i class="fa fa-plus">导出
</el-button>
</div>
</div>
<javascript>
import dict from '@/api/dict'
export default{
data(){
return {
list:[]
}
},
created(){
this.getDictList(1)
},
methods:{
exportData(){
//调用导出接口
window.location.href="http://localhost:8202/admin/cmn/dict/exportData"
}
}
}
</javascript>
导入
要求实体类的id必须取消自动添加(直接继承BaseMapper默认是自动增长)
Controller
@PostMapping("importData")
public Result importDict(MultipartFile file){
dictService.importDictData(file);
return Result.ok();
}
Service实现类
@Override
public void importDictData(MultipartFile file){
try{
EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(baseMapper)).sheet().doRead();
}catch(IOException e){
e.printStackTrace();
}
}
Listener监听器
public class DictListener extends AnalysisEventListener<DictEeVo>{
//通过构造的方式将mapper注入
private DictMapper dictMapper;
public DictListener(DictMapper dictMapper){
this.dictMapper = dictMapper;
}
//一行一行读取,dictEeVo为读取后封装对象
@Override
public void invoke(DictEeVo dictEeVo,AnalysisContext analysisContext){
Dict dict = new Dict();
BeanUtils.copyProperties(dictEeVo,dict);
dictMapper.insert(dict);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext){
}
}
前端list.vue导入按钮
<el-button type="text" @click="importData"><i class="fa fa-plus"/>导入</el-button>
methods:{
importData(){}
}
==============================================
SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验
一、自定义注解
该注解主要标记相应字段与cell的对应关系
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ImportValidation{
//下标,与excel中列对应,从0开始
int index();
//是否必填,默认是必填
boolean nullAble() default true;
// 字典的Code,用于字典转换
String domainCode() default "";
//字典的名称,用于错误提醒
String name() default "";
}
二、定义一个公共的静态方法
该公共方法需要包含三个参数
- class:用于组装数据
- Map<Integer,String[]>:保存从excel中读取的全部内容
- domainCodes:用于涉及的字段转换,调用方应将字段按照code组装成Map的形式以供使用
public static Result assembleExcelData(Class entryClass, Map<Integer, String[]> excelData,
Map<String,Object> domainCodes){
//保存返回的结果
Result result = new Result();
//组装后的数据LIST
List<Object> returnList = new ArrayList<>();
//保存校验失败信息
StringBuilder errorMsg = new StringBuilder();
//循环excel数据
excelData.forEach((i,cells)->{
Object vo = null;
try {
//按照传入的Class,生成对应实例
vo= entryClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
//获取并循环Bean中的所有字段,进行校验和组装
for (Field field : entryClass.getDeclaredFields()) {
//如果包含有ImportValidation注解的话,才进行处理。
if (field.isAnnotationPresent(ImportValidation.class)) {
ImportValidation annotation = field.getAnnotation(ImportValidation.class);
//cell下表
int index = annotation.index();
//字典Code
String domainCode = annotation.domainCode();
//是否必填
boolean nullAble = annotation.nullAble();
//字段名称
String name = annotation.name();
//获取单元格内容,并前后去空格处理
String cellData = cells[index].trim();
/*如果字段为空,且字段设置不能为空,则进行错误提醒*/
try {
//若必填,则进行判断校验并提醒
if (StringUtils.isEmpty(cellData) && !nullAble) {
errorMsg.append("第").append(i).append("行: ").append(name).append("字段不能为空!\r\n");
}
/*如果字典编码为空,则可以直接赋值*/
else if (StringUtils.isEmpty(domainCode) || StringUtils.isEmpty(cellData)) {
//给对应字段赋值
setFiled(field, vo, cellData);
} else {
//进行字典转换
List<Map> domains = (List<Map>) domainCodes.get(domainCode);
boolean match = false;
for (Map map : domains) {
if (map.get("TEXT").equals(cellData)) {
//给对应字段赋值
setFiled(field, vo, String.valueOf(map.get("VALUE")));
match = true;
break;
}
}
/*如果没有匹配,则转换失败*/
if (!match) {
errorMsg.append("第").append(i).append("行: ").append(name).append("字段字典值不存在!!\r\n");
}
}
} catch (Exception e) {
errorMsg.append("第").append(i).append("行: ").append(name).append("字段填写格式不正确!!\r\n");
}
}
}
//组装LIST
returnList.add(vo);
});
//如果有错误信息的话,返回错误信息,返回错误标记
if (errorMsg.length()>0){
result = Result.buildError();
result.setMsg(errorMsg.toString());
}
//放入组装后的LIST。校验失败的字段值为空
result.setData(returnList);
return result;
}
//反射给Filed赋值
public static void setFiled(Field filed,Object vo,String data) throws IllegalAccessException {
try {
//当单元格值不为空的时候才需要进行赋值操作
if (StringUtils.isNotEmpty(data)){
//获取Bean 属性字段的类型
Type fileType = filed.getGenericType();
filed.setAccessible(true);
//如果是String
if (fileType.equals(String.class)){
filed.set(vo,data);
}
//如果是int
else if(fileType.equals(int.class)||fileType.equals(Integer.class)){
filed.set(vo,Integer.valueOf(data));
}
//如果是Double
else if(fileType.equals(Double.class)||fileType.equals(double.class)){
filed.set(vo,Double.valueOf(data));
}
//如果是Long
else if(fileType.equals(Long.class)||fileType.equals(long.class)){
filed.set(vo,Long.valueOf(data));
}
//如果是BigDecimal
else if(fileType.equals(BigDecimal.class)){
filed.set(vo,new BigDecimal(data));
}
//如果是日期
else if(fileType.equals(Date.class)){
filed.set(vo, DateUtils.parseIso8601DateTime(data));
}
}
} catch (Exception e) {
throw e;
}
}
三、使用
如果校验失败的话是给前端返回一个错误提醒内容的txt文件。可自行根据项目情况处理。校验成功则做插入的操作。
String domainCodesStr = "MM_DIC_PART_ATTR,MM_DIC_PART_TYPE,MM_DIC_PART_BELONG,MM_DIC_BASE_UNIT," +
"MM_DIC_PART_SOURCE,MM_DIC_W_UNIT,MM_MIN_SHELF_LIFE_UNIT,MM_CURRENCY";
/*查询相关字典,进行校验和转换*/
Map<String, Object> domainsCodes = wsDataDomainService.getDataByDomainCodes(domainCodesStr.split(","));
/*校验并组装数据*/
Result result = ExcelUtils.assembleExcelData(MmPartNumber.class, excelData, domainsCodes);
if (result.getCode() != 0) {
String realPath = SpringContextHolder.getServletContext().getRealPath("/");
String destination = realPath + "导入错误信息.txt";
/*返回错误信息文件*/
File file = new File(destination);
if (!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(result.getMsg());
fileWriter.close();
HttpServletResponse response = context.getHttpServletResponse();
FileDownload.fileDownload(response, realPath + "导入错误信息.txt", "导入错误信息.txt");
} else {
//TODO BatchInsert
}
效果