|
|
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;
|
|
|
}
|
|
|
} |