通用Excel表格导出(Map类型数据导出为表格)

背景

为提升代码开发效率,项目使用了通用查询(动态数据表、动态条件、动态列名等),各表查询通过同一个页面展现,前端通过获取路径上的表名调用同一个后端控制器——动态获取到查询条件、数据列名、不同表数据等。
而既然查询这方面的业务都已经通过一个控制器(controller)处理了,那么关于表格导出这方面再每个表编写一个导出方法是不是也就不合适了?因此,这边介绍一下通用的数据导出为Excel表格导出:

功能描述

由于要求导出的表格数据需要是与当前条件查询出来的结果一致,而这也无法直接通过页面得到(因为页面数据是当前页),因此要求传入参数是一个条件对象——包含表名和查询条件(分页信息当然就可有可无了,传了也忽略掉,反正用不到);
后端根据获取到的查询条件到数据库进行查询,将获取到的数据封装到表格对象中,存放到Response中(也就省的返回了)

目录结构

通用Excel表格导出(Map类型数据导出为表格)_第1张图片

controller

这里的SysTableDataController就是整个的通用控制器,前后端的通用交互都是向这个控制器进行请求(是这么打算的)

service

控制器controller只进行业务上的逻辑处理,实际的数据处理逻辑放在服务层service;
与数据库的交互部分直接调用mapper接口;

个人习惯:
不乐意多写一个service接口然后建一个serviceImpl来实现,虽然有一定的多实现预留意义,但是实际开发极少出现多实现(数据库交互方面),至少我没遇到过,然后就确信我不乐意这么干了(手动狗头)

mapper

mapper层只负责与数据库交互

vo

为通用查询而编写的JavaBean,方便对分页数据的获取,vo的作用应该不必展开说明了

上代码(部分展示)

后端代码

核心代码

Controller

需要隐藏其他代码,博主为了省事把import全删了,勿怪勿怪(手动狗头)

package com.rongyi.web.controller.system;

/**
 * 动态获取表信息
 *
 * @author ruoyi
 */
@RestController
@RequestMapping("/system/table")
public class SysTableDataController extends BaseController {
	// 特地为表格导出而建的几个成员变量
	// 分页信息
    private PageVo pageVo;
    // 国际化语种(这个与核心业务无关)
    private String language;
    // 导出表格的文件名
    private String fileName;

    @Resource
    private Sys001Service sys001service;
    @Resource
    private Sys102Mapper sys102Mapper;
    @Resource
    private Sys102Service sys102Service;
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private TableDataService tableDataService;
    
    @ApiOperation("通用导出")
    @GetMapping("/export")
    public void export() throws ParseException {
    	// 获取返回信息对象
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        // 设置好文件类型和编码类型
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 调用具体实现方法
        tableDataService.dataExport(response, pageVo, language, fileName);
        // 打印在控制台(方便查看导出状态)
        System.out.println("数据导出成功!");
    }

    @ApiOperation("通用导出准备")
    @PostMapping("/readyExport")
    // 受限于个人水平
    // post方法传入参数(因为get方法才可以在页面弹出下载)
    public AjaxResult readyExport(@RequestBody PageVo queryParams) throws ParseException {
    	// 将分页信息和语种信息在后端保存
        pageVo = queryParams;
        language = LanguageUtils.getLanguage();
        // s001获取表描述
        // 所有表的基本信息都保存在s001表中,展示的代码更多是想要分享思路
        Sys001 sys001 = sys001service.s001ByshortName(queryParams.getShortName());
        // 保存文件名
        fileName = sys001.getS001NameCn();
        // 返回给前端文件名(因为最后的文件名是由前端控制的)
        return AjaxResult.success("准备完成", fileName + ".xlsx");
    }
}

Service

package com.rongyi.basedoc.service;

@Service
public class TableDataService {
    @Resource
    private TableDataMapper tableDataMapper;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private Sys001Service sys001Service;
    @Resource
    private Sys102Mapper sys102Mapper;

