序号
- 前言
- 需求
- 不通过excel,实现省市区级联
- 实战
- pom.xml配置
- controller配置
- service类
- 业务处理类
- 测试
 
前言
首先,我们先来了解一下java实现模板下载的几种方式
- 1、使用poi实现
- 2、使用阿里的easyexcel实现
 今天社长就给大家说一下easyexcel的实现模板下载的之旅。在这里社长祝福各位小朋友大朋友,儿童节快乐
需求
接到一个需求,实现模板的下载,并且要支持省市区三级级联,点击所属省份的时候,所属市能动态显示对应的市,选择市这一列时,所属区下拉框显示对应的区。
下图是对应模板的信息,下方第二个图是stationTemplate.xlsx文件的内容。各位社友可以作为参考,主要是理解实现的逻辑。
 
 
不通过excel,实现省市区级联
实现的逻辑:
- 创建一个sheet,第一列是父节点,从第二列开始是子节点对应的值
- 创建名称管理器,建立key value关系,方便做单元格变动时,动态显示子节点的列表
- 给省或者市设置验证规则
  
  
  
  
 点击公式里面的名称管理器,实际上就是跟我们创建的sheet建立练习,点击佛山市时,显示顺德区和南海区。达到级联的效果。
  
实战
pom.xml配置
  <!-- easypoi-spring-boot-starter -->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.1.3</version>
        </dependency>
controller配置
package com.zyee.iopace.web.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zyee.iopace.web.config.annotation.Log;
import com.zyee.iopace.web.dto.DataCondition;
import com.zyee.iopace.web.dto.dept.DeptSearchDto;
import com.zyee.iopace.web.dto.station.DataListConditionDto;
import com.zyee.iopace.web.dto.station.PrimitiveReportDto;
import com.zyee.iopace.web.dto.station.StationPhotoDto;
import com.zyee.iopace.web.entity.Cern;
import com.zyee.iopace.web.entity.Dept;
import com.zyee.iopace.web.entity.Station;
import com.zyee.iopace.web.entity.StationPicture;
import com.zyee.iopace.web.enums.BusinessType;
import com.zyee.iopace.web.enums.EcologyType;
import com.zyee.iopace.web.response.ResponseResult;
import com.zyee.iopace.web.service.CernService;
import com.zyee.iopace.web.service.StationPictureService;
import com.zyee.iopace.web.service.StationService;
import com.zyee.iopace.web.utils.BeanCopyUtils;
import com.zyee.iopace.web.utils.PageUtils;
import com.zyee.iopace.web.vo.station.PrimitiveReportVo;
import com.zyee.iopace.web.vo.station.StationCommVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * 站点模块
 * @author wu
 * @version 1.0
 * @date 2023/5/16 14:55
 */
