xiepufeng
9 months ago
13 changed files with 1151 additions and 25 deletions
@ -0,0 +1,144 @@ |
|||||
|
package com.zc.business.domain; |
||||
|
|
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.ruoyi.common.core.domain.BaseEntity; |
||||
|
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
||||
|
import lombok.Data; |
||||
|
import org.apache.commons.codec.digest.DigestUtils; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.Objects; |
||||
|
|
||||
|
/** |
||||
|
* 交通断面数据统计定义 |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Data |
||||
|
public class DcTrafficSectionData extends BaseEntity { |
||||
|
|
||||
|
/** |
||||
|
* 主键 |
||||
|
*/ |
||||
|
@TableId |
||||
|
private String id; |
||||
|
|
||||
|
/** |
||||
|
* 车流量 |
||||
|
*/ |
||||
|
private Integer trafficVolume; |
||||
|
|
||||
|
/** |
||||
|
* 平均速度 |
||||
|
*/ |
||||
|
private Integer averageSpeed; |
||||
|
|
||||
|
/** |
||||
|
* 所属设备 |
||||
|
*/ |
||||
|
private Long deviceId; |
||||
|
|
||||
|
/** |
||||
|
* 统计时间 |
||||
|
*/ |
||||
|
private Date statisticalDate; |
||||
|
|
||||
|
/** |
||||
|
* 道路方向 |
||||
|
*/ |
||||
|
private Byte direction; |
||||
|
|
||||
|
/** |
||||
|
* 时段类型 |
||||
|
* 1-年 2-月 3-季 4-日 |
||||
|
*/ |
||||
|
private Byte periodType; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 所在桩号 |
||||
|
*/ |
||||
|
private Integer stakeMark; |
||||
|
|
||||
|
/** |
||||
|
* 设置交通数据的统计周期类型。 |
||||
|
* @param periodType 统计周期类型的枚举值。 |
||||
|
*/ |
||||
|
public void setPeriodType(TrafficDataPeriodTypeEnum periodType) { |
||||
|
this.periodType = periodType.getCode(); // 将枚举类型转换为代码值存储
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 重写equals方法,用于比较两个对象是否相等。 |
||||
|
* @param o 要与当前对象比较的对象。 |
||||
|
* @return 如果两个对象相等,则返回true;否则返回false。 |
||||
|
*/ |
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
if (this == o) return true; // 同一对象比较,直接返回true
|
||||
|
if (o == null || getClass() != o.getClass()) return false; // 对象为空或类型不同,返回false
|
||||
|
DcTrafficSectionData that = (DcTrafficSectionData) o; // 类型转换
|
||||
|
return Objects.equals(deviceId, that.deviceId) && |
||||
|
Objects.equals(statisticalDate, that.statisticalDate) && |
||||
|
Objects.equals(direction, that.direction) && |
||||
|
Objects.equals(periodType, that.periodType) && |
||||
|
Objects.equals(stakeMark, that.stakeMark); // 比较各属性值
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 重写hashCode方法,基于对象的属性生成哈希码。 |
||||
|
* @return 对象的哈希码值。 |
||||
|
*/ |
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
return Objects.hash(deviceId, statisticalDate, direction, periodType, stakeMark); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置统计日期,根据不同的统计周期类型来调整日期,使其对应周期的起始日期。 |
||||
|
* @param statisticalDate 统计日期,原始日期。 |
||||
|
*/ |
||||
|
public void setStatisticalDate(Date statisticalDate) { |
||||
|
TrafficDataPeriodTypeEnum typeEnum = TrafficDataPeriodTypeEnum.valueOfCode(periodType); |
||||
|
setStatisticalDate(statisticalDate, typeEnum); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据给定的统计周期类型和日期,设置统计日期为相应周期的起始日期。 |
||||
|
* @param statisticalDate 原始统计日期。 |
||||
|
* @param typeEnum 统计周期类型。 |
||||
|
*/ |
||||
|
public void setStatisticalDate(Date statisticalDate, TrafficDataPeriodTypeEnum typeEnum) { |
||||
|
switch (typeEnum) { |
||||
|
case DAY: |
||||
|
// 设置为当天的起始时间
|
||||
|
this.statisticalDate = DateUtil.beginOfDay(statisticalDate); |
||||
|
break; |
||||
|
case MONTH: |
||||
|
// 设置为当月的起始日期
|
||||
|
this.statisticalDate = DateUtil.beginOfMonth(statisticalDate); |
||||
|
break; |
||||
|
case QUARTER: |
||||
|
// 设置为当季度的起始日期
|
||||
|
this.statisticalDate = DateUtil.beginOfQuarter(statisticalDate); |
||||
|
break; |
||||
|
case YEAR: |
||||
|
// 设置为当年的起始日期
|
||||
|
this.statisticalDate = DateUtil.beginOfYear(statisticalDate); |
||||
|
break; |
||||
|
default: |
||||
|
// 如果不是预定义的周期类型,则不做任何处理
|
||||
|
this.statisticalDate = statisticalDate; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据设备ID、统计时间、方向、时段类型、桩号生成一个唯一ID |
||||
|
*/ |
||||
|
public void generateUniqueId() { |
||||
|
String combinedAttributes = deviceId + "_" + DateUtil.format(statisticalDate, "yyyyMMdd_HHmmss") + "_" + direction + "_" + periodType + "_" + stakeMark; |
||||
|
this.id = DigestUtils.md5Hex(combinedAttributes); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
package com.zc.business.enums; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
|
||||
|
/** |
||||
|
* 定义一个枚举类型 TrafficDataPeriodTypeEnum,用于表示周期类型(年、月、季、日) |
||||
|
*/ |
||||
|
@Getter |
||||
|
public enum TrafficDataPeriodTypeEnum { |
||||
|
// 枚举成员:年,关联字节型代码 1 和描述 "年"
|
||||
|
YEAR((byte) 1, "年"), |
||||
|
|
||||
|
// 枚举成员:月,关联字节型代码 2 和描述 "月"
|
||||
|
MONTH((byte) 2, "月"), |
||||
|
|
||||
|
// 枚举成员:季,关联字节型代码 3 和描述 "季"
|
||||
|
QUARTER((byte) 3, "季"), |
||||
|
|
||||
|
// 枚举成员:日,关联字节型代码 4 和描述 "日"
|
||||
|
DAY((byte) 4, "日"); |
||||
|
|
||||
|
// 每个枚举成员的属性:代码,存储对应的字节型数值
|
||||
|
private final Byte code; |
||||
|
|
||||
|
// 每个枚举成员的属性:描述,存储该周期类型的中文描述
|
||||
|
private final String description; |
||||
|
|
||||
|
// 构造方法,初始化每个枚举成员的代码和描述信息
|
||||
|
TrafficDataPeriodTypeEnum(Byte code, String description) { |
||||
|
this.code = code; |
||||
|
this.description = description; |
||||
|
} |
||||
|
|
||||
|
// 反向查找方法,根据给定的字节型代码查找对应的枚举值
|
||||
|
// 如果找到匹配项,则返回该枚举类型;否则抛出IllegalArgumentException异常
|
||||
|
public static TrafficDataPeriodTypeEnum valueOfCode(Byte code) { |
||||
|
// 遍历所有PeriodTypeEnum枚举值
|
||||
|
for (TrafficDataPeriodTypeEnum type : values()) { |
||||
|
// 如果当前枚举类型的code与输入的code相等
|
||||
|
if (type.getCode().equals(code)) { |
||||
|
// 返回找到的枚举类型
|
||||
|
return type; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 如果循环结束都没有找到匹配的code,则抛出异常,说明提供的code非法
|
||||
|
throw new IllegalArgumentException("无效的周期类型代码: " + code); |
||||
|
} |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package com.zc.business.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
/** |
||||
|
* 交通断面数据统计Mapper接口 |
||||
|
* |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Mapper |
||||
|
public interface DcTrafficSectionDataMapper extends BaseMapper<DcTrafficSectionData> { |
||||
|
|
||||
|
/** |
||||
|
* 插入或更新交通路段数据。 |
||||
|
* |
||||
|
* @param trafficSectionData 交通路段数据对象,包含需要插入或更新的数据。 |
||||
|
* @return 返回一个布尔值,表示操作是否成功。true表示插入或更新成功,false表示失败。 |
||||
|
*/ |
||||
|
boolean insertOrUpdate(DcTrafficSectionData trafficSectionData); |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
package com.zc.business.service; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.baomidou.mybatisplus.extension.service.IService; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
|
||||
|
public interface DcTrafficSectionDataService extends IService<DcTrafficSectionData> { |
||||
|
|
||||
|
/** |
||||
|
* 处理实时接收到的设备消息,并将其转换为交通断面统计数据对象并缓存。 |
||||
|
* |
||||
|
* @param msg 设备发送的JSON格式实时消息 |
||||
|
*/ |
||||
|
void processRealtimeMessage(JSONObject msg); |
||||
|
} |
@ -0,0 +1,173 @@ |
|||||
|
package com.zc.business.service.impl; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
|
import com.ruoyi.common.utils.DateUtils; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
||||
|
import com.zc.business.statistics.cache.*; |
||||
|
import com.zc.business.mapper.DcTrafficSectionDataMapper; |
||||
|
import com.zc.business.service.DcTrafficSectionDataService; |
||||
|
import com.zc.business.statistics.handler.RealtimeTrafficStatistics; |
||||
|
import org.springframework.scheduling.annotation.Scheduled; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import javax.annotation.Resource; |
||||
|
import java.util.Map; |
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
/** |
||||
|
* 通断面数据服务实现类,负责处理实时设备消息、缓存数据、定时任务以及数据保存等功能。 |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Service |
||||
|
public class DcTrafficSectionDataServiceImpl |
||||
|
extends ServiceImpl<DcTrafficSectionDataMapper, DcTrafficSectionData> |
||||
|
implements DcTrafficSectionDataService { |
||||
|
|
||||
|
@Resource |
||||
|
private DcTrafficSectionDataMapper dcTrafficSectionDataMapper; |
||||
|
|
||||
|
/** |
||||
|
* 处理实时接收到的设备消息,并将其转换为交通断面统计数据对象并缓存。 |
||||
|
* |
||||
|
* @param msg 设备发送的JSON格式实时消息 |
||||
|
*/ |
||||
|
@Override |
||||
|
public void processRealtimeMessage(JSONObject msg) { |
||||
|
|
||||
|
// 1. 将设备消息转换为交通断面数据统计定义对象
|
||||
|
DcTrafficSectionData dcTrafficSectionData = convertToTrafficStatistics(msg); |
||||
|
|
||||
|
// 2. 将转换后的数据添加到缓存中
|
||||
|
DailyTrafficStatisticsCache.addCacheData(dcTrafficSectionData); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将设备实时消息转换为交通断面数据统计定义对象 |
||||
|
* |
||||
|
* @param msg JSON格式的设备实时消息 |
||||
|
* @return 转换后的交通断面数据统计定义对象 |
||||
|
*/ |
||||
|
public DcTrafficSectionData convertToTrafficStatistics(JSONObject msg) { |
||||
|
DcTrafficSectionData dcTrafficSectionData = new DcTrafficSectionData(); |
||||
|
|
||||
|
// TODO
|
||||
|
return dcTrafficSectionData; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 定义每小时第20分钟执行的任务,用于清除过期缓存数据并将缓存中的数据整合后保存至数据库。 |
||||
|
*/ |
||||
|
@Scheduled(cron = "0 20 * * * ?") // 每小时的20分整点执行该任务
|
||||
|
public void performHourlyCleanupAndPersist() { |
||||
|
// 清除已过期的缓存数据
|
||||
|
DailyTrafficStatisticsCache.clearExpiredData(); |
||||
|
MonthlyTrafficStatisticsCache.clearExpiredData(); |
||||
|
QuarterlyTrafficStatisticsCache.clearExpiredData(); |
||||
|
YearlyTrafficStatisticsCache.clearExpiredData(); |
||||
|
// 整合缓存数据并保存至数据库
|
||||
|
// 将缓存中的数据按日统计后保存至数据库
|
||||
|
// 添加月交通断面数据到缓存中
|
||||
|
persistAggregatedData(DailyTrafficStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.DAY, MonthlyTrafficStatisticsCache::addCacheData); |
||||
|
// 将缓存中的数据按月统计后保存至数据库
|
||||
|
// 添加季度交通断面数据到缓存中
|
||||
|
persistAggregatedData(MonthlyTrafficStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.MONTH, QuarterlyTrafficStatisticsCache::addCacheData); |
||||
|
// 将缓存中的数据按季度统计后保存至数据库
|
||||
|
// 添加年交通断面数据到缓存中
|
||||
|
persistAggregatedData(QuarterlyTrafficStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.QUARTER, YearlyTrafficStatisticsCache::addCacheData); |
||||
|
// 将缓存中的数据按年统计后保存至数据库
|
||||
|
persistAggregatedData(YearlyTrafficStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.YEAR, (a) -> {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将缓存中的数据统计后保存至数据库。 |
||||
|
*/ |
||||
|
public void persistAggregatedData( |
||||
|
Map<String, ? extends AbstractTrafficStatisticsCache> cache, |
||||
|
TrafficDataPeriodTypeEnum periodType, |
||||
|
Consumer<DcTrafficSectionData> consumer |
||||
|
) { |
||||
|
for (AbstractTrafficStatisticsCache data : cache.values()) { |
||||
|
// 如果数据已经存储过,则跳过此次处理
|
||||
|
if (data.isStored()) { |
||||
|
continue; |
||||
|
} |
||||
|
DcTrafficSectionData aggregatedData = RealtimeTrafficStatistics.trafficStatistics(data.getData(), periodType); |
||||
|
if (dcTrafficSectionDataMapper.insertOrUpdate(aggregatedData)) { |
||||
|
// 设置数据已存储状态
|
||||
|
data.setStored(true); |
||||
|
// 调用回调函数
|
||||
|
consumer.accept(aggregatedData); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将计算给定列表中所有交通断面数据的统计结果保存至数据库 |
||||
|
*/ |
||||
|
public boolean persistData(DcTrafficSectionData aggregatedData) { |
||||
|
|
||||
|
if (aggregatedData == null) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 创建更新条件封装器
|
||||
|
LambdaUpdateWrapper<DcTrafficSectionData> updateWrapper = new LambdaUpdateWrapper<>(); |
||||
|
|
||||
|
// 设置更新条件:根据设备ID、方向、时段类型、桩号、统计时间查找记录
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getDeviceId, aggregatedData.getDeviceId()); |
||||
|
// 方向
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getDirection, aggregatedData.getDirection()); |
||||
|
// 时段类型
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getPeriodType, aggregatedData.getPeriodType()); |
||||
|
// 桩号
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getStakeMark, aggregatedData.getStakeMark()); |
||||
|
// 统计时间
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getStatisticalDate, aggregatedData.getStatisticalDate()); |
||||
|
|
||||
|
|
||||
|
if (this.update(aggregatedData, updateWrapper)) { |
||||
|
return true; |
||||
|
} else { |
||||
|
// 更新失败则尝试插入
|
||||
|
aggregatedData.setCreateTime(DateUtils.getNowDate()); |
||||
|
return this.save(aggregatedData); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* public boolean persistData(DcTrafficSectionData aggregatedData) { |
||||
|
|
||||
|
if (aggregatedData == null) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 创建更新条件封装器
|
||||
|
LambdaUpdateWrapper<DcTrafficSectionData> updateWrapper = new LambdaUpdateWrapper<>(); |
||||
|
|
||||
|
// 设置更新条件:根据设备ID、方向、时段类型、桩号、统计时间查找记录
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getDeviceId, aggregatedData.getDeviceId()); |
||||
|
// 方向
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getDirection, aggregatedData.getDirection()); |
||||
|
// 时段类型
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getPeriodType, aggregatedData.getPeriodType()); |
||||
|
// 桩号
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getStakeMark, aggregatedData.getStakeMark()); |
||||
|
// 统计时间
|
||||
|
updateWrapper.eq(DcTrafficSectionData::getStatisticalDate, aggregatedData.getStatisticalDate()); |
||||
|
|
||||
|
|
||||
|
if (this.update(aggregatedData, updateWrapper)) { |
||||
|
return true; |
||||
|
} else { |
||||
|
// 更新失败则尝试插入
|
||||
|
aggregatedData.setCreateTime(DateUtils.getNowDate()); |
||||
|
return this.save(aggregatedData); |
||||
|
} |
||||
|
}*/ |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,47 @@ |
|||||
|
package com.zc.business.statistics.cache; |
||||
|
|
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* 交通断面数据缓存定义 |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Getter |
||||
|
@Setter |
||||
|
public abstract class AbstractTrafficStatisticsCache { |
||||
|
|
||||
|
// 日志记录器
|
||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); |
||||
|
|
||||
|
/** |
||||
|
* 缓存对象的键,由设备ID与统计日期组成 |
||||
|
*/ |
||||
|
private String cacheKey; |
||||
|
|
||||
|
/** |
||||
|
* 设备上报日期字符串 |
||||
|
*/ |
||||
|
private String statisticalDateStr; |
||||
|
|
||||
|
/** |
||||
|
* 数据最后添加到缓存中的时间 |
||||
|
*/ |
||||
|
private Date lastAddedTime; |
||||
|
|
||||
|
/** |
||||
|
* 标记该缓存实例是否已存储过数据 |
||||
|
*/ |
||||
|
private boolean stored; |
||||
|
|
||||
|
/** |
||||
|
* 存储具体交通断面数据的列表 |
||||
|
*/ |
||||
|
private Collection<DcTrafficSectionData> data; |
||||
|
} |
@ -0,0 +1,139 @@ |
|||||
|
package com.zc.business.statistics.cache; |
||||
|
|
||||
|
import cn.hutool.core.date.DateUnit; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
|
||||
|
import java.util.*; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* 以天为单位的交通交通断面数据缓存类,用于存储和管理设备上报的交通断面统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Getter |
||||
|
@Setter |
||||
|
public class DailyTrafficStatisticsCache extends AbstractTrafficStatisticsCache { |
||||
|
|
||||
|
@Getter |
||||
|
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
||||
|
private static final Map<String, DailyTrafficStatisticsCache> cache = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
// 最大缓存时间(单位:秒)
|
||||
|
private static final long MAX_CACHE_TIME = 25 * 60 * 60; // 缓存数据最长保留25小时
|
||||
|
|
||||
|
// 最大容量限制(一个设备),防止内存溢出
|
||||
|
private static final int MAX_CAPACITY = 60/5 * (24 + 1) + 1000; // 缓存的最大条目数
|
||||
|
|
||||
|
// 私有构造函数,确保只能通过静态方法获取实例
|
||||
|
private DailyTrafficStatisticsCache() { |
||||
|
super(); |
||||
|
this.setData(new ArrayList<>()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加交通断面数据到缓存中 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 待添加的交通断面统计数据 |
||||
|
*/ |
||||
|
public static void addCacheData(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取或新建对应的缓存实例
|
||||
|
DailyTrafficStatisticsCache instance = getInstance(dcTrafficSectionData); |
||||
|
|
||||
|
// 检查缓存容量是否达到上限
|
||||
|
if (instance.getData().size() >= MAX_CAPACITY) { |
||||
|
instance.getLogger().error("交通断面数据缓存出现异常,最大缓存量达到设定上线 {}, 当前设备是 {}", MAX_CAPACITY, dcTrafficSectionData.getDeviceId()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 更新最后添加时间
|
||||
|
instance.setLastAddedTime(DateUtil.date()); |
||||
|
|
||||
|
// 更新状态
|
||||
|
instance.setStored(false); |
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
String key = generateCacheKey(dcTrafficSectionData); |
||||
|
|
||||
|
// 设置key和thatDay属性(仅在首次添加时)
|
||||
|
if (instance.getCacheKey() == null) { |
||||
|
instance.setCacheKey(key); |
||||
|
instance.setStatisticalDateStr(formattedDate); |
||||
|
} |
||||
|
|
||||
|
dcTrafficSectionData.setStatisticalDate(dcTrafficSectionData.getStatisticalDate(), TrafficDataPeriodTypeEnum.DAY); |
||||
|
|
||||
|
// 将新数据添加到数据列表中
|
||||
|
instance.getData().add(dcTrafficSectionData); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取或创建对应设备与日期的DcTrafficSectionDataCache实例 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 交通断面数据统计定义 |
||||
|
* @return 对应的交通断面数据缓存实例 |
||||
|
*/ |
||||
|
private static DailyTrafficStatisticsCache getInstance(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 使用toKey方法生成唯一键,并根据此键计算并返回缓存实例
|
||||
|
return cache.computeIfAbsent(generateCacheKey(dcTrafficSectionData), k -> new DailyTrafficStatisticsCache()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 该方法用于生成一个基于DcTrafficSectionData对象属性的唯一键字符串。 |
||||
|
* 这个键由设备ID与统计日期两部分组成,通常用于索引或标识特定设备在某一日期的交通流量数据。 |
||||
|
* |
||||
|
* @param dcTrafficSectionData DcTrafficSectionData类型的对象,包含设备ID和统计日期等信息 |
||||
|
* @return 格式化后的字符串键,格式为 "deviceId|formattedDate" |
||||
|
*/ |
||||
|
public static String generateCacheKey(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取设备ID
|
||||
|
Long deviceId = dcTrafficSectionData.getDeviceId(); |
||||
|
|
||||
|
// 获取并格式化统计日期
|
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
|
||||
|
// 使用"|"字符连接设备ID和日期,形成唯一键
|
||||
|
return deviceId + "|" + formattedDate; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除所有过期的交通断面数据缓存项 |
||||
|
*/ |
||||
|
public static void clearExpiredData() { |
||||
|
// 使用stream API找出所有过期的数据缓存项键
|
||||
|
Set<String> keysToRemove = cache.keySet().stream() |
||||
|
.filter(DailyTrafficStatisticsCache::isCacheItemExpire) |
||||
|
.collect(Collectors.toSet()); |
||||
|
|
||||
|
// 安全地从缓存中删除这些过期项
|
||||
|
keysToRemove.forEach(cache::remove); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查给定缓存键所对应的缓存项是否已经过期 |
||||
|
* |
||||
|
* @param key 缓存key |
||||
|
* @return 如果已过期则返回true,否则返回false |
||||
|
*/ |
||||
|
private static boolean isCacheItemExpire(String key) { |
||||
|
Date lastAddedTime = cache.get(key).getLastAddedTime(); |
||||
|
Date currentTime = DateUtil.date(); |
||||
|
long betweenSecond = DateUtil.between(lastAddedTime, currentTime, DateUnit.SECOND); |
||||
|
return betweenSecond > MAX_CACHE_TIME; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将 Date 类型的日期格式化为指定格式的字符串。 |
||||
|
* |
||||
|
* @param date 需要格式化的 Date 对象。 |
||||
|
* @return 格式化后的日期字符串。 |
||||
|
*/ |
||||
|
private static String formatDate(Date date) { |
||||
|
// 使用 DateUtil 工具类将 date 格式化为指定格式的字符串
|
||||
|
return DateUtil.formatDate(date); |
||||
|
} |
||||
|
} |
@ -0,0 +1,139 @@ |
|||||
|
package com.zc.business.statistics.cache; |
||||
|
|
||||
|
import cn.hutool.core.date.DateUnit; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
|
||||
|
import java.util.*; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* 以月为单位的交通交通断面数据缓存类,用于存储和管理设备上报的交通断面统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Getter |
||||
|
@Setter |
||||
|
public class MonthlyTrafficStatisticsCache extends AbstractTrafficStatisticsCache { |
||||
|
|
||||
|
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
||||
|
@Getter |
||||
|
private static final Map<String, MonthlyTrafficStatisticsCache> cache = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
// 最大缓存时间(单位:秒)
|
||||
|
private static final long MAX_CACHE_TIME = (60 * 60) * (31 * 24 + 1); |
||||
|
|
||||
|
// 最大容量限制,防止内存溢出
|
||||
|
private static final int MAX_CAPACITY = 31 + 1000; // 缓存的最大条目数
|
||||
|
|
||||
|
|
||||
|
// 私有构造函数,确保只能通过静态方法获取实例
|
||||
|
private MonthlyTrafficStatisticsCache() { |
||||
|
super(); |
||||
|
this.setData(new HashSet<>()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加交通断面数据到缓存中 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 待添加的交通断面统计数据 |
||||
|
*/ |
||||
|
public static void addCacheData(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取或新建对应的缓存实例
|
||||
|
MonthlyTrafficStatisticsCache instance = getInstance(dcTrafficSectionData); |
||||
|
|
||||
|
// 检查缓存容量是否达到上限
|
||||
|
if (instance.getData().size() >= MAX_CAPACITY) { |
||||
|
instance.getLogger().error("交通断面数据缓存出现异常,最大缓存量达到设定上线 {}, 当前设备是 {}", MAX_CAPACITY, dcTrafficSectionData.getDeviceId()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 更新最后添加时间
|
||||
|
instance.setLastAddedTime(DateUtil.date()); |
||||
|
|
||||
|
// 更新状态
|
||||
|
instance.setStored(false); |
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
String key = generateCacheKey(dcTrafficSectionData); |
||||
|
|
||||
|
// 设置key和thatDay属性(仅在首次添加时)
|
||||
|
if (instance.getCacheKey() == null) { |
||||
|
instance.setCacheKey(key); |
||||
|
instance.setStatisticalDateStr(formattedDate); |
||||
|
} |
||||
|
|
||||
|
dcTrafficSectionData.setStatisticalDate(dcTrafficSectionData.getStatisticalDate(), TrafficDataPeriodTypeEnum.MONTH); |
||||
|
|
||||
|
// 将新数据添加到数据列表中
|
||||
|
instance.getData().add(dcTrafficSectionData); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取或创建对应设备与日期的DcTrafficSectionDataCache实例 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 交通断面数据统计定义 |
||||
|
* @return 对应的交通断面数据缓存实例 |
||||
|
*/ |
||||
|
private static MonthlyTrafficStatisticsCache getInstance(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 使用toKey方法生成唯一键,并根据此键计算并返回缓存实例
|
||||
|
return cache.computeIfAbsent(generateCacheKey(dcTrafficSectionData), k -> new MonthlyTrafficStatisticsCache()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 该方法用于生成一个基于DcTrafficSectionData对象属性的唯一键字符串。 |
||||
|
* 这个键由设备ID与统计日期两部分组成,通常用于索引或标识特定设备在某一日期的交通流量数据。 |
||||
|
* |
||||
|
* @param dcTrafficSectionData DcTrafficSectionData类型的对象,包含设备ID和统计日期等信息 |
||||
|
* @return 格式化后的字符串键,格式为 "deviceId|formattedDate" |
||||
|
*/ |
||||
|
public static String generateCacheKey(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取设备ID
|
||||
|
Long deviceId = dcTrafficSectionData.getDeviceId(); |
||||
|
|
||||
|
// 获取并格式化统计日期
|
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
|
||||
|
// 使用"|"字符连接设备ID和日期,形成唯一键
|
||||
|
return deviceId + "|" + formattedDate; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除所有过期的交通断面数据缓存项 |
||||
|
*/ |
||||
|
public static void clearExpiredData() { |
||||
|
// 使用stream API找出所有过期的数据缓存项键
|
||||
|
Set<String> keysToRemove = cache.keySet().stream() |
||||
|
.filter(MonthlyTrafficStatisticsCache::isCacheItemExpire) |
||||
|
.collect(Collectors.toSet()); |
||||
|
|
||||
|
// 安全地从缓存中删除这些过期项
|
||||
|
keysToRemove.forEach(cache::remove); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查给定缓存键所对应的缓存项是否已经过期 |
||||
|
* |
||||
|
* @param key 缓存key |
||||
|
* @return 如果已过期则返回true,否则返回false |
||||
|
*/ |
||||
|
private static boolean isCacheItemExpire(String key) { |
||||
|
Date lastAddedTime = cache.get(key).getLastAddedTime(); |
||||
|
Date currentTime = DateUtil.date(); |
||||
|
long betweenSecond = DateUtil.between(lastAddedTime, currentTime, DateUnit.SECOND); |
||||
|
return betweenSecond > MAX_CACHE_TIME; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将 Date 类型的日期格式化为 "yyyy-MM-01" 格式的字符串。 |
||||
|
* @param date 需要格式化的日期对象。 |
||||
|
* @return 格式化后的日期字符串。 |
||||
|
*/ |
||||
|
private static String formatDate(Date date) { |
||||
|
// 使用 DateUtil 工具类将 date 格式化为指定格式的字符串
|
||||
|
return DateUtil.format(date, "yyyy-MM-01"); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,144 @@ |
|||||
|
package com.zc.business.statistics.cache; |
||||
|
|
||||
|
import cn.hutool.core.date.DateUnit; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.HashSet; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* 以季度为单位的交通交通断面数据缓存类,用于存储和管理设备上报的交通断面统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Getter |
||||
|
@Setter |
||||
|
public class QuarterlyTrafficStatisticsCache extends AbstractTrafficStatisticsCache { |
||||
|
|
||||
|
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
||||
|
@Getter |
||||
|
private static final Map<String, QuarterlyTrafficStatisticsCache> cache = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
// 最大缓存时间(单位:秒)
|
||||
|
private static final long MAX_CACHE_TIME = (60 * 60) * (3 * 31 * 24 + 1); |
||||
|
|
||||
|
// 最大容量限制,防止内存溢出
|
||||
|
private static final int MAX_CAPACITY = 3 + 1000; // 缓存的最大条目数
|
||||
|
|
||||
|
|
||||
|
// 私有构造函数,确保只能通过静态方法获取实例
|
||||
|
private QuarterlyTrafficStatisticsCache() { |
||||
|
super(); |
||||
|
this.setData(new HashSet<>()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加交通断面数据到缓存中 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 待添加的交通断面统计数据 |
||||
|
*/ |
||||
|
public static void addCacheData(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取或新建对应的缓存实例
|
||||
|
QuarterlyTrafficStatisticsCache instance = getInstance(dcTrafficSectionData); |
||||
|
|
||||
|
// 检查缓存容量是否达到上限
|
||||
|
if (instance.getData().size() >= MAX_CAPACITY) { |
||||
|
instance.getLogger().error("交通断面数据缓存出现异常,最大缓存量达到设定上线 {}, 当前设备是 {}", MAX_CAPACITY, dcTrafficSectionData.getDeviceId()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 更新最后添加时间
|
||||
|
instance.setLastAddedTime(DateUtil.date()); |
||||
|
|
||||
|
// 更新状态
|
||||
|
instance.setStored(false); |
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
String key = generateCacheKey(dcTrafficSectionData); |
||||
|
|
||||
|
// 设置key和thatDay属性(仅在首次添加时)
|
||||
|
if (instance.getCacheKey() == null) { |
||||
|
instance.setCacheKey(key); |
||||
|
instance.setStatisticalDateStr(formattedDate); |
||||
|
} |
||||
|
|
||||
|
dcTrafficSectionData.setStatisticalDate(dcTrafficSectionData.getStatisticalDate(), TrafficDataPeriodTypeEnum.QUARTER); |
||||
|
|
||||
|
// 将新数据添加到数据列表中
|
||||
|
instance.getData().add(dcTrafficSectionData); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取或创建对应设备与日期的DcTrafficSectionDataCache实例 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 交通断面数据统计定义 |
||||
|
* @return 对应的交通断面数据缓存实例 |
||||
|
*/ |
||||
|
private static QuarterlyTrafficStatisticsCache getInstance(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 使用toKey方法生成唯一键,并根据此键计算并返回缓存实例
|
||||
|
return cache.computeIfAbsent(generateCacheKey(dcTrafficSectionData), k -> new QuarterlyTrafficStatisticsCache()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 该方法用于生成一个基于DcTrafficSectionData对象属性的唯一键字符串。 |
||||
|
* 这个键由设备ID与统计日期两部分组成,通常用于索引或标识特定设备在某一日期的交通流量数据。 |
||||
|
* |
||||
|
* @param dcTrafficSectionData DcTrafficSectionData类型的对象,包含设备ID和统计日期等信息 |
||||
|
* @return 格式化后的字符串键,格式为 "deviceId|formattedDate" |
||||
|
*/ |
||||
|
public static String generateCacheKey(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取设备ID
|
||||
|
Long deviceId = dcTrafficSectionData.getDeviceId(); |
||||
|
|
||||
|
// 获取并格式化统计日期
|
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
|
||||
|
// 使用"|"字符连接设备ID和日期,形成唯一键
|
||||
|
return deviceId + "|" + formattedDate; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除所有过期的交通断面数据缓存项 |
||||
|
*/ |
||||
|
public static void clearExpiredData() { |
||||
|
// 使用stream API找出所有过期的数据缓存项键
|
||||
|
Set<String> keysToRemove = cache.keySet().stream() |
||||
|
.filter(QuarterlyTrafficStatisticsCache::isCacheItemExpire) |
||||
|
.collect(Collectors.toSet()); |
||||
|
|
||||
|
// 安全地从缓存中删除这些过期项
|
||||
|
keysToRemove.forEach(cache::remove); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查给定缓存键所对应的缓存项是否已经过期 |
||||
|
* |
||||
|
* @param key 缓存key |
||||
|
* @return 如果已过期则返回true,否则返回false |
||||
|
*/ |
||||
|
private static boolean isCacheItemExpire(String key) { |
||||
|
Date lastAddedTime = cache.get(key).getLastAddedTime(); |
||||
|
Date currentTime = DateUtil.date(); |
||||
|
long betweenSecond = DateUtil.between(lastAddedTime, currentTime, DateUnit.SECOND); |
||||
|
return betweenSecond > MAX_CACHE_TIME; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 将 Date 类型的日期格式化为 "yyyy-MM-01" 格式字符串。 |
||||
|
* |
||||
|
* @param date 需要格式化的日期对象。 |
||||
|
* @return 格式化后的日期字符串,格式为 "yyyy-MM-01"。 |
||||
|
*/ |
||||
|
private static String formatDate(Date date) { |
||||
|
// 使用 DateUtil 工具类将 date 格式化为指定格式的字符串
|
||||
|
return DateUtil.format(date, "yyyy-MM-01"); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,143 @@ |
|||||
|
package com.zc.business.statistics.cache; |
||||
|
|
||||
|
import cn.hutool.core.date.DateUnit; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.HashSet; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* 以年为单位的交通交通断面数据缓存类,用于存储和管理设备上报的交通断面统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
||||
|
* @author xiepufeng |
||||
|
*/ |
||||
|
@Getter |
||||
|
@Setter |
||||
|
public class YearlyTrafficStatisticsCache extends AbstractTrafficStatisticsCache { |
||||
|
|
||||
|
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
||||
|
@Getter |
||||
|
private static final Map<String, YearlyTrafficStatisticsCache> cache = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
// 最大缓存时间(单位:秒)
|
||||
|
private static final long MAX_CACHE_TIME = (60 * 60) * (366 * 24 + 1); |
||||
|
|
||||
|
// 最大容量限制,防止内存溢出
|
||||
|
private static final int MAX_CAPACITY = 4 + 1000; // 缓存的最大条目数
|
||||
|
|
||||
|
|
||||
|
// 私有构造函数,确保只能通过静态方法获取实例
|
||||
|
private YearlyTrafficStatisticsCache() { |
||||
|
super(); |
||||
|
this.setData(new HashSet<>()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加交通断面数据到缓存中 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 待添加的交通断面统计数据 |
||||
|
*/ |
||||
|
public static void addCacheData(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取或新建对应的缓存实例
|
||||
|
YearlyTrafficStatisticsCache instance = getInstance(dcTrafficSectionData); |
||||
|
|
||||
|
// 检查缓存容量是否达到上限
|
||||
|
if (instance.getData().size() >= MAX_CAPACITY) { |
||||
|
instance.getLogger().error("交通断面数据缓存出现异常,最大缓存量达到设定上线 {}, 当前设备是 {}", MAX_CAPACITY, dcTrafficSectionData.getDeviceId()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 更新最后添加时间
|
||||
|
instance.setLastAddedTime(DateUtil.date()); |
||||
|
|
||||
|
// 更新状态
|
||||
|
instance.setStored(false); |
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
String key = generateCacheKey(dcTrafficSectionData); |
||||
|
|
||||
|
// 设置key和thatDay属性(仅在首次添加时)
|
||||
|
if (instance.getCacheKey() == null) { |
||||
|
instance.setCacheKey(key); |
||||
|
instance.setStatisticalDateStr(formattedDate); |
||||
|
} |
||||
|
|
||||
|
dcTrafficSectionData.setStatisticalDate(dcTrafficSectionData.getStatisticalDate(), TrafficDataPeriodTypeEnum.YEAR); |
||||
|
|
||||
|
// 将新数据添加到数据列表中
|
||||
|
instance.getData().add(dcTrafficSectionData); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取或创建对应设备与日期的DcTrafficSectionDataCache实例 |
||||
|
* |
||||
|
* @param dcTrafficSectionData 交通断面数据统计定义 |
||||
|
* @return 对应的交通断面数据缓存实例 |
||||
|
*/ |
||||
|
private static YearlyTrafficStatisticsCache getInstance(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 使用toKey方法生成唯一键,并根据此键计算并返回缓存实例
|
||||
|
return cache.computeIfAbsent(generateCacheKey(dcTrafficSectionData), k -> new YearlyTrafficStatisticsCache()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 该方法用于生成一个基于DcTrafficSectionData对象属性的唯一键字符串。 |
||||
|
* 这个键由设备ID与统计日期两部分组成,通常用于索引或标识特定设备在某一日期的交通流量数据。 |
||||
|
* |
||||
|
* @param dcTrafficSectionData DcTrafficSectionData类型的对象,包含设备ID和统计日期等信息 |
||||
|
* @return 格式化后的字符串键,格式为 "deviceId|formattedDate" |
||||
|
*/ |
||||
|
public static String generateCacheKey(DcTrafficSectionData dcTrafficSectionData) { |
||||
|
// 获取设备ID
|
||||
|
Long deviceId = dcTrafficSectionData.getDeviceId(); |
||||
|
|
||||
|
// 获取并格式化统计日期
|
||||
|
String formattedDate = formatDate(dcTrafficSectionData.getStatisticalDate()); |
||||
|
|
||||
|
// 使用"|"字符连接设备ID和日期,形成唯一键
|
||||
|
return deviceId + "|" + formattedDate; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除所有过期的交通断面数据缓存项 |
||||
|
*/ |
||||
|
public static void clearExpiredData() { |
||||
|
// 使用stream API找出所有过期的数据缓存项键
|
||||
|
Set<String> keysToRemove = cache.keySet().stream() |
||||
|
.filter(YearlyTrafficStatisticsCache::isCacheItemExpire) |
||||
|
.collect(Collectors.toSet()); |
||||
|
|
||||
|
// 安全地从缓存中删除这些过期项
|
||||
|
keysToRemove.forEach(cache::remove); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查给定缓存键所对应的缓存项是否已经过期 |
||||
|
* |
||||
|
* @param key 缓存key |
||||
|
* @return 如果已过期则返回true,否则返回false |
||||
|
*/ |
||||
|
private static boolean isCacheItemExpire(String key) { |
||||
|
Date lastAddedTime = cache.get(key).getLastAddedTime(); |
||||
|
Date currentTime = DateUtil.date(); |
||||
|
long betweenSecond = DateUtil.between(lastAddedTime, currentTime, DateUnit.SECOND); |
||||
|
return betweenSecond > MAX_CACHE_TIME; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将 Date 类型的日期格式化为 "yyyy-01-01" 格式的字符串。 |
||||
|
* |
||||
|
* @param date 需要格式化的日期对象。 |
||||
|
* @return 格式化后的日期字符串。 |
||||
|
*/ |
||||
|
private static String formatDate(Date date) { |
||||
|
// 使用 DateUtil 工具类将 date 格式化为 "yyyy-01-01" 格式的字符串
|
||||
|
return DateUtil.format(date, "yyyy-01-01"); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,70 @@ |
|||||
|
package com.zc.business.statistics.handler; |
||||
|
|
||||
|
import com.ruoyi.common.utils.DateUtils; |
||||
|
import com.zc.business.domain.DcTrafficSectionData; |
||||
|
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
||||
|
import org.springframework.util.CollectionUtils; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
|
||||
|
public class RealtimeTrafficStatistics { |
||||
|
|
||||
|
/** |
||||
|
* 对给定的交通数据集合进行统计分析,返回一个综合交通数据对象。 |
||||
|
* |
||||
|
* @param dataCollection 交通数据集合,不可为null或空。包含多个交通路段的详细数据。 |
||||
|
* @param trafficDataPeriodType 交通数据的时段类型,例如:小时、日、周等。 |
||||
|
* @return 综合交通数据对象,包含车流量总和、平均车速等统计结果。如果输入数据为空,则返回null。 |
||||
|
*/ |
||||
|
public static DcTrafficSectionData trafficStatistics(Collection<DcTrafficSectionData> dataCollection, TrafficDataPeriodTypeEnum trafficDataPeriodType) { |
||||
|
|
||||
|
// 判断输入数据是否为空
|
||||
|
if (CollectionUtils.isEmpty(dataCollection)) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// 创建一个汇总统计用的对象
|
||||
|
DcTrafficSectionData aggregatedData = new DcTrafficSectionData(); |
||||
|
|
||||
|
// 初始化车流量总和和计算平均车速所需的分子部分
|
||||
|
int trafficVolume = 0; |
||||
|
double numerator = 0; |
||||
|
|
||||
|
// 遍历原始数据列表,累加车流量并计算平均车速
|
||||
|
for (DcTrafficSectionData data: dataCollection) { |
||||
|
// 累加车流量
|
||||
|
trafficVolume += data.getTrafficVolume(); |
||||
|
// 计算分子部分
|
||||
|
numerator += data.getAverageSpeed() * data.getTrafficVolume(); |
||||
|
} |
||||
|
|
||||
|
// 使用第一个数据项的信息填充汇总统计对象的基本属性(设备ID、桩号、方向)
|
||||
|
DcTrafficSectionData firstDcTrafficSectionData = dataCollection.iterator().next(); |
||||
|
// 设备id
|
||||
|
aggregatedData.setDeviceId(firstDcTrafficSectionData.getDeviceId()); |
||||
|
// 桩号
|
||||
|
aggregatedData.setStakeMark(firstDcTrafficSectionData.getStakeMark()); |
||||
|
// 道路方向
|
||||
|
aggregatedData.setDirection(firstDcTrafficSectionData.getDirection()); |
||||
|
|
||||
|
// 计算平均车速并设置到汇总统计对象中
|
||||
|
if (trafficVolume != 0) { |
||||
|
aggregatedData.setAverageSpeed((int) Math.round(numerator / trafficVolume)); |
||||
|
} else { |
||||
|
// 若车流量为0,则默认设置平均车速为0
|
||||
|
aggregatedData.setAverageSpeed(0); |
||||
|
} |
||||
|
// 时段类型
|
||||
|
aggregatedData.setPeriodType(trafficDataPeriodType); |
||||
|
// 设置统计时间
|
||||
|
aggregatedData.setStatisticalDate(firstDcTrafficSectionData.getStatisticalDate(), trafficDataPeriodType); |
||||
|
// 车流量
|
||||
|
aggregatedData.setTrafficVolume(trafficVolume); |
||||
|
// 更新或插入操作
|
||||
|
aggregatedData.setUpdateTime(DateUtils.getNowDate()); |
||||
|
// 生成主键
|
||||
|
aggregatedData.generateUniqueId(); |
||||
|
|
||||
|
return aggregatedData; |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<!DOCTYPE mapper |
||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.zc.business.mapper.DcTrafficSectionDataMapper"> |
||||
|
|
||||
|
<!-- 插入或更新交通路段数据 --> |
||||
|
<insert id="insertOrUpdate" parameterType="com.zc.business.domain.DcTrafficSectionData"> |
||||
|
INSERT INTO |
||||
|
dc_traffic_section_data |
||||
|
( |
||||
|
id, |
||||
|
traffic_volume, |
||||
|
average_speed, |
||||
|
device_id, |
||||
|
statistical_date, |
||||
|
direction, |
||||
|
period_type, |
||||
|
stake_mark, |
||||
|
create_time |
||||
|
) |
||||
|
VALUES |
||||
|
( |
||||
|
#{id}, |
||||
|
#{trafficVolume}, |
||||
|
#{averageSpeed}, |
||||
|
#{deviceId}, |
||||
|
#{statisticalDate}, |
||||
|
#{direction}, |
||||
|
#{periodType}, |
||||
|
#{stakeMark}, |
||||
|
NOW()) |
||||
|
ON DUPLICATE KEY UPDATE |
||||
|
traffic_volume = VALUES(traffic_volume), |
||||
|
average_speed = VALUES(average_speed), |
||||
|
device_id = VALUES(device_id), |
||||
|
statistical_date = VALUES(statistical_date), |
||||
|
direction = VALUES(direction), |
||||
|
period_type = VALUES(period_type), |
||||
|
stake_mark = VALUES(stake_mark), |
||||
|
update_time = VALUES(NOW()) |
||||
|
</insert> |
||||
|
|
||||
|
</mapper> |
Loading…
Reference in new issue