运营区边缘计算:区域数据缓存
This commit is contained in:
parent
3df6c925ea
commit
db2d7734c8
@ -0,0 +1,339 @@
|
|||||||
|
package com.cdzy.operations.component;
|
||||||
|
|
||||||
|
import com.cdzy.operations.utils.RedisUtil;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.locationtech.jts.geom.*;
|
||||||
|
import org.locationtech.jts.io.WKTReader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网格索引构建器(Spring Bean 版本,无本地缓存)
|
||||||
|
* 将运营区多边形边界切分为网格,每个网格关联其覆盖的边界线段。
|
||||||
|
* 提供构建索引、发布到 Redis、以及实时判断车辆是否靠近边缘的功能。
|
||||||
|
* 每次判断均直接访问 Redis,保证数据实时性。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GridIndexBuilder {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GridIndexBuilder.class);
|
||||||
|
private static final double EARTH_RADIUS = 6371000; // 地球半径(米)
|
||||||
|
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
|
||||||
|
private static final WKTReader WKT_READER = new WKTReader();
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
// 配置参数(可从 application.yml 注入)
|
||||||
|
@Value("${fence.threshold.meters:100}")
|
||||||
|
private double thresholdMeters;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
// 无参构造(Spring使用)
|
||||||
|
public GridIndexBuilder() {}
|
||||||
|
|
||||||
|
// 带参构造(方便手动调用或单元测试)
|
||||||
|
public GridIndexBuilder(double thresholdMeters, RedisUtil redisUtil) {
|
||||||
|
this.thresholdMeters = thresholdMeters;
|
||||||
|
this.redisUtil = redisUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 索引构建与发布 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建网格索引并发布到 Redis
|
||||||
|
* @param polygon 运营区多边形
|
||||||
|
* @param regionId 运营区编号
|
||||||
|
*/
|
||||||
|
public void buildIndex(Polygon polygon, Long regionId) {
|
||||||
|
// 1. 获取外包矩形
|
||||||
|
Envelope envelope = polygon.getEnvelopeInternal();
|
||||||
|
double minLon = envelope.getMinX();
|
||||||
|
double maxLon = envelope.getMaxX();
|
||||||
|
double minLat = envelope.getMinY();
|
||||||
|
double maxLat = envelope.getMaxY();
|
||||||
|
|
||||||
|
// 2. 计算网格尺寸(米转度)
|
||||||
|
double centerLat = (minLat + maxLat) / 2;
|
||||||
|
double lonPerMeter = 1 / (111320 * Math.cos(Math.toRadians(centerLat)));
|
||||||
|
double latPerMeter = (double)1 / 110574;
|
||||||
|
double gridLonWidth = (thresholdMeters / 2) * lonPerMeter;
|
||||||
|
double gridLatWidth = (thresholdMeters / 2) * latPerMeter;
|
||||||
|
|
||||||
|
// 3. 提取所有边界线段
|
||||||
|
List<Coordinate[]> edges = extractEdges(polygon);
|
||||||
|
|
||||||
|
// 4. 构建网格索引
|
||||||
|
Map<String, List<Coordinate[]>> gridIndex = new HashMap<>();
|
||||||
|
for (Coordinate[] seg : edges) {
|
||||||
|
Coordinate a = seg[0];
|
||||||
|
Coordinate b = seg[1];
|
||||||
|
|
||||||
|
double minSegLon = Math.min(a.x, b.x) - gridLonWidth;
|
||||||
|
double maxSegLon = Math.max(a.x, b.x) + gridLonWidth;
|
||||||
|
double minSegLat = Math.min(a.y, b.y) - gridLatWidth;
|
||||||
|
double maxSegLat = Math.max(a.y, b.y) + gridLatWidth;
|
||||||
|
|
||||||
|
int minLonIdx = (int) Math.floor((minSegLon - minLon) / gridLonWidth);
|
||||||
|
int maxLonIdx = (int) Math.floor((maxSegLon - minLon) / gridLonWidth);
|
||||||
|
int minLatIdx = (int) Math.floor((minSegLat - minLat) / gridLatWidth);
|
||||||
|
int maxLatIdx = (int) Math.floor((maxSegLat - minLat) / gridLatWidth);
|
||||||
|
|
||||||
|
for (int lonIdx = minLonIdx; lonIdx <= maxLonIdx; lonIdx++) {
|
||||||
|
for (int latIdx = minLatIdx; latIdx <= maxLatIdx; latIdx++) {
|
||||||
|
String gridId = lonIdx + "_" + latIdx;
|
||||||
|
gridIndex.computeIfAbsent(gridId, k -> new ArrayList<>()).add(seg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 创建元数据
|
||||||
|
GridMetadata metadata = new GridMetadata(minLon, minLat, gridLonWidth, gridLatWidth, thresholdMeters);
|
||||||
|
|
||||||
|
// 6. 发布到 Redis
|
||||||
|
try {
|
||||||
|
publishToRedis(gridIndex, metadata, polygon, regionId);
|
||||||
|
log.info("网格索引发布成功,regionId: {}, 网格数量: {}", regionId, gridIndex.size());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("发布网格索引到 Redis 失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从多边形提取所有边界线段
|
||||||
|
*/
|
||||||
|
private List<Coordinate[]> extractEdges(Polygon polygon) {
|
||||||
|
List<Coordinate[]> edges = new ArrayList<>();
|
||||||
|
LineString exterior = polygon.getExteriorRing();
|
||||||
|
addEdgesFromLineString(exterior, edges);
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEdgesFromLineString(LineString lineString, List<Coordinate[]> edges) {
|
||||||
|
Coordinate[] coords = lineString.getCoordinates();
|
||||||
|
for (int i = 0; i < coords.length - 1; i++) {
|
||||||
|
edges.add(new Coordinate[]{coords[i], coords[i + 1]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布网格索引、元数据和多边形到 Redis
|
||||||
|
*/
|
||||||
|
private void publishToRedis(Map<String, List<Coordinate[]>> gridIndex, GridMetadata metadata,
|
||||||
|
Polygon polygon, Long regionId) throws Exception {
|
||||||
|
String gridKey = "bike:grid:" + regionId;
|
||||||
|
String metaKey = "bike:grid:meta:" + regionId;
|
||||||
|
String wktKey = "bike:fence:wkt:" + regionId;
|
||||||
|
|
||||||
|
// 清理可能存在的类型错误键
|
||||||
|
checkAndCleanKey(gridKey, "hash");
|
||||||
|
checkAndCleanKey(metaKey, "string");
|
||||||
|
checkAndCleanKey(wktKey, "string");
|
||||||
|
|
||||||
|
// 写入网格索引(Hash)
|
||||||
|
Map<String, Object> hashMap = new HashMap<>();
|
||||||
|
for (Map.Entry<String, List<Coordinate[]>> entry : gridIndex.entrySet()) {
|
||||||
|
hashMap.put(entry.getKey(), serializeSegments(entry.getValue()));
|
||||||
|
}
|
||||||
|
redisUtil.hSetAll(gridKey, hashMap,RedisUtil.Database.DB2);
|
||||||
|
|
||||||
|
// 写入元数据(String JSON)
|
||||||
|
String metaJson = objectMapper.writeValueAsString(metadata);
|
||||||
|
redisUtil.set(RedisUtil.Database.DB2,metaKey, metaJson);
|
||||||
|
|
||||||
|
// 写入多边形 WKT(用于包含判断)
|
||||||
|
redisUtil.set(RedisUtil.Database.DB2,wktKey, polygon.toText());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并清理不匹配类型的 key
|
||||||
|
*/
|
||||||
|
private void checkAndCleanKey(String key, String expectedType) {
|
||||||
|
String type = redisUtil.type(key, RedisUtil.Database.DB2);
|
||||||
|
if (!"none".equals(type) && !expectedType.equals(type)) {
|
||||||
|
redisUtil.delete(RedisUtil.Database.DB2,key);
|
||||||
|
log.warn("清理类型不匹配的 key: {}, 原类型: {}, 期望类型: {}", key, type, expectedType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 实时判断方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断车辆是否接近运营区边缘(从内部骑出)
|
||||||
|
* 每次调用均直接访问 Redis,保证数据实时性。
|
||||||
|
* @param regionId 运营区ID
|
||||||
|
* @param lon 经度
|
||||||
|
* @param lat 纬度
|
||||||
|
* @return true 表示需要提醒(距离小于阈值且车辆在多边形内),否则 false
|
||||||
|
*/
|
||||||
|
public boolean isNearEdge(Long regionId, double lon, double lat) {
|
||||||
|
try {
|
||||||
|
// 1. 从 Redis 获取多边形 WKT 并判断是否在多边形内部
|
||||||
|
String wktKey = "bike:fence:wkt:" + regionId;
|
||||||
|
Object wktObj = redisUtil.get(RedisUtil.Database.DB2,wktKey);
|
||||||
|
if (wktObj == null) {
|
||||||
|
log.warn("多边形数据不存在,regionId: {}", regionId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Polygon polygon = (Polygon) WKT_READER.read(wktObj.toString());
|
||||||
|
Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(lon, lat));
|
||||||
|
if (!polygon.contains(point)) {
|
||||||
|
return false; // 不在内部,不处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 从 Redis 获取网格元数据
|
||||||
|
String metaKey = "bike:grid:meta:" + regionId;
|
||||||
|
Object metaObj = redisUtil.get(RedisUtil.Database.DB2,metaKey);
|
||||||
|
if (metaObj == null) {
|
||||||
|
log.warn("网格元数据不存在,regionId: {}", regionId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GridMetadata metadata = objectMapper.readValue(metaObj.toString(), GridMetadata.class);
|
||||||
|
|
||||||
|
// 3. 计算网格及邻域
|
||||||
|
int lonIdx = (int) Math.floor((lon - metadata.minLon) / metadata.gridLonWidth);
|
||||||
|
int latIdx = (int) Math.floor((lat - metadata.minLat) / metadata.gridLatWidth);
|
||||||
|
List<String> neighborIds = getNeighborGridIds(lonIdx, latIdx);
|
||||||
|
|
||||||
|
// 4. 从 Redis 获取这些网格的线段数据
|
||||||
|
String gridKey = "bike:grid:" + regionId;
|
||||||
|
List<Coordinate[]> candidateSegments = new ArrayList<>();
|
||||||
|
for (String gridId : neighborIds) {
|
||||||
|
Object jsonObj = redisUtil.hGet(gridKey, gridId);
|
||||||
|
if (jsonObj != null) {
|
||||||
|
candidateSegments.addAll(deserializeSegments(jsonObj.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 计算距离并判断
|
||||||
|
return isDistanceLessThanThreshold(lon, lat, candidateSegments, metadata.threshold);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("判断边缘距离异常,regionId: {}, lon: {}, lat: {}", regionId, lon, lat, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 距离计算工具 ----------
|
||||||
|
private boolean isDistanceLessThanThreshold(double lon, double lat, List<Coordinate[]> segments, double threshold) {
|
||||||
|
if (segments == null || segments.isEmpty()) return false;
|
||||||
|
Coordinate point = new Coordinate(lon, lat);
|
||||||
|
double minDist = Double.MAX_VALUE;
|
||||||
|
for (Coordinate[] seg : segments) {
|
||||||
|
double d = distanceToSegment(point, seg[0], seg[1]);
|
||||||
|
if (d < minDist) {
|
||||||
|
minDist = d;
|
||||||
|
if (minDist < threshold) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minDist < threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double distanceToSegment(Coordinate p, Coordinate a, Coordinate b) {
|
||||||
|
double refLat = Math.toRadians((a.y + b.y) / 2);
|
||||||
|
double[] pM = toMeters(p, refLat);
|
||||||
|
double[] aM = toMeters(a, refLat);
|
||||||
|
double[] bM = toMeters(b, refLat);
|
||||||
|
|
||||||
|
double dx = bM[0] - aM[0];
|
||||||
|
double dy = bM[1] - aM[1];
|
||||||
|
double lenSq = dx * dx + dy * dy;
|
||||||
|
|
||||||
|
if (lenSq == 0) {
|
||||||
|
return Math.hypot(pM[0] - aM[0], pM[1] - aM[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double t = ((pM[0] - aM[0]) * dx + (pM[1] - aM[1]) * dy) / lenSq;
|
||||||
|
t = Math.max(0, Math.min(1, t));
|
||||||
|
|
||||||
|
double projX = aM[0] + t * dx;
|
||||||
|
double projY = aM[1] + t * dy;
|
||||||
|
|
||||||
|
return Math.hypot(pM[0] - projX, pM[1] - projY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double[] toMeters(Coordinate p, double refLat) {
|
||||||
|
double lonRad = Math.toRadians(p.x);
|
||||||
|
double latRad = Math.toRadians(p.y);
|
||||||
|
double x = lonRad * Math.cos(refLat) * EARTH_RADIUS;
|
||||||
|
double y = latRad * EARTH_RADIUS;
|
||||||
|
return new double[]{x, y};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 静态工具方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取网格ID
|
||||||
|
*/
|
||||||
|
public static String getGridId(double lon, double lat, GridMetadata metadata) {
|
||||||
|
int lonIdx = (int) Math.floor((lon - metadata.minLon) / metadata.gridLonWidth);
|
||||||
|
int latIdx = (int) Math.floor((lat - metadata.minLat) / metadata.gridLatWidth);
|
||||||
|
return lonIdx + "_" + latIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取邻域网格ID列表(3x3)
|
||||||
|
*/
|
||||||
|
public static List<String> getNeighborGridIds(int lonIdx, int latIdx) {
|
||||||
|
List<String> ids = new ArrayList<>();
|
||||||
|
for (int dl = -1; dl <= 1; dl++) {
|
||||||
|
for (int db = -1; db <= 1; db++) {
|
||||||
|
ids.add((lonIdx + dl) + "_" + (latIdx + db));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反序列化线段 JSON
|
||||||
|
*/
|
||||||
|
public static List<Coordinate[]> deserializeSegments(String json) throws Exception {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
List<List<double[]>> segList = mapper.readValue(json,
|
||||||
|
mapper.getTypeFactory().constructCollectionType(List.class,
|
||||||
|
mapper.getTypeFactory().constructCollectionType(List.class, double[].class)));
|
||||||
|
List<Coordinate[]> result = new ArrayList<>();
|
||||||
|
for (List<double[]> seg : segList) {
|
||||||
|
Coordinate a = new Coordinate(seg.get(0)[0], seg.get(0)[1]);
|
||||||
|
Coordinate b = new Coordinate(seg.get(1)[0], seg.get(1)[1]);
|
||||||
|
result.add(new Coordinate[]{a, b});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化线段列表为 JSON
|
||||||
|
*/
|
||||||
|
private String serializeSegments(List<Coordinate[]> segments) throws Exception {
|
||||||
|
List<List<double[]>> segList = segments.stream()
|
||||||
|
.map(seg -> Arrays.asList(
|
||||||
|
new double[]{seg[0].x, seg[0].y},
|
||||||
|
new double[]{seg[1].x, seg[1].y}))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return objectMapper.writeValueAsString(segList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 元数据内部类 ====================
|
||||||
|
public static class GridMetadata {
|
||||||
|
public double minLon;
|
||||||
|
public double minLat;
|
||||||
|
public double gridLonWidth;
|
||||||
|
public double gridLatWidth;
|
||||||
|
public double threshold;
|
||||||
|
|
||||||
|
// 无参构造(Jackson 反序列化需要)
|
||||||
|
public GridMetadata() {}
|
||||||
|
|
||||||
|
public GridMetadata(double minLon, double minLat, double gridLonWidth, double gridLatWidth, double threshold) {
|
||||||
|
this.minLon = minLon;
|
||||||
|
this.minLat = minLat;
|
||||||
|
this.gridLonWidth = gridLonWidth;
|
||||||
|
this.gridLatWidth = gridLatWidth;
|
||||||
|
this.threshold = threshold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package com.cdzy.operations.service.impl;
|
|||||||
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.cdzy.common.ex.EbikeException;
|
import com.cdzy.common.ex.EbikeException;
|
||||||
|
import com.cdzy.operations.component.GridIndexBuilder;
|
||||||
import com.cdzy.operations.enums.RegionStatus;
|
import com.cdzy.operations.enums.RegionStatus;
|
||||||
import com.cdzy.operations.mapper.EbikeOperationConfigMapper;
|
import com.cdzy.operations.mapper.EbikeOperationConfigMapper;
|
||||||
import com.cdzy.operations.mapper.EbikeOperationLockConfigMapper;
|
import com.cdzy.operations.mapper.EbikeOperationLockConfigMapper;
|
||||||
@ -55,7 +56,11 @@ public class EbikeRegionServiceImpl extends ServiceImpl<EbikeRegionMapper, Ebike
|
|||||||
@Resource
|
@Resource
|
||||||
private EbikeOperationReturnConfigMapper returnConfigMapper;
|
private EbikeOperationReturnConfigMapper returnConfigMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private GridIndexBuilder gridIndexBuilder;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public void save(EbikeRegionVo ebikeRegion) {
|
public void save(EbikeRegionVo ebikeRegion) {
|
||||||
EbikeRegion entity = EbikeRegion.builder()
|
EbikeRegion entity = EbikeRegion.builder()
|
||||||
.operatorId(ebikeRegion.getOperatorId())
|
.operatorId(ebikeRegion.getOperatorId())
|
||||||
@ -66,6 +71,7 @@ public class EbikeRegionServiceImpl extends ServiceImpl<EbikeRegionMapper, Ebike
|
|||||||
.createdBy(StpUtil.getLoginIdAsLong())
|
.createdBy(StpUtil.getLoginIdAsLong())
|
||||||
.build();
|
.build();
|
||||||
this.mapper.insert(entity);
|
this.mapper.insert(entity);
|
||||||
|
gridIndexBuilder.buildIndex(entity.getRegionPolygon(),entity.getRegionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -80,6 +86,7 @@ public class EbikeRegionServiceImpl extends ServiceImpl<EbikeRegionMapper, Ebike
|
|||||||
region.setRegionPolygon(ebikeRegion.getRegionPolygon());
|
region.setRegionPolygon(ebikeRegion.getRegionPolygon());
|
||||||
region.setRegionName(ebikeRegion.getRegionName());
|
region.setRegionName(ebikeRegion.getRegionName());
|
||||||
region.setRegionSimpleName(ebikeRegion.getRegionSimpleName());
|
region.setRegionSimpleName(ebikeRegion.getRegionSimpleName());
|
||||||
|
gridIndexBuilder.buildIndex(region.getRegionPolygon(),region.getRegionId());
|
||||||
this.mapper.update(region);
|
this.mapper.update(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package com.cdzy.operations.utils;
|
package com.cdzy.operations.utils;
|
||||||
|
|
||||||
import com.cdzy.common.enums.EbikeContents;
|
import com.cdzy.common.enums.EbikeContents;
|
||||||
import com.cdzy.operations.model.vo.EbikeEcuSnInfoVo;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
@ -180,8 +179,8 @@ public class RedisUtil {
|
|||||||
* @param key 键
|
* @param key 键
|
||||||
* @param map 多个Hash键值对
|
* @param map 多个Hash键值对
|
||||||
*/
|
*/
|
||||||
public void hSetAll(String key, Map<String, Object> map) {
|
public void hSetAll(String key, Map<String, Object> map,Integer DB) {
|
||||||
redisTemplate.opsForHash().putAll(key, map);
|
getRedisTemplate(DB).opsForHash().putAll(key, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,7 +309,7 @@ public class RedisUtil {
|
|||||||
* @param timeout 超时时间
|
* @param timeout 超时时间
|
||||||
* @param unit 时间单位
|
* @param unit 时间单位
|
||||||
*/
|
*/
|
||||||
public Boolean tryLock(String key, Object value, long timeout, TimeUnit unit) {
|
public Boolean tryLock(String key, Object value, long timeout, TimeUnit unit) {
|
||||||
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
|
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,6 +331,7 @@ public class RedisUtil {
|
|||||||
getRedisTemplate(database).opsForValue().set(key, value);
|
getRedisTemplate(database).opsForValue().set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置键值对到指定数据库并设置过期时间
|
* 设置键值对到指定数据库并设置过期时间
|
||||||
*/
|
*/
|
||||||
@ -603,4 +603,13 @@ public class RedisUtil {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 key 的类型
|
||||||
|
* @param key 键
|
||||||
|
* @return 类型字符串(如 "string", "hash", "none" 等)
|
||||||
|
*/
|
||||||
|
public String type(String key,Integer DB) {
|
||||||
|
return getRedisTemplate(DB).type(key).code();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user