@Slf4j
@Api(tags = "站点管理")
@RestController
@RequestMapping("/station")
public class StationController {
    @Autowired
    private StationService stationService;
    @ApiOperation(value = "站点模板下载", produces = "application/octet-stream")
    @Log(title = "站点模板下载", businessType = BusinessType.DOWNLOAD)
    @GetMapping("/downloadStationTemplate")
    public void  downloadStationTemplate(HttpServletResponse response) {
        stationService.downloadStationTemplate(response);
    }
}
- application/octet-stream 是解决swagger下载时乱码问题
service类
package com.zyee.iopace.web.service.impl;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zyee.iopace.web.config.exception.BusinessException;
import com.zyee.iopace.web.entity.*;
import com.zyee.iopace.web.dto.StationDto;
import com.zyee.iopace.web.dao.StationMapper;
import com.zyee.iopace.web.service.ModuleService;
import com.zyee.iopace.web.service.StationService;
import com.zyee.iopace.web.service.UserService;
import com.zyee.iopace.web.service.UserStationService;
import com.zyee.iopace.web.service.impl.excel.ElementSheetWriteHandler;
import com.zyee.iopace.web.service.impl.excel.StationSheetWriteHandler;
import com.zyee.iopace.web.utils.UserUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
import static com.baomidou.mybatisplus.core.toolkit.IdWorker.getId;
@Service
public class StationServiceImpl extends ServiceImpl<StationMapper, Station> implements StationService {
    @Value("${station.template-path}")
    private String templatePath;
    @Value("${downloadTemplate.stationPath}")
    private String stationPath;
    @Override
    public void downloadStationTemplate(HttpServletResponse response) {
        try {
            String path = templatePath + File.separator  + stationPath;
            String fileName = URLEncoder.encode("站点模版下载.xls", "utf-8");
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition","attachment;filename=" + fileName);
            // 写出数据
            EasyExcel.write(response.getOutputStream()).withTemplate(path).registerWriteHandler(new StationSheetWriteHandler(moduleService))
                    .sheet().doWrite((Collection<?>) null);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("下载模板失败");
        }
    }
}
- path对应的是下载模板存放在服务器的路径,一般为网络地址(第三方)
- 注意:用swagger模拟下载时,一定要加上produces属性的设置,不然会显示乱码
业务处理类
package com.zyee.iopace.web.service.impl.excel;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zyee.iopace.web.entity.ElementRange;
import com.zyee.iopace.web.entity.Module;
import com.zyee.iopace.web.enums.CharStatusEnum;
import com.zyee.iopace.web.enums.EcologyType;
import com.zyee.iopace.web.enums.RangeType;
import com.zyee.iopace.web.service.ElementRangeService;
import com.zyee.iopace.web.service.ModuleService;
import com.zyee.iopace.web.utils.ExcelUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFDataValidationConstraint;
import org.apache.poi.xssf.usermodel.XSSFDataValidationHelper;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 站点模块
 * @author wude
 * @date 2023/05/10
 */
