You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ExpressDeliveryStation/src/main/java/com/controller/OverduePackageService.java

235 lines
8.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* 滞留件管理服务类
* 核心功能:滞留件检测、通知管理、超时处理、统计分析
*/
public class OverduePackageService {
// 配置参数(可通过配置文件加载)
private static final int MAX_FREE_DAYS = 7; // 最大免费存放天数
private static final double DAILY_STORAGE_FEE = 1.0; // 每日滞留费用(元)
private static final int NOTICE_INTERVAL = 3; // 提醒间隔天数
// 依赖服务注入
private final ExpressService expressService;
private final NotificationService notificationService;
// 内部状态管理
private final Map<String, OverdueRecord> overdueRecords = new HashMap<>();
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
/**
* 构造函数(依赖注入模式)
*/
public OverduePackageService(ExpressService expressService,
NotificationService notificationService) {
this.expressService = expressService;
this.notificationService = notificationService;
initializeOverdueMonitoring();
}
/**
* 初始化滞留件监控线程
*/
private void initializeOverdueMonitoring() {
// 创建定时任务每24小时执行一次检测
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
detectAndProcessOverduePackages();
}
}, 0, 24 * 60 * 60 * 1000);
}
/**
* 滞留件检测与处理主逻辑
*/
public synchronized void detectAndProcessOverduePackages() {
// 1. 获取所有待取件包裹
List<ExpressItem> allPackages = expressService.getAllPackages()
.stream()
.filter(item -> "待取件".equals(item.getStatus()))
.collect(Collectors.toList());
// 2. 计算存放天数并识别滞留件
List<ExpressItem> overduePackages = new ArrayList<>();
for (ExpressItem item : allPackages) {
int storageDays = calculateStorageDays(item.getArrivalTime());
item.setStorageDays(storageDays);
if (storageDays > MAX_FREE_DAYS) {
overduePackages.add(item);
recordOverdueHistory(item, storageDays);
}
}
// 3. 执行滞留处理流程
overduePackages.forEach(this::processOverduePackage);
}
/**
* 计算包裹存放天数(精确到小时)
*/
private int calculateStorageDays(Date arrivalTime) {
long now = System.currentTimeMillis();
long elapsed = now - arrivalTime.getTime();
return (int) (elapsed / (24 * 3600 * 1000));
}
/**
* 记录滞留历史(防重复处理)
*/
private void recordOverdueHistory(ExpressItem item, int currentDays) {
String trackingNumber = item.getTrackingNumber();
overdueRecords.putIfAbsent(trackingNumber, new OverdueRecord());
OverdueRecord record = overdueRecords.get(trackingNumber);
if (record.getLastNoticeDay() == 0 ||
(currentDays - record.getLastNoticeDay()) >= NOTICE_INTERVAL) {
record.setLastNoticeDay(currentDays);
record.incrementNoticeCount();
}
}
/**
* 单个滞留件处理流程
*/
private void processOverduePackage(ExpressItem item) {
// 1. 费用计算
double overdueFee = calculateOverdueFee(item.getStorageDays());
// 2. 生成通知内容
String noticeContent = buildNoticeContent(item, overdueFee);
// 3. 发送通知(支持多通道)
sendMultiChannelNotice(item.getPhone(), noticeContent);
// 4. 标记为滞留状态(可选扩展)
if (item.getStorageDays() > MAX_FREE_DAYS * 2) {
expressService.markAsOverdue(item.getTrackingNumber());
}
}
/**
* 计算滞留费用(简单线性计费)
*/
private double calculateOverdueFee(int storageDays) {
int overdueDays = storageDays - MAX_FREE_DAYS;
return overdueDays > 0 ? overdueDays * DAILY_STORAGE_FEE : 0;
}
/**
* 构建多格式通知内容
*/
private String buildNoticeContent(ExpressItem item, double fee) {
return String.format("""
【快递滞留提醒】
运单号:%s
收件人:%s
滞留天数:%d天
当前费用:%.2f元
请尽快取件避免额外费用!
""",
item.getTrackingNumber(),
item.getRecipient(),
item.getStorageDays(),
fee);
}
/**
* 多通道通知发送(短信+邮件+系统消息)
*/
private void sendMultiChannelNotice(String phone, String content) {
// 短信通知
notificationService.sendSMS(phone, content);
// 邮件通知(需实现邮件服务)
// notificationService.sendEmail(recipientEmail, "快递滞留提醒", content);
// 系统内消息(需集成消息中心)
// messageCenter.pushSystemNotice(userId, content);
}
/**
* 滞留件统计报表生成
*/
public String generateOverdueReport() {
Map<Integer, Long> dailyCount = overdueRecords.values().stream()
.collect(Collectors.groupingBy(
OverdueRecord::getNoticeCount,
Collectors.counting()
));
StringBuilder report = new StringBuilder();
report.append("===== 滞留件统计报表 =====\n");
report.append("统计时间:").append(dateFormat.format(new Date())).append("\n");
report.append("-------------------------\n");
report.append("总滞留包裹数:").append(overdueRecords.size()).append("\n");
dailyCount.forEach((times, count) ->
report.append(String.format("被提醒%d次包裹数%d\n", times, count)));
return report.toString();
}
/**
* 滞留记录实体类
*/
private static class OverdueRecord {
private int lastNoticeDay; // 最后一次通知时的存放天数
private int noticeCount; // 通知发送次数
// Getter/Setter
public int getLastNoticeDay() { return lastNoticeDay; }
public void setLastNoticeDay(int day) { this.lastNoticeDay = day; }
public int getNoticeCount() { return noticeCount; }
public void incrementNoticeCount() { this.noticeCount++; }
}
/**
* 通知服务接口(需具体实现)
*/
public interface NotificationService {
void sendSMS(String phone, String message);
// void sendEmail(String to, String subject, String content);
}
/**
* 滞留件查询服务
*/
public List<ExpressItem> queryOverduePackages(int minDays, int maxDays) {
return expressService.getAllPackages().stream()
.filter(item -> {
int days = item.getStorageDays();
return days >= minDays && days <= maxDays;
})
.sorted(Comparator.comparingInt(ExpressItem::getStorageDays).reversed())
.collect(Collectors.toList());
}
/**
* 批量处理滞留件(自动退回)
*/
public int batchReturnOverduePackages(int maxDays) {
List<ExpressItem> toReturn = queryOverduePackages(maxDays, Integer.MAX_VALUE);
toReturn.forEach(item -> expressService.markAsReturned(item.getTrackingNumber()));
return toReturn.size();
}
/**
* 滞留费用明细查询
*/
public Map<String, Double> getOverdueFees(String trackingNumber) {
OverdueRecord record = overdueRecords.get(trackingNumber);
if (record == null) return Collections.emptyMap();
Map<String, Double> feeDetail = new HashMap<>();
feeDetail.put("baseFee", (double) (record.getNoticeCount() * NOTICE_INTERVAL));
feeDetail.put("totalFee", calculateOverdueFee(record.getNoticeCount() * NOTICE_INTERVAL));
return feeDetail;
}
}