前言:
上午就把今天任务完成了,就继续往后学了一些知识,晚上写下笔记总结一下。
今日完成任务:
- 调用百度地图开放平台,优化用户下单业务
- 学习SpringTask,定时处理超时、派送中订单
- 学习WebSocket,完成来单提醒、催单业务
- 完成 数据统计四个接口
- 学习Apache POI 在java中操作excel
今日收获:
1.学习了SpringTask这个轻量级框架,来解决一些定时处理的问题。
Spring Task
是spring框
架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。主要的作用就是定时的去执行某段Java代码。
像这种定时的去执行某个任务,在生活中是比较常见的,例如:闹钟、信用卡每月还款提醒、入职纪念日为用户发送通知等。这些操作不可能有我们开发者/管理端,每次去点击提醒,所有需要根据业务逻辑,定时的去执行某一段代码。所有说,只要是需要定时处理的场景都可以使用Spring Task
,Java中还存在其他任务调度工具,如下方案:
方案 | 复杂度 | 分布式支持 | 持久化 | 管理界面 | 适用场景 |
---|---|---|---|---|---|
Spring Task | 简单 | 否(单机) | 否 | 无 | 单机简单定时任务 |
Quartz | 中等 | 是(需配置) | 是(可选) | 无(需自研) | 复杂调度、高可靠 |
ScheduledExecutorService | 简单 | 否 | 否 | 无 | 并行定时任务 |
XXL-JOB / Elastic-Job | 中等 | 是 | 是 | 有 | 分布式系统、集中管理 |
消息队列延迟 | 复杂 | 是 | 是 | 依赖MQ | 高可靠、解耦场景 |
如何去使用SpringTask任务调度工具定时的执行某段Java代码?
这个框架小到已经包含在SpringBoot
的起步依赖中了,所以我们无需引入依赖。首先我们需要在启动类上加上@EnableScheduled注解
。然后创建一个包,然后去定义一个task类即可,如下图:
注意这里的@Scheduled就可,在里面我们可以通过cron表达式去设置该任务自动执行的时间。每次到这个时间就会去执行如下方法。这里的cron表达式我们无需记忆,需要的时候可以通过在线工具去获取到https://cron.qqe2.com/。
这里在该项目中,具体的使用:
- 超时未支付订单
每隔1min,去数据库中查询超时的订单(15分钟内未支付,订单状态为未支付的订单),然后通过循环将所有超时订单都保存在列表中,通过循环对所有订单状态进行更新。
这里做法存在一些问题:
1)订单量大的时候 每隔一分钟就去查询数据库中的数所有的订单数据,对数据库的压力大。(这里可以用redis来解决吧,每隔一分钟去redis缓存中查询,只有查到超时订单的时候,才会对数据库进行更新操作,从而减小数据库的压力)
2)如果订单还要1s 倒计时 未支付 这个时候 订单并没有自动处理成已取消 导致到了时间 但是用户仍然付款了
- 派送中订单:
每天凌晨去数据库中查找派送中的订单(订单状态为派送中的订单),道理同上,对所有订单状态进行更新。
2.学习WebSocket,来完成来来单提醒、催单业务
WebSocket
是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信-浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
HTTP协议和WebSocket协议对比:
- HTTP协议是短连接,WebSocket是长连接
- HTTP请求是单向的,基于请求和响应模式
- WebSocket支持双向通信,客户端 <—>服务端
- 二者底层都是TCP连接
使用WebSocket
使用前需导入WebSocket
的依赖,然后创建Websocket包,创建一个WebSocket
服务端组件WebSocketServer
,用于和客户端通信,这个服务端组件WebSocketServer
写法比较固定,可以参考下面的代码:
package com.sky.websocket;import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;/*** WebSocket服务*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {//存放会话对象private static Map<String, Session> sessionMap = new HashMap();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {System.out.println("客户端:" + sid + "建立连接");sessionMap.put(sid, session);}/*** 收到客户端消息后调用的方法* @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, @PathParam("sid") String sid) {System.out.println("收到来自客户端:" + sid + "的信息:" + message);}/*** 连接关闭调用的方法* @param sid*/@OnClosepublic void onClose(@PathParam("sid") String sid) {System.out.println("连接断开:" + sid);sessionMap.remove(sid);}/*** 群发* @param message*/public void sendToAllClient(String message) {Collection<Session> sessions = sessionMap.values();for (Session session : sessions) {try {//服务器向客户端发送消息session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}}
后我们需要在配置类中创建配置类WebSocketConfiguration
.
package com.sky.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** WebSocket配置类,用于注册WebSocket的Bean*/
@Configuration
public class WebSocketConfiguration {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
下面解释为什么需要书写这个配置类:
1)WebSocketServer 的作用
WebSocketServer 是一个具体的 WebSocket
服务端实现,用于处理客户端连接、消息接收、连接关闭等逻辑。
它通过注解(如 @ServerEndpoint、@OnOpen、@OnMessage 等)定义了 WebSocket 的行为,包括:建立连接时的逻辑 (@OnOpen)。接收客户端消息时的处理逻辑(@OnMessage)。连接关闭时的清理逻辑 (@OnClose)。提供群发消息的功能。
2)WebSocketConfiguration 的作用
WebSocketConfiguration 是一个配置类,主要目的是注册 WebSocket 相关的 Bean。
其核心是通过 ServerEndpointExporter 来扫描和注册所有使用 @ServerEndpoint 注解的类(例如 WebSocketServer)。如果没有 ServerEndpointExporter,Spring 容器不会自动识别和管理 @ServerEndpoint 注解的类,导致 WebSocket 功能无法正常工作。
3)为什么需要两个类?
职责分离:WebSocketServer 负责具体的业务逻辑,而 WebSocketConfiguration 负责基础配置。这种设计符合单一职责原则,使代码更清晰、易于维护。
Spring 管理依赖:@ServerEndpoint 注解本身是由 Java EE 提供的,Spring 并不直接支持它。通过 ServerEndpointExporter,Spring 才能将 @ServerEndpoint 注解的类纳入其管理范围。
灵活性:如果未来需要扩展 WebSocket 功能(例如添加拦截器或自定义配置),可以在 WebSocketConfiguration 中进行扩展,而无需修改 WebSocketServer。
简而言之:Spring并不识别带有@ServerEndpoint
注解的WebSocketServer
类,该注解由javaEE提供的,Spring并不支持。因此我们需要通过注册配置类ServerEndpointExporter
,该配置类会扫描和注册使用@ServerEndpoint
注解的类,加入到IOC容器中,使得WebSocket
可以正常工作
Nginx的反向代理
具体可以看Day1天的笔记
3.Apache POI
Apache POl是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POl在 Java 程序中对Miscrosoft Office各种文件进行读写操作。一般情况下,POI都是用于操作 Excel 文件。
简而言之:Apache POI就是定义了一系列API
供我们在java程序中操作excel。除了Apache POI 还有easyExcel等开源工具。
基本思路:1)设计excel模板 2)查询数据库 3)填充数据到模板 4)通过输出流 下载到浏览器
下面是Apache POI操作java的两个Demo文件 供我们熟悉POI操作的相关API
/*** 使用POI 写入excel文件内容* @throws IOException*/@Testpublic void testWrite() throws IOException {//创建excel文件XSSFWorkbook excel = new XSSFWorkbook();//创建sheetXSSFSheet sheet = excel.createSheet("info");//创建行 这里起始行为0 类似数组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("青岛");//写入文件FileOutputStream fileOutputStream = new FileOutputStream("D:\\info.xlsx");excel.write(fileOutputStream);}
/*** 使用POI 读取文件内容**/@Testpublic void testRead() throws IOException {//打开excelXSSFWorkbook excel = new XSSFWorkbook(new FileInputStream(new File("D:\\info.xlsx")));//读取sheet页XSSFSheet sheet = excel.getSheet("info");//获取最后一行下标int lastRowNumber = sheet.getLastRowNum();for (int i = 1; i <= lastRowNumber; i++) {XSSFRow row = sheet.getRow(i);String value1 = row.getCell(1).getStringCellValue();String value2 = row.getCell(2).getStringCellValue();System.out.println(value1 + " " + value2);}}
杂项知识点:
JDK8时间API
JDK 8 引入了全新的时间 API:java.time
包,它是基于 ThreeTen 项目实现的,更加清晰、易用、不可变、线程安全。
主要类如下:
类名 | 用途 |
---|---|
LocalDate | 只包含日期,无时区(如:2025-09-04) |
LocalTime | 只包含时间,无时区(如:10:30:45) |
LocalDateTime | 日期 + 时间,无时区(如:2025-09-04T10:30:45) |
ZonedDateTime | 带有时区的日期时间(如:2025-09-04T10:30:45+08:00[Asia/Shanghai]) |
Instant | 时间戳,表示从 1970-01-01T00:00:00Z 起的秒或纳秒数 |
Duration | 表示两个时间点之间的时间量(以秒或纳秒为单位) |
Period | 表示两个日期之间的年月日差异(以年、月、日为单位) |
获取每天的日期列表
List<LocalDate> dateList = new ArrayList<>() ;
//1.日期列表
dateList.add(begin);
while(!begin.equals(end)){//未达到结束时间begin = begin.plusDays(1);dateList.add(begin);
}
//列表转字符串
StringUtils.join(dateList,",");
总结:
今天学了挺多小的知识点,然后自己还把数据统计的那几个接口完成了,感觉那几个接口都好像,一些重复性的逻辑,然后呢把这个项目整体都完成了,后边的计划就是明天写一篇项目总结,然后休息两天,看一下后边的计划。