1. Spring Task
1.1 基本概念
Spring Task
是 Spring 框架提供的轻量级任务调度工具,其核心定位是一个定时任务框架,主要作用是按照预定的时间自动执行指定的 Java 代码逻辑,适用于需要周期性或延迟执行的业务场景。
1.2 Cron 表达式详解
Cron
表达式是一种用于配置任务调度时间规则的字符串,广泛应用于各种需要定时执行任务的场景中。它由6个或7个域组成,其中年份字段是可选项,各域之间通过空格分隔。每个域代表不同的时间单位,从秒开始到年结束(如果包含年的话)
1.2.1 域的顺序与含义
- 秒(0-59)
- 分钟(0-59)
- 小时(0-23)
- 日(1-31)
- 月(1-12 或 JAN-DEC)
- 周(1-7 或 SUN-SAT)
- 年(可选,1970-2099)
其中,日和周通常不同时设置,设置其中一个时,另一个用 ?
表示
1.2.2 常用通配符
符号 | 含义 |
---|---|
* | 所有值 |
? | 不指定值,通常用于"日"或"周"域 |
- | 指定范围,如 10-12 表示 10,11,12 |
, | 指定多个值,如 MON,WED,FRI |
/ | 指定增量,如 0/15 表示从0开始每15个单位,*/10 表示任意开始每10个单位 |
推荐使用 Cron 表达式在线生成器 或者DeepSeek等AI语言大模型,可直观生成所需表达式。
1.2.3 常用表达式示例
表达式 | 说明 |
---|---|
*/5 * * * * ? | 每隔5秒执行一次 |
0 */1 * * * ? | 每隔1分钟执行一次 |
0 0 5-15 * * ? | 每天5-15点整点触发 |
0 0/3 * * * ? | 每三分钟触发一次 |
0 0-5 14 * * ? | 每天下午2点到2:05期间每分钟触发 |
0 0/5 14 * * ? | 每天下午2点到2:55期间每5分钟触发 |
0 0/5 14,18 * * ? | 每天下午2-2:55和6-6:55期间每5分钟触发 |
0 0/30 9-17 * * ? | 朝九晚五工作时间内每半小时触发 |
0 0 10,14,16 * * ? | 每天上午10点,下午2点,4点触发 |
1.3 代码实现
1.3.1 启动类配置
必须添加@EnableScheduling
注解
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
@EnableScheduling
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
1.3.2 定时任务类实现
/**
* 自定义定时任务类
*/
@Component
@Slf4j
public class MyTask {
/**
* 定时任务 每隔5秒触发一次
*/
@Scheduled(cron = "0/5 * * * * ?")
public void executeTask(){
log.info("定时任务开始执行:{}",new Date());
}
}
1.3.3 效果展示
2. WebSocket
2.1 基本概念
WebSocket 是一种基于 TCP 的新型网络协议,实现了浏览器与服务器的全双工通信。只需一次握手即可建立持久性连接,支持双向数据传输。其适用于视频弹幕、网页聊天、体育实况更新、股票实时更新等场景。
2.2 与 HTTP 协议对比
特性 | HTTP 协议 | WebSocket 协议 |
---|---|---|
连接方式 | 短连接(每次请求后断开) | 长连接(持久性连接) |
通信方向 | 单向(客户端请求,服务端响应) | 双向(双方可主动发送消息) |
底层协议 | TCP | TCP |
适用场景 | 传统网页请求 | 实时性要求高的应用 |
2.3 WebSocket 的局限性
- 服务器资源消耗:维护长连接需要额外资源
- 浏览器兼容性:不同浏览器支持程度不一
- 网络依赖性:对网络稳定性要求较高,需实现重连机制
- 不能完全替代HTTP:传统请求响应场景仍适用HTTP
2.4 WebSocket 实现步骤
2.4.1 客户端实现 (websocket.html)
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Demo</title>
</head>
<body>
<input id="text" type="text" />
<button onclick="send()">发送消息</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
var clientId = Math.random().toString(36).substr(2);
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(){
setMessageInnerHTML("连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
</script>
</html>
</script>
</html>
2.4.2 服务端实现
Maven依赖 (pom.xml):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocket服务端组件:
/**
* WebSocket服务
*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public 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();
}
}
}
}
WebSocket配置类:
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
定时推送任务:
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
@Scheduled(cron = "0/5 * * * * ?")
public void sendPeriodicMessage() {
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
webSocketServer.sendToAllClient("服务器时间: " + time);
}
}
2.4.3 效果展示
3. Apache ECharts
3.1 基本概念
Apache ECharts 是一款基于 JavaScript 的开源数据可视化图表库,由百度团队开发并捐赠给 Apache 基金会。它提供直观、生动、可交互且可个性化定制的数据可视化解决方案。
3.2 主要特点
- 丰富的图表类型:支持折线图、柱状图、饼图、散点图等30+种图表
- 跨平台兼容:支持PC和移动设备,兼容当前主流浏览器
- 深度交互:支持数据筛选、区域缩放、值域漫游等交互功能
- 动态数据:轻松实现数据动态更新和动画效果
- 主题定制:内置多套主题,支持自定义主题
3.3 案例:营业额统计
3.3.1 需求分析
该营业额统计功能通过动态折线图直观展示选定时间范围内的每日营业数据,用户可通过顶部时间选择器灵活设置统计周期(如近7天、近30日或本周等),系统将自动计算对应时间段内每一天的营业额总额,并以日期为横坐标、金额为纵坐标生成可视化折线图。当用户将鼠标悬停在图表任意数据节点时,会实时显示该日期对应的具体营业额数值,实现数据与日期的精准对应展示。整个图表数据完全动态生成,无需固定写死日期,确保用户可以根据实际分析需求自由切换不同统计周期,实时掌握营业额的每日变化趋势。
3.3.2 Service层代码
ReportServiceImpl.java
@Service // 标识为Spring服务层组件
@Slf4j // 自动生成日志记录器
public class ReportServiceImpl implements ReportService {
@Autowired // 自动注入订单Mapper
private OrderMapper orderMapper;
/**
* 根据时间区间统计营业额数据
*
* @param begin 开始日期
* @param end 结束日期
* @return TurnoverReportVO 包含日期列表和对应营业额数据的VO对象
*/
public TurnoverReportVO getTurnover(LocalDate begin, LocalDate end) {
// ========== 1. 生成日期序列 ==========
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin); // 添加开始日期
// 循环生成从begin到end的所有日期
while (!begin.equals(end)) {
begin = begin.plusDays(1); // 日期递增1天
dateList.add(begin); // 添加到日期列表
}
// ========== 2. 查询每日营业额数据 ==========
List<Double> turnoverList = new ArrayList<>();
for (LocalDate date : dateList) {
// 构造查询时间范围(当天00:00:00到23:59:59)
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
// 构建查询参数Map
Map<String, Object> map = new HashMap<>();
map.put("status", Orders.COMPLETED); // 只查询已完成订单
map.put("begin", beginTime); // 开始时间
map.put("end", endTime); // 结束时间
// 查询当日营业额总和
Double turnover = orderMapper.sumByMap(map);
// 处理null值情况,转换为0.0
turnover = turnover == null ? 0.0 : turnover;
turnoverList.add(turnover); // 添加到营业额列表
}
// ========== 3. 封装返回数据 ==========
return TurnoverReportVO.builder()
.dateList(StringUtils.join(dateList, ",")) // 日期列表转为逗号分隔字符串
.turnoverList(StringUtils.join(turnoverList, ",")) // 营业额列表转为逗号分隔字符串
.build();
}
}
注意:具体返回数据一般由前端来决定,前端展示图表,具体折现图对应数据是什么格式,是有固定的要求的。所以说,后端需要去适应前端,它需要什么格式的数据,我们就给它返回什么格式的数据。
3.3.3 Controller层代码
ReportController.java
@RestController
@RequestMapping("/admin/report")
@Slf4j
@Api(tags = "统计报表相关接口")
public class ReportController {
@Autowired
private ReportService reportService;
/**
* 营业额数据统计
*
* @param begin
* @param end
* @return
*/
@GetMapping("/turnoverStatistics")
@ApiOperation("营业额数据统计")
public Result<TurnoverReportVO> turnoverStatistics(
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate end) {
return Result.success(reportService.getTurnover(begin, end));
}
}
3.3.4 Mapper层代码
OrderMapper.java
/**
* 根据动态条件统计营业额
* @param map
*/
Double sumByMap(Map map);
OrderMapper.xml
<select id="sumByMap" resultType="java.lang.Double">
select sum(amount) from orders
<where>
<if test="status != null">
and status = #{status}
</if>
<if test="begin != null">
and order_time >= #{begin}
</if>
<if test="end != null">
and order_time <= #{end}
</if>
</where>
</select>
3.3.5 效果展示
包括营业额统计、用户统计、订单统计、销量排名TOP10的展示图形绘制。
4. Apache POI
4.1 基本概念
Apache POI 是一个开源的Java API,用于处理Microsoft Office格式文件。它提供了对Word、Excel、PowerPoint等Office文档的读写能力。
4.2 核心类功能解析
类名 | 用途 | 代码示例 | 说明 |
---|---|---|---|
XSSFWorkbook | 处理.xlsx格式Excel文件 | new XSSFWorkbook(inputStream) | 基于模板输入流创建Excel工作簿对象 |
XSSFSheet | 操作工作表 | excel.getSheet("Sheet1") | 获取指定名称的工作表 |
XSSFRow | 操作行数据 | sheet.getRow(3) | 获取指定索引的行(从0开始) |
XSSFCell | 操作单元格 | row.getCell(2) | 获取指定索引的单元格(从0开始) |
4.3 案例:导出运营数据Excel报表
4.3.1 需求分析
在数据统计页面有一个数据导出按钮,点击该按钮会下载一个Excel格式的文件。该文件包含最近30日的运营数据,表格格式已经被模板固定,由概览数据和明细数据两部分组成。导出报表后,相应的数据会自动填充到表格中,方便用户进行存档。
4.3.2 Service层代码
该接口无需传递参数,因为导出的固定是最近30天的运营数据,时间范围由后端自动计算处理;而 HttpServletResponse
参数用于通过输出流将生成的Excel文件推送给浏览器触发下载。
/**导出近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();
}
}
4.3.3 Controller层代码
/**
* 导出运营数据报表
* @param response
*/
@GetMapping("/export")
@ApiOperation("导出运营数据报表")
public void export(HttpServletResponse response){
reportService.exportBusinessData(response);
}
4.3.4 运营数据报表模板
4.3.5 效果展示