苍穹外卖-工作台实现、Apache POI、导出Excel报表

目录

[1. 工作台](#1. 工作台)

[1.1 需求分析和设计](#1.1 需求分析和设计)

[1.1.1 产品原型](#1.1.1 产品原型)

[1.1.2 接口设计](#1.1.2 接口设计)

[1.2 代码实现](#1.2 代码实现)

[1.2.1 Controller层](#1.2.1 Controller层)

[1.2.2 Service层接口](#1.2.2 Service层接口)

[1.2.3 Service层实现类](#1.2.3 Service层实现类)

[1.2.4 Mapper层](#1.2.4 Mapper层)

[1.3 功能测试](#1.3 功能测试)

[2. Apache POI](#2. Apache POI)

[2.1 介绍](#2.1 介绍)

[2.2 入门案例](#2.2 入门案例)

[2.2.1 将数据写入Excel文件](#2.2.1 将数据写入Excel文件)

[2.2.2 通过POI基于模板写入数据到execl](#2.2.2 通过POI基于模板写入数据到execl)

[2.2.3 读取Excel文件中的数据](#2.2.3 读取Excel文件中的数据)

[3.2 代码开发](#3.2 代码开发)

[3.2.1 实现步骤](#3.2.1 实现步骤)

[3.2.2 Controller层](#3.2.2 Controller层)

[3.2.3 Service层接口](#3.2.3 Service层接口)

[3.2.4 Service层实现类](#3.2.4 Service层实现类)

[3.3 功能测试](#3.3 功能测试)

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

工作台效果图:

数据导出效果图:

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

1. 工作台

1.1 需求分析和设计

1.1.1 产品原型

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

工作台展示的数据:

  • 今日数据

  • 订单管理

  • 菜品总览

  • 套餐总览

  • 订单信息

原型图:

名词解释:

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

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

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

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

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

1.1.2 接口设计

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

接口设计:

  • 今日数据接口

  • 订单管理接口

  • 菜品总览接口

  • 套餐总览接口

  • 订单搜索(已完成)

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

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

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

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

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

1.2 代码实现

1.2.1 Controller层

添加WorkSpaceController.java

java 复制代码
package com.sky.controller.admin;

import com.sky.service.WorkspaceService;
import com.sky.result.Result;
import com.sky.vo.BusinessDataVO;
import com.sky.vo.DishOverViewVO;
import com.sky.vo.OrderOverViewVO;
import com.sky.vo.SetmealOverViewVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.LocalTime;

/**
 * 工作台
 */
@RestController
@RequestMapping("/admin/workspace")
@Slf4j
@Api(tags = "工作台相关接口")
public class WorkSpaceController {

    @Autowired
    private WorkspaceService workspaceService;

    /**
     * 工作台今日数据查询
     * @return
     */
    @GetMapping("/businessData")
    @ApiOperation("工作台今日数据查询")
    public Result<BusinessDataVO> businessData(){
        //获得当天的开始时间
        LocalDateTime begin = LocalDateTime.now().with(LocalTime.MIN);
        //获得当天的结束时间
        LocalDateTime end = LocalDateTime.now().with(LocalTime.MAX);

        BusinessDataVO businessDataVO = workspaceService.getBusinessData(begin, end);
        return Result.success(businessDataVO);
    }

    /**
     * 查询订单管理数据
     * @return
     */
    @GetMapping("/overviewOrders")
    @ApiOperation("查询订单管理数据")
    public Result<OrderOverViewVO> orderOverView(){
        return Result.success(workspaceService.getOrderOverView());
    }

    /**
     * 查询菜品总览
     * @return
     */
    @GetMapping("/overviewDishes")
    @ApiOperation("查询菜品总览")
    public Result<DishOverViewVO> dishOverView(){
        return Result.success(workspaceService.getDishOverView());
    }

    /**
     * 查询套餐总览
     * @return
     */
    @GetMapping("/overviewSetmeals")
    @ApiOperation("查询套餐总览")
    public Result<SetmealOverViewVO> setmealOverView(){
        return Result.success(workspaceService.getSetmealOverView());
    }
}

1.2.2 Service层接口

添加WorkspaceService.java

java 复制代码
package com.sky.service;

import com.sky.vo.BusinessDataVO;
import com.sky.vo.DishOverViewVO;
import com.sky.vo.OrderOverViewVO;
import com.sky.vo.SetmealOverViewVO;
import java.time.LocalDateTime;

public interface WorkspaceService {

    /**
     * 根据时间段统计营业数据
     * @param begin
     * @param end
     * @return
     */
    BusinessDataVO getBusinessData(LocalDateTime begin, LocalDateTime end);

    /**
     * 查询订单管理数据
     * @return
     */
    OrderOverViewVO getOrderOverView();

    /**
     * 查询菜品总览
     * @return
     */
    DishOverViewVO getDishOverView();

    /**
     * 查询套餐总览
     * @return
     */
    SetmealOverViewVO getSetmealOverView();

}

1.2.3 Service层实现类

添加WorkspaceServiceImpl.java

java 复制代码
@Service
@Slf4j
public class WorkspaceServiceImpl implements WorkspaceService {

    @Autowired
    private OrdersMapper orderMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 根据时间段统计营业数据
     * @param begin
     * @param end
     * @return
     */
    public BusinessDataVO getBusinessData(LocalDateTime begin, LocalDateTime end) {
        /**
         * 营业额:当日已完成订单的总金额
         * 有效订单:当日已完成订单的数量
         * 订单完成率:有效订单数 / 总订单数
         * 平均客单价:营业额 / 有效订单数
         * 新增用户:当日新增用户的数量
         */

        Map map = new HashMap();
        map.put("beginTime",begin);
        map.put("endTime",end);

        //查询总订单数
        Integer totalOrderCount = orderMapper.countByMap(map);

        map.put("status", Orders.COMPLETED);
        //营业额
        Double turnover = orderMapper.sumByMap(map);
        turnover = turnover == null? 0.0 : turnover;

        //有效订单数
        Integer validOrderCount = orderMapper.countByMap(map);

        Double unitPrice = 0.0;

        Double orderCompletionRate = 0.0;
        if(totalOrderCount != 0 && validOrderCount != 0){
            //订单完成率
            orderCompletionRate = validOrderCount.doubleValue() / totalOrderCount;
            //平均客单价
            unitPrice = turnover / validOrderCount;
        }

        //新增用户数
        Integer newUsers = userMapper.countByMap(map);

        return BusinessDataVO.builder()
                .turnover(turnover)
                .validOrderCount(validOrderCount)
                .orderCompletionRate(orderCompletionRate)
                .unitPrice(unitPrice)
                .newUsers(newUsers)
                .build();
    }


    /**
     * 查询订单管理数据
     *
     * @return
     */
    public OrderOverViewVO getOrderOverView() {
        Map map = new HashMap();
        map.put("beginTime", LocalDateTime.now().with(LocalTime.MIN));
        map.put("status", Orders.TO_BE_CONFIRMED);

        //待接单
        Integer waitingOrders = orderMapper.countByMap(map);

        //待派送
        map.put("status", Orders.CONFIRMED);
        Integer deliveredOrders = orderMapper.countByMap(map);

        //已完成
        map.put("status", Orders.COMPLETED);
        Integer completedOrders = orderMapper.countByMap(map);

        //已取消
        map.put("status", Orders.CANCELLED);
        Integer cancelledOrders = orderMapper.countByMap(map);

        //全部订单
        map.put("status", null);
        Integer allOrders = orderMapper.countByMap(map);

        return OrderOverViewVO.builder()
                .waitingOrders(waitingOrders)
                .deliveredOrders(deliveredOrders)
                .completedOrders(completedOrders)
                .cancelledOrders(cancelledOrders)
                .allOrders(allOrders)
                .build();
    }

    /**
     * 查询菜品总览
     *
     * @return
     */
    public DishOverViewVO getDishOverView() {
        Map map = new HashMap();
        map.put("status", StatusConstant.ENABLE);
        Integer sold = dishMapper.countByMap(map);

        map.put("status", StatusConstant.DISABLE);
        Integer discontinued = dishMapper.countByMap(map);

        return DishOverViewVO.builder()
                .sold(sold)
                .discontinued(discontinued)
                .build();
    }

    /**
     * 查询套餐总览
     *
     * @return
     */
    public SetmealOverViewVO getSetmealOverView() {
        Map map = new HashMap();
        map.put("status", StatusConstant.ENABLE);
        Integer sold = setmealMapper.countByMap(map);

        map.put("status", StatusConstant.DISABLE);
        Integer discontinued = setmealMapper.countByMap(map);

        return SetmealOverViewVO.builder()
                .sold(sold)
                .discontinued(discontinued)
                .build();
    }
}

一定要注意这里map里的属性名称要与xml文件中对应,否则sql语句的查询条件会出现错误。

1.2.4 Mapper层

在SetmealMapper中添加countByMap方法定义

java 复制代码
/**
     * 根据条件统计套餐数量
     * @param map
     * @return
     */
    Integer countByMap(Map map);

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

XML 复制代码
<!--    根据状态统计套餐数量-->
    <select id="countByMap" resultType="java.lang.Integer">
        select count(id) from setmeal
        <where>
            <if test="status != null">
                and status = #{status}
            </if>
            <if test="categoryId != null">
                and category_id = #{categoryId}
            </if>
        </where>
    </select>

在DishMapper中添加countByMap方法定义

java 复制代码
/**
     * 根据状态统计菜品数量
     * @param map
     * @return
     */
    Integer countByMap(Map map);

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

XML 复制代码
<!--    根据状态统计菜品数量-->
    <select id="countByMap" resultType="java.lang.Integer">
        select count(id) from dish
        <where>
            <if test="status != null">
                and status = #{status}
            </if>
            <if test="categoryId != null">
                and category_id = #{categoryId}
            </if>
        </where>
    </select>

1.3 功能测试

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

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

1). 今日数据查询

2). 订单管理数据查询

2. Apache POI

2.1 介绍

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

Apache POI 的应用场景:

  • 银行网银系统导出交易明细
  • 各种业务系统导出Excel报表
  • 批量导入业务数据

2.2 入门案例

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

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

XML 复制代码
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.16</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.16</version>
</dependency>

2.2.1 将数据写入Excel文件

1). 代码开发

java 复制代码
/**
 * POI读写excel测试
 */
public class TestPOI {
    /**
     * Excel的组成
     *      excel文件--->工作表对象
     *      excel里面可以有很多个Sheet表--->工作表对象
     *      Sheet表里面有很多行--->行对象
     *      行里面有很多单元格--->单元格对象
     *      单元格里面有数据--->数据对象
     */


    //需求:通过POI写入数据到执行的excel文件中
    @Test
    public void testWrite() throws Exception {
        //1.通过POI创建工作簿对象(文件)对象--excel对象
        XSSFWorkbook workbook = new XSSFWorkbook();

        //2.通过XSSFWorkbook创建工作表对象--Sheet对象
        XSSFSheet sheet = workbook.createSheet("itcast");

        //3.通过XSSFSheet创建行对象--Row对象,下标从0开始,创建第一行就写0
        XSSFRow row = sheet.createRow(0);

        //4.通过XSSFRow创建单元格对象--Cell对象(下标从0开始),并往格子中填充数据
        row.createCell(0).setCellValue("姓名");
        row.createCell(1).setCellValue("爱好");

        //第二行
        XSSFRow row1 = sheet.createRow(1);
        row1.createCell(0).setCellValue("张三");
        row1.createCell(1).setCellValue("足球");

        //5.将excel文件写出到指定的文件中
        FileOutputStream fos = new FileOutputStream("D:/itcast.xlsx");
        workbook.write(fos);
        System.out.println("写出完毕...");

        //6.释放资源
        fos.close();
        workbook.close();
    }

2). 实现效果

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

2.2.2 通过POI基于模板写入数据到execl

java 复制代码
//需求:通过POI写入数据到执行的excel文件中--基于模板写入
    @Test
    public void testWrite2() throws Exception {
        //1.基于excel模板文件通过POI创建工作簿对象(文件)对象--有字体模板的excel对象
        FileInputStream fis = new FileInputStream("D:\\demo.xlsx");
        XSSFWorkbook workbook = new XSSFWorkbook(fis);

        //2.通过XSSFWorkbook获取工作表对象--Sheet对象,注意不要createSheet,会覆盖的。
        XSSFSheet sheet = workbook.getSheet("itcast");

        //3.通过XSSFSheet获取行对象--Row对象,下标从0开始,创建第一行就写0
        XSSFRow row = sheet.getRow(0);

        //4.通过XSSFRow创建单元格对象--Cell对象(下标从0开始),并往格子中填充数据
        row.getCell(0).setCellValue("姓名");
        row.getCell(1).setCellValue("爱好");

        //第二行
        XSSFRow row1 = sheet.getRow(1);
        row1.getCell(0).setCellValue("张三");
        row1.getCell(1).setCellValue("足球");

        //第三行
        XSSFRow row2 = sheet.getRow(2);
        row2.getCell(0).setCellValue("李四");
        row2.getCell(1).setCellValue("书法");

        //5.将excel文件写出到指定的文件中
        FileOutputStream fos = new FileOutputStream("D:\\demo1.xlsx");
        workbook.write(fos);
        System.out.println("写出完毕...");

        //6.释放资源
        fos.close();
        workbook.close();
    }

一定注意这里面的create换成了get,如果继续用create会覆盖原本单元格里的格式

2.2.3 读取Excel文件中的数据

1). 代码开发

java 复制代码
 //需求:通过POI读磁盘中指定的excel文件到Java内存中,并输出到控制台
    @Test
    public void testRead() throws Exception {
        //1.基于excel模板文件通过POI创建工作簿对象(文件)对象--有字体模板的excel对象
        FileInputStream fis = new FileInputStream("D:\\itcast.xlsx");
        XSSFWorkbook workbook = new XSSFWorkbook(fis);

        //2.通过XSSFWorkbook获取工作表对象--Sheet对象,注意不要createSheet,会覆盖的。
        XSSFSheet sheet = workbook.getSheet("itcast");

        //3.通过XSSFSheet获取行对象--Row对象,下标从0开始,创建第一行就写0
//        XSSFRow row = sheet.getRow(0);

//        //4.通过XSSFRow创建单元格对象--Cell对象(下标从0开始),并往格子中填充数据
//        String name = row.getCell(0).getStringCellValue();
//        String hobby = row.getCell(1).getStringCellValue();
//        System.out.println(name + "|" + hobby);
//        //读第二行
//        XSSFRow row1 = sheet.getRow(1);
//        String name1 = row1.getCell(0).getStringCellValue();
//        String hobby1 = row1.getCell(1).getStringCellValue();
//        System.out.println(name1 + "|" + hobby1);

        //循环读取表中的每一行数据
        int firstRowNum = sheet.getFirstRowNum();
        int lastRowNum = sheet.getLastRowNum();
        System.out.println("firstRowNum = " + firstRowNum);
        System.out.println("lastRowNum = " + lastRowNum);
        for (int i = firstRowNum; i <= lastRowNum; i++) {
            XSSFRow row = sheet.getRow(i);
            String name = row.getCell(0).getStringCellValue();
            String hobby = row.getCell(1).getStringCellValue();
            System.out.println(name + "|" + hobby);
        }


        //6.释放资源
        fis.close();
        workbook.close();
    }

2). 实现效果

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

3.2 代码开发


3.2.1 实现步骤

1). 设计Excel模板文件

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

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

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

3.2.2 Controller层

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

java 复制代码
	/**
     * 导出运营数据报表
     * @param response
     */
    @GetMapping("/export")
    @ApiOperation("导出运营数据报表")
    public void export(HttpServletResponse response){
        reportService.exportBusinessData(response);
    }

3.2.3 Service层接口

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

java 复制代码
	/**
     * 导出近30天的运营数据报表
     * @param response
     **/
    void exportBusinessData(HttpServletResponse response);

3.2.4 Service层实现类

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

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

java 复制代码
    /**导出近30天的运营数据报表
     * @param response
     **/
    public void exportBusinessData(HttpServletResponse response) {
        LocalDate begin = LocalDate.now().minusDays(30);
        LocalDate end = LocalDate.now().minusDays(1);
        //查询概览运营数据,提供给Excel模板文件
        BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(begin,LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");
        try {
            //基于提供好的模板文件创建一个新的Excel表格对象
            XSSFWorkbook excel = new XSSFWorkbook(inputStream);
            //获得Excel文件中的一个Sheet页
            XSSFSheet sheet = excel.getSheet("Sheet1");

            sheet.getRow(1).getCell(1).setCellValue(begin + "至" + end);
            //获得第4行
            XSSFRow row = sheet.getRow(3);
            //获取单元格
            row.getCell(2).setCellValue(businessData.getTurnover());
            row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
            row.getCell(6).setCellValue(businessData.getNewUsers());
            row = sheet.getRow(4);
            row.getCell(2).setCellValue(businessData.getValidOrderCount());
            row.getCell(4).setCellValue(businessData.getUnitPrice());
            for (int i = 0; i < 30; i++) {
                LocalDate date = begin.plusDays(i);
               //准备明细数据
                businessData = workspaceService.getBusinessData(LocalDateTime.of(date,LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX));
                row = sheet.getRow(7 + i);
                row.getCell(1).setCellValue(date.toString());
                row.getCell(2).setCellValue(businessData.getTurnover());
                row.getCell(3).setCellValue(businessData.getValidOrderCount());
                row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
                row.getCell(5).setCellValue(businessData.getUnitPrice());
                row.getCell(6).setCellValue(businessData.getNewUsers());
            }
            //通过输出流将文件下载到客户端浏览器中
            ServletOutputStream out = response.getOutputStream();
            excel.write(out);
            //关闭资源
            out.flush();
            out.close();
            excel.close();

        }catch (IOException e){
            e.printStackTrace();
        }
    }

3.3 功能测试

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

进入数据统计

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

相关推荐
Roye_ack18 小时前
【项目实战 Day12】springboot + vue 苍穹外卖系统(Apache POI + 工作台模块 + Excel表格导出 完结)
java·spring boot·后端·excel·苍穹外卖
IccBoY1 天前
Java采用easyexcel组件进行excel表格单元格的自动合并
java·开发语言·excel
风车带走过往1 天前
Excel 常用功能自救手册:遇到问题快速排查指南 (个人备忘版)
excel
跟着珅聪学java1 天前
EasyExcel 读取 Excel 文件指南
excel
芭拉拉小魔仙2 天前
Vue项目中如何实现表格选中数据的 Excel 导出
前端·vue.js·excel
RE-19012 天前
Excel基础知识 - 导图笔记
数据分析·学习笔记·excel·思维导图·基础知识·函数应用
Love__Tay2 天前
【数据分析与可视化】2025年一季度金融业主要行业资产、负债、权益结构与增速对比
金融·excel·pandas·matplotlib
泉城老铁2 天前
导出大量数据时如何优化内存使用?SXSSFWorkbook的具体实现方法是什么?
spring boot·后端·excel
Damon小智2 天前
玩转ClaudeCode:通过Excel-MCP实现数据清洗并写入Excel
ai·excel·ai编程·claude·chrome devtools·rpa·claude code