	// 通用导出
    public void dataExport(HttpServletResponse response, PageVo queryParams, String language, String fileName) throws ParseException {
        // 调用本类方法获取需要导出的数据
        queryParams.setExport(true); // true表示由导出调用查询方法
        List<Map<String, Object>> mapList = pageTableDate(queryParams);
        // 根据表名简称shortName获取到s001中该表的完整信息(方便获取需要的表信息)
        Sys001 s001 = sys001Service.s001ByshortName(queryParams.getShortName());
        // 根据s001的fields——flag_show筛选出需要导出的字段数据
        // 在页面中展示的数据列才需要导出到表格,这里也就是进行过滤
        List<Map<String, String>> fieldsMap = s001.getFieldsMaps();
        // 需要导出的字段保存到字符串集合cols 中
        List<String> cols = new ArrayList<>();
        for (Map<String, String> map : fieldsMap) {
        	// 根据flag_show来判断是否显示(是否需要导出到表格)
            if (map.get("flag_show").contains("Y")) {
                // 需要去掉双引号
                // 自己编写的StringToJson工具类,可能会遗漏双引号
                cols.add(map.get("fields_name_camel").replaceAll("\"", ""));
            }
        }
        // 数据准备好了,准备添加到表格对象中
        //定义一个新的工作簿
        XSSFWorkbook wb = new XSSFWorkbook();
        //创建一个Sheet页,一般也只有一夜,这里的处理是将页面与文件名一致
        XSSFSheet sheet = wb.createSheet(fileName);
        // 样式看需求设置,其实个人认为最重要的数据是否正确,样式更多的是锦上添花
        // 设置行高
        sheet.setDefaultRowHeight((short) (2 * 256));
        //为有数据的每列设置列宽
        for (int i = 0; i < cols.size(); i++) {
            sheet.setColumnWidth(i, 8000);
        }
        // 设置单元格字体样式
        XSSFFont font = wb.createFont();
        font.setFontName("等线");
        font.setFontHeightInPoints((short) 16);
        // 创建单元格文字居中样式并设置标题单元格居中
        XSSFCellStyle cellStyle = wb.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 创建行、列对象,遍历数据对象添加进表格
        XSSFRow rows;
        XSSFCell cells;
        // 第一行当然是给数据的列名了
        //在这个sheet页里创建一行
        rows = sheet.createRow(0);
        // 给该行数据赋值
        // 统一一下,行用i,列用j
        for (int j = 0; j < cols.size(); j++) {
        	// 列
            cells = rows.createCell(j);
            // 通过列名、语言获取国际化信息
            // 国际化方法,与核心业务无关
            String field = sys102Mapper.oneByCol(cols.get(j), language);
            // 将数据存入列中 —— 三元内容是国际化的需求
            cells.setCellValue(field != null ? field : cols.get(j));
        }
        // 循环拿到的数据给所有行每一列设置对应的值
        // 以下就是将数据存入到表格中了,具体判断有兴趣的可以看,大概率与 君 的实际需求出入不小
        for (int i = 0; i < mapList.size(); i++) {
            //在这个sheet页里创建一行
            rows = sheet.createRow(i + 1);
            // 获取到状态值对应描述
            List<Map<String,String>> stateMsgs = sys102Mapper.listStateDesc(language);
            Map<String, String> stateMap = new HashMap<>();
            for (Map<String, String> stateMsg : stateMsgs) {
                String varKey = stateMsg.get("s102_var_key");
                varKey = varKey.substring(varKey.indexOf(".") + 1);
                stateMap.put(varKey, stateMsg.get("s102_var_desc"));
            }
            // 给该行数据赋值
            for (int j = 0; j < cols.size(); j++) {
                String value = null;
                try {
                    String col = cols.get(j);
                    if (col.contains("DateCreate")) {
                        Date dataValue = (Date) mapList.get(i).get(col);
                        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        value = dateFormat.format(dataValue);
                    } else if (col.contains("FlagState")) {
                        value = stateMap.get(mapList.get(i).get(col));
                    } else {
                        value = mapList.get(i).get(col).toString();
                    }
                } catch (Exception e) {
//                    System.out.println(e.getMessage());
                }
                cells = rows.createCell(j);
                cells.setCellValue(value);
            }
        }
        // 记得关闭流、表格对象
        try {
            ServletOutputStream outputStream = response.getOutputStream();
            wb.write(outputStream);
            wb.close();
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    // 通用查询
    // 数据查询有兴趣的可以看看,本文章的核心内容还是表格导出
    public List<Map<String, Object>> pageTableDate(PageVo queryParams) throws ParseException {
        // 保存条件对象集合
        List<Map<String, Object>> queries = queryParams.getQueries();
        // 创建Builder进行查询条件拼接
        StringBuilder queryString = new StringBuilder();
        // 遍历集合
        for (Map<String, Object> query : queries) {
            // 判断为输入类型条件
            if (query.get("queryParam") != null && !"".equals(query.get("queryParam"))) {
                // 拼接格式:and instr(column,"value")
                queryString.append(" and instr(");
                queryString.append(query.get("fieldtb"));
                queryString.append(", \"");
                String queryParam = (String) query.get("queryParam");
                queryParam = queryParam.trim();
                queryString.append(queryParam);
                queryString.append("\")");
            }
            if (query.get("queryParamRange") != null){
                List<String> date = (ArrayList) query.get("queryParamRange");
                // 开始日期
                String startDate = date.get(0);
                // 结束日期
                String endDate = date.get(1);
                // 拼接格式:create_time >= str_to_date('2019-12-30', '%Y-%m-%d')
                // 不再使用str_to_date函数——本身就是这样的格式
                queryString.append(" and ");
                queryString.append(query.get("fieldtb"));
                queryString.append(" >= '");
                queryString.append(startDate);
                queryString.append("'");
                // 拼接格式:create_time < str_to_date('2019-12-30', '%Y-%m-%d')
                queryString.append(" and ");
                queryString.append(query.get("fieldtb"));
                queryString.append(" <= '");
                // 规定格式调整日期
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
                Date parse = simpleDateFormat.parse(endDate);
                // 需要获取当日所有数据,因此加一天(默认为0点)
                Long endLong = parse.getTime() + 1 * 24 * 60 * 60 * 1000;
                // 从新转为String类型数据
                endDate = simpleDateFormat.format(new Date(endLong));
                queryString.append(endDate);
                queryString.append("'");
            }
            if (query.get("queryParamSelect") != null && !"".equals(query.get("queryParamSelect"))) {
                queryString.append(" and ");
                queryString.append(query.get("field_source"));
                queryString.append(" = \"");
                queryString.append(query.get("queryParamSelect"));
                queryString.append("\"");
            }
        }
        // 表名简称
        String shortName = queryParams.getShortName();
        // 给查询条件加上where
        // 转为String保存
        String endQueryString = "";
        if (queryString.length() > 0 ) {
            endQueryString = " where" + queryString.substring(4);
        }
        // 获取sys001
        Sys001 sys001 = sys001Service.s001ByshortName(shortName);
        // s001中的查询语句,排序规则
        String sql = "", orderBy = "";
        // redis中存在则直接获取到sql和orderBy
        // TODO 不确定sql和camel有没有区别,先放着
        if (queryParams.getExport() != null && !queryParams.getExport()) {
            sql = sys001.getS001Sql();
            orderBy = sys001.getS001VarSort();
        }else {
            sql = sys001.getS001SqlCamel();
            orderBy = sys001.getS001VarSort();
        }
        // 判断是否有条件语句
        if (endQueryString != null && endQueryString.length() > 0) {
            sql = sql + endQueryString;
        }
        //表格排序
        Map<String, String> orders = queryParams.getOrder();
        if (orders.get("orderState")!=null&& orders.get("orderState")!=""){
            String orderState = orders.get("orderState");
            String orderColumn = orders.get("orderColumn");
            sql +=  " order by "+orderColumn+" "+ orderState;
        }
        // 拼接上排序规则
        if (orderBy != null && orderBy.length() > 0) {
            if (sql.contains("order by")){
                sql+= ", "+orderBy;
            }else {
                sql = sql + " order by " + orderBy;
            }
        }
        // 开启分页——非导出:导出需要查询到全部数据
        if (queryParams.getExport() == null || queryParams.getExport()) {
            startPage(queryParams.getPageNum(), queryParams.getPageSize());
        }
        // 通过sql查询到数据
        List<Map<String, Object>> mapList = tableDataMapper.pageBySql(sql);
        // 遍历数据、对数据的某些字段值进行处理
        for (Map<String, Object> map : mapList) {
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                // 时区原因:判断字段数据值为Date相关类型(这里知道是LocalDateTime)
                if (entry.getValue().getClass().toString().contains("Date")) {
                    // 时区问题莫名消失……如果出现打开以下注释
                    // 先转换为对应的LocalDateTime类型数据
                    // LocalDateTime value = (LocalDateTime) entry.getValue();
                    // 时区+8
                    // entry.setValue(value.plusHours(8L));
                    String dateString = entry.getValue().toString().replace("T", " ");
                    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    try {
                        // 拼接日期时间显示格式
                        Date date = format.parse(dateString.length() == 19 ? dateString : dateString + ":00");
                        entry.setValue(date);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        // 返回处理好的表数据
        return mapList;
    }
}

Mapper

package com.rongyi.basedoc.mapper;

@Mapper
public interface TableDataMapper {
    // 直接使用整个sql语句进行查询获取数据
    @Select("${sql}")
    List<Map<String, Object>> pageBySql(String sql);
}

前端代码(就不介绍模块了)

vue

/** 导出按钮操作 */
handleExport() {
  tableExport(this.queryParams).then((res) =>{
    let link = document.createElement("a");
    link.style.display = "none";
    link.href =
      process.env.VUE_APP_BASE_API + "/system/table/export";
    // link.href = 'http://127.0.0.1:8088/system/file/flowFile/' + file.name + '/' + file.id;
    document.body.appendChild(link);
    // 文件名
    link.download = res.data;
    link.click();
    document.body.removeChild(link); // 下载完成移除元素
  })
}

js

// 导出
export function tableExport(queryParams) {
    return request({
        url: '/system/table/readyExport',
        method: 'post',
        data: queryParams
    })
}

End

这里使用的是若依框架,导入方法需要添加进白名单
(SecurityConfig类中)(因为没有带请求头,而token在请求头)

多次提到,本篇博客最主要的是给诸君提供一个Map类型数据导出为表格的思路,如果有想要更深入交流的可以留言评论或者私信,欢迎来访

你可能感兴趣的:(Maven,Java,Web项目技术,java,spring,数据库,Excel导出)