From ba0063053554fc9137fba61ad966cb3c93592785 Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Fri, 9 May 2025 08:54:40 +0800 Subject: [PATCH] =?UTF-8?q?2025.5.9=20spring=20task=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=BB=98=E6=AC=BE=E8=B6=85=E6=97=B6=E8=87=AA=E5=8A=A8=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E8=AE=A2=E5=8D=95=20=E6=AF=8F=E6=97=A5=E5=87=8C?= =?UTF-8?q?=E6=99=A8=E8=87=AA=E5=8A=A8=E8=AE=BE=E7=BD=AE=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E9=85=8D=E9=80=81=E4=B8=AD=E7=9A=84=E8=AE=A2=E5=8D=95=20=20web?= =?UTF-8?q?socket=E6=9D=A5=E5=8D=95=E6=8F=90=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/TemplateNotFoundException.java | 6 + .../controller/admin/ReportController.java | 102 ++++++ .../controller/admin/WorkSpaceController.java | 77 +++++ .../main/java/com/sky/mapper/DishMapper.java | 8 + .../main/java/com/sky/mapper/OrderMapper.java | 46 ++- .../java/com/sky/mapper/SetmealMapper.java | 8 + .../main/java/com/sky/mapper/UserMapper.java | 9 + .../java/com/sky/service/ReportService.java | 50 +++ .../com/sky/service/WorkspaceService.java | 38 +++ .../sky/service/impl/OrderServiceImpl.java | 7 +- .../sky/service/impl/ReportServiceImpl.java | 305 ++++++++++++++++++ .../service/impl/WorkspaceServiceImpl.java | 165 ++++++++++ .../src/main/resources/mapper/DishMapper.xml | 11 + .../src/main/resources/mapper/OrderMapper.xml | 42 +++ .../main/resources/mapper/SetmealMapper.xml | 11 + .../src/main/resources/mapper/UserMapper.xml | 11 + .../resources/template/data_template.xlsx | Bin 0 -> 12570 bytes 17 files changed, 883 insertions(+), 13 deletions(-) create mode 100644 sky-common/src/main/java/com/sky/exception/TemplateNotFoundException.java create mode 100644 sky-server/src/main/java/com/sky/controller/admin/ReportController.java create mode 100644 sky-server/src/main/java/com/sky/controller/admin/WorkSpaceController.java create mode 100644 sky-server/src/main/java/com/sky/service/ReportService.java create mode 100644 sky-server/src/main/java/com/sky/service/WorkspaceService.java create mode 100644 sky-server/src/main/java/com/sky/service/impl/ReportServiceImpl.java create mode 100644 sky-server/src/main/java/com/sky/service/impl/WorkspaceServiceImpl.java create mode 100644 sky-server/src/main/resources/template/data_template.xlsx diff --git a/sky-common/src/main/java/com/sky/exception/TemplateNotFoundException.java b/sky-common/src/main/java/com/sky/exception/TemplateNotFoundException.java new file mode 100644 index 0000000..259d554 --- /dev/null +++ b/sky-common/src/main/java/com/sky/exception/TemplateNotFoundException.java @@ -0,0 +1,6 @@ +package com.sky.exception; + +public class TemplateNotFoundException extends BaseException{ + public TemplateNotFoundException(){} + public TemplateNotFoundException(String msg){super(msg);} +} diff --git a/sky-server/src/main/java/com/sky/controller/admin/ReportController.java b/sky-server/src/main/java/com/sky/controller/admin/ReportController.java new file mode 100644 index 0000000..dd9afd6 --- /dev/null +++ b/sky-server/src/main/java/com/sky/controller/admin/ReportController.java @@ -0,0 +1,102 @@ +package com.sky.controller.admin; + +import com.sky.result.Result; +import com.sky.service.ReportService; +import com.sky.vo.OrderReportVO; +import com.sky.vo.SalesTop10ReportVO; +import com.sky.vo.TurnoverReportVO; +import com.sky.vo.UserReportVO; +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.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDate; + +/** + * 数据统计相关接口 + */ +@RestController +@RequestMapping("/admin/report") +@Api(tags = "数据统计相关接口") +@Slf4j +public class ReportController { + + @Autowired + private ReportService reportService; + + /** + * 营业额统计 + * @param begin + * @param end + * @return + */ + @GetMapping("/turnoverStatistics") + @ApiOperation("营业额统计") + public Result turnoverStatistics( + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ + log.info("营业额数据统计:{},{}",begin,end); + return Result.success(reportService.getTurnoverStatistics(begin,end)); + } + + /** + * 用户统计 + * @param begin + * @param end + * @return + */ + @GetMapping("/userStatistics") + @ApiOperation("用户统计") + public Result userStatistics( + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ + log.info("用户数据统计:{},{}",begin,end); + return Result.success(reportService.getUserStatistics(begin,end)); + } + + /** + * 订单统计 + * @param begin + * @param end + * @return + */ + @GetMapping("/ordersStatistics") + @ApiOperation("订单统计") + public Result ordersStatistics( + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ + log.info("订单数据统计:{},{}",begin,end); + return Result.success(reportService.getOrderStatistics(begin,end)); + } + + /** + * 销量排名top10 + * @param begin + * @param end + * @return + */ + @GetMapping("/top10") + @ApiOperation("销量排名top10") + public Result top10( + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, + @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ + log.info("销量排名top10:{},{}",begin,end); + return Result.success(reportService.getSalesTop10(begin,end)); + } + + /** + * 导出运营数据报表 + * @param response + */ + @GetMapping("/export") + @ApiOperation("导出运营数据报表") + public void export(HttpServletResponse response){ + reportService.exportBusinessData(response); + } +} diff --git a/sky-server/src/main/java/com/sky/controller/admin/WorkSpaceController.java b/sky-server/src/main/java/com/sky/controller/admin/WorkSpaceController.java new file mode 100644 index 0000000..44610ad --- /dev/null +++ b/sky-server/src/main/java/com/sky/controller/admin/WorkSpaceController.java @@ -0,0 +1,77 @@ +package com.sky.controller.admin; + +import com.sky.result.Result; +import com.sky.service.WorkspaceService; +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 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 orderOverView(){ + return Result.success(workspaceService.getOrderOverView()); + } + + /** + * 查询菜品总览 + * @return + */ + @GetMapping("/overviewDishes") + @ApiOperation("查询菜品总览") + public Result dishOverView(){ + return Result.success(workspaceService.getDishOverView()); + } + + /** + * 查询套餐总览 + * @return + */ + @GetMapping("/overviewSetmeals") + @ApiOperation("查询套餐总览") + public Result setmealOverView(){ + return Result.success(workspaceService.getSetmealOverView()); + } +} diff --git a/sky-server/src/main/java/com/sky/mapper/DishMapper.java b/sky-server/src/main/java/com/sky/mapper/DishMapper.java index fb000a3..eed1e9f 100644 --- a/sky-server/src/main/java/com/sky/mapper/DishMapper.java +++ b/sky-server/src/main/java/com/sky/mapper/DishMapper.java @@ -11,6 +11,7 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; +import java.util.Map; @Mapper public interface DishMapper { @@ -53,4 +54,11 @@ public interface DishMapper { * 菜品分页查询 */ Page pageQuery(DishPageQueryDTO dishPageQueryDTO); + + /** + * 根据条件统计菜品数量 + * @param map + * @return + */ + Integer countByMap(Map map); } diff --git a/sky-server/src/main/java/com/sky/mapper/OrderMapper.java b/sky-server/src/main/java/com/sky/mapper/OrderMapper.java index f5c9295..5561fdb 100644 --- a/sky-server/src/main/java/com/sky/mapper/OrderMapper.java +++ b/sky-server/src/main/java/com/sky/mapper/OrderMapper.java @@ -19,11 +19,12 @@ public interface OrderMapper { void insert(Orders order); /** - * 根据订单号查询订单 + * 根据订单号和用户id查询订单 * @param orderNumber + * @param userId */ - @Select("select * from orders where number = #{orderNumber}") - Orders getByNumber(String orderNumber); + @Select("select * from orders where number = #{orderNumber} and user_id= #{userId}") + Orders getByNumberAndUserId(String orderNumber, Long userId); /** * 修改订单信息 @@ -31,14 +32,6 @@ public interface OrderMapper { */ void update(Orders orders); - /** - * 根据订单状态和下单时间查询订单 - * @param status - * @param orderTime - * @return - */ - @Select("select * from orders where status = #{status} and order_time < #{orderTime}") - List getByStatusAndOrderTimeLT(Integer status, LocalDateTime orderTime); /** * 分页条件查询并按下单时间排序 @@ -59,4 +52,35 @@ public interface OrderMapper { */ @Select("select count(id) from orders where status = #{status}") Integer countStatus(Integer status); + + /** + * 根据订单状态和下单时间查询订单 + * @param status + * @param orderTime + * @return + */ + @Select("select * from orders where status = #{status} and order_time < #{orderTime}") + List getByStatusAndOrderTimeLT(Integer status, LocalDateTime orderTime); + + /** + * 根据动态条件统计营业额数据 + * @param map + * @return + */ + Double sumByMap(Map map); + + /** + * 根据动态条件统计订单数量 + * @param map + * @return + */ + Integer countByMap(Map map); + + /** + * 统计指定时间区间内的销量排名前10 + * @param begin + * @param end + * @return + */ + List getSalesTop10(LocalDateTime begin,LocalDateTime end); } diff --git a/sky-server/src/main/java/com/sky/mapper/SetmealMapper.java b/sky-server/src/main/java/com/sky/mapper/SetmealMapper.java index 20760ae..1ca4f68 100644 --- a/sky-server/src/main/java/com/sky/mapper/SetmealMapper.java +++ b/sky-server/src/main/java/com/sky/mapper/SetmealMapper.java @@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; +import java.util.Map; @Mapper public interface SetmealMapper { @@ -69,4 +70,11 @@ public interface SetmealMapper { "from setmeal_dish sd left join dish d on sd.dish_id = d.id " + "where sd.setmeal_id = #{setmealId}") List getDishItemBySetmealId(Long setmealId); + + /** + * 根据条件统计套餐数量 + * @param map + * @return + */ + Integer countByMap(Map map); } diff --git a/sky-server/src/main/java/com/sky/mapper/UserMapper.java b/sky-server/src/main/java/com/sky/mapper/UserMapper.java index d9bf388..151b782 100644 --- a/sky-server/src/main/java/com/sky/mapper/UserMapper.java +++ b/sky-server/src/main/java/com/sky/mapper/UserMapper.java @@ -4,6 +4,8 @@ import com.sky.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; +import java.util.Map; + @Mapper public interface UserMapper { @Select("select * from user where openid=#{openid}") @@ -17,4 +19,11 @@ public interface UserMapper { @Select("select * from user where id=#{userId}") User getById(Long userId); + + /** + * 根据动态条件统计用户数量 + * @param map + * @return + */ + Integer countByMap(Map map); } diff --git a/sky-server/src/main/java/com/sky/service/ReportService.java b/sky-server/src/main/java/com/sky/service/ReportService.java new file mode 100644 index 0000000..393e572 --- /dev/null +++ b/sky-server/src/main/java/com/sky/service/ReportService.java @@ -0,0 +1,50 @@ +package com.sky.service; + +import com.sky.vo.OrderReportVO; +import com.sky.vo.SalesTop10ReportVO; +import com.sky.vo.TurnoverReportVO; +import com.sky.vo.UserReportVO; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDate; + +public interface ReportService { + + /** + * 统计指定时间区间内的营业额数据 + * @param begin + * @param end + * @return + */ + TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end); + + /** + * 统计指定时间区间内的用户数据 + * @param begin + * @param end + * @return + */ + UserReportVO getUserStatistics(LocalDate begin, LocalDate end); + + /** + * 统计指定时间区间内的订单数据 + * @param begin + * @param end + * @return + */ + OrderReportVO getOrderStatistics(LocalDate begin, LocalDate end); + + /** + * 统计指定时间区间内的销量排名前10 + * @param begin + * @param end + * @return + */ + SalesTop10ReportVO getSalesTop10(LocalDate begin, LocalDate end); + + /** + * 导出运营数据报表 + * @param response + */ + void exportBusinessData(HttpServletResponse response); +} diff --git a/sky-server/src/main/java/com/sky/service/WorkspaceService.java b/sky-server/src/main/java/com/sky/service/WorkspaceService.java new file mode 100644 index 0000000..d5cd511 --- /dev/null +++ b/sky-server/src/main/java/com/sky/service/WorkspaceService.java @@ -0,0 +1,38 @@ +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(); + +} diff --git a/sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java b/sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java index dc65427..a2f6676 100644 --- a/sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java +++ b/sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java @@ -225,8 +225,11 @@ public class OrderServiceImpl implements OrderService { */ public void paySuccess(String outTradeNo) { - // 根据订单号查询订单 - Orders ordersDB = orderMapper.getByNumber(outTradeNo); + // 当前登录用户id + Long userId = BaseContext.getCurrentId(); + + // 根据订单号查询当前用户的订单 + Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId); // 根据订单id更新订单的状态、支付方式、支付状态、结账时间 Orders orders = Orders.builder() diff --git a/sky-server/src/main/java/com/sky/service/impl/ReportServiceImpl.java b/sky-server/src/main/java/com/sky/service/impl/ReportServiceImpl.java new file mode 100644 index 0000000..318593d --- /dev/null +++ b/sky-server/src/main/java/com/sky/service/impl/ReportServiceImpl.java @@ -0,0 +1,305 @@ +package com.sky.service.impl; + +import com.sky.constant.OrdersConstant; +import com.sky.dto.GoodsSalesDTO; + +import com.sky.exception.TemplateNotFoundException; +import com.sky.mapper.OrderMapper; +import com.sky.mapper.UserMapper; +import com.sky.service.ReportService; +import com.sky.service.WorkspaceService; +import com.sky.vo.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class ReportServiceImpl implements ReportService { + + @Autowired + private OrderMapper orderMapper; + @Autowired + private UserMapper userMapper; + @Autowired + private WorkspaceService workspaceService; + + /** + * 统计指定时间区间内的营业额数据 + * + * @param begin + * @param end + * @return + */ + public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) { + //当前集合用于存放从begin到end范围内的每天的日期 + List dateList = new ArrayList<>(); + + dateList.add(begin); + + while (!begin.equals(end)) { + //日期计算,计算指定日期的后一天对应的日期 + begin = begin.plusDays(1); + dateList.add(begin); + } + + //存放每天的营业额 + List turnoverList = new ArrayList<>(); + for (LocalDate date : dateList) { + //查询date日期对应的营业额数据,营业额是指:状态为“已完成”的订单金额合计 + LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN); + LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX); + + // select sum(amount) from orders where order_time > beginTime and order_time < endTime and status = 5 + Map map = new HashMap(); + map.put("begin", beginTime); + map.put("end", endTime); + map.put("status", OrdersConstant.COMPLETED); + Double turnover = orderMapper.sumByMap(map); + turnover = turnover == null ? 0.0 : turnover; + turnoverList.add(turnover); + } + + //封装返回结果 + return TurnoverReportVO + .builder() + .dateList(StringUtils.join(dateList, ",")) + .turnoverList(StringUtils.join(turnoverList, ",")) + .build(); + } + + /** + * 统计指定时间区间内的用户数据 + * + * @param begin + * @param end + * @return + */ + public UserReportVO getUserStatistics(LocalDate begin, LocalDate end) { + //存放从begin到end之间的每天对应的日期 + List dateList = new ArrayList<>(); + + dateList.add(begin); + + while (!begin.equals(end)) { + begin = begin.plusDays(1); + dateList.add(begin); + } + + //存放每天的新增用户数量 select count(id) from user where create_time < ? and create_time > ? + List newUserList = new ArrayList<>(); + //存放每天的总用户数量 select count(id) from user where create_time < ? + List totalUserList = new ArrayList<>(); + + for (LocalDate date : dateList) { + LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN); + LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX); + + Map map = new HashMap(); + map.put("end", endTime); + + //总用户数量 + Integer totalUser = userMapper.countByMap(map); + + map.put("begin", beginTime); + //新增用户数量 + Integer newUser = userMapper.countByMap(map); + + totalUserList.add(totalUser); + newUserList.add(newUser); + } + + //封装结果数据 + return UserReportVO + .builder() + .dateList(StringUtils.join(dateList, ",")) + .totalUserList(StringUtils.join(totalUserList, ",")) + .newUserList(StringUtils.join(newUserList, ",")) + .build(); + } + + /** + * 统计指定时间区间内的订单数据 + * @param begin + * @param end + * @return + */ + public OrderReportVO getOrderStatistics(LocalDate begin, LocalDate end) { + //存放从begin到end之间的每天对应的日期 + List dateList = new ArrayList<>(); + + dateList.add(begin); + + while (!begin.equals(end)) { + begin = begin.plusDays(1); + dateList.add(begin); + } + + //存放每天的订单总数 + List orderCountList = new ArrayList<>(); + //存放每天的有效订单数 + List validOrderCountList = new ArrayList<>(); + + //遍历dateList集合,查询每天的有效订单数和订单总数 + for (LocalDate date : dateList) { + //查询每天的订单总数 select count(id) from orders where order_time > ? and order_time < ? + LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN); + LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX); + Integer orderCount = getOrderCount(beginTime, endTime, null); + + //查询每天的有效订单数 select count(id) from orders where order_time > ? and order_time < ? and status = 5 + Integer validOrderCount = getOrderCount(beginTime, endTime, OrdersConstant.COMPLETED); + + orderCountList.add(orderCount); + validOrderCountList.add(validOrderCount); + } + + //计算时间区间内的订单总数量 + Integer totalOrderCount = orderCountList.stream().reduce(Integer::sum).get(); + + //计算时间区间内的有效订单数量 + Integer validOrderCount = validOrderCountList.stream().reduce(Integer::sum).get(); + + Double orderCompletionRate = 0.0; + if(totalOrderCount != 0){ + //计算订单完成率 + orderCompletionRate = validOrderCount.doubleValue() / totalOrderCount; + } + + return OrderReportVO.builder() + .dateList(StringUtils.join(dateList,",")) + .orderCountList(StringUtils.join(orderCountList,",")) + .validOrderCountList(StringUtils.join(validOrderCountList,",")) + .totalOrderCount(totalOrderCount) + .validOrderCount(validOrderCount) + .orderCompletionRate(orderCompletionRate) + .build(); + } + + /** + * 根据条件统计订单数量 + * @param begin + * @param end + * @param status + * @return + */ + private Integer getOrderCount(LocalDateTime begin, LocalDateTime end, Integer status){ + Map map = new HashMap(); + map.put("begin",begin); + map.put("end",end); + map.put("status",status); + + return orderMapper.countByMap(map); + } + + /** + * 统计指定时间区间内的销量排名前10 + * @param begin + * @param end + * @return + */ + public SalesTop10ReportVO getSalesTop10(LocalDate begin, LocalDate end) { + LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN); + LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX); + + List salesTop10 = orderMapper.getSalesTop10(beginTime, endTime); + List names = salesTop10.stream().map(GoodsSalesDTO::getName).collect(Collectors.toList()); + String nameList = StringUtils.join(names, ","); + + List numbers = salesTop10.stream().map(GoodsSalesDTO::getNumber).collect(Collectors.toList()); + String numberList = StringUtils.join(numbers, ","); + + //封装返回结果数据 + return SalesTop10ReportVO + .builder() + .nameList(nameList) + .numberList(numberList) + .build(); + } + + /** + * 导出运营数据报表 + * @param response + */ + public void exportBusinessData(HttpServletResponse response) { + //1. 查询数据库,获取营业数据---查询最近30天的运营数据 + LocalDate dateBegin = LocalDate.now().minusDays(30); + LocalDate dateEnd = LocalDate.now().minusDays(1); + + //查询概览数据 + BusinessDataVO businessDataVO = workspaceService.getBusinessData(LocalDateTime.of(dateBegin, LocalTime.MIN), LocalDateTime.of(dateEnd, LocalTime.MAX)); + + //2. 通过POI将数据写入到Excel文件中 + InputStream in = this.getClass().getClassLoader().getResourceAsStream("template/data_template.xlsx"); + if (in == null) { + throw new TemplateNotFoundException("找不到模板文件:template/data_template.xlsx"); + } + + try { + //基于模板文件创建一个新的Excel文件 + XSSFWorkbook excel = new XSSFWorkbook(in); + + //获取表格文件的Sheet页 + XSSFSheet sheet = excel.getSheet("Sheet1"); + + //填充数据--时间 + sheet.getRow(1).getCell(1).setCellValue("时间:" + dateBegin + "至" + dateEnd); + + //获得第4行 + XSSFRow row = sheet.getRow(3); + row.getCell(2).setCellValue(businessDataVO.getTurnover()); + row.getCell(4).setCellValue(businessDataVO.getOrderCompletionRate()); + row.getCell(6).setCellValue(businessDataVO.getNewUsers()); + + //获得第5行 + row = sheet.getRow(4); + row.getCell(2).setCellValue(businessDataVO.getValidOrderCount()); + row.getCell(4).setCellValue(businessDataVO.getUnitPrice()); + + //填充明细数据 + for (int i = 0; i < 30; i++) { + LocalDate date = dateBegin.plusDays(i); + //查询某一天的营业数据 + BusinessDataVO 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()); + } + + //3. 通过输出流将Excel文件下载到客户端浏览器 + ServletOutputStream out = response.getOutputStream(); + excel.write(out); + + //关闭资源 + out.close(); + excel.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } +} diff --git a/sky-server/src/main/java/com/sky/service/impl/WorkspaceServiceImpl.java b/sky-server/src/main/java/com/sky/service/impl/WorkspaceServiceImpl.java new file mode 100644 index 0000000..e7e25c6 --- /dev/null +++ b/sky-server/src/main/java/com/sky/service/impl/WorkspaceServiceImpl.java @@ -0,0 +1,165 @@ +package com.sky.service.impl; + +import com.sky.constant.OrdersConstant; +import com.sky.constant.StatusConstant; +import com.sky.entity.Orders; +import com.sky.mapper.DishMapper; +import com.sky.mapper.OrderMapper; +import com.sky.mapper.SetmealMapper; +import com.sky.mapper.UserMapper; +import com.sky.service.WorkspaceService; +import com.sky.vo.BusinessDataVO; +import com.sky.vo.DishOverViewVO; +import com.sky.vo.OrderOverViewVO; +import com.sky.vo.SetmealOverViewVO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.HashMap; +import java.util.Map; + +@Service +@Slf4j +public class WorkspaceServiceImpl implements WorkspaceService { + + @Autowired + private OrderMapper 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("begin",begin); + map.put("end",end); + + //查询总订单数 + Integer totalOrderCount = orderMapper.countByMap(map); + + map.put("status", OrdersConstant.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("begin", LocalDateTime.now().with(LocalTime.MIN)); + map.put("status", OrdersConstant.TO_BE_CONFIRMED); + + //待接单 + Integer waitingOrders = orderMapper.countByMap(map); + + //待派送 + map.put("status", OrdersConstant.CONFIRMED); + Integer deliveredOrders = orderMapper.countByMap(map); + + //已完成 + map.put("status", OrdersConstant.COMPLETED); + Integer completedOrders = orderMapper.countByMap(map); + + //已取消 + map.put("status", OrdersConstant.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(); + } +} diff --git a/sky-server/src/main/resources/mapper/DishMapper.xml b/sky-server/src/main/resources/mapper/DishMapper.xml index a82798a..32de785 100644 --- a/sky-server/src/main/resources/mapper/DishMapper.xml +++ b/sky-server/src/main/resources/mapper/DishMapper.xml @@ -57,4 +57,15 @@ order by create_time desc + \ No newline at end of file diff --git a/sky-server/src/main/resources/mapper/OrderMapper.xml b/sky-server/src/main/resources/mapper/OrderMapper.xml index 8dd5784..3f64147 100644 --- a/sky-server/src/main/resources/mapper/OrderMapper.xml +++ b/sky-server/src/main/resources/mapper/OrderMapper.xml @@ -66,4 +66,46 @@ order by order_time desc + + + diff --git a/sky-server/src/main/resources/mapper/SetmealMapper.xml b/sky-server/src/main/resources/mapper/SetmealMapper.xml index e29f5ad..c5a09de 100644 --- a/sky-server/src/main/resources/mapper/SetmealMapper.xml +++ b/sky-server/src/main/resources/mapper/SetmealMapper.xml @@ -57,4 +57,15 @@ + \ No newline at end of file diff --git a/sky-server/src/main/resources/mapper/UserMapper.xml b/sky-server/src/main/resources/mapper/UserMapper.xml index c7473e2..9dbb61a 100644 --- a/sky-server/src/main/resources/mapper/UserMapper.xml +++ b/sky-server/src/main/resources/mapper/UserMapper.xml @@ -7,4 +7,15 @@ insert into user (openid, name, phone, sex, id_number, avatar, create_time) values (#{openid}, #{name}, #{phone}, #{sex}, #{idNumber}, #{avatar}, #{createTime}) + \ No newline at end of file diff --git a/sky-server/src/main/resources/template/data_template.xlsx b/sky-server/src/main/resources/template/data_template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..76806c553826045fe5f3fc6f81dd7e2cef56b3a9 GIT binary patch literal 12570 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 literal 0 HcmV?d00001