7 changed files with 202 additions and 13 deletions
@ -0,0 +1,148 @@ |
|||||
|
package cc.hiver.mall.utils; |
||||
|
|
||||
|
import cc.hiver.core.common.redis.RedisTemplateHelper; |
||||
|
import cn.hutool.json.JSONUtil; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* 店铺主页数缓存工具类 |
||||
|
* <p> |
||||
|
* 底层使用 Redis Hash 结构,保证按 shopId + countType 维度的 O(1) 读写效率。 |
||||
|
* <pre> |
||||
|
* Key = SHOP_INDEX_COUNT:{shopId} |
||||
|
* Field = countType |
||||
|
* Value = Map<String,Integer> 的 JSON 序列化 |
||||
|
* </pre> |
||||
|
* <p> |
||||
|
* |
||||
|
* @author system |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class ShopIndexCountCacheUtil { |
||||
|
|
||||
|
/** Redis Key 前缀 */ |
||||
|
private static final String KEY_PREFIX = "SHOP:STATS:COUNT:"; |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisTemplateHelper redisTemplateHelper; |
||||
|
|
||||
|
// ================================================================
|
||||
|
// Key 构建
|
||||
|
// ================================================================
|
||||
|
private String buildKey(String shopId) { |
||||
|
return KEY_PREFIX + shopId; |
||||
|
} |
||||
|
|
||||
|
// ================================================================
|
||||
|
// 核心:更新操作 (增/改)
|
||||
|
// ================================================================
|
||||
|
|
||||
|
/** |
||||
|
* 更新单个字段的数值(支持原子增减) |
||||
|
* 场景:订单状态变更时,只修改特定计数(如:待出餐 +1) |
||||
|
* |
||||
|
* @param shopId 店铺ID |
||||
|
* @param field 字段名(如 "pendingCooking") |
||||
|
* @param delta 变化量(正数表示增加,负数表示减少) |
||||
|
*/ |
||||
|
public void incrementField(String shopId, String field, long delta) { |
||||
|
if (StringUtils.isBlank(shopId) || StringUtils.isBlank(field)) { |
||||
|
return; |
||||
|
} |
||||
|
try { |
||||
|
String key = buildKey(shopId); |
||||
|
// Redis hIncrBy 自动处理不存在的情况(默认为0)
|
||||
|
redisTemplateHelper.hIncrBy(key, field, delta); |
||||
|
log.debug("更新计数缓存: shopId={}, field={}, delta={}", shopId, field, delta); |
||||
|
} catch (Exception e) { |
||||
|
log.error("更新计数缓存失败: shopId={}, field={}", shopId, field, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 覆盖式更新(全量替换) |
||||
|
* 场景:后台管理手动修正数据,或初始化数据 |
||||
|
*/ |
||||
|
public void put(String shopId, ShopStatsCountDTO statsDTO) { |
||||
|
if (StringUtils.isBlank(shopId) || statsDTO == null) { |
||||
|
return; |
||||
|
} |
||||
|
try { |
||||
|
String key = buildKey(shopId); |
||||
|
String json = JSONUtil.toJsonStr(statsDTO); |
||||
|
redisTemplateHelper.hPut(key, statsDTO.getShopId(), json); |
||||
|
log.info("全量覆盖计数缓存: shopId={}", shopId); |
||||
|
} catch (Exception e) { |
||||
|
log.error("全量覆盖计数缓存失败: shopId={}", shopId, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ================================================================
|
||||
|
// 查询操作 (查)
|
||||
|
// ================================================================
|
||||
|
|
||||
|
/** |
||||
|
* 获取所有统计数据 |
||||
|
*/ |
||||
|
public ShopStatsCountDTO getAll(String shopId) { |
||||
|
if (StringUtils.isBlank(shopId)) { |
||||
|
return null; |
||||
|
} |
||||
|
try { |
||||
|
String key = buildKey(shopId); |
||||
|
Map<Object, Object> entries = redisTemplateHelper.hGetAll(key); |
||||
|
if (entries == null || entries.isEmpty()) { |
||||
|
return null; |
||||
|
} |
||||
|
// 将 Map 转回 DTO 对象
|
||||
|
String json = JSONUtil.toJsonStr(entries); |
||||
|
return JSONUtil.toBean(json, ShopStatsCountDTO.class); |
||||
|
} catch (Exception e) { |
||||
|
log.error("获取计数缓存失败: shopId={}", shopId, e); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取单个字段的数值 |
||||
|
* 场景:首页只展示“待出餐”数量,无需查出整个对象 |
||||
|
*/ |
||||
|
public Integer getCount(String shopId, String field) { |
||||
|
if (StringUtils.isBlank(shopId) || StringUtils.isBlank(field)) { |
||||
|
return 0; |
||||
|
} |
||||
|
try { |
||||
|
Object val = redisTemplateHelper.hGet(buildKey(shopId), field); |
||||
|
return val == null ? 0 : Integer.parseInt(val.toString()); |
||||
|
} catch (Exception e) { |
||||
|
log.error("获取单个计数失败: shopId={}, field={}", shopId, field, e); |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ================================================================
|
||||
|
// 删除操作 (删)
|
||||
|
// ================================================================
|
||||
|
|
||||
|
/** |
||||
|
* 删除指定店铺的所有统计数据缓存 |
||||
|
*/ |
||||
|
public void remove(String shopId) { |
||||
|
if (StringUtils.isBlank(shopId)) { |
||||
|
return; |
||||
|
} |
||||
|
try { |
||||
|
redisTemplateHelper.delete(buildKey(shopId)); |
||||
|
log.info("清除店铺计数缓存: shopId={}", shopId); |
||||
|
} catch (Exception e) { |
||||
|
log.error("清除店铺计数缓存失败: shopId={}", shopId, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
package cc.hiver.mall.utils; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
@Data |
||||
|
public class ShopStatsCountDTO implements Serializable { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
private String shopId; // 冗余存储,方便排查
|
||||
|
private Integer pendingConsumption = 0; // 待消费
|
||||
|
private Integer pendingCooking = 0; // 待出餐
|
||||
|
private Integer pendingPickup = 0; // 待取货
|
||||
|
private Integer pendingDelivery = 0; // 待送达
|
||||
|
|
||||
|
public ShopStatsCountDTO() {} |
||||
|
|
||||
|
public ShopStatsCountDTO(String shopId) { |
||||
|
this.shopId = shopId; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue