• 苍穹外卖-day12 - 工作台 - Apache POI - 导出运营数据Excel报表


    课程内容

    功能实现:工作台数据导出

    工作台效果图:

    数据导出效果图:

    在数据统计页面点击数据导出:生成Excel报表

    1. 工作台

    1.1 需求分析和设计

    1.1.1 产品原型

    工作台是系统运营的数据看板,并提供快捷操作入口,可以有效提高商家的工作效率。

    工作台展示的数据:

    • 今日数据

    • 订单管理

    • 菜品总览

    • 套餐总览

    • 订单信息

    原型图:

    名词解释:

    • 营业额:已完成订单的总金额

    • 有效订单:已完成订单的数量

    • 订单完成率:有效订单数 / 总订单数 * 100%

    • 平均客单价:营业额 / 有效订单数

    • 新增用户:新增用户的数量

    1.1.2 接口设计

    通过上述原型图分析,共包含6个接口。

    接口设计:

    • 今日数据接口

    • 订单管理接口

    • 菜品总览接口

    • 套餐总览接口

    • 订单搜索(已完成)

    • 各个状态的订单数量统计(已完成)

    1). 今日数据的接口设计

    2). 订单管理的接口设计

    3). 菜品总览的接口设计

    4). 套餐总览的接口设计

    1.2 代码导入

    直接导入课程资料中的工作台模块功能代码即可:

    1.2.1 Controller层

    添加WorkSpaceController.java

    1. package com.sky.controller.admin;
    2. import com.sky.result.Result;
    3. import com.sky.service.WorkspaceService;
    4. import com.sky.vo.BusinessDataVO;
    5. import com.sky.vo.DishOverViewVO;
    6. import com.sky.vo.OrderOverViewVO;
    7. import com.sky.vo.SetmealOverViewVO;
    8. import io.swagger.annotations.Api;
    9. import io.swagger.annotations.ApiOperation;
    10. import lombok.extern.slf4j.Slf4j;
    11. import org.springframework.beans.factory.annotation.Autowired;
    12. import org.springframework.web.bind.annotation.GetMapping;
    13. import org.springframework.web.bind.annotation.RequestMapping;
    14. import org.springframework.web.bind.annotation.RestController;
    15. import java.time.LocalDateTime;
    16. import java.time.LocalTime;
    17. /**
    18. * 工作台
    19. */
    20. @RestController
    21. @RequestMapping("/admin/workspace")
    22. @Slf4j
    23. @Api(tags = "工作台相关接口")
    24. public class WorkSpaceController {
    25.    @Autowired
    26.    private WorkspaceService workspaceService;
    27.    /**
    28.     * 工作台今日数据查询
    29.     * @return
    30.     */
    31.    @GetMapping("/businessData")
    32.    @ApiOperation("工作台今日数据查询")
    33.    public Result<BusinessDataVO> businessData(){
    34.        //获得当天的开始时间
    35.        LocalDateTime begin = LocalDateTime.now().with(LocalTime.MIN);
    36.        //获得当天的结束时间
    37.        LocalDateTime end = LocalDateTime.now().with(LocalTime.MAX);
    38.        BusinessDataVO businessDataVO = workspaceService.getBusinessData(begin, end);
    39.        return Result.success(businessDataVO);
    40.   }
    41.    /**
    42.     * 查询订单管理数据
    43.     * @return
    44.     */
    45.    @GetMapping("/overviewOrders")
    46.    @ApiOperation("查询订单管理数据")
    47.    public Result<OrderOverViewVO> orderOverView(){
    48.        return Result.success(workspaceService.getOrderOverView());
    49.   }
    50.    /**
    51.     * 查询菜品总览
    52.     * @return
    53.     */
    54.    @GetMapping("/overviewDishes")
    55.    @ApiOperation("查询菜品总览")
    56.    public Result<DishOverViewVO> dishOverView(){
    57.        return Result.success(workspaceService.getDishOverView());
    58.   }
    59.    /**
    60.     * 查询套餐总览
    61.     * @return
    62.     */
    63.    @GetMapping("/overviewSetmeals")
    64.    @ApiOperation("查询套餐总览")
    65.    public Result<SetmealOverViewVO> setmealOverView(){
    66.        return Result.success(workspaceService.getSetmealOverView());
    67.   }
    68. }

    1.2.2 Service层接口

    添加WorkspaceService.java

    1. package com.sky.service;
    2. import com.sky.vo.BusinessDataVO;
    3. import com.sky.vo.DishOverViewVO;
    4. import com.sky.vo.OrderOverViewVO;
    5. import com.sky.vo.SetmealOverViewVO;
    6. import java.time.LocalDateTime;
    7. public interface WorkspaceService {
    8.    /**
    9.     * 根据时间段统计营业数据
    10.     * @param begin
    11.     * @param end
    12.     * @return
    13.     */
    14.    BusinessDataVO getBusinessData(LocalDateTime begin, LocalDateTime end);
    15.    /**
    16.     * 查询订单管理数据
    17.     * @return
    18.     */
    19.    OrderOverViewVO getOrderOverView();
    20.    /**
    21.     * 查询菜品总览
    22.     * @return
    23.     */
    24.    DishOverViewVO getDishOverView();
    25.    /**
    26.     * 查询套餐总览
    27.     * @return
    28.     */
    29.    SetmealOverViewVO getSetmealOverView();
    30. }

    1.2.3 Service层实现类

    添加WorkspaceServiceImpl.java

    1. package com.sky.service.impl;
    2. import com.sky.constant.StatusConstant;
    3. import com.sky.entity.Orders;
    4. import com.sky.mapper.DishMapper;
    5. import com.sky.mapper.OrderMapper;
    6. import com.sky.mapper.SetmealMapper;
    7. import com.sky.mapper.UserMapper;
    8. import com.sky.service.WorkspaceService;
    9. import com.sky.vo.BusinessDataVO;
    10. import com.sky.vo.DishOverViewVO;
    11. import com.sky.vo.OrderOverViewVO;
    12. import com.sky.vo.SetmealOverViewVO;
    13. import lombok.extern.slf4j.Slf4j;
    14. import org.springframework.beans.factory.annotation.Autowired;
    15. import org.springframework.stereotype.Service;
    16. import java.time.LocalDateTime;
    17. import java.time.LocalTime;
    18. import java.util.HashMap;
    19. import java.util.Map;
    20. @Service
    21. @Slf4j
    22. public class WorkspaceServiceImpl implements WorkspaceService {
    23.    @Autowired
    24.    private OrderMapper orderMapper;
    25.    @Autowired
    26.    private UserMapper userMapper;
    27.    @Autowired
    28.    private DishMapper dishMapper;
    29.    @Autowired
    30.    private SetmealMapper setmealMapper;
    31.    /**
    32.     * 根据时间段统计营业数据
    33.     * @param begin
    34.     * @param end
    35.     * @return
    36.     */
    37.    public BusinessDataVO getBusinessData(LocalDateTime begin, LocalDateTime end) {
    38.        /**
    39.         * 营业额:当日已完成订单的总金额
    40.         * 有效订单:当日已完成订单的数量
    41.         * 订单完成率:有效订单数 / 总订单数
    42.         * 平均客单价:营业额 / 有效订单数
    43.         * 新增用户:当日新增用户的数量
    44.         */
    45.        Map map = new HashMap();
    46.        map.put("begin",begin);
    47.        map.put("end",end);
    48.        //查询总订单数
    49.        Integer totalOrderCount = orderMapper.countByMap(map);
    50.        map.put("status", Orders.COMPLETED);
    51.        //营业额
    52.        Double turnover = orderMapper.sumByMap(map);
    53.        turnover = turnover == null? 0.0 : turnover;
    54.        //有效订单数
    55.        Integer validOrderCount = orderMapper.countByMap(map);
    56.        Double unitPrice = 0.0;
    57.        Double orderCompletionRate = 0.0;
    58.        if(totalOrderCount != 0 && validOrderCount != 0){
    59.            //订单完成率
    60.            orderCompletionRate = validOrderCount.doubleValue() / totalOrderCount;
    61.            //平均客单价
    62.            unitPrice = turnover / validOrderCount;
    63.       }
    64.        //新增用户数
    65.        Integer newUsers = userMapper.countByMap(map);
    66.        return BusinessDataVO.builder()
    67.               .turnover(turnover)
    68.               .validOrderCount(validOrderCount)
    69.               .orderCompletionRate(orderCompletionRate)
    70.               .unitPrice(unitPrice)
    71.               .newUsers(newUsers)
    72.               .build();
    73.   }
    74.    /**
    75.     * 查询订单管理数据
    76.     *
    77.     * @return
    78.     */
    79.    public OrderOverViewVO getOrderOverView() {
    80.        Map map = new HashMap();
    81.        map.put("begin", LocalDateTime.now().with(LocalTime.MIN));
    82.        map.put("status", Orders.TO_BE_CONFIRMED);
    83.        //待接单
    84.        Integer waitingOrders = orderMapper.countByMap(map);
    85.        //待派送
    86.        map.put("status", Orders.CONFIRMED);
    87.        Integer deliveredOrders = orderMapper.countByMap(map);
    88.        //已完成
    89.        map.put("status", Orders.COMPLETED);
    90.        Integer completedOrders = orderMapper.countByMap(map);
    91.        //已取消
    92.        map.put("status", Orders.CANCELLED);
    93.        Integer cancelledOrders = orderMapper.countByMap(map);
    94.        //全部订单
    95.        map.put("status", null);
    96.        Integer allOrders = orderMapper.countByMap(map);
    97.        return OrderOverViewVO.builder()
    98.               .waitingOrders(waitingOrders)
    99.               .deliveredOrders(deliveredOrders)
    100.               .completedOrders(completedOrders)
    101.               .cancelledOrders(cancelledOrders)
    102.               .allOrders(allOrders)
    103.               .build();
    104.   }
    105.    /**
    106.     * 查询菜品总览
    107.     *
    108.     * @return
    109.     */
    110.    public DishOverViewVO getDishOverView() {
    111.        Map map = new HashMap();
    112.        map.put("status", StatusConstant.ENABLE);
    113.        Integer sold = dishMapper.countByMap(map);
    114.        map.put("status", StatusConstant.DISABLE);
    115.        Integer discontinued = dishMapper.countByMap(map);
    116.        return DishOverViewVO.builder()
    117.               .sold(sold)
    118.               .discontinued(discontinued)
    119.               .build();
    120.   }
    121.    /**
    122.     * 查询套餐总览
    123.     *
    124.     * @return
    125.     */
    126.    public SetmealOverViewVO getSetmealOverView() {
    127.        Map map = new HashMap();
    128.        map.put("status", StatusConstant.ENABLE);
    129.        Integer sold = setmealMapper.countByMap(map);
    130.        map.put("status", StatusConstant.DISABLE);
    131.        Integer discontinued = setmealMapper.countByMap(map);
    132.        return SetmealOverViewVO.builder()
    133.               .sold(sold)
    134.               .discontinued(discontinued)
    135.               .build();
    136.   }
    137. }

    1.2.4 Mapper层

    在SetmealMapper中添加countByMap方法定义

    1. /**
    2.     * 根据条件统计套餐数量
    3.     * @param map
    4.     * @return
    5.     */
    6.    Integer countByMap(Map map);

    在SetmealMapper.xml中添加对应SQL实现

    1. <select id="countByMap" resultType="java.lang.Integer">
    2.       select count(id) from setmeal
    3.        <where>
    4.            <if test="status != null">
    5.               and status = #{status}
    6.            </if>
    7.            <if test="categoryId != null">
    8.               and category_id = #{categoryId}
    9.            </if>
    10.        </where>
    11. </select>

    在DishMapper中添加countByMap方法定义

    1. /**
    2.     * 根据条件统计菜品数量
    3.     * @param map
    4.     * @return
    5.     */
    6.    Integer countByMap(Map map);

    在DishMapper.xml中添加对应SQL实现

    1. <select id="countByMap" resultType="java.lang.Integer">
    2.       select count(id) from dish
    3.        <where>
    4.            <if test="status != null">
    5.               and status = #{status}
    6.            </if>
    7.            <if test="categoryId != null">
    8.               and category_id = #{categoryId}
    9.            </if>
    10.        </where>
    11. </select>

    1.3 功能测试

    可以通过如下方式进行测试:

    • 通过接口文档测试

    • 前后端联调测试

    接下来我们使用上述两种方式分别测试。

    1.3.1 前后端联调测试

    启动nginx,访问 http://localhost,进入工作台

    进入开发者模式,分别查看今日数据、订单管理、菜品总览、套餐总览

    1). 今日数据查询

    2). 订单管理数据查询

    3). 菜品总览查询

    4). 套餐总览查询

    2. Apache POI

    2.1 介绍

    Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。 一般情况下,POI 都是用于操作 Excel 文件。

    Apache POI 的应用场景:

    • 银行网银系统导出交易明细

    • 批量导入业务数据

    2.2 入门案例

    Apache POI既可以将数据写入Excel文件,也可以读取Excel文件中的数据,接下来分别进行实现。

    Apache POI的maven坐标:(项目中已导入)

    1. <dependency>
    2.    <groupId>org.apache.poi</groupId>
    3.    <artifactId>poi</artifactId>
    4.    <version>3.16</version>
    5. </dependency>
    6. <dependency>
    7.    <groupId>org.apache.poi</groupId>
    8.    <artifactId>poi-ooxml</artifactId>
    9.    <version>3.16</version>
    10. </dependency>

    2.2.1 将数据写入Excel文件

    1). 代码开发

    1. package com.sky.test;
    2. import org.apache.poi.xssf.usermodel.XSSFCell;
    3. import org.apache.poi.xssf.usermodel.XSSFRow;
    4. import org.apache.poi.xssf.usermodel.XSSFSheet;
    5. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
    6. import java.io.File;
    7. import java.io.FileInputStream;
    8. import java.io.FileOutputStream;
    9. public class POITest {
    10.    /**
    11.     * 基于POI向Excel文件写入数据
    12.     * @throws Exception
    13.     */
    14.    public static void write() throws Exception{
    15.        //在内存中创建一个Excel文件对象
    16.        XSSFWorkbook excel = new XSSFWorkbook();
    17.        //创建Sheet页
    18.        XSSFSheet sheet = excel.createSheet("itcast");
    19.        //在Sheet页中创建行,0表示第1
    20.        XSSFRow row1 = sheet.createRow(0);
    21.        //创建单元格并在单元格中设置值,单元格编号也是从0开始,1表示第2个单元格
    22.        row1.createCell(1).setCellValue("姓名");
    23.        row1.createCell(2).setCellValue("城市");
    24.        XSSFRow row2 = sheet.createRow(1);
    25.        row2.createCell(1).setCellValue("张三");
    26.        row2.createCell(2).setCellValue("北京");
    27.        XSSFRow row3 = sheet.createRow(2);
    28.        row3.createCell(1).setCellValue("李四");
    29.        row3.createCell(2).setCellValue("上海");
    30.        FileOutputStream out = new FileOutputStream(new File("D:\\itcast.xlsx"));
    31.        //通过输出流将内存中的Excel文件写入到磁盘上
    32.        excel.write(out);
    33.        //关闭资源
    34.        out.flush();
    35.        out.close();
    36.        excel.close();
    37.   }
    38.    public static void main(String[] args) throws Exception {
    39.        write();
    40.   }
    41. }

    2). 实现效果

    在D盘中生成itcast.xlsx文件,创建名称为itcast的Sheet页,同时将内容成功写入。

    2.2.2 读取Excel文件中的数据

    1). 代码开发

    1. package com.sky.test;
    2. import org.apache.poi.xssf.usermodel.XSSFCell;
    3. import org.apache.poi.xssf.usermodel.XSSFRow;
    4. import org.apache.poi.xssf.usermodel.XSSFSheet;
    5. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
    6. import java.io.File;
    7. import java.io.FileInputStream;
    8. import java.io.FileOutputStream;
    9. public class POITest {
    10.    /**
    11.     * 基于POI读取Excel文件
    12.     * @throws Exception
    13.     */
    14.    public static void read() throws Exception{
    15.        FileInputStream in = new FileInputStream(new File("D:\\itcast.xlsx"));
    16.        //通过输入流读取指定的Excel文件
    17.        XSSFWorkbook excel = new XSSFWorkbook(in);
    18.        //获取Excel文件的第1个Sheet页
    19.        XSSFSheet sheet = excel.getSheetAt(0);
    20.        //获取Sheet页中的最后一行的行号
    21.        int lastRowNum = sheet.getLastRowNum();
    22.        for (int i = 0; i <= lastRowNum; i++) {
    23.            //获取Sheet页中的行
    24.            XSSFRow titleRow = sheet.getRow(i);
    25.            //获取行的第2个单元格
    26.            XSSFCell cell1 = titleRow.getCell(1);
    27.            //获取单元格中的文本内容
    28.            String cellValue1 = cell1.getStringCellValue();
    29.            //获取行的第3个单元格
    30.            XSSFCell cell2 = titleRow.getCell(2);
    31.            //获取单元格中的文本内容
    32.            String cellValue2 = cell2.getStringCellValue();
    33.            System.out.println(cellValue1 + " " +cellValue2);
    34.       }
    35.        //关闭资源
    36.        in.close();
    37.        excel.close();
    38.   }
    39.    public static void main(String[] args) throws Exception {
    40.        read();
    41.   }
    42. }
     
    

    2). 实现效果

    将itcast.xlsx文件中的数据进行读取

    3. 导出运营数据Excel报表

    3.1 需求分析和设计

    3.1.1 产品原型

    在数据统计页面,有一个数据导出的按钮,点击该按钮时,其实就会下载一个文件。这个文件实际上是一个Excel形式的文件,文件中主要包含最近30日运营相关的数据。表格的形式已经固定,主要由概览数据和明细数据两部分组成。真正导出这个报表之后,相对应的数字就会填充在表格中,就可以进行存档。

    原型图:

    导出的Excel报表格式:

    业务规则:

    • 导出Excel形式的报表文件

    • 导出最近30天的运营数据

    3.1.2 接口设计

    通过上述原型图设计对应的接口。

    注意:

    • 当前接口没有传递参数,因为导出的是最近30天的运营数据,后端计算即可,所以不需要任何参数

    • 当前接口没有返回数据,因为报表导出功能本质上是文件下载,服务端会通过输出流将Excel文件下载到客户端浏览器

    3.2 代码开发

    3.2.1 实现步骤

    1). 设计Excel模板文件

    2). 查询近30天的运营数据

    3). 将查询到的运营数据写入模板文件

    4). 通过输出流将Excel文件下载到客户端浏览器

    3.2.2 Controller层

    根据接口定义,在ReportController中创建export方法:

    1. /**
    2.     * 导出运营数据报表
    3.     * @param response
    4.     */
    5.    @GetMapping("/export")
    6.    @ApiOperation("导出运营数据报表")
    7.    public void export(HttpServletResponse response){
    8.        reportService.exportBusinessData(response);
    9.   }

    3.2.3 Service层接口

    在ReportService接口中声明导出运营数据报表的方法:

    1. /**
    2.     * 导出近30天的运营数据报表
    3.     * @param response
    4.     **/
    5.    void exportBusinessData(HttpServletResponse response);

    3.2.4 Service层实现类

    在ReportServiceImpl实现类中实现导出运营数据报表的方法:

    提前将资料中的运营数据报表模板.xlsx拷贝到项目的resources/template目录中

    1.    /**导出近30天的运营数据报表
    2.     * @param response
    3.     **/
    4.    public void exportBusinessData(HttpServletResponse response) {
    5.        LocalDate begin = LocalDate.now().minusDays(30);
    6.        LocalDate end = LocalDate.now().minusDays(1);
    7.        //查询概览运营数据,提供给Excel模板文件
    8.        BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(begin,LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
    9.        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");
    10.        try {
    11.            //基于提供好的模板文件创建一个新的Excel表格对象
    12.            XSSFWorkbook excel = new XSSFWorkbook(inputStream);
    13.            //获得Excel文件中的一个Sheet页
    14.            XSSFSheet sheet = excel.getSheet("Sheet1");
    15.            sheet.getRow(1).getCell(1).setCellValue(begin + "至" + end);
    16.            //获得第4
    17.            XSSFRow row = sheet.getRow(3);
    18.            //获取单元格
    19.            row.getCell(2).setCellValue(businessData.getTurnover());
    20.            row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
    21.            row.getCell(6).setCellValue(businessData.getNewUsers());
    22.            row = sheet.getRow(4);
    23.            row.getCell(2).setCellValue(businessData.getValidOrderCount());
    24.            row.getCell(4).setCellValue(businessData.getUnitPrice());
    25.            for (int i = 0; i < 30; i++) {
    26.                LocalDate date = begin.plusDays(i);
    27.               //准备明细数据
    28.                businessData = workspaceService.getBusinessData(LocalDateTime.of(date,LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX));
    29.                row = sheet.getRow(7 + i);
    30.                row.getCell(1).setCellValue(date.toString());
    31.                row.getCell(2).setCellValue(businessData.getTurnover());
    32.                row.getCell(3).setCellValue(businessData.getValidOrderCount());
    33.                row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
    34.                row.getCell(5).setCellValue(businessData.getUnitPrice());
    35.                row.getCell(6).setCellValue(businessData.getNewUsers());
    36.           }
    37.            //通过输出流将文件下载到客户端浏览器中
    38.            ServletOutputStream out = response.getOutputStream();
    39.            excel.write(out);
    40.            //关闭资源
    41.            out.flush();
    42.            out.close();
    43.            excel.close();
    44.       }catch (IOException e){
    45.            e.printStackTrace();
    46.       }
    47.   }

    3.3 功能测试

    直接使用前后端联调测试。

    进入数据统计

    点击数据导出:Excel报表下载成功

  • 相关阅读:
    【力客热题HOT100】-【052】146 LRU缓存
    kubernetes——快速部署
    uniapp封装mixins实现H5和微信小程序的微信支付
    【Java基础知识 46】GET和POST的区别,java模拟postman发post请求
    北斗导航 | 最小二乘模糊度估计方法(LSAR:Least Squares Ambiguity Resolution)
    【算法 | 模拟No.4】AcWing 756. 蛇形矩阵 & AcWing 40. 顺时针打印矩阵
    【深度学习1】 BGD梯度下降算法
    MongoDB综合实战篇(超容易)
    java 字符串练习(罗马数字的转换)
    《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(16)-Charles其他骚操作之大结局
  • 原文地址:https://blog.csdn.net/ros275229/article/details/136311985