|
|
@@ -15,6 +15,8 @@ import com.kym.service.admin.ConnectorInfoService;
|
|
|
import com.kym.service.admin.EquipmentRelationService;
|
|
|
import com.kym.service.admin.PlatformService;
|
|
|
import com.kym.service.admin.StationService;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
|
|
import org.springframework.context.ApplicationListener;
|
|
|
@@ -23,8 +25,8 @@ import org.springframework.stereotype.Component;
|
|
|
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
-import java.util.Objects;
|
|
|
-import java.util.concurrent.ConcurrentHashMap;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.function.Supplier;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -35,90 +37,220 @@ import java.util.stream.Collectors;
|
|
|
public enum KymCache {
|
|
|
INSTANCE;
|
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(KymCache.class);
|
|
|
|
|
|
- private static ConcurrentHashMap<String, String> CONNECTOR_ID_SHORT_ID_MAPPING = new ConcurrentHashMap<>();
|
|
|
+ /**
|
|
|
+ * 缓存过期时间:24小时
|
|
|
+ */
|
|
|
+ private static final long CACHE_EXPIRE_HOURS = 24;
|
|
|
|
|
|
+ /**
|
|
|
+ * 带数据源切换的数据库查询(用于缓存miss时按需加载)
|
|
|
+ *
|
|
|
+ * @param supplier 数据库查询逻辑
|
|
|
+ * @param <T> 返回类型
|
|
|
+ * @return 查询结果
|
|
|
+ */
|
|
|
+ private <T> T executeWithDataSource(Supplier<T> supplier) {
|
|
|
+ DynamicDataSourceContextHolder.push("db-admin");
|
|
|
+ try {
|
|
|
+ return supplier.get();
|
|
|
+ } finally {
|
|
|
+ DynamicDataSourceContextHolder.poll();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 获取枪头状态
|
|
|
+ * 获取枪头状态(带空值保护和缓存miss时按需加载)
|
|
|
*
|
|
|
- * @param connectorId
|
|
|
- * @return
|
|
|
+ * @param connectorId 充电接口ID
|
|
|
+ * @return 状态值,如果不存在返回null
|
|
|
*/
|
|
|
public Integer getConnectorStatus(String connectorId) {
|
|
|
- return Integer.valueOf(Objects.requireNonNull(KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_STATUS + connectorId)));
|
|
|
+ if (connectorId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ String value = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_STATUS + connectorId);
|
|
|
+ if (value == null) {
|
|
|
+ // 缓存miss,从数据库加载
|
|
|
+ Integer status = loadConnectorStatusFromDb(connectorId);
|
|
|
+ if (status != null) {
|
|
|
+ // 重新缓存
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_STATUS + connectorId, String.valueOf(status), CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+ if ("-1".equals(value)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return Integer.valueOf(value);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("获取枪头状态失败,connectorId: {}", connectorId, e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从数据库加载枪头状态
|
|
|
+ */
|
|
|
+ private Integer loadConnectorStatusFromDb(String connectorId) {
|
|
|
+ return executeWithDataSource(() -> {
|
|
|
+ ConnectorInfoService service = SpringUtil.getBean(ConnectorInfoService.class);
|
|
|
+ ConnectorInfo info = service.lambdaQuery().eq(ConnectorInfo::getConnectorId, connectorId).one();
|
|
|
+ return info != null ? info.getStatus() : null;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 缓存枪头状态
|
|
|
+ * 缓存枪头状态(带过期时间)
|
|
|
*
|
|
|
- * @param map
|
|
|
+ * @param map 状态映射
|
|
|
*/
|
|
|
public void putConnectorId2Status(Map<String, Integer> map) {
|
|
|
- map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue().set(RedisKeys.CONNECTOR_ID_TO_STATUS + k, String.valueOf(v)));
|
|
|
+ map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_STATUS + k, String.valueOf(v), CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * 缓存枪头短编号
|
|
|
+ * 缓存枪头短编号(带过期时间,移除本地Map缓存)
|
|
|
*
|
|
|
- * @param map
|
|
|
+ * @param map 短编号映射
|
|
|
*/
|
|
|
public void putConnectorId2ShortId(Map<String, String> map) {
|
|
|
- CONNECTOR_ID_SHORT_ID_MAPPING.putAll(map);
|
|
|
- map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue().set(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + k, v));
|
|
|
+ map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 通过设备编号或者枪头编号获取设备短编号
|
|
|
+ * 缓存短编号到connectorId的反向索引(带过期时间)
|
|
|
*
|
|
|
- * @param id
|
|
|
- * @return
|
|
|
+ * @param map 短编号 -> connectorId 映射
|
|
|
+ */
|
|
|
+ public void putShortId2ConnectorId(Map<String, String> map) {
|
|
|
+ map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过设备编号或者枪头编号获取设备短编号(带缓存miss时按需加载)
|
|
|
+ *
|
|
|
+ * @param id 设备编号或枪头编号
|
|
|
+ * @return 短编号
|
|
|
*/
|
|
|
public String getShortIdByEquipmentIdOrConnectorId(String id) {
|
|
|
var connectorId = getConnectorId(id);
|
|
|
- return KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + connectorId);
|
|
|
+ if (connectorId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String shortId = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + connectorId);
|
|
|
+ if (shortId == null) {
|
|
|
+ // 缓存miss,从数据库加载
|
|
|
+ EquipmentRelation relation = loadEquipmentRelationByConnectorId(connectorId);
|
|
|
+ if (relation != null) {
|
|
|
+ shortId = relation.getShortId();
|
|
|
+ // 重新缓存
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + connectorId, shortId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ // 同时缓存反向索引
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + shortId, connectorId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return shortId;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从数据库加载设备关系
|
|
|
+ */
|
|
|
+ private EquipmentRelation loadEquipmentRelationByConnectorId(String connectorId) {
|
|
|
+ return executeWithDataSource(() -> {
|
|
|
+ EquipmentRelationService service = SpringUtil.getBean(EquipmentRelationService.class);
|
|
|
+ return service.getByConnectorId(connectorId);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
|
|
|
public void putConnectorId2StationId(Map<String, String> map) {
|
|
|
- map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue().set(RedisKeys.CONNECTOR_ID_TO_STATION_ID + k, v));
|
|
|
+ map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_STATION_ID + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 通过设备编号或者枪头编号获取站点id
|
|
|
+ * 通过设备编号或者枪头编号获取站点id(带缓存miss时按需加载)
|
|
|
*
|
|
|
- * @param id
|
|
|
- * @return
|
|
|
+ * @param id 设备编号或枪头编号
|
|
|
+ * @return 站点ID
|
|
|
*/
|
|
|
public String getStationIdByEquipmentIdOrConnectorId(String id) {
|
|
|
var connectorId = getConnectorId(id);
|
|
|
- return KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_STATION_ID + connectorId);
|
|
|
+ if (connectorId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String stationId = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_STATION_ID + connectorId);
|
|
|
+ if (stationId == null) {
|
|
|
+ // 缓存miss,从数据库加载
|
|
|
+ EquipmentRelation relation = loadEquipmentRelationByConnectorId(connectorId);
|
|
|
+ if (relation != null) {
|
|
|
+ stationId = relation.getStationId();
|
|
|
+ // 重新缓存
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_STATION_ID + connectorId, stationId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return stationId;
|
|
|
}
|
|
|
|
|
|
public void putStationId2Name(Map<String, String> map) {
|
|
|
- map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue().set(RedisKeys.STATION_ID_TO_NAME + k, v));
|
|
|
+ map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.STATION_ID_TO_NAME + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 通过站点id获取站点名称
|
|
|
+ * 通过站点id获取站点名称(带缓存miss时按需加载)
|
|
|
*
|
|
|
- * @param stationId
|
|
|
- * @return
|
|
|
+ * @param stationId 站点ID
|
|
|
+ * @return 站点名称
|
|
|
*/
|
|
|
public String getStationNameById(String stationId) {
|
|
|
- return KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.STATION_ID_TO_NAME + stationId);
|
|
|
+ if (stationId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String stationName = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.STATION_ID_TO_NAME + stationId);
|
|
|
+ if (stationName == null) {
|
|
|
+ // 缓存miss,从数据库加载
|
|
|
+ stationName = loadStationNameFromDb(stationId);
|
|
|
+ if (stationName != null) {
|
|
|
+ // 重新缓存
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.STATION_ID_TO_NAME + stationId, stationName, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return stationName;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从数据库加载站点名称
|
|
|
+ */
|
|
|
+ private String loadStationNameFromDb(String stationId) {
|
|
|
+ return executeWithDataSource(() -> {
|
|
|
+ StationService service = SpringUtil.getBean(StationService.class);
|
|
|
+ Station station = service.lambdaQuery().eq(Station::getStationId, stationId).one();
|
|
|
+ return station != null ? station.getStationName() : null;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 通过充电接口id获取站点名称
|
|
|
+ * 通过充电接口id获取站点名称(复用getStationNameById的缓存miss加载逻辑)
|
|
|
*
|
|
|
- * @param connectorId
|
|
|
- * @return
|
|
|
+ * @param connectorId 充电接口ID
|
|
|
+ * @return 站点名称
|
|
|
*/
|
|
|
public String getStationNameByConnectorId(String connectorId) {
|
|
|
var stationId = getStationIdByEquipmentIdOrConnectorId(connectorId);
|
|
|
- return KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.STATION_ID_TO_NAME + stationId);
|
|
|
+ return getStationNameById(stationId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -147,16 +279,18 @@ public enum KymCache {
|
|
|
}
|
|
|
|
|
|
public void putConnectorId2ParkingNo(Map<String, String> map) {
|
|
|
- map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue().set(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + k, v));
|
|
|
+ map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 站点费率
|
|
|
+ * 站点费率(带过期时间)
|
|
|
*
|
|
|
- * @param map
|
|
|
+ * @param map 费率映射
|
|
|
*/
|
|
|
public void putStationId2PolicyInfo(Map<String, List<PlatformPolicyInfoVo>> map) {
|
|
|
- map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue().set(RedisKeys.STATION_ID_POLICY_INFO + k, JSONObject.toJSONString(v)));
|
|
|
+ map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.STATION_ID_POLICY_INFO + k, JSONObject.toJSONString(v), CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -171,30 +305,92 @@ public enum KymCache {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @param id
|
|
|
- * @return
|
|
|
+ * 通过设备编号或枪头编号获取停车位号(带缓存miss时按需加载)
|
|
|
+ *
|
|
|
+ * @param id 设备编号或枪头编号
|
|
|
+ * @return 停车位号
|
|
|
*/
|
|
|
public String getParkNoByEquipmentIdOrConnectorId(String id) {
|
|
|
var connectorId = getConnectorId(id);
|
|
|
- return KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + connectorId);
|
|
|
+ if (connectorId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String parkingNo = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + connectorId);
|
|
|
+ if (parkingNo == null) {
|
|
|
+ // 缓存miss,从数据库加载
|
|
|
+ EquipmentRelation relation = loadEquipmentRelationByConnectorId(connectorId);
|
|
|
+ if (relation != null) {
|
|
|
+ parkingNo = relation.getParkingNo();
|
|
|
+ // 重新缓存
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + connectorId, parkingNo, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return parkingNo;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 将equipmentId,connectorId,shortId转化成17为的connectorId
|
|
|
+ * 将equipmentId,connectorId,shortId转化成17位的connectorId
|
|
|
+ * 从Redis查找短编号对应的完整connectorId
|
|
|
*
|
|
|
- * @param id
|
|
|
- * @return
|
|
|
+ * @param id 设备ID(可能是17位、16位或6位短编号)
|
|
|
+ * @return 17位的connectorId
|
|
|
*/
|
|
|
public String getConnectorId(String id) {
|
|
|
+ if (id == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
return switch (id.length()) {
|
|
|
case 17 -> id;
|
|
|
case 16 -> id.concat("1");
|
|
|
- case 6 ->
|
|
|
- CONNECTOR_ID_SHORT_ID_MAPPING.entrySet().stream().filter(entry -> id.equals(entry.getValue())).map(Map.Entry::getKey).findFirst().get();
|
|
|
+ case 6 -> {
|
|
|
+ // 从Redis反向查找短编号对应的connectorId
|
|
|
+ String connectorId = getConnectorIdByShortId(id);
|
|
|
+ yield connectorId;
|
|
|
+ }
|
|
|
default -> null;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 通过短编号从Redis查找对应的connectorId(带缓存miss时按需加载)
|
|
|
+ *
|
|
|
+ * @param shortId 短编号
|
|
|
+ * @return connectorId
|
|
|
+ */
|
|
|
+ private String getConnectorIdByShortId(String shortId) {
|
|
|
+ // 从Redis反向索引中查找
|
|
|
+ String connectorId = KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .get(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + shortId);
|
|
|
+ if (connectorId != null) {
|
|
|
+ return connectorId;
|
|
|
+ }
|
|
|
+ // 缓存miss,从数据库加载
|
|
|
+ connectorId = loadConnectorIdByShortId(shortId);
|
|
|
+ if (connectorId != null) {
|
|
|
+ // 重新缓存反向索引
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + shortId, connectorId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ // 同时缓存正向索引
|
|
|
+ KymCacheInjector.redisTemplate.opsForValue()
|
|
|
+ .set(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + connectorId, shortId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ return connectorId;
|
|
|
+ }
|
|
|
+ log.warn("未找到短编号对应的connectorId,shortId: {}", shortId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从数据库通过短编号加载connectorId
|
|
|
+ */
|
|
|
+ private String loadConnectorIdByShortId(String shortId) {
|
|
|
+ return executeWithDataSource(() -> {
|
|
|
+ EquipmentRelationService service = SpringUtil.getBean(EquipmentRelationService.class);
|
|
|
+ EquipmentRelation relation = service.getByShortId(shortId);
|
|
|
+ return relation != null ? relation.getConnectorId() : null;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
@Component
|
|
|
public static class KymCacheInjector implements ApplicationListener<ApplicationStartedEvent> {
|
|
|
@@ -227,6 +423,9 @@ public enum KymCache {
|
|
|
|
|
|
var connectorId2ShortId = equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getConnectorId, EquipmentRelation::getShortId));
|
|
|
KymCache.INSTANCE.putConnectorId2ShortId(connectorId2ShortId);
|
|
|
+ // 同时建立反向索引:短编号 -> connectorId
|
|
|
+ var shortId2ConnectorId = equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getShortId, EquipmentRelation::getConnectorId));
|
|
|
+ KymCache.INSTANCE.putShortId2ConnectorId(shortId2ConnectorId);
|
|
|
|
|
|
var connector2Station = equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getConnectorId, EquipmentRelation::getStationId));
|
|
|
KymCache.INSTANCE.putConnectorId2StationId(connector2Station);
|