SpringMVC在内存中直接生成Excel让用户在浏览器中直接下载使用

需求是这样子的?

  1. 如何取出数据库的SCHEMA
  2. 如何生成Excel, 以及第一行的字段COMMENT
  3. 如何直接将字节流返回给浏览器, 告诉浏览器直接下载

如何查询出数据库的SCHEMA, 这里贴出核心的SQL语句

SELECT COLUMN_NAME AS field, COLUMN_COMMENT AS comment  
FROM INFORMATION_SCHEMA.COLUMNS  
WHERE table_name = ?  

由于注释有的会很长, 全部取出来作为excel的一格, 会很不好, 其实我们只是需要重要的意思, 所以, 这里前提条件就是将注释用分隔符分开 例如: name=>名称: 用户昵称, 这里我就可以用":"来分割, 只取出"名称"来. 所以, 一件方便的事情是需要很多的约定的, 尤其是团队合作中.

为了拿到一张表的字段名, 和注释, 就提取了一个公用的类:

/**
 * Created with antnest-platform
 * User: Vernon.Chen
 * Date: 2015/3/16
 * Time: 17:03
 */
@Service
public class QuerySchema {

    @Resource
    private JdbcTemplate jdbcTemplate;

    public List<Map<String, Object>> getSchemaByTableName(String tableName) {
        if (StringUtils.isBlank(tableName)) {
            return null;
        }
        List<Map<String, Object>> schema = new ArrayList<Map<String, Object>>();
        StringBuilder sqlSB = new StringBuilder();
        sqlSB.append(" SELECT COLUMN_NAME AS field, COLUMN_COMMENT AS comment ");
        sqlSB.append(" FROM INFORMATION_SCHEMA.COLUMNS ");
        sqlSB.append(" WHERE table_name = ? ");
        schema = jdbcTemplate.queryForList(sqlSB.toString(), new Object[]{tableName});
        if (schema != null && schema.size() > 0) {
            for (Map<String, Object> map : schema) {
                String comment = (String) map.get("comment");
                if (StringUtils.isNotBlank(comment) && comment.indexOf(":") > -1) {
                    map.put("comment", comment.substring(0, comment.indexOf(":")));
                }
            }
        }
        return schema;
    }

}

这返回的结果就是一List, 里面每一个Map都是一个{"field":"XXX","comment":"XXX"}.

下面就是如何生成Excel呢? 也是比较总要的一步. 生成Excel我所知道jar包有2个, 一个是jxl.jar, 还有一个就是项目中用到的poi.

<dependency>  
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>${poi.version}</version>
</dependency>  

这里我的版本是:

<poi.version>3.10-FINAL</poi.version>  

然后开看一个组装Excel的代码. 由于不需要将生成的文件持久化到本地, 所以直接返回byte[]就好.

@Override
    public byte[] selectExcel() throws Exception {
        ByteArrayOutputStream out = null;
        try {
            HSSFWorkbook workbook = new HSSFWorkbook();
            generateExcelForAs(cellMapper.selectExcel(), workbook);
            out = new ByteArrayOutputStream();
            HSSFWorkbook hssWb = (HSSFWorkbook) workbook;
            hssWb.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return out.toByteArray();
    }

迭代生成每一行

private void generateExcelForAs(List<Map<String, Object>> list, HSSFWorkbook workbook) {  
        if (list == null || list.size() == 0) {
            return ;
        }
        List<Map<String, Object>> title = querySchema.getSchemaByTableName("t_cell");
        int excelRow = 0;
        try {
            HSSFSheet sheet = workbook.createSheet();
            for (int i = 0; i < list.size(); i++) {
                if (i == 0) {
                    Map<String, Object> first = list.get(0);
                    // 添加标题
                    int column = 0;
                    HSSFRow row = sheet.createRow(excelRow);
                    for (Map<String, Object> map : title) {
                        row.createCell(column).setCellValue(map.get("comment").toString());
                        column++;
                    }
                    excelRow++;
                    column = 0;
                    // 还需添加第1行的数据, 从0开始
                    row = sheet.createRow(excelRow);
                    for (Map<String, Object> map : title) {
                        if (!first.containsKey(map.get("field"))) {
                            column++;
                        } else {
                            String value = first.get(map.get("field")) == null ? "" : first.get(map.get("field")).toString();
                            row.createCell(column).setCellValue(value);
                            column++;
                        }
                    }
                    excelRow++;
                } else {
                    HSSFRow row = sheet.createRow(excelRow);
                    Map<String, Object> rowMap = list.get(i);
                    int column = 0;
                    for (Map<String, Object> map : title) {
                        if (!rowMap.containsKey(map.get("field"))) {
                            column++;
                        } else {
                            String value = rowMap.get(map.get("field")) == null ? "" : rowMap.get(map.get("field")).toString();
                            row.createCell(column).setCellValue(value);
                            column++;
                        }
                    }
                    excelRow++;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

代码比较挫, 效率也低, 没办法, 因为需要按照指定的顺序生成. 也许有更好的办法, 我现在只是要按时完成需求. 这里就返回了想要的byte[].

最后一步就是在Response里返回流了.

@RequestMapping(value = "/xxx")
    public void cell(HttpServletResponse response) throws Exception {
        byte[] bytes = cellService.selectExcel();
        response.setContentType("application/x-msdownload");
        response.setHeader("Content-Disposition", "attachment;filename=" + UUIDUtil.getUUID() + ".xls");
        response.setContentLength(bytes.length);
        response.getOutputStream().write(bytes);
        response.getOutputStream().flush();
        response.getOutputStream().close();
    }

这里注意的就是:

response.setContentType("application/x-msdownload");  
response.setHeader("Content-Disposition", "attachment;filename=" + UUIDUtil.getUUID() + ".xls");  

一个是告诉浏览器需要下载, 下面一个就是告诉流程器下载是的文件名字. 这里我用的UUID.

至此, 就完全OK了