06_进销存系统_进阶功能
[TOC]
- Excel 导出:使用 EasyExcel 导出订单报表。
- 定时任务:实现库存不足时的自动预警。
八、Excel 导出 (EasyExcel)
在进销存系统中,财务和库管人员经常需要将订单数据导出为 Excel 表格进行线下核对。我们将使用阿里巴巴开源的 EasyExcel 来实现这一功能。
8.1 EasyExcel 基础语法
EasyExcel 是一个基于 Java 的简单、省内存的读写 Excel 的开源项目。
写 Excel 的基本步骤:
- 定义实体类:使用
@ExcelProperty注解定义表头。 - 调用写方法:
EasyExcel.write(fileName, Class).sheet("sheetName").doWrite(dataList);
示例代码:
// 1. 定义实体
@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
}
// 2. 写 Excel
List<DemoData> list = new ArrayList<>();
list.add(new DemoData("a",new Date(),11.11));
list.add(new DemoData("b",new Date(),22.22));
EasyExcel.write("D:/demo.xlsx", DemoData.class).sheet("测试").doWrite(list);
8.2 引入依赖
在 pom.xml 中添加 EasyExcel 依赖:
<!-- EasyExcel (Excel处理) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
8.3 定义导出实体 (OrderExportVO)
导出到 Excel 的数据结构通常与数据库表不同,我们需要定义一个专门的 VO,并使用注解控制表头。
package com.atguigu.liteims.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@ColumnWidth(20) // 设置默认列宽
public class OrderExportVO {
@ExcelProperty("订单号")
private String orderNo;
@ExcelProperty("客户名称")
private String customerName;
@ExcelProperty("订单金额")
private BigDecimal totalAmount;
@ExcelProperty("订单状态")
private String statusStr; // 导出时将 0/1/2 转换为中文
@ExcelProperty("创建时间")
@ColumnWidth(45)
private LocalDateTime createTime;
}
8.4 接口实现 (ExcelController)
package com.atguigu.liteims.controller;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.atguigu.liteims.entity.Customer;
import com.atguigu.liteims.entity.SaleOrder;
import com.atguigu.liteims.mapper.CustomerMapper;
import com.atguigu.liteims.mapper.SaleOrderMapper;
import com.atguigu.liteims.vo.OrderExportVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
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.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/excel")
@Tag(name = "数据导出")
public class ExcelController {
@Autowired
private SaleOrderMapper saleOrderMapper;
@Autowired
private CustomerMapper customerMapper;
@GetMapping("/orders/export")
@Operation(summary = "导出订单报表")
public void exportOrders(HttpServletResponse response) throws IOException {
// 1. 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("销售订单报表", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 2. 查询数据
List<SaleOrder> orders = saleOrderMapper.selectList(new QueryWrapper<SaleOrder>().orderByDesc("create_time"));
// 批量查询客户信息,避免循环查库 (性能优化)
List<Long> customerIds = orders.stream().map(SaleOrder::getCustomerId).distinct().collect(Collectors.toList());
Map<Long, String> customerMap = customerIds.isEmpty() ? Map.of() :
customerMapper.selectBatchIds(customerIds).stream()
.collect(Collectors.toMap(Customer::getId, Customer::getName));
// 3. 转换为导出对象
List<OrderExportVO> exportList = new ArrayList<>();
for (SaleOrder order : orders) {
OrderExportVO vo = new OrderExportVO();
vo.setOrderNo(order.getOrderNo());
vo.setCustomerName(customerMap.getOrDefault(order.getCustomerId(), "未知客户"));
vo.setTotalAmount(order.getTotalAmount());
vo.setCreateTime(order.getCreateTime());
// 状态转换
String statusStr = switch (order.getStatus()) {
case 1 -> "已完成";
case 2 -> "已取消";
default -> "待处理";
};
vo.setStatusStr(statusStr);
exportList.add(vo);
}
// 4. 写出 Excel
EasyExcel.write(response.getOutputStream(), OrderExportVO.class)
.sheet("订单列表")
.doWrite(exportList);
}
}
九、定时任务 (库存预警)
为了防止商品缺货,系统需要定期检查库存。如果发现库存低于预警值(例如 10),则在后台打印警告日志(实际项目中可能会发送邮件或短信)。
9.1 Spring Task 基础语法
Spring Boot 内置了定时任务支持,无需引入额外依赖。
使用步骤:
- 开启支持:在启动类添加
@EnableScheduling。 - 定义任务:在方法上添加
@Scheduled注解。
常用 Cron 表达式:
0 * * * * ?: 每分钟执行一次0 0 0 * * ?: 每天凌晨执行一次0/5 * * * * ?: 每5秒执行一次- https://developer.aliyun.com/article/942392
- https://cron.qqe2.com/
9.2 开启定时任务
在启动类 LiteImsApplication.java 上添加 @EnableScheduling 注解。
@SpringBootApplication
@EnableScheduling // 开启定时任务支持
public class LiteImsApplication {
// ...
}
9.3 任务实现 (StockTask)
package com.atguigu.liteims.task;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.atguigu.liteims.entity.Product;
import com.atguigu.liteims.mapper.ProductMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class StockTask {
@Autowired
private ProductMapper productMapper;
/**
* 每分钟检查一次库存
* Cron表达式: 秒 分 时 日 月 周
*/
@Scheduled(cron = "0 * * * * ?")
public void checkStock() {
log.info("开始执行库存检查任务...");
// 查询库存 < 10 的商品
List<Product> lowStockProducts = productMapper.selectList(
new QueryWrapper<Product>().lt("stock", 10)
);
if (lowStockProducts.isEmpty()) {
log.info("库存充足,无预警信息。");
} else {
for (Product p : lowStockProducts) {
log.warn("【库存预警】商品 ID: {}, 名称: {}, 当前库存: {}",
p.getId(), p.getName(), p.getStock());
}
}
}
}
评论