public class StationSheetWriteHandler implements SheetWriteHandler {
   private ModuleService moduleService;
    public StationSheetWriteHandler(ModuleService moduleServices){
        this.moduleService = moduleServices;
    }
    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
    }
    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        //所有一级
        List<String> mapOneList = new ArrayList<String>();
        mapOneList.add("广东省");
        mapOneList.add("湖北省");
        //关系map
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        map.put("广东省", Arrays.asList("广州市", "佛山市"));
        map.put("湖北省", Arrays.asList("武汉市", "荆州市"));
        map.put("广州市", Arrays.asList("白云区", "越秀区"));
        map.put("佛山市", Arrays.asList("顺德区", "南海区"));
        Workbook workbook = writeWorkbookHolder.getWorkbook();
        //三级联动 sheet
        Sheet mapSheet = writeWorkbookHolder.getWorkbook().createSheet("SHEET_MAP");
        // true:隐藏/false:显示
        //省市区关系sheet
        //workbook.setSheetHidden(workbook.getSheetIndex(mapSheet), true);// 设置sheet是否隐藏
        writeData(workbook, mapSheet, mapOneList, map);
        //将省份放入下拉列表中
        String[] provinceArr = mapOneList.toArray(new String[mapOneList.size()]);
        ///开始设置(大类小类)下拉框
        DataValidationHelper dvHelper = writeSheetHolder.getSheet().getDataValidationHelper();
        // 大类规则
        DataValidationConstraint expConstraint = dvHelper.createExplicitListConstraint(provinceArr);
        CellRangeAddressList expRangeAddressList = new CellRangeAddressList(1, 999, 8, 8);
        setValidation(writeSheetHolder.getSheet(), dvHelper, expConstraint, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
        // 小类规则(各单元格按个设置)
        // "INDIRECT($A$" + 2 + ")" 表示规则数据会从名称管理器中获取key与单元格 A2 值相同的数据,如果A2是浙江省,那么此处就是浙江省下面的市
        // 为了让每个单元格的公式能动态适应,使用循环挨个给公式。
        // 循环几次,就有几个单元格生效,次数要和上面的大类影响行数一一对应,要不然最后几个没对上的单元格实现不了级联
        for (int i = 2; i < 1000; i++) {
            CellRangeAddressList rangeAddressList = new CellRangeAddressList(i-1 , i-1, 9, 9);
            DataValidationConstraint formula = dvHelper.createFormulaListConstraint("INDIRECT($I$" + i + ")");
            setValidation(writeSheetHolder.getSheet(), dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
        }
        for (int i = 2; i < 1000; i++) {
            CellRangeAddressList rangeAddressList = new CellRangeAddressList(i-1 , i-1, 10, 10);
            DataValidationConstraint formula = dvHelper.createFormulaListConstraint("INDIRECT($J$" + i + ")");
            setValidation(writeSheetHolder.getSheet(), dvHelper, formula, rangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
        }
        int number = writeWorkbookHolder.getWorkbook().getNumberOfSheets();
        //将新建立的sheet页隐藏掉
        for (int i = 1; i < number;i++){
            writeWorkbookHolder.getWorkbook().setSheetHidden(i,true);
        }
    }
    /**
     * 设置验证规则
     * @param sheet			sheet对象
     * @param helper		验证助手
     * @param constraint	createExplicitListConstraint
     * @param addressList	验证位置对象
     * @param msgHead		错误提示头
     * @param msgContext	错误提示内容
     */
    private void setValidation(Sheet sheet, DataValidationHelper helper, DataValidationConstraint constraint, CellRangeAddressList addressList, String msgHead, String msgContext) {
        DataValidation dataValidation = helper.createValidation(constraint, addressList);
        dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
        dataValidation.setShowErrorBox(true);
        dataValidation.setSuppressDropDownArrow(true);
        dataValidation.createErrorBox(msgHead, msgContext);
        sheet.addValidationData(dataValidation);
    }
    private static void setDataValid(Workbook workbook, Sheet mainSheet, List<String> provinceList, Map<String, List<String>> siteMap, CharStatusEnum charEnum) {
        //设置省份下拉
        DataValidationHelper dvHelper = new XSSFDataValidationHelper((XSSFSheet) mainSheet);
        String[] dataArray = provinceList.toArray(new String[0]);
        Sheet hidden = workbook.createSheet("hidden");
        Cell cell = null;
        for (int i = 0, length = dataArray.length; i < length; i++) {
            String name = dataArray[i];
            Row row = hidden.createRow(i);
            cell = row.createCell(0);
            cell.setCellValue(name);
        }
        Name namedCell = workbook.createName();
        namedCell.setNameName("hidden");
        namedCell.setRefersToFormula("hidden!$A$1:$A$" + dataArray.length);
        //加载数据,将名称为hidden的
        XSSFDataValidationConstraint constraint = new XSSFDataValidationConstraint(DataValidationConstraint.ValidationType.LIST, "hidden");
        // 四个参数分别是:起始行、终止行、起始列、终止列.
        // 1 (省份下拉框代表从excel第1+1行开始) 10(省份下拉框代表从excel第1+10行结束) 1(代表第几列开始,0是第一列,1是第二列) 1(代表第几列结束,0是第一列,1是第二列)
        //  CellRangeAddressList provinceRangeAddressList = new CellRangeAddressList(1, 10, 1, 1);
        CellRangeAddressList provinceRangeAddressList = new CellRangeAddressList(1, 255, charEnum.getValue() - 1, charEnum.getValue() - 1);
        DataValidation provinceDataValidation = dvHelper.createValidation(constraint, provinceRangeAddressList);
        provinceDataValidation.createErrorBox("error", "请选择正确");
        provinceDataValidation.setShowErrorBox(true);
        // provinceDataValidation.setSuppressDropDownArrow(true);
        //将第二个sheet设置为隐藏
        // true:隐藏/false:显示
        workbook.setSheetHidden(workbook.getSheetIndex(hidden), true);// 设置sheet是否隐藏
        mainSheet.addValidationData(provinceDataValidation);
        // 设置市、区下拉
        // i <= 10 ,10代表市、区下拉框到10+1行结束
        for (int i = 0; i <= 10; i++) {
            // setDataValidation('B', mainSheet, i + 1, 2);// "B"是指父类所在的列,i+1初始值为1代表从第2行开始,2要与“B”对应,为B的列号加1,假如第一个参数为“C”,那么最后一个参数就3
            setDataValidation(charEnum.getCharacter(), mainSheet, i + 1, charEnum.getValue());// "B"是指父类所在的列,i+1初始值为1代表从第2行开始,2要与“B”对应,为B的列号加1,假如第一个参数为“C”,那么最后一个参数就3
        }
    }
    private static DataValidation getDataValidationByFormula(String formulaString, int naturalRowIndex, int naturalColumnIndex, DataValidationHelper dvHelper) {
        DataValidationConstraint dvConstraint = dvHelper.createFormulaListConstraint(formulaString);
        CellRangeAddressList regions = new CellRangeAddressList(naturalRowIndex, 65535, naturalColumnIndex, naturalColumnIndex);
        DataValidation data_validation_list = dvHelper.createValidation(dvConstraint, regions);
        data_validation_list.setEmptyCellAllowed(false);
        if (data_validation_list instanceof XSSFDataValidationHelper) {
            // data_validation_list.setSuppressDropDownArrow(true);
            data_validation_list.setShowErrorBox(true);
        } else {
            // data_validation_list.setSuppressDropDownArrow(false);
        }
        // 设置输入信息提示信息
        data_validation_list.createPromptBox("下拉选择提示", "请使用下拉方式选择合适的值!");
        return data_validation_list;
    }
    /**
     * 设置有效性
     *
     * @param offset 主影响单元格所在列,即此单元格由哪个单元格影响联动
     * @param sheet
     * @param rowNum 行数
     * @param colNum 列数
     */
    private static void setDataValidation(char offset, Sheet sheet, int rowNum, int colNum) {
        DataValidationHelper dvHelper = new XSSFDataValidationHelper((XSSFSheet) sheet);
        DataValidation dataValidationList1;
        DataValidation dataValidationList2;
        dataValidationList1 = getDataValidationByFormula("INDIRECT($" + offset + (rowNum) + ")", rowNum - 1, colNum, dvHelper);
        dataValidationList2 = getDataValidationByFormula("INDIRECT($" + (char) (offset + 1) + (rowNum) + ")", rowNum - 1, colNum + 1, dvHelper);
        sheet.addValidationData(dataValidationList1);
        sheet.addValidationData(dataValidationList2);
    }
    private static void writeData(Workbook hssfWorkBook, Sheet mapSheet, List<String> provinceList, Map<String, List<String>> siteMap) {
        //循环将父数据写入siteSheet的第1行中
        int siteRowId = 0;
        Row provinceRow = mapSheet.createRow(siteRowId++);
        provinceRow.createCell(0).setCellValue("父列表");
        for (int i = 0; i < provinceList.size(); i++) {
            //有多少个省,创建多少个下拉框
            provinceRow.createCell(i + 1).setCellValue(provinceList.get(i));
        }
        // 将具体的数据写入到每一行中,行开头为父级区域,后面是子区域。
        Iterator<String> keyIterator = siteMap.keySet().iterator();
        while (keyIterator.hasNext()) {
            String key = keyIterator.next();
            List<String> son = siteMap.get(key);
            Row siteRow = mapSheet.createRow(siteRowId++);
            siteRow.createCell(0).setCellValue(key);
            for (int i = 0; i < son.size(); i++) {
                siteRow.createCell(i + 1).setCellValue(son.get(i));
            }
            // 添加名称管理器
            String range = ExcelUtils.getRange(1, siteRowId, son.size());
            Name name = hssfWorkBook.createName();
            name.setNameName(key);
            String formula = mapSheet.getSheetName() + "!" + range;
            name.setRefersToFormula(formula);
        }
    }
}
- mapOneList为省份模拟数据,需要从数据库取出,需要省市区对应字典表的sql,可以在下方@我
- map 是省份对应的市,以及市对应的区
  
- 第一个CellRangeAddressList的参数 1 999 8 8,表示1到999行都新增下拉框,8和8表示那一列
- dvHelper.createFormulaListConstraint(“INDIRECT( J J J” + i + “)”) 这里的“ J J J”,J对应的就是区,I对应的就是市
- writeWorkbookHolder.getWorkbook().setSheetHidden(i,true); 隐藏新增的sheet
测试

 点击发送->下载文件
 



















