苍穹外卖学习Day10Day11Day12
- 手机
- 2025-08-03 05:33:02

前言
用于记录苍穹外卖Day10、Day11、Day12的学习
Day10 订单状态定时处理 来电提醒 客户催单 订单状态定时处理 Spring TaskSpring Task是一个任务调度工具,可以按照约定的时间自动执行某个代码逻辑(定时自动执行某段Java代码)
cron表达式:
cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间。
构成规则:分为6或7个域,用空格隔开,每个域代表一个含义。从左到右依次为秒、分钟、小时、日、月、周、年(可选)
cron在线生成器:https://cron.qqe2.com
Spring Task使用步骤:
导入坐标spring-context启动类添加注解@EnableScheduling开启任务调度自定义定时任务类 需求开发存在的问题:
下单后未支付,订单一直处于”待支付“状态用户收货后管理端未点击完成按钮,订单一直处于”派送中“状态只需自定义个任务处理类来定时处理即可:
//定时任务类,定时处理订单状态 @Component @Slf4j public class OrderTask { @Autowired private OrderMapper orderMapper; //处理下单后未支付超时的情况 @Scheduled(cron = "0 * * * * ? *")//每分钟触发一次 // @Scheduled(cron="0/5 * * * * ?") public void processTimeOut(){ log.info("定时处理下单未支付的订单"); //当前时间减15分钟 LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(-15); List<Orders> list = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, localDateTime); if(list!=null&&list.size()>0){ for (Orders orders : list) { orders.setStatus(Orders.CANCELLED); orders.setConsignee("订单超时,自动取消"); orders.setCancelTime(LocalDateTime.now()); orderMapper.update(orders); } } } //处理一直处于派送中,没有完成的订单 @Scheduled(cron = "0 0 1 * * ?")//每天凌晨一点触发 // @Scheduled(cron="0/10 * * * * ?") public void processDeliveryOrder(){ log.info("定时处理一直在派送的订单"); LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(-60); List<Orders> list = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, localDateTime); if(list!=null&&list.size()>0){ for (Orders orders : list) { orders.setStatus(Orders.COMPLETED); orderMapper.update(orders); } } } } 来电提醒 WebSocketWebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工通信–浏览器和服务器只需完成一次握手,两者之间即可建立持久性的连接,并进行双向数据传输。
WebSocket和HTTP对比:
HTTP是短连接;WebSocket是长连接HTTP是单向通信,基于请求响应模型;WebSocket支持双向通信。WebSocket和HTTP都是基于TCP协议应用场景:
视频弹幕实况更新网页聊天 需求开发实现思路:
通过WebSocket实现管理端页面和服务端保持长连接当客户支付后,调用WebSocket的相关API从服务端向客户端推送消息客户端解析服务端发送的消息,判断是来电提醒还是客户催单,并进行相应的语音播报约定服务端向客户端发送的消息的数据格式为JSON,字段包括:type(消息类型,1为来单提醒、2为客户催单)、orderId、content(消息内容)这里我们只需要在支付成功后提示管理端即可,在OrderServiceImpl的paySuccess方法中:
//通过WebSocket向客户端浏览器推送数据 Map map=new HashMap(); map.put("type",1); map.put("orderId",ordersDB.getId()); map.put("content","订单号:"+outTradeNo); String Json= JSON.toJSONString(map); webSocketServer.sendToAllClient(Json);注意:启动项目的时候看看你是否连接上WebSocket,如果没连接上可能是因为自己修改过端口号的问题,将端口号改回80或者改下前端代码即可。
客户催单实现思路和来电提醒差不多。当用户在客户端点击催单按钮时,发起请求
OrderController @GetMapping("/reminder/{id}") @ApiOperation("客户催单") public Result reminder(@PathVariable Long id){ orderService.reminder(id); return Result.success(); } OrderServiceImpl public void reminder(Long id) { // 根据id查询订单 Orders orders1= orderMapper.getById(id); // 校验订单是否存在 if (orders1 == null) { throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR); } //通过WebSocket向客户端浏览器推送数据 Map map=new HashMap(); map.put("type",2); map.put("orderId",id); map.put("content","订单号:"+orders1.getNumber()); String Json= JSON.toJSONString(map); webSocketServer.sendToAllClient(Json); } Day11 数据统计-图形报表效果如下所示:
Apache EChartsApache ECharts是一款基于JavaScript的数据可视化图表库,提供直观、生动、可交互、可个性化定制的数据可视化图表。简单来说,它就是一款数据可视化工具。我们只需大致知道它是干啥的,它是在前端使用的,后端开发中我们使用不到。
营业额统计业务规则:
营业额指订单状态为已完成的订单金额合计基于可视化报表的折线图展示营业额数据,x轴为日期,y轴为营业额根据时间选择区间,展示每天的营业额数据ReportController:注意时间的数据格式
@RestController @Slf4j @RequestMapping("/admin/report") @Api(tags = "数据统计相关接口") public class ReportController { @Autowired private ReportService reportService; @GetMapping("/turnoverStatistics") @ApiOperation("营业额统计") public Result<TurnoverReportVO> turnoverStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ log.info("营业额数据统计:{},{}",begin,end); TurnoverReportVO turnoverReportVO=reportService.getTurnoverStatistics(begin,end); return Result.success(turnoverReportVO); } }ReportServiceImpl:
这里我的实现方法与课程中略有不同,可以参考一下
public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) { TurnoverReportVO turnoverReportVO=new TurnoverReportVO(); //1.封装日期数据 //先去得到一个日期集合,包含begin到end中的所有日期 List<LocalDate> dateList=new ArrayList<>(); dateList.add(begin); while(!begin.equals(end)){ begin=begin.plusDays(1); dateList.add(begin); } //将集合转成字符串的同时在每个元素间加一个逗号 String dateList1=StringUtils.join(dateList,","); turnoverReportVO.setDateList(dateList1); //2.封装营业额数据 //查询对应日期的订单的总营业额 List<Double> moneyList=new ArrayList<>(); for (LocalDate localDate : dateList) { //根据日期查询状态为已完成的订单的营业额 //00:00:00 LocalDateTime beginTime=LocalDateTime.of(localDate, LocalTime.MIN); //23:59:59 LocalDateTime endTime=LocalDateTime.of(localDate, LocalTime.MAX); Map map=new HashMap(); map.put("begin",beginTime); map.put("end",endTime); map.put("status", Orders.COMPLETED); Double money=orderMapper.getSumByMap(map); if(money==null){ money=0.0; } moneyList.add(money); } String moneyList1=StringUtils.join(moneyList,","); turnoverReportVO.setTurnoverList(moneyList1); return turnoverReportVO; } 用户统计ReportController:
@GetMapping("/userStatistics") @ApiOperation("用户统计") public Result<UserReportVO> userStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ log.info("用户统计:{},{}",begin,end); UserReportVO userReportVO=reportService.getUserStatistics(begin,end); return Result.success(userReportVO); }ReportServiceImpl:
//用户数据统计 public UserReportVO getUserStatistics(LocalDate begin, LocalDate end) { UserReportVO userReportVO=new UserReportVO(); //1.封装日期数据 //先去得到一个日期集合,包含begin到end中的所有日期 List<LocalDate> dateList=new ArrayList<>(); dateList.add(begin); while(!begin.equals(end)){ begin=begin.plusDays(1); dateList.add(begin); } //将集合转成字符串的同时在每个元素间加一个逗号 String dateList1=StringUtils.join(dateList,","); userReportVO.setDateList(dateList1); //2.封装用户总量数据 List<Integer> totalList=new ArrayList<>(); //3.封装新增用户数量数据 List<Integer> newList=new ArrayList<>(); for (LocalDate localDate : dateList) { //查询用户表createTime这一天的用户的总量 //00:00:00 LocalDateTime beginTime=LocalDateTime.of(localDate, LocalTime.MIN); //23:59:59 LocalDateTime endTime=LocalDateTime.of(localDate, LocalTime.MAX); Map map=new HashMap(); map.put("end",endTime); Integer total=userMapper.countByMap(map); if(total==null){ total=0; } totalList.add(total); map.put("begin",beginTime); Integer today= userMapper.countByMap(map); if(today==null){ today=0; } newList.add(today); } String userList1=StringUtils.join(totalList,","); userReportVO.setTotalUserList(userList1); String list1=StringUtils.join(newList,","); userReportVO.setNewUserList(list1); return userReportVO; } 订单统计业务规则:
两条折线,一条代表总订单数,另一条代表有效订单数(状态为已完成的订单)展示订单总数、有效订单数、订单完成率数据ReportController:
@GetMapping("/ordersStatistics") @ApiOperation("订单统计") public Result<OrderReportVO> orderStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ log.info("订单统计:{},{}",begin,end); OrderReportVO orderReportVO=reportService.getOrderReportStatistics(begin,end); return Result.success(orderReportVO); }ReportServiceImpl:
@Override public OrderReportVO getOrderReportStatistics(LocalDate begin, LocalDate end) { OrderReportVO orderReportVO=new OrderReportVO(); //1.封装日期数据 List<LocalDate> dateList=new ArrayList<>(); dateList.add(begin); while(!begin.equals(end)){ begin=begin.plusDays(1); dateList.add(begin); } //将集合转成字符串的同时在每个元素间加一个逗号 String dateList1=StringUtils.join(dateList,","); orderReportVO.setDateList(dateList1); //2.订单总数 List<Integer> totalOrder=new ArrayList<>(); //3.有效订单数 List<Integer> realOrder=new ArrayList<>(); //每天的订单总数以及有效订单数 for (LocalDate localDate : dateList) { //00:00:00 LocalDateTime beginTime=LocalDateTime.of(localDate, LocalTime.MIN); //23:59:59 LocalDateTime endTime=LocalDateTime.of(localDate, LocalTime.MAX); Map map=new HashMap(); map.put("begin",beginTime); map.put("end",endTime); Integer total=orderMapper.getByMap(map); if(total==null){ total=0; } totalOrder.add(total); map.put("status",Orders.COMPLETED); Integer real=orderMapper.getByMap(map); if(real==null){ real=0; } realOrder.add(real); } String totalOrder1=StringUtils.join(totalOrder,","); String realOrder1=StringUtils.join(realOrder,","); //计算时间区间内的订单总数量 Integer sum=0; for (Integer integer : totalOrder) { sum+=integer; } //计算时间区间内的有效订单数量 Integer real=0; for (Integer integer : realOrder) { real+=integer; } //计算订单完成率 double orderCompletionRate=0.0; if (sum!=0) { orderCompletionRate= (double) real /sum; } orderReportVO.setOrderCompletionRate(orderCompletionRate); orderReportVO.setOrderCountList(totalOrder1); orderReportVO.setValidOrderCountList(realOrder1); orderReportVO.setTotalOrderCount(sum); orderReportVO.setValidOrderCount(real); System.out.println(orderReportVO); return orderReportVO; } 销量排名统计ReportController:
@GetMapping("/top10") @ApiOperation("销量排名top10") public Result<SalesTop10ReportVO> top10(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ log.info("销量排名top10:{},{}",begin,end); SalesTop10ReportVO salesTop10ReportVO=reportService.getTop10(begin,end); return Result.success(salesTop10ReportVO); }ReportServiceImpl:
public SalesTop10ReportVO getTop10(LocalDate begin, LocalDate end) { SalesTop10ReportVO salesTop10ReportVO=new SalesTop10ReportVO(); //00:00:00 LocalDateTime beginTime=LocalDateTime.of(begin, LocalTime.MIN); //23:59:59 LocalDateTime endTime=LocalDateTime.of(end, LocalTime.MAX); List<GoodsSalesDTO> goodsSalesDTOList=orderMapper.getSalesTop10(beginTime,endTime); //遍历取出DTO中的numa和number放到对应的集合中去 List<String> nameList=new ArrayList<>(); List<String> numberList=new ArrayList<>(); for (GoodsSalesDTO goodsSalesDTO : goodsSalesDTOList) { nameList.add(goodsSalesDTO.getName()); numberList.add(String.valueOf(goodsSalesDTO.getNumber())); } String nameLists=StringUtils.join(nameList,","); String numberLists=StringUtils.join(numberList,","); salesTop10ReportVO.setNameList(nameLists); salesTop10ReportVO.setNumberList(numberLists); return salesTop10ReportVO; }OrderMapper.xml:
分析一下这里的SQL语句,因为我们要根据订单状态(对应orders表中的status)查订单的名称以及数量(对应order_details表中的number),所以涉及到联表查询。查询前10,所以只需limit 0,10
<!--统计销量前10--> <select id="getSalesTop10" resultType="com.sky.dto.GoodsSalesDTO"> select od.name,sum(od.number) number from order_detail od,orders o where od.id=o.id and o.status=5 <if test="begin!=null">and order_time > #{begin}</if> <if test="end!=null">and order_time < #{end}</if> group by od.name order by number desc limit 0,10 </select> Day12 数据统计-Excel报表 工作台这里课程中的代码是直接导入的,我还是选择手敲一遍。
工作台展示的数据:
今日数据订单管理菜品总览套餐总览订单信息为了简便展示,这里我直接给出一个类中的全部代码了,可以根据注释理解。
WorkSpaceController @RestController @RequestMapping("/admin/workspace") @Slf4j @Api(tags = "工作台相关接口") public class WorkSpaceController { @Autowired private WorkSpaceService workSpaceService; //查询工作台今日数据 @GetMapping("/businessData") @ApiOperation("查询工作台今日数据") public Result<BusinessDataVO> businessData(){ log.info("查询工作台今日数据"); //获取开始时间 LocalDateTime beginTime=LocalDateTime.now().with(LocalTime.MIN); //获取结束时间 LocalDateTime endTime=LocalDateTime.now().with(LocalTime.MAX); BusinessDataVO businessDataVO=workSpaceService.getBusinessData(beginTime,endTime); return Result.success(businessDataVO); } //查询订单管理数据 @GetMapping("/overviewOrders") @ApiOperation("查询订单管理数据") public Result<OrderOverViewVO> overViewOrders(){ log.info("查询订单管理数据"); return Result.success(workSpaceService.getOrderOverView()); } //查询菜品总览 @GetMapping("/overviewDishes") @ApiOperation("查询菜品总览") public Result<DishOverViewVO> overViewDishes(){ return Result.success(workSpaceService.getDishOverView()); } //查询套餐总览 @GetMapping("/overviewSetmeals") @ApiOperation("查询套餐总览") public Result<SetmealOverViewVO> overViewSetmeal(){ return Result.success(workSpaceService.getSetmealOvermeal()); } } WorkSpaceService public interface WorkSpaceService { BusinessDataVO getBusinessData(LocalDateTime beginTime, LocalDateTime endTime); OrderOverViewVO getOrderOverView(); DishOverViewVO getDishOverView(); SetmealOverViewVO getSetmealOvermeal(); } WorkSpaceServiceImpl(这里很多方法我们都在OrderMapper中已经写好了,直接调用即可) @Service public class WorkSpaceServiceImpl implements WorkSpaceService { @Autowired private OrderMapper orderMapper; @Autowired private UserMapper userMapper; @Autowired private DishMapper dishMapper; @Autowired private SetmealMapper setmealMapper; //查询工作台今日数据 public BusinessDataVO getBusinessData(LocalDateTime beginTime, LocalDateTime endTime) { BusinessDataVO businessDataVO=new BusinessDataVO(); Map map=new HashMap(); map.put("begin",beginTime); map.put("end",endTime); //订单总数 Integer total=orderMapper.getByMap(map); map.put("status",5); //营业额 Double sum = orderMapper.getSumByMap(map); sum = sum == null? 0.0 : sum; //有效订单数 Integer real=orderMapper.getByMap(map); //平均客单价 double average=0.0; //订单完成率 double complete=0.0; if(total!=0&&real!=0){ complete= (double) real /total; average=sum/real; } //新增用户数 Integer newUser=userMapper.countByMap(map); businessDataVO.setTurnover(sum); businessDataVO.setNewUsers(newUser); businessDataVO.setOrderCompletionRate(complete); businessDataVO.setValidOrderCount(real); businessDataVO.setUnitPrice(average); return businessDataVO; } //查询订单管理数据 @Override public OrderOverViewVO getOrderOverView() { OrderOverViewVO orderOverViewVO=new OrderOverViewVO(); Map map = new HashMap(); map.put("begin", LocalDateTime.now().with(LocalTime.MIN)); //待接单 map.put("status", Orders.TO_BE_CONFIRMED); Integer status1=orderMapper.getByMap(map); //待派送 map.put("status",Orders.CONFIRMED); Integer status2=orderMapper.getByMap(map); //已完成 map.put("status",Orders.COMPLETED); Integer status3=orderMapper.getByMap(map); //已取消 map.put("status",Orders.CANCELLED); Integer status4=orderMapper.getByMap(map); //全部订单 map.put("status",null); Integer status5=orderMapper.getByMap(map); orderOverViewVO.setWaitingOrders(status1); orderOverViewVO.setDeliveredOrders(status2); orderOverViewVO.setCompletedOrders(status3); orderOverViewVO.setCancelledOrders(status4); orderOverViewVO.setAllOrders(status5); return orderOverViewVO; } //查询菜品总览 @Override public DishOverViewVO getDishOverView() { DishOverViewVO dishOverViewVO=new DishOverViewVO(); Integer on=dishMapper.onStatus(); Integer off=dishMapper.offStatus(); dishOverViewVO.setSold(on); dishOverViewVO.setDiscontinued(off); return dishOverViewVO; } //查询套餐总览 @Override public SetmealOverViewVO getSetmealOvermeal() { SetmealOverViewVO setmealOverViewVO=new SetmealOverViewVO(); Integer on=setmealMapper.onStatus(); Integer off=setmealMapper.offStatus(); setmealOverViewVO.setSold(on); setmealOverViewVO.setDiscontinued(off); return setmealOverViewVO; } } DishMapper(这里是SQL语句少我使用这种方法,标准的应该是使用动态SQL) @Select("select count(id) from dish where status=1") Integer onStatus(); @Select("select count(id) from dish where status=0") Integer offStatus(); SetmealMapper @Select("select count(id) from setmeal where status=1") Integer onStatus(); @Select("select count(id) from setmeal where status=0") Integer offStatus(); Apache POI简介:Apache POI可以处理Office的各种文件格式。允许我们使用POI在Java程序中对Office文件进行读写操作。一般用于处理Excel文件。
实例:
写入Excel文件:
//在内存中创建一个excel文件 XSSFWorkbook excel=new XSSFWorkbook(); //在excel文件中创建一个sheet页同时指定其名称为info XSSFSheet sheet=excel.createSheet("info"); //创建行对象,行和列都从0开始,这里我们指定1表示是第二行 XSSFRow row= sheet.createRow(1); //创建单元格并写入内容 row.createCell(1).setCellValue("姓名"); row.createCell(2).setCellValue("爱好"); row= sheet.createRow(2); //创建单元格并写入内容 row.createCell(1).setCellValue("张三"); row.createCell(2).setCellValue("篮球"); row= sheet.createRow(3); //创建单元格并写入内容 row.createCell(1).setCellValue("李四"); row.createCell(2).setCellValue("游泳"); //通过输出流将内存中的Excel文件写入到磁盘中 FileOutputStream out=new FileOutputStream(new File("E:\\Takeout\\info.xlsx")); excel.write(out); out.close(); excel.close();读取Excel文件:
InputStream in=new FileInputStream(new File("E:\\Takeout\\info.xlsx")); //读取磁盘上已经存在的Excel文件 XSSFWorkbook excel=new XSSFWorkbook(in); //读取Excel文件中第一个Sheet页 XSSFSheet sheet= excel.getSheetAt(0); //获取Sheet页中最后一行的的行号(有内容的最后一行) int lastRowNum=sheet.getLastRowNum(); for (int i = 1; i <= lastRowNum; i++) { //获取某一行 XSSFRow row= sheet.getRow(i); //获取单元格对象 String stringCellValue1 = row.getCell(1).getStringCellValue(); String stringCellValue2 = row.getCell(2).getStringCellValue(); System.out.println(stringCellValue1+" "+stringCellValue2); } excel.close(); in.close(); 导出Excel报表业务规则:
导出Excel文件形式的报表文件导出进30天的运营数据实现步骤:
设计Excel模板文件查询近30日的运营数据将查询到的运营数据写入模板文件内通过输出流将Excel文件下载到客户端浏览器实现:
ReportController @GetMapping("/export") @ApiOperation("导出Excel报表") public Result export(HttpServletResponse response){ log.info("导出Excel报表"); reportService.export(response); return Result.success(); } ReportServiceImpl //导出运营数据报表 @Override public void export(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/运营数据报表模板.xlsx"); try{ //基于模板文件创建一个新的Excel文件 XSSFWorkbook excel=new XSSFWorkbook(in); //获取报表文件的Sheet页 XSSFSheet sheet= excel.getSheet("Sheet1"); //填充概览数据-时间 sheet.getRow(1).getCell(1).setCellValue("时间:"+dateBegin+"到"+dateEnd); //填充概览数据其他数据 //第四行 XSSFRow row= sheet.getRow(3); row.getCell(2).setCellValue(businessDataVO.getTurnover()); row.getCell(4).setCellValue(businessDataVO.getOrderCompletionRate()); row.getCell(6).setCellValue(businessDataVO.getNewUsers()); //第五行 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 businessDataVO1=workSpaceService.getBusinessData(LocalDateTime.of(date,LocalTime.MIN),LocalDateTime.of(date,LocalTime.MAX)); //获取某一行并填充数据 row= sheet.getRow(7+i); row.getCell(1).setCellValue(businessDataVO1.toString()); row.getCell(2).setCellValue(businessDataVO1.getTurnover()); row.getCell(3).setCellValue(businessDataVO1.getValidOrderCount()); row.getCell(4).setCellValue(businessDataVO1.getOrderCompletionRate()); row.getCell(5).setCellValue(businessDataVO1.getUnitPrice()); row.getCell(6).setCellValue(businessDataVO1.getNewUsers()); } //3.通过输出流将Excel文件下载到客户端浏览器 ServletOutputStream out= response.getOutputStream(); excel.write(out); //关闭资源 out.close(); excel.close(); }catch (Exception e){ e.printStackTrace(); }这里我们需要注意的一个地方,这里老师没有加上这个后缀,不加的话我这里会报错:
苍穹外卖的学习就到这里啦,完结撒花!!!
苍穹外卖学习Day10Day11Day12由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“苍穹外卖学习Day10Day11Day12”