xiepufeng
6 months ago
32 changed files with 1755 additions and 270 deletions
@ -0,0 +1,89 @@ |
|||
package com.zc.business.domain; |
|||
|
|||
import cn.hutool.core.date.DateUtil; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import org.apache.commons.codec.digest.DigestUtils; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* 门架交通特征指数 |
|||
* @author xiepufeng |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
public class DcGantryMetricsStatisticsData extends DcStatisticsData { |
|||
|
|||
/** |
|||
* 门架编号 |
|||
*/ |
|||
private String gantryCode; |
|||
|
|||
/** |
|||
* 拥堵度 |
|||
* 1-畅通 |
|||
* 2-基本畅通 |
|||
* 3-轻度拥堵 |
|||
* 4-中度拥堵 |
|||
* 5-重度拥堵 |
|||
*/ |
|||
private Integer crowdingRate; |
|||
|
|||
/** |
|||
* 交通组成特征指数(大车占比) |
|||
* 1-低 |
|||
* 2-中 |
|||
* 3-高 |
|||
*/ |
|||
private Integer trafficCompositionRate; |
|||
|
|||
/** |
|||
* 饱和度指数 |
|||
*/ |
|||
private Double saturationRate; |
|||
|
|||
/** |
|||
* 重写equals方法,用于比较两个DcGantryStatisticsData对象是否相等。 |
|||
* |
|||
* @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
|
|||
|
|||
DcGantryMetricsStatisticsData that = (DcGantryMetricsStatisticsData) o; // 类型转换
|
|||
|
|||
// 使用Objects.equals方法比较对象的各个属性值
|
|||
return Objects.equals(gantryCode, that.getGantryCode()) && |
|||
Objects.equals(getReportTime(), that.getReportTime()) && |
|||
Objects.equals(getPeriodType(), that.getPeriodType()); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 重写hashCode方法,基于对象的属性生成哈希码。 |
|||
* @return 对象的哈希码值。 |
|||
*/ |
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(gantryCode, getReportTime(), getPeriodType()); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 生成唯一标识符。 |
|||
* 该方法通过将多个属性结合,然后对结合后的字符串进行MD5哈希处理来生成一个唯一标识符。 |
|||
* 结合的属性包括:门架站点标识、统计日期、道路方向、统计粒度和出入类型。 |
|||
*/ |
|||
@Override |
|||
public void generateUniqueId() { |
|||
// 将多个属性按照指定格式组合成一个字符串
|
|||
String combinedAttributes = gantryCode + "_" + DateUtil.format(getStatisticalDate(), "yyyyMMdd_HHmmss") + "_" + getPeriodType(); |
|||
// 对组合后的字符串进行MD5哈希处理,生成唯一标识符,并赋值给当前对象的id属性
|
|||
this.setId(DigestUtils.md5Hex(combinedAttributes)); |
|||
} |
|||
} |
@ -0,0 +1,100 @@ |
|||
package com.zc.business.domain; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import java.util.Date; |
|||
|
|||
@Getter |
|||
@Setter |
|||
public class DcTrafficFlowStatisticsData extends DcStatisticsData { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** |
|||
* 车流量 |
|||
*/ |
|||
private Integer trafficVolume; |
|||
|
|||
/** |
|||
* 1型客车车流量 |
|||
*/ |
|||
private Integer type1PassengerFlow; |
|||
|
|||
/** |
|||
* 2型客车车流量 |
|||
*/ |
|||
private Integer type2PassengerFlow; |
|||
|
|||
/** |
|||
* 3型客车车流量 |
|||
*/ |
|||
private Integer type3PassengerFlow; |
|||
|
|||
/** |
|||
* 4型客车车流量 |
|||
*/ |
|||
private Integer type4PassengerFlow; |
|||
|
|||
/** |
|||
* 1型货车车流量 |
|||
*/ |
|||
private Integer type1TruckFlow; |
|||
|
|||
/** |
|||
* 2型货车车流量 |
|||
*/ |
|||
private Integer type2TruckFlow; |
|||
|
|||
/** |
|||
* 3型货车车流量 |
|||
*/ |
|||
private Integer type3TruckFlow; |
|||
|
|||
/** |
|||
* 4型货车车流量 |
|||
*/ |
|||
private Integer type4TruckFlow; |
|||
|
|||
/** |
|||
* 5型货车车流量 |
|||
*/ |
|||
private Integer type5TruckFlow; |
|||
|
|||
/** |
|||
* 6型货车车流量 |
|||
*/ |
|||
private Integer type6TruckFlow; |
|||
|
|||
/** |
|||
* 1型专项作业车车流量 |
|||
*/ |
|||
private Integer type1SpecialVehicleFlow; |
|||
|
|||
/** |
|||
* 2型专项作业车车流量 |
|||
*/ |
|||
private Integer type2SpecialVehicleFlow; |
|||
|
|||
/** |
|||
* 3型专项作业车车流量 |
|||
*/ |
|||
private Integer type3SpecialVehicleFlow; |
|||
|
|||
/** |
|||
* 4型专项作业车车流量 |
|||
*/ |
|||
private Integer type4SpecialVehicleFlow; |
|||
|
|||
/** |
|||
* 5型专项作业车车流量 |
|||
*/ |
|||
private Integer type5SpecialVehicleFlow; |
|||
|
|||
/** |
|||
* 6型专项作业车车流量 |
|||
*/ |
|||
private Integer type6SpecialVehicleFlow; |
|||
} |
@ -0,0 +1,72 @@ |
|||
package com.zc.business.enums; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
/** |
|||
* 交通组成特征指数(大车占比)枚举类 |
|||
* 包括低、中、高三类。 |
|||
* @author xiepufeng |
|||
*/ |
|||
@Getter |
|||
public enum TrafficCompositionRateEnum { |
|||
// 低交通组成率
|
|||
LOW(1, "低"), |
|||
// 中交通组成率
|
|||
MEDIUM(2, "中"), |
|||
// 高交通组成率
|
|||
HIGH(3, "高"); |
|||
|
|||
// 码值,代表每个枚举值的唯一标识
|
|||
private final int code; |
|||
// 描述,对每个枚举值的级别进行文字描述
|
|||
private final String description; |
|||
|
|||
/** |
|||
* 构造方法,初始化枚举值的码值和描述。 |
|||
* |
|||
* @param code 码值 |
|||
* @param description 描述 |
|||
*/ |
|||
TrafficCompositionRateEnum(int code, String description) { |
|||
this.code = code; |
|||
this.description = description; |
|||
} |
|||
|
|||
/** |
|||
* 根据码值获取枚举值。 |
|||
* |
|||
* @param code 码值 |
|||
* @return 对应码值的枚举常量 |
|||
* @throws IllegalArgumentException 如果码值无效,则抛出此异常 |
|||
*/ |
|||
// 根据索引值获取枚举值,提供一个静态方法
|
|||
public static TrafficCompositionRateEnum fromCode(int code) { |
|||
for (TrafficCompositionRateEnum value : values()) { |
|||
if (value.getCode() == code) { |
|||
return value; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException("无效的指数值: " + code); |
|||
} |
|||
|
|||
/** |
|||
* 根据描述字符串获取交通组成比率枚举值。 |
|||
* 该方法遍历所有TrafficCompositionRateEnum枚举常量,找到其描述与输入字符串匹配的枚举值。 |
|||
* 如果找到匹配的枚举值,则返回该枚举值;如果没有匹配的枚举值,则抛出IllegalArgumentException异常。 |
|||
* 这个方法的存在是为了提供一种通过枚举的描述而不是枚举名称来获取枚举常量的方法。 |
|||
* |
|||
* @param description 描述字符串,用于匹配枚举值的描述。 |
|||
* @return 匹配描述的TrafficCompositionRateEnum枚举值。 |
|||
* @throws IllegalArgumentException 如果没有找到匹配的枚举值,则抛出此异常。 |
|||
*/ |
|||
public static TrafficCompositionRateEnum fromDescription(String description) { |
|||
for (TrafficCompositionRateEnum value : values()) { |
|||
if (value.getDescription().equals(description)) { |
|||
return value; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException("无效的指数值: " + description); |
|||
} |
|||
|
|||
} |
|||
|
@ -0,0 +1,32 @@ |
|||
package com.zc.business.mapper; |
|||
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 门架指标数据统计Mapper接口 |
|||
* |
|||
* @author xiepufeng |
|||
*/ |
|||
@Mapper |
|||
public interface DcGantryMetricsStatisticsDataMapper extends BaseMapper<DcGantryMetricsStatisticsData> { |
|||
|
|||
/** |
|||
* 插入或更新门架指标数据。 |
|||
* |
|||
* @param gantryTrafficMetricsData 门架指标数据对象,包含需要插入或更新的数据。 |
|||
* @return 返回一个布尔值,表示操作是否成功。true表示插入或更新成功,false表示失败。 |
|||
*/ |
|||
boolean insertOrUpdate(DcGantryMetricsStatisticsData gantryTrafficMetricsData); |
|||
|
|||
/** |
|||
* 批量插入或更新新门架指标数据 |
|||
* |
|||
* @param gantryTrafficMetricsDataList 要插入或更新的新门架指标数据对象列表 |
|||
* @return 影响的记录数(通常对于批量操作,这个值可能不准确,因为每个 INSERT/UPDATE 可能影响不同的记录数) |
|||
*/ |
|||
int insertOrUpdateBatch(List<DcGantryMetricsStatisticsData> gantryTrafficMetricsDataList); |
|||
} |
@ -0,0 +1,42 @@ |
|||
package com.zc.business.service; |
|||
|
|||
import com.baomidou.mybatisplus.extension.service.IService; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 门架指标统计服务接口,提供处理交通数据的相关方法 |
|||
* @author xiepufeng |
|||
*/ |
|||
public interface IDcGantryMetricsStatisticsDataService extends IService<DcGantryMetricsStatisticsData> { |
|||
|
|||
/** |
|||
* 获取过去一小时的DcGantryMetricsStatisticsData数据列表。 |
|||
* |
|||
* <p>此方法不接受任何参数,返回一个包含过去一小时所有符合条件的DcGantryMetricsStatisticsData对象的列表。 |
|||
* 该列表可用于进一步的数据分析和处理。 |
|||
* |
|||
* @return List<DcGantryMetricsStatisticsData> - 过去一小时的DcGantryMetricsStatisticsData数据列表。 |
|||
*/ |
|||
List<DcGantryMetricsStatisticsData> lastHourData(); |
|||
|
|||
/** |
|||
* 获取当月的DcGantryMetricsStatisticsData数据列表。 |
|||
* |
|||
* <p>此方法不接受任何参数,返回一个包含当月所有符合条件的DcGantryMetricsStatisticsData对象的列表。 |
|||
* 该列表可用于进一步的数据分析和处理。 |
|||
* |
|||
* @return List<DcGantryMetricsStatisticsData> - 当月的DcGantryMetricsStatisticsData数据列表。 |
|||
*/ |
|||
List<DcGantryMetricsStatisticsData> currentMonthData(); |
|||
|
|||
/** |
|||
* 根据请求条件获取门架指标统计数据 |
|||
* @param request 请求条件 |
|||
* @return 门架指标统计数据列表 |
|||
*/ |
|||
List<DcGantryMetricsStatisticsData> gantryMetricsData(DcGantryMetricsStatisticsData request); |
|||
} |
@ -0,0 +1,272 @@ |
|||
package com.zc.business.service.impl; |
|||
|
|||
import cn.hutool.core.date.DateTime; |
|||
import cn.hutool.core.date.DateUtil; |
|||
import com.alibaba.fastjson.JSONArray; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|||
import com.ruoyi.common.exception.ServiceException; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.domain.DcGantryStatisticsData; |
|||
import com.zc.business.enums.ChannelCongestionLevelEnum; |
|||
import com.zc.business.enums.TrafficCompositionRateEnum; |
|||
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
|||
import com.zc.business.mapper.DcGantryMetricsStatisticsDataMapper; |
|||
import com.zc.business.service.IDcGantryMetricsStatisticsDataService; |
|||
import com.zc.business.service.IDcTrafficStatisticsService; |
|||
import com.zc.business.statistics.cache.metrics.DailyGantryMetricsStatisticsCache; |
|||
import com.zc.business.statistics.cache.metrics.MonthlyGantryMetricsStatisticsCache; |
|||
import com.zc.business.statistics.cache.metrics.QuarterlyGantryMetricsStatisticsCache; |
|||
import com.zc.business.statistics.cache.metrics.YearlyGantryMetricsStatisticsCache; |
|||
import com.zc.common.core.httpclient.exception.HttpException; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.Resource; |
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 门架指标统计服务实现类,负责处理实时设备消息、缓存数据、定时任务以及数据保存等功能。 |
|||
* |
|||
* @author xiepufeng |
|||
*/ |
|||
@Service |
|||
public class IDcGantryMetricsStatisticsDataServiceImpl |
|||
extends ServiceImpl<DcGantryMetricsStatisticsDataMapper, DcGantryMetricsStatisticsData> |
|||
implements IDcGantryMetricsStatisticsDataService { |
|||
|
|||
// 日志记录器
|
|||
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); |
|||
|
|||
@Resource |
|||
private IDcTrafficStatisticsService dcTrafficStatisticsService; |
|||
|
|||
@Resource |
|||
private ThreadPoolTaskExecutor threadPoolTaskExecutor; |
|||
|
|||
@Resource |
|||
private DcGantryMetricsStatisticsDataMapper gantryMetricsStatisticsDataMapper; |
|||
|
|||
/** |
|||
* 初始化方法,用于在对象创建后恢复各种周期的交通数据缓存。 |
|||
* 该方法标注了@PostConstruct注解,确保在依赖注入完成后调用。 |
|||
*/ |
|||
@PostConstruct |
|||
public void init() { |
|||
threadPoolTaskExecutor.execute(() -> { |
|||
// 获取当前月份的门架指标数据列表
|
|||
List<DcGantryMetricsStatisticsData> gantryMetricsStatisticsDataList = currentMonthData(); |
|||
recoveryHourlyCache(gantryMetricsStatisticsDataList); // 恢复小时缓存数据
|
|||
recoveryDailyCache(gantryMetricsStatisticsDataList); // 恢复日缓存数据
|
|||
recoveryMonthlyCache(); // 恢复每月交通门架缓存
|
|||
recoveryQuarterlyCache(); // 恢复每季度交通门架缓
|
|||
recoveryYearlyCache(); // 恢复每年交通门架缓存
|
|||
}); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 获取过去一小时的DcGantryMetricsStatisticsData数据列表。 |
|||
* |
|||
* <p>此方法不接受任何参数,返回一个包含过去一小时所有符合条件的DcGantryMetricsStatisticsData对象的列表。 |
|||
* 该列表可用于进一步的数据分析和处理。 |
|||
* |
|||
* @return List<DcGantryMetricsStatisticsData> - 过去一小时的DcGantryMetricsStatisticsData数据列表。 |
|||
*/ |
|||
@Override |
|||
public List<DcGantryMetricsStatisticsData> lastHourData() { |
|||
|
|||
// 计算一小时前的时间点
|
|||
DateTime lastHour = DateUtil.offsetHour(new Date(), -1); |
|||
|
|||
String startTime = DateUtil.beginOfHour(lastHour).toString(); |
|||
String endTime = DateUtil.endOfHour(lastHour).toString(); |
|||
|
|||
// 获取当月门架数指标据
|
|||
JSONArray jsonArray = null; |
|||
try { |
|||
jsonArray = dcTrafficStatisticsService.gantryMetrics(startTime, endTime); |
|||
// 计算每个门架数指标据的统计信息,并添加到每日门架指标统计缓存中
|
|||
return calculateGantryMetricsStatistics(jsonArray); |
|||
|
|||
} catch (HttpException | IOException e) { |
|||
logger.error("获取过去一小时的门架指标数据失败", e); |
|||
return new ArrayList<>(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取当月的门架指标数据列表。 |
|||
* |
|||
* <p>此方法不接受任何参数,返回一个包含当月所有符合条件的DcGantryMetricsStatisticsData对象的列表。 |
|||
* 该列表可用于进一步的数据分析和处理。 |
|||
* |
|||
* @return List<DcGantryMetricsStatisticsData> - 当月的DcGantryMetricsStatisticsData数据列表。 |
|||
*/ |
|||
@Override |
|||
public List<DcGantryMetricsStatisticsData> currentMonthData() { |
|||
String startTime = DateUtil.beginOfMonth(new Date()).toString(); |
|||
String endTime = DateUtil.now(); |
|||
|
|||
// 获取当月门架数指标据
|
|||
JSONArray jsonArray = null; |
|||
try { |
|||
jsonArray = dcTrafficStatisticsService.gantryMetrics(startTime, endTime); |
|||
// 计算每个门架数指标据的统计信息,并添加到每日门架指标统计缓存中
|
|||
return calculateGantryMetricsStatistics(jsonArray); |
|||
|
|||
} catch (HttpException | IOException e) { |
|||
logger.error("获取当月门架指标数据失败", e); |
|||
return new ArrayList<>(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 根据请求条件获取门架指标统计数据 |
|||
* |
|||
* @param request 请求条件 |
|||
* @return 门架指标统计数据列表 |
|||
*/ |
|||
@Override |
|||
public List<DcGantryMetricsStatisticsData> gantryMetricsData(DcGantryMetricsStatisticsData request) { |
|||
if (request.getStartTime() == null || request.getEndTime() == null) { |
|||
throw new ServiceException("开始时间或结束时间不能为空"); |
|||
} |
|||
|
|||
if (request.getPeriodType() == null) { |
|||
throw new ServiceException("时段类型不能为空"); |
|||
} |
|||
|
|||
LambdaQueryWrapper<DcGantryMetricsStatisticsData> queryWrapper = new LambdaQueryWrapper<>(); |
|||
|
|||
if (request.getStatisticalDate() != null) { |
|||
queryWrapper.eq(DcGantryMetricsStatisticsData::getStatisticalDate, request.getStatisticalDate()); |
|||
} |
|||
|
|||
// 如果请求中包含唯一标识符,则根据唯一标识符进行过滤
|
|||
if (request.getId() != null) { |
|||
queryWrapper.eq(DcGantryMetricsStatisticsData::getId, request.getId()); |
|||
} |
|||
|
|||
queryWrapper.eq(DcGantryMetricsStatisticsData::getPeriodType, request.getPeriodType()); |
|||
queryWrapper.eq(DcGantryMetricsStatisticsData::getGantryCode, request.getGantryCode()); |
|||
queryWrapper.between(DcGantryMetricsStatisticsData::getStatisticalDate, request.getStartTime(), request.getEndTime()); |
|||
|
|||
return list(queryWrapper); |
|||
} |
|||
|
|||
/** |
|||
* 恢复小时缓存数据的方法 |
|||
* 该方法首先会获取当前月份的门架入口数据, |
|||
* 然后分别计算每个门架的统计信息,并将这些统计信息添加到每日交通门架指标统计缓存中。 |
|||
*/ |
|||
private void recoveryHourlyCache(List<DcGantryMetricsStatisticsData> gantryMetricsStatisticsDataList) { |
|||
gantryMetricsStatisticsDataMapper.insertOrUpdateBatch(gantryMetricsStatisticsDataList); |
|||
} |
|||
|
|||
/** |
|||
* 恢复日缓存数据的方法 |
|||
* 该方法首先会获取当前月份的门架入口数据, |
|||
* 然后分别计算每个门架的统计信息,并将这些统计信息添加到每日交通门架指标统计缓存中。 |
|||
*/ |
|||
private void recoveryDailyCache(List<DcGantryMetricsStatisticsData> gantryMetricsStatisticsDataList) { |
|||
|
|||
// 计算每个门架数指标据的统计信息,并添加到每日门架指标统计缓存中
|
|||
gantryMetricsStatisticsDataList.forEach(DailyGantryMetricsStatisticsCache::addCacheData); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 恢复每月交通门架缓存的方法。 |
|||
* 通过查询当前月份至今的每日交通数据,并将其添加到每月门架指标统计缓存中。 |
|||
*/ |
|||
private void recoveryMonthlyCache() { |
|||
// 构建查询条件,查询当前月份至今的每日交通数据
|
|||
LambdaQueryWrapper<DcGantryMetricsStatisticsData> queryWrapper = new LambdaQueryWrapper<>(); |
|||
queryWrapper.eq(DcGantryMetricsStatisticsData::getPeriodType, TrafficDataPeriodTypeEnum.DAY); |
|||
queryWrapper.between(DcGantryMetricsStatisticsData::getStatisticalDate, DateUtil.beginOfMonth(new Date()), new Date()); |
|||
List<DcGantryMetricsStatisticsData> dcGantryMetricsStatisticsDataList = this.list(queryWrapper); |
|||
// 遍历查询结果,将每日数据添加到每月门架指标统计缓存中
|
|||
dcGantryMetricsStatisticsDataList.forEach(MonthlyGantryMetricsStatisticsCache::addCacheData); |
|||
} |
|||
|
|||
/** |
|||
* 恢复每季度交通门架缓存的方法。 |
|||
* 通过查询当前季度至今的每月交通数据,并将其添加到每季度门架指标统计缓存中。 |
|||
*/ |
|||
private void recoveryQuarterlyCache() { |
|||
// 构建查询条件,查询当前季度至今的每月交通数据
|
|||
LambdaQueryWrapper<DcGantryMetricsStatisticsData> queryWrapper = new LambdaQueryWrapper<>(); |
|||
queryWrapper.eq(DcGantryMetricsStatisticsData::getPeriodType, TrafficDataPeriodTypeEnum.MONTH); |
|||
queryWrapper.between(DcGantryMetricsStatisticsData::getStatisticalDate, DateUtil.beginOfQuarter(new Date()), new Date()); |
|||
List<DcGantryMetricsStatisticsData> dcGantryMetricsStatisticsDataList = this.list(queryWrapper); |
|||
// 遍历查询结果,将每月数据添加到每季度门架指标统计缓存
|
|||
dcGantryMetricsStatisticsDataList.forEach(QuarterlyGantryMetricsStatisticsCache::addCacheData); |
|||
} |
|||
|
|||
/** |
|||
* 恢复每年交通门架缓存的方法。 |
|||
* 通过查询当前年份至今的每季度交通数据,并将其添加到每年门架指标统计缓存中。 |
|||
*/ |
|||
private void recoveryYearlyCache() { |
|||
// 构建查询条件,查询当前年份至今的每季度交通数据
|
|||
LambdaQueryWrapper<DcGantryMetricsStatisticsData> queryWrapper = new LambdaQueryWrapper<>(); |
|||
queryWrapper.eq(DcGantryMetricsStatisticsData::getPeriodType, TrafficDataPeriodTypeEnum.QUARTER); |
|||
queryWrapper.between(DcGantryMetricsStatisticsData::getStatisticalDate, DateUtil.beginOfYear(new Date()), new Date()); |
|||
List<DcGantryMetricsStatisticsData> dcGantryMetricsStatisticsDataList = this.list(queryWrapper); |
|||
// 遍历查询结果,将每季度数据添加到每年门架指标统计缓存
|
|||
dcGantryMetricsStatisticsDataList.forEach(YearlyGantryMetricsStatisticsCache::addCacheData); |
|||
} |
|||
|
|||
/** |
|||
* 计算门架指标数据 |
|||
* |
|||
* @param jsonArray 门架指标数据 |
|||
* @return 门架指标数据列表 |
|||
*/ |
|||
private List<DcGantryMetricsStatisticsData> calculateGantryMetricsStatistics(JSONArray jsonArray) { |
|||
|
|||
if (jsonArray == null || jsonArray.isEmpty()) { |
|||
return new ArrayList<>(); |
|||
} |
|||
|
|||
List<DcGantryMetricsStatisticsData> dcGantryMetricsStatisticsDataList = new ArrayList<>(); |
|||
|
|||
jsonArray.forEach(item -> { |
|||
DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData = new DcGantryMetricsStatisticsData(); |
|||
|
|||
JSONObject jsonObject = (JSONObject) item; |
|||
// 门架编号
|
|||
dcGantryMetricsStatisticsData.setGantryCode(jsonObject.getString("gantry_id")); |
|||
// 拥堵等级
|
|||
dcGantryMetricsStatisticsData.setCrowdingRate(ChannelCongestionLevelEnum.fromDescription(jsonObject.getString("crowding_rate")).getLevel()); |
|||
// 交通组成特征指数(大车占比)
|
|||
dcGantryMetricsStatisticsData.setTrafficCompositionRate(TrafficCompositionRateEnum.fromDescription(jsonObject.getString("traffic_composition_rate")).getCode()); |
|||
// 饱和度
|
|||
dcGantryMetricsStatisticsData.setSaturationRate(jsonObject.getDouble("saturation_rate")); |
|||
// 时间
|
|||
Date dataHour = DateUtil.date(jsonObject.getLong("data_hour")); |
|||
// 上报时间
|
|||
dcGantryMetricsStatisticsData.setReportTime(dataHour); |
|||
// 统计时间
|
|||
dcGantryMetricsStatisticsData.setStatisticalDate(DateUtil.beginOfHour(dataHour)); |
|||
// 时间颗粒度(小时)
|
|||
dcGantryMetricsStatisticsData.setPeriodType(TrafficDataPeriodTypeEnum.HOUR); |
|||
// 生成唯一ID
|
|||
dcGantryMetricsStatisticsData.generateUniqueId(); |
|||
|
|||
dcGantryMetricsStatisticsDataList.add(dcGantryMetricsStatisticsData); |
|||
}); |
|||
|
|||
return dcGantryMetricsStatisticsDataList; |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,147 @@ |
|||
package com.zc.business.statistics.cache.metrics; |
|||
|
|||
import cn.hutool.core.date.DateUnit; |
|||
import cn.hutool.core.date.DateUtil; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
|||
import com.zc.business.statistics.cache.AbstractTrafficStatisticsCache; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
import java.util.Date; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* |
|||
* 以天为单位的门架指标缓存数据类,用于存储和管理门架统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
|||
* @author xiepufeng |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
public class DailyGantryMetricsStatisticsCache extends AbstractTrafficStatisticsCache<DcGantryMetricsStatisticsData> { |
|||
|
|||
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
|||
@Getter |
|||
private static final Map<String, DailyGantryMetricsStatisticsCache> cache = new ConcurrentHashMap<>(); |
|||
|
|||
// 最大缓存时间(单位:秒)
|
|||
private static final long MAX_CACHE_TIME = 26 * 60 * 60; // 缓存数据最长保留25小时
|
|||
|
|||
// 最大容量限制,防止内存溢出
|
|||
private static final int MAX_CAPACITY = 24 + 1 + 1000; // 缓存的最大条目数
|
|||
|
|||
// 私有构造函数,确保只能通过静态方法获取实例
|
|||
private DailyGantryMetricsStatisticsCache() { |
|||
} |
|||
|
|||
/** |
|||
* 添加门架数据到缓存中 |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 待添加的门架数据 |
|||
*/ |
|||
public static void addCacheData(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取或新建对应的缓存实例
|
|||
DailyGantryMetricsStatisticsCache instance = getInstance(dcGantryMetricsStatisticsData); |
|||
|
|||
// 检查缓存容量是否达到上限
|
|||
if (instance.getData().size() >= MAX_CAPACITY) { |
|||
instance.getLogger().error("门架指标缓存数据出现异常,最大缓存量达到设定上线 {}, 当前门架是 {}", MAX_CAPACITY, dcGantryMetricsStatisticsData.getGantryCode()); |
|||
return; |
|||
} |
|||
|
|||
// 更新最后添加时间
|
|||
instance.setLastAddedTime(DateUtil.date()); |
|||
|
|||
// 更新状态
|
|||
instance.setStored(false); |
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
String key = generateCacheKey(dcGantryMetricsStatisticsData); |
|||
|
|||
// 设置key和thatDay属性(仅在首次添加时)
|
|||
if (instance.getCacheKey() == null) { |
|||
instance.setCacheKey(key); |
|||
instance.setStatisticalDateStr(formattedDate); |
|||
} |
|||
|
|||
// 更新上报时间
|
|||
dcGantryMetricsStatisticsData.setReportTime(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
// 更新数据周期类型
|
|||
dcGantryMetricsStatisticsData.setPeriodType(TrafficDataPeriodTypeEnum.DAY); |
|||
// 更新统计日期
|
|||
dcGantryMetricsStatisticsData.setStatisticalDate(dcGantryMetricsStatisticsData.getStatisticalDate(), TrafficDataPeriodTypeEnum.DAY); |
|||
|
|||
// 移除旧数据
|
|||
instance.getData().remove(dcGantryMetricsStatisticsData); |
|||
// 添加数据
|
|||
instance.getData().add(dcGantryMetricsStatisticsData); |
|||
} |
|||
|
|||
/** |
|||
* 获取或创建对应设备与日期的DailyTrafficGantryStatisticsCache实例 |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 门架数据统计定义 |
|||
* @return 对应的门架指标缓存数据实例 |
|||
*/ |
|||
private static DailyGantryMetricsStatisticsCache getInstance(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 使用toKey方法生成唯一键,并根据此键计算并返回缓存实例
|
|||
return cache.computeIfAbsent(generateCacheKey(dcGantryMetricsStatisticsData), k -> new DailyGantryMetricsStatisticsCache()); |
|||
} |
|||
|
|||
/** |
|||
* 生成缓存键。 |
|||
* <p>此方法通过组合门架编号、统计日期来生成一个唯一的缓存键。</p> |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 门架统计数据对象,包含门架编号、统计日期 |
|||
* @return 缓存键,格式为"门架编号|格式化后的统计日期" |
|||
*/ |
|||
public static String generateCacheKey(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取门架标识
|
|||
String gantryCode = dcGantryMetricsStatisticsData.getGantryCode(); |
|||
|
|||
// 格式化统计日期
|
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
|
|||
// 组合门架编号、格式化后的统计日期作为缓存键
|
|||
return gantryCode + "|" + formattedDate; |
|||
} |
|||
|
|||
/** |
|||
* 清除所有过期的门架指标缓存数据项 |
|||
*/ |
|||
public static void clearExpiredData() { |
|||
// 使用stream API找出所有过期的数据缓存项键
|
|||
Set<String> keysToRemove = cache.keySet().stream() |
|||
.filter(DailyGantryMetricsStatisticsCache::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,152 @@ |
|||
package com.zc.business.statistics.cache.metrics; |
|||
|
|||
import cn.hutool.core.date.DateUnit; |
|||
import cn.hutool.core.date.DateUtil; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
|||
import com.zc.business.statistics.cache.AbstractTrafficStatisticsCache; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
import java.util.Date; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* |
|||
* 以月为单位的门架指标数据缓存类,用于存储和管理门架统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
|||
* @author xiepufeng |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
public class MonthlyGantryMetricsStatisticsCache extends AbstractTrafficStatisticsCache<DcGantryMetricsStatisticsData> { |
|||
|
|||
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
|||
@Getter |
|||
private static final Map<String, MonthlyGantryMetricsStatisticsCache> cache = new ConcurrentHashMap<>(); |
|||
|
|||
// 最大缓存时间(单位:秒)
|
|||
private static final long MAX_CACHE_TIME = (60 * 60) * (31 * 24 + 2); |
|||
|
|||
// 最大容量限制,防止内存溢出
|
|||
private static final int MAX_CAPACITY = 31 + 1000; // 缓存的最大条目数
|
|||
|
|||
|
|||
// 私有构造函数,确保只能通过静态方法获取实例
|
|||
private MonthlyGantryMetricsStatisticsCache() { |
|||
} |
|||
|
|||
/** |
|||
* 添加门架指标数据到缓存中 |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 待添加的门架指标数据 |
|||
*/ |
|||
public static void addCacheData(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取或新建对应的缓存实例
|
|||
MonthlyGantryMetricsStatisticsCache instance = getInstance(dcGantryMetricsStatisticsData); |
|||
|
|||
// 检查缓存容量是否达到上限
|
|||
if (instance.getData().size() >= MAX_CAPACITY) { |
|||
instance.getLogger().error("门架指标数据缓存出现异常,最大缓存量达到设定上线 {}, 当前门架是 {}", MAX_CAPACITY, dcGantryMetricsStatisticsData.getGantryCode()); |
|||
return; |
|||
} |
|||
|
|||
// 更新最后添加时间
|
|||
instance.setLastAddedTime(DateUtil.date()); |
|||
|
|||
// 更新状态
|
|||
instance.setStored(false); |
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
String key = generateCacheKey(dcGantryMetricsStatisticsData); |
|||
|
|||
// 设置key和thatDay属性(仅在首次添加时)
|
|||
if (instance.getCacheKey() == null) { |
|||
instance.setCacheKey(key); |
|||
instance.setStatisticalDateStr(formattedDate); |
|||
} |
|||
|
|||
// 更新上报时间
|
|||
dcGantryMetricsStatisticsData.setReportTime(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
// 更新数据周期类型
|
|||
dcGantryMetricsStatisticsData.setPeriodType(TrafficDataPeriodTypeEnum.MONTH); |
|||
// 更新统计日期
|
|||
dcGantryMetricsStatisticsData.setStatisticalDate(dcGantryMetricsStatisticsData.getStatisticalDate(), TrafficDataPeriodTypeEnum.MONTH); |
|||
|
|||
// 移除旧数据
|
|||
instance.getData().remove(dcGantryMetricsStatisticsData); |
|||
// 添加数据
|
|||
instance.getData().add(dcGantryMetricsStatisticsData); |
|||
} |
|||
|
|||
/** |
|||
* 获取月门架信息缓存的实例。 |
|||
* <p> |
|||
* 根据传入的门架在一定周期(月)内的统计数据,计算并返回对应的缓存实例。 |
|||
* 如果缓存中已存在该实例,则直接返回;否则,创建新的缓存实例并加入缓存。 |
|||
* </p> |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 门架统计数据,用于生成缓存键。 |
|||
* @return 缓存中的或新创建的月门架统计信息缓存实例。 |
|||
*/ |
|||
private static MonthlyGantryMetricsStatisticsCache getInstance(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 使用toKey方法生成唯一键,并根据此键计算并返回缓存实例
|
|||
return cache.computeIfAbsent(generateCacheKey(dcGantryMetricsStatisticsData), k -> new MonthlyGantryMetricsStatisticsCache()); |
|||
} |
|||
|
|||
/** |
|||
* 生成缓存键。 |
|||
* <p>此方法通过组合门架编号、统计日期和访问类型来生成一个唯一的缓存键。</p> |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 门架统计数据对象,包含门架编号、统计日期 |
|||
* @return 缓存键,格式为"门架编号|格式化后的统计日期" |
|||
*/ |
|||
public static String generateCacheKey(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取门架标识
|
|||
String gantryCode = dcGantryMetricsStatisticsData.getGantryCode(); |
|||
|
|||
// 格式化统计日期
|
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
|
|||
// 组合门架编号、格式化后的统计日期和访问类型作为缓存键
|
|||
return gantryCode + "|" + formattedDate; |
|||
} |
|||
|
|||
/** |
|||
* 清除所有过期的交通断面数据缓存项 |
|||
*/ |
|||
public static void clearExpiredData() { |
|||
// 使用stream API找出所有过期的数据缓存项键
|
|||
Set<String> keysToRemove = cache.keySet().stream() |
|||
.filter(MonthlyGantryMetricsStatisticsCache::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,154 @@ |
|||
package com.zc.business.statistics.cache.metrics; |
|||
|
|||
import cn.hutool.core.date.DateUnit; |
|||
import cn.hutool.core.date.DateUtil; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
|||
import com.zc.business.statistics.cache.AbstractTrafficStatisticsCache; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
import java.util.Date; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 以季度为单位的门架数据缓存类,用于存储和管理门架统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
|||
* @author xiepufeng |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
public class QuarterlyGantryMetricsStatisticsCache extends AbstractTrafficStatisticsCache<DcGantryMetricsStatisticsData> { |
|||
|
|||
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
|||
@Getter |
|||
private static final Map<String, QuarterlyGantryMetricsStatisticsCache> cache = new ConcurrentHashMap<>(); |
|||
|
|||
// 最大缓存时间(单位:秒)
|
|||
private static final long MAX_CACHE_TIME = (60 * 60) * (3 * 31 * 24 + 2); |
|||
|
|||
// 最大容量限制,防止内存溢出
|
|||
private static final int MAX_CAPACITY = 3 + 1000; // 缓存的最大条目数
|
|||
|
|||
|
|||
// 私有构造函数,确保只能通过静态方法获取实例
|
|||
private QuarterlyGantryMetricsStatisticsCache() { |
|||
} |
|||
|
|||
/** |
|||
* 添加门架数据到缓存中 |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 待添加的门架数据 |
|||
*/ |
|||
public static void addCacheData(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取或新建对应的缓存实例
|
|||
QuarterlyGantryMetricsStatisticsCache instance = getInstance(dcGantryMetricsStatisticsData); |
|||
|
|||
// 检查缓存容量是否达到上限
|
|||
if (instance.getData().size() >= MAX_CAPACITY) { |
|||
instance.getLogger().error("门架数据缓存出现异常,最大缓存量达到设定上线 {}, 当前门架是 {}", MAX_CAPACITY, dcGantryMetricsStatisticsData.getGantryCode()); |
|||
return; |
|||
} |
|||
|
|||
// 更新最后添加时间
|
|||
instance.setLastAddedTime(DateUtil.date()); |
|||
|
|||
// 更新状态
|
|||
instance.setStored(false); |
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
String key = generateCacheKey(dcGantryMetricsStatisticsData); |
|||
|
|||
// 设置key和thatDay属性(仅在首次添加时)
|
|||
if (instance.getCacheKey() == null) { |
|||
instance.setCacheKey(key); |
|||
instance.setStatisticalDateStr(formattedDate); |
|||
} |
|||
|
|||
// 更新上报时间
|
|||
dcGantryMetricsStatisticsData.setReportTime(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
// 更新数据周期类型
|
|||
dcGantryMetricsStatisticsData.setPeriodType(TrafficDataPeriodTypeEnum.QUARTER); |
|||
// 更新统计日期
|
|||
dcGantryMetricsStatisticsData.setStatisticalDate(dcGantryMetricsStatisticsData.getStatisticalDate(), TrafficDataPeriodTypeEnum.QUARTER); |
|||
|
|||
// 移除旧数据
|
|||
instance.getData().remove(dcGantryMetricsStatisticsData); |
|||
// 添加数据
|
|||
instance.getData().add(dcGantryMetricsStatisticsData); |
|||
} |
|||
|
|||
/** |
|||
* 获取季度门架统计信息缓存的实例。 |
|||
* <p> |
|||
* 根据传入的门架在一定周期(季度)内的统计数据,计算并返回对应的缓存实例。 |
|||
* 如果缓存中已存在该实例,则直接返回;否则,创建新的缓存实例并加入缓存。 |
|||
* </p> |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 门架统计数据,用于生成缓存键。 |
|||
* @return 缓存中的或新创建的季度门架统计信息缓存实例。 |
|||
*/ |
|||
private static QuarterlyGantryMetricsStatisticsCache getInstance(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 根据传入数据生成唯一键,并尝试从缓存中获取或创建新的缓存实例
|
|||
return cache.computeIfAbsent(generateCacheKey(dcGantryMetricsStatisticsData), k -> new QuarterlyGantryMetricsStatisticsCache()); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 生成缓存键。 |
|||
* <p>此方法通过组合门架编号、统计日期和访问类型来生成一个唯一的缓存键。</p> |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 门架统计数据对象,包含门架编号、统计日期和访问类型 |
|||
* @return 缓存键,格式为"门架编号|格式化后的统计日期|访问类型" |
|||
*/ |
|||
public static String generateCacheKey(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取门架标识
|
|||
String gantryCode = dcGantryMetricsStatisticsData.getGantryCode(); |
|||
|
|||
// 格式化统计日期
|
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
|
|||
// 组合门架编号、格式化后的统计日期缓存键
|
|||
return gantryCode + "|" + formattedDate; |
|||
} |
|||
|
|||
/** |
|||
* 清除所有过期的交通断面数据缓存项 |
|||
*/ |
|||
public static void clearExpiredData() { |
|||
// 使用stream API找出所有过期的数据缓存项键
|
|||
Set<String> keysToRemove = cache.keySet().stream() |
|||
.filter(QuarterlyGantryMetricsStatisticsCache::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,148 @@ |
|||
package com.zc.business.statistics.cache.metrics; |
|||
|
|||
import cn.hutool.core.date.DateUnit; |
|||
import cn.hutool.core.date.DateUtil; |
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
|||
import com.zc.business.statistics.cache.AbstractTrafficStatisticsCache; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
import java.util.Date; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 以年为单位的门架数据缓存类,用于存储和管理门架统计数据,同时提供了数据缓存的有效性管理和清理功能。 |
|||
* @author xiepufeng |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
public class YearlyGantryMetricsStatisticsCache extends AbstractTrafficStatisticsCache<DcGantryMetricsStatisticsData> { |
|||
|
|||
// 静态缓存容器,使用ConcurrentHashMap保证线程安全
|
|||
@Getter |
|||
private static final Map<String, YearlyGantryMetricsStatisticsCache> cache = new ConcurrentHashMap<>(); |
|||
|
|||
// 最大缓存时间(单位:秒)
|
|||
private static final long MAX_CACHE_TIME = (60 * 60) * (366 * 24 + 2); |
|||
|
|||
// 最大容量限制,防止内存溢出
|
|||
private static final int MAX_CAPACITY = 4 + 1000; // 缓存的最大条目数
|
|||
|
|||
|
|||
// 私有构造函数,确保只能通过静态方法获取实例
|
|||
private YearlyGantryMetricsStatisticsCache() { |
|||
} |
|||
|
|||
/** |
|||
* 添加门架数据到缓存中 |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 待添加的门架数据 |
|||
*/ |
|||
public static void addCacheData(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取或新建对应的缓存实例
|
|||
YearlyGantryMetricsStatisticsCache instance = getInstance(dcGantryMetricsStatisticsData); |
|||
|
|||
// 检查缓存容量是否达到上限
|
|||
if (instance.getData().size() >= MAX_CAPACITY) { |
|||
instance.getLogger().error("门架数据缓存出现异常,最大缓存量达到设定上线 {}, 当前门架是 {}", MAX_CAPACITY, dcGantryMetricsStatisticsData.getGantryCode()); |
|||
return; |
|||
} |
|||
|
|||
// 更新最后添加时间
|
|||
instance.setLastAddedTime(DateUtil.date()); |
|||
|
|||
// 更新状态
|
|||
instance.setStored(false); |
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
String key = generateCacheKey(dcGantryMetricsStatisticsData); |
|||
|
|||
// 设置key和thatDay属性(仅在首次添加时)
|
|||
if (instance.getCacheKey() == null) { |
|||
instance.setCacheKey(key); |
|||
instance.setStatisticalDateStr(formattedDate); |
|||
} |
|||
|
|||
// 更新上报时间
|
|||
dcGantryMetricsStatisticsData.setReportTime(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
// 更新数据周期类型
|
|||
dcGantryMetricsStatisticsData.setPeriodType(TrafficDataPeriodTypeEnum.YEAR); |
|||
// 更新统计日期
|
|||
dcGantryMetricsStatisticsData.setStatisticalDate(dcGantryMetricsStatisticsData.getStatisticalDate(), TrafficDataPeriodTypeEnum.YEAR); |
|||
|
|||
// 移除旧数据
|
|||
instance.getData().remove(dcGantryMetricsStatisticsData); |
|||
// 添加数据
|
|||
instance.getData().add(dcGantryMetricsStatisticsData); |
|||
} |
|||
|
|||
/** |
|||
* 获取或创建对应设备与日期的YearlyTrafficSectionStatisticsCache实例 |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 交通断面数据统计定义 |
|||
* @return 对应的交通断面数据缓存实例 |
|||
*/ |
|||
private static YearlyGantryMetricsStatisticsCache getInstance(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 使用toKey方法生成唯一键,并根据此键计算并返回缓存实例
|
|||
return cache.computeIfAbsent(generateCacheKey(dcGantryMetricsStatisticsData), k -> new YearlyGantryMetricsStatisticsCache()); |
|||
} |
|||
|
|||
/** |
|||
* 生成缓存键。 |
|||
* <p>此方法通过组合门架编号、统计日期来生成一个唯一的缓存键。</p> |
|||
* |
|||
* @param dcGantryMetricsStatisticsData 门架统计数据对象,包含门架编号、统计日期 |
|||
* @return 缓存键,格式为"门架编号|格式化后的统计日期" |
|||
*/ |
|||
public static String generateCacheKey(DcGantryMetricsStatisticsData dcGantryMetricsStatisticsData) { |
|||
// 获取门架站点标识
|
|||
String gantryCode = dcGantryMetricsStatisticsData.getGantryCode(); |
|||
|
|||
// 格式化统计日期
|
|||
String formattedDate = formatDate(dcGantryMetricsStatisticsData.getStatisticalDate()); |
|||
|
|||
// 组合门架编号、格式化后的统计日期作为缓存键
|
|||
return gantryCode + "|" + formattedDate; |
|||
} |
|||
|
|||
/** |
|||
* 清除所有过期的交通断面数据缓存项 |
|||
*/ |
|||
public static void clearExpiredData() { |
|||
// 使用stream API找出所有过期的数据缓存项键
|
|||
Set<String> keysToRemove = cache.keySet().stream() |
|||
.filter(YearlyGantryMetricsStatisticsCache::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,157 @@ |
|||
package com.zc.business.statistics.handler; |
|||
|
|||
import com.zc.business.domain.DcGantryMetricsStatisticsData; |
|||
import com.zc.business.enums.TrafficDataPeriodTypeEnum; |
|||
import com.zc.business.mapper.DcGantryMetricsStatisticsDataMapper; |
|||
import com.zc.business.service.IDcGantryMetricsStatisticsDataService; |
|||
import com.zc.business.statistics.cache.AbstractTrafficStatisticsCache; |
|||
import com.zc.business.statistics.cache.metrics.DailyGantryMetricsStatisticsCache; |
|||
import com.zc.business.statistics.cache.metrics.MonthlyGantryMetricsStatisticsCache; |
|||
import com.zc.business.statistics.cache.metrics.QuarterlyGantryMetricsStatisticsCache; |
|||
import com.zc.business.statistics.cache.metrics.YearlyGantryMetricsStatisticsCache; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.util.CollectionUtils; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.function.Consumer; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* TrafficGantryStatistics类用于处理门架指标的统计数据。 |
|||
* @author xiepufeng |
|||
*/ |
|||
@Component |
|||
public class TrafficGantryMetricsStatistics { |
|||
|
|||
// 日志记录器
|
|||
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); |
|||
|
|||
@Resource |
|||
private IDcGantryMetricsStatisticsDataService dcGantryMetricsStatisticsDataService; |
|||
|
|||
@Resource |
|||
private DcGantryMetricsStatisticsDataMapper dcGantryMetricsStatisticsDataMapper; |
|||
|
|||
|
|||
/** |
|||
* 定义每小时第20分钟执行的任务,用于清除过期缓存数据并将缓存中的数据整合后保存至数据库。 |
|||
*/ |
|||
@Scheduled(cron = "0 20 * * * ?") // 每小时的20分整点执行该任务
|
|||
public void performHourlyCleanupAndPersist() { |
|||
|
|||
List<DcGantryMetricsStatisticsData> lastHourData = dcGantryMetricsStatisticsDataService.lastHourData(); |
|||
// 数据库批量插入最近一小时的数据
|
|||
dcGantryMetricsStatisticsDataMapper.insertOrUpdateBatch(lastHourData); |
|||
|
|||
// 添加日门架指标数据到缓存中
|
|||
lastHourData.forEach(DailyGantryMetricsStatisticsCache::addCacheData); |
|||
|
|||
// 清除已过期的缓存数据
|
|||
DailyGantryMetricsStatisticsCache.clearExpiredData(); |
|||
MonthlyGantryMetricsStatisticsCache.clearExpiredData(); |
|||
QuarterlyGantryMetricsStatisticsCache.clearExpiredData(); |
|||
YearlyGantryMetricsStatisticsCache.clearExpiredData(); |
|||
|
|||
// 整合缓存数据并保存至数据库
|
|||
// 将缓存中的数据按日统计后保存至数据库
|
|||
// 添加月门架指标数据到缓存中
|
|||
persistAggregatedData(DailyGantryMetricsStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.DAY, MonthlyGantryMetricsStatisticsCache::addCacheData); |
|||
// 将缓存中的数据按月统计后保存至数据库
|
|||
// 添加季度门架指标数据到缓存中
|
|||
persistAggregatedData(MonthlyGantryMetricsStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.MONTH, QuarterlyGantryMetricsStatisticsCache::addCacheData); |
|||
// 将缓存中的数据按季度统计后保存至数据库
|
|||
// 添加年门架指标数据到缓存中
|
|||
persistAggregatedData(QuarterlyGantryMetricsStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.QUARTER, YearlyGantryMetricsStatisticsCache::addCacheData); |
|||
// 将缓存中的数据按年统计后保存至数据库
|
|||
persistAggregatedData(YearlyGantryMetricsStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.YEAR, (a) -> {}); |
|||
} |
|||
|
|||
/** |
|||
* 将缓存中的数据统计后保存至数据库。 |
|||
*/ |
|||
public void persistAggregatedData( |
|||
Map<String, ? extends AbstractTrafficStatisticsCache<DcGantryMetricsStatisticsData>> cache, |
|||
TrafficDataPeriodTypeEnum periodType, |
|||
Consumer<DcGantryMetricsStatisticsData> consumer |
|||
) { |
|||
for (AbstractTrafficStatisticsCache<DcGantryMetricsStatisticsData> data : cache.values()) { |
|||
// 如果数据已经存储过,则跳过此次处理
|
|||
if (data.isStored()) { |
|||
continue; |
|||
} |
|||
DcGantryMetricsStatisticsData aggregatedData = trafficStatistics(data.getData(), periodType); |
|||
if (dcGantryMetricsStatisticsDataMapper.insertOrUpdate(aggregatedData)) { |
|||
// 设置数据已存储状态
|
|||
data.setStored(true); |
|||
// 调用回调函数
|
|||
consumer.accept(aggregatedData); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 对给定的门架指标统计数据集合进行汇总处理。 |
|||
* |
|||
* @param dataCollection 门架指标统计数据的集合,不可为null或空。 |
|||
* @param trafficDataPeriodType 统计数据的时段类型,例如:小时、日、月等。 |
|||
* @return 汇总后的门架指标统计数据对象,如果输入数据为空则返回null。 |
|||
*/ |
|||
public DcGantryMetricsStatisticsData trafficStatistics(Collection<DcGantryMetricsStatisticsData> dataCollection, TrafficDataPeriodTypeEnum trafficDataPeriodType) { |
|||
|
|||
// 判断输入数据是否为空
|
|||
if (CollectionUtils.isEmpty(dataCollection)) { |
|||
return null; |
|||
} |
|||
|
|||
// 创建一个汇总统计用的对象
|
|||
DcGantryMetricsStatisticsData aggregatedData = new DcGantryMetricsStatisticsData(); |
|||
|
|||
// 获取拥堵度众数
|
|||
Integer mostFrequentCrowdingRate = dataCollection.stream() |
|||
.collect(Collectors.groupingBy(DcGantryMetricsStatisticsData::getCrowdingRate, Collectors.counting())) |
|||
.entrySet().stream() |
|||
.max(Map.Entry.comparingByValue()) |
|||
.map(Map.Entry::getKey) |
|||
.orElse(null); // 如果没有数据,则返回null
|
|||
|
|||
// 获取交通占比众数
|
|||
Integer mostFrequentTrafficCompositionRate = dataCollection.stream() |
|||
.collect(Collectors.groupingBy(DcGantryMetricsStatisticsData::getTrafficCompositionRate, Collectors.counting())) |
|||
.entrySet().stream() |
|||
.max(Map.Entry.comparingByValue()) |
|||
.map(Map.Entry::getKey) |
|||
.orElse(null); // 如果没有数据,则返回null
|
|||
|
|||
// 获取饱和度平均值
|
|||
Double saturationRateAverage = dataCollection.stream() |
|||
.mapToDouble(DcGantryMetricsStatisticsData::getSaturationRate) |
|||
.average() |
|||
.orElse(0.00); // 如果没有数据,则返回0.0
|
|||
|
|||
aggregatedData.setCrowdingRate(mostFrequentCrowdingRate); |
|||
aggregatedData.setTrafficCompositionRate(mostFrequentTrafficCompositionRate); |
|||
aggregatedData.setSaturationRate(saturationRateAverage); |
|||
|
|||
// 使用第一个数据项的信息填充汇总统计对象的基本属性
|
|||
DcGantryMetricsStatisticsData firstDcGantryMetricsStatisticsData = dataCollection.iterator().next(); |
|||
// 设置门架指标标识
|
|||
aggregatedData.setGantryCode(firstDcGantryMetricsStatisticsData.getGantryCode()); |
|||
// 设置统计时间
|
|||
aggregatedData.setStatisticalDate(firstDcGantryMetricsStatisticsData.getStatisticalDate(), trafficDataPeriodType); |
|||
// 上报时间
|
|||
aggregatedData.setReportTime(firstDcGantryMetricsStatisticsData.getReportTime()); |
|||
// 时段类型
|
|||
aggregatedData.setPeriodType(trafficDataPeriodType); |
|||
// 生成主键
|
|||
aggregatedData.generateUniqueId(); |
|||
|
|||
return aggregatedData; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,77 @@ |
|||
<?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.DcGantryMetricsStatisticsDataMapper"> |
|||
|
|||
<!-- 插入或更新门架指标数据 --> |
|||
<insert id="insertOrUpdate" parameterType="com.zc.business.domain.DcGantryMetricsStatisticsData"> |
|||
INSERT INTO |
|||
dc_gantry_metrics_statistics_data |
|||
( |
|||
id, |
|||
gantry_code, |
|||
statistical_date, |
|||
period_type, |
|||
crowding_rate, |
|||
traffic_composition_rate, |
|||
saturation_rate, |
|||
create_time |
|||
) |
|||
VALUES |
|||
( |
|||
#{id}, |
|||
#{gantryCode}, |
|||
#{statisticalDate}, |
|||
#{periodType}, |
|||
#{crowdingRate}, |
|||
#{trafficCompositionRate}, |
|||
#{saturationRate}, |
|||
NOW()) |
|||
ON DUPLICATE KEY UPDATE |
|||
gantry_code = VALUES(gantry_code), |
|||
statistical_date = VALUES(statistical_date), |
|||
period_type = VALUES(period_type), |
|||
crowding_rate = VALUES(crowding_rate), |
|||
traffic_composition_rate = VALUES(traffic_composition_rate), |
|||
saturation_rate = VALUES(saturation_rate), |
|||
update_time = NOW() |
|||
</insert> |
|||
|
|||
<insert id="insertOrUpdateBatch" parameterType="list"> |
|||
INSERT INTO |
|||
dc_gantry_metrics_statistics_data |
|||
( |
|||
id, |
|||
gantry_code, |
|||
statistical_date, |
|||
period_type, |
|||
crowding_rate, |
|||
traffic_composition_rate, |
|||
saturation_rate, |
|||
create_time |
|||
) |
|||
VALUES |
|||
<foreach collection="list" item="item" index="index" separator=","> |
|||
( |
|||
#{item.id}, |
|||
#{item.gantryCode}, |
|||
#{item.statisticalDate}, |
|||
#{item.periodType}, |
|||
#{item.crowdingRate}, |
|||
#{item.trafficCompositionRate}, |
|||
#{item.saturationRate}, |
|||
NOW() |
|||
) |
|||
</foreach> |
|||
ON DUPLICATE KEY UPDATE |
|||
gantry_code = VALUES(gantry_code), |
|||
statistical_date = VALUES(statistical_date), |
|||
period_type = VALUES(period_type), |
|||
crowding_rate = VALUES(crowding_rate), |
|||
traffic_composition_rate = VALUES(traffic_composition_rate), |
|||
saturation_rate = VALUES(saturation_rate), |
|||
update_time = NOW() |
|||
</insert> |
|||
|
|||
</mapper> |
Loading…
Reference in new issue