前言:
上午就把今天任務完成了,就繼續往后學了一些知識,晚上寫下筆記總結一下。
今日完成任務:
- 調用百度地圖開放平臺,優化用戶下單業務
- 學習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,",");
總結:
? 今天學了挺多小的知識點,然后自己還把數據統計的那幾個接口完成了,感覺那幾個接口都好像,一些重復性的邏輯,然后呢把這個項目整體都完成了,后邊的計劃就是明天寫一篇項目總結,然后休息兩天,看一下后邊的計劃。