linxk 1 săptămână în urmă
părinte
comite
f03d57910d

+ 300 - 3
cif-service/src/main/java/com/txz/cif/core/RedisUtil.java

@@ -1,13 +1,12 @@
 package com.txz.cif.core;
 
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations;
 import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 
 import javax.annotation.Resource;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 
@@ -592,4 +591,302 @@ public class RedisUtil {
 		}
 	}
 
+
+	//===============================ZSet(排行榜)=============================
+	/**
+	 * 向有序集合添加元素(用于初始化或更新用户分数)
+	 * @param key 有序集合的键(排行榜名称)
+	 * @param value 元素值(如用户ID)
+	 * @param score 分数(排序依据)
+	 * @return true=新增元素,false=更新已有元素
+	 */
+	public Boolean zAdd(String key, Object value, double score) {
+		try {
+			return redisTemplate.opsForZSet().add(key, value, score);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	/**
+	 * 向有序集合添加元素并设置过期时间
+	 * @param key 有序集合的键
+	 * @param value 元素值
+	 * @param score 分数
+	 * @param time 过期时间
+	 * @param timeUnit 时间单位
+	 * @return true=操作成功
+	 */
+	public Boolean zAdd(String key, Object value, double score, long time, TimeUnit timeUnit) {
+		try {
+			Boolean result = redisTemplate.opsForZSet().add(key, value, score);
+			if (time > 0) {
+				expire(key, time, timeUnit);
+			}
+			return result;
+		} catch (Exception e) {
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	/**
+	 * 增加有序集合中元素的分数(用于排行榜分数累加)
+	 * @param key 有序集合的键
+	 * @param value 元素值
+	 * @param delta 增加的分数(正数增加,负数减少)
+	 * @return 增加后的分数
+	 */
+	public Double zIncrBy(String key, Object value, double delta) {
+		try {
+			return redisTemplate.opsForZSet().incrementScore(key, value, delta);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+	/**
+	 * 获取元素在有序集合中的排名(从高到低)
+	 * @param key 有序集合的键
+	 * @param value 元素值
+	 * @return 排名(0表示第一名,null表示元素不存在)
+	 */
+	public Long zRevRank(String key, Object value) {
+		try {
+			return redisTemplate.opsForZSet().reverseRank(key, value);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+	/**
+	 * 获取元素在有序集合中的分数
+	 * @param key 有序集合的键
+	 * @param value 元素值
+	 * @return 分数(null表示元素不存在)
+	 */
+	public Double zScore(String key, Object value) {
+		try {
+			return redisTemplate.opsForZSet().score(key, value);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+	/**
+	 * 获取有序集合中指定排名范围的元素(从高到低)
+	 * @param key 有序集合的键
+	 * @param start 起始排名(0表示第一名)
+	 * @param end 结束排名(如9表示前10名)
+	 * @return 元素列表(仅包含值,不包含分数)
+	 */
+	public Set<Object> zRevRange(String key, long start, long end) {
+		try {
+			return redisTemplate.opsForZSet().reverseRange(key, start, end);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+	/**
+	 * 获取有序集合中指定排名范围的元素(包含分数,从高到低)
+	 * @param key 有序集合的键
+	 * @param start 起始排名(0表示第一名)
+	 * @param end 结束排名(如9表示前10名)
+	 * @return 元素-分数映射集合(ZSetOperations.TypedTuple包含value和score)
+	 */
+	public Set<ZSetOperations.TypedTuple<Object>> zRevRangeWithScores(String key, long start, long end) {
+		try {
+			return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+	/**
+	 * 获取有序集合中分数在指定范围内的元素数量
+	 * @param key 有序集合的键
+	 * @param min 最小分数
+	 * @param max 最大分数
+	 * @return 元素数量
+	 */
+	public Long zCount(String key, double min, double max) {
+		try {
+			return redisTemplate.opsForZSet().count(key, min, max);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return 0L;
+		}
+	}
+
+	/**
+	 * 获取有序集合的总元素数量(排行榜总人数)
+	 * @param key 有序集合的键
+	 * @return 总数量
+	 */
+	public Long zSize(String key) {
+		try {
+			return redisTemplate.opsForZSet().size(key);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return 0L;
+		}
+	}
+
+	/**
+	 * 从有序集合中删除元素
+	 * @param key 有序集合的键
+	 * @param values 要删除的元素(可多个)
+	 * @return 成功删除的数量
+	 */
+	public Long zRemove(String key, Object... values) {
+		try {
+			return redisTemplate.opsForZSet().remove(key, values);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return 0L;
+		}
+	}
+
+
+// ========================= ZSet 分数编码工具方法 =========================
+	/**
+	 * 生成复合分数(原始分数 + 时间戳)
+	 * 同原始分数时,较早录入的元素分数更高(排前面)
+	 * @param originalScore 原始分数(业务分数)
+	 * @param timestamp 录入时间戳(毫秒),建议使用System.currentTimeMillis()
+	 * @return 复合分数(可直接用于Redis的ZSet)
+	 */
+	private double encodeScore(double originalScore, long timestamp) {
+		// 权重:确保原始分数为第一排序维度(1e18需大于最大可能的时间戳,避免冲突)
+		long weight = 1000_000_000_000_000_000L; // 1e18
+		// 反向时间戳:将时间戳反转,使较早的时间戳对应更大的值
+		long reversedTimestamp = Long.MAX_VALUE - timestamp;
+		// 复合分数 = 原始分数 * 权重 + 反向时间戳(保证原始分数优先,同分时时间早的排前面)
+		return originalScore * weight + reversedTimestamp;
+	}
+
+	/**
+	 * 从复合分数中解析原始分数
+	 * @param encodedScore 复合分数
+	 * @return 原始业务分数
+	 */
+	public double decodeOriginalScore(double encodedScore) {
+		long weight = 1000_000_000_000_000_000L;
+		return Math.floor(encodedScore / weight);
+	}
+
+	/**
+	 * 从复合分数中解析录入时间戳
+	 * @param encodedScore 复合分数
+	 * @return 录入时的时间戳(毫秒)
+	 */
+	public long decodeTimestamp(double encodedScore) {
+		long weight = 1000_000_000_000_000_000L;
+		long reversedTimestamp = (long) (encodedScore % weight);
+		return Long.MAX_VALUE - reversedTimestamp;
+	}
+
+
+
+
+// ========================= 支持时间排序的排行榜方法 =========================
+	/**
+	 * 新增/更新用户分数(同分时,先录入的排前面)
+	 * @param key 排行榜键名
+	 * @param userId 用户ID(元素值)
+	 * @param originalScore 原始业务分数
+	 * @param timestamp 录入时间戳(毫秒,建议使用System.currentTimeMillis())
+	 * @return true=新增元素,false=更新已有元素
+	 */
+	public Boolean zAddWithTimeOrder(String key, Object userId, double originalScore, long timestamp) {
+		try {
+			double encodedScore = encodeScore(originalScore, timestamp);
+			return redisTemplate.opsForZSet().add(key, userId, encodedScore);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	/**
+	 * 新增/更新用户分数并设置过期时间
+	 * @param key 排行榜键名
+	 * @param userId 用户ID
+	 * @param originalScore 原始业务分数
+	 * @param timestamp 录入时间戳
+	 * @param time 过期时间
+	 * @param timeUnit 时间单位
+	 * @return true=操作成功
+	 */
+	public Boolean zAddWithTimeOrder(String key, Object userId, double originalScore, long timestamp,
+									 long time, TimeUnit timeUnit) {
+		try {
+			double encodedScore = encodeScore(originalScore, timestamp);
+			Boolean result = redisTemplate.opsForZSet().add(key, userId, encodedScore);
+			if (time > 0) {
+				expire(key, time, timeUnit);
+			}
+			return result;
+		} catch (Exception e) {
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	/**
+	 * 获取用户的原始分数(业务分数)
+	 * @param key 排行榜键名
+	 * @param userId 用户ID
+	 * @return 原始分数(null表示用户不存在)
+	 */
+	public Double zGetOriginalScore(String key, Object userId) {
+		Double encodedScore = zScore(key, userId); // 复用之前的zScore方法
+		return encodedScore != null ? decodeOriginalScore(encodedScore) : null;
+	}
+
+	/**
+	 * 获取用户的录入时间戳
+	 * @param key 排行榜键名
+	 * @param userId 用户ID
+	 * @return 时间戳(毫秒,null表示用户不存在)
+	 */
+	public Long zGetTimestamp(String key, Object userId) {
+		Double encodedScore = zScore(key, userId);
+		return encodedScore != null ? decodeTimestamp(encodedScore) : null;
+	}
+
+	/**
+	 * 获取排行榜指定范围(从高到低),包含原始分数和时间戳
+	 * @param key 排行榜键名
+	 * @param start 起始排名(0=第一名)
+	 * @param end 结束排名(如9=前10名)
+	 * @return 列表元素包含:用户ID、原始分数、时间戳
+	 */
+	public List<Map<String, Object>> zRevRangeWithDetails(String key, long start, long end) {
+		try {
+			Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
+			if (tuples == null) {
+				return null;
+			}
+			List<Map<String, Object>> result = new ArrayList<>();
+			for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
+				Map<String, Object> detail = new HashMap<>();
+				detail.put("userId", tuple.getValue());
+				detail.put("originalScore", decodeOriginalScore(tuple.getScore()));
+				detail.put("timestamp", decodeTimestamp(tuple.getScore()));
+				result.add(detail);
+			}
+			return result;
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
 }

+ 2 - 0
cif-service/src/main/java/com/txz/cif/core/cache/CacheType.java

@@ -11,6 +11,8 @@ public enum CacheType {
 	 */
 	AppSecretKey,
 
+	Top,
+
 	sjtcRefundCallback,
 
 	// ------------------------- 订单相关 -------------------------

+ 13 - 5
cif-service/src/main/java/com/txz/cif/web/RedEnvelopeApiController.java

@@ -1,14 +1,13 @@
 package com.txz.cif.web;
 import cn.hutool.core.date.DateUtil;
-import com.txz.cif.core.AuthService;
-import com.txz.cif.core.Result;
-import com.txz.cif.core.ResultGenerator;
+import cn.hutool.json.JSONUtil;
+import com.txz.cif.core.*;
+import com.txz.cif.core.cache.CacheKey;
+import com.txz.cif.core.cache.CacheType;
 import com.txz.cif.model.RedEnvelope;
 import com.txz.cif.model.User;
 import com.txz.cif.service.RedEnvelopeService;
 
-import com.txz.cif.core.ResultCode;
-
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import com.txz.cif.service.UserService;
@@ -50,6 +49,9 @@ public class RedEnvelopeApiController {
 	@Resource
 	private AuthService authService;
 
+	@Resource
+	private RedisUtil redisUtil;
+
 	@Resource
 	private UserService userService;
 
@@ -97,6 +99,11 @@ public class RedEnvelopeApiController {
 	@GetMapping("/top")
 	@ApiOperation(value = "排行榜type 1 7天收益排行",httpMethod = "GET")
 	public Result<List<UserTopBo>> top(@RequestParam(defaultValue = "1") Integer type,@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "10") Integer size) {
+		String key = CacheKey.generateKey(CacheType.Top,type+":"+page+":"+size).toString();
+		Object o = redisUtil.get(key);
+		if (o != null){
+			return ResultGenerator.genSuccessResult(JSONUtil.toBean(o.toString(),PageInfo.class));
+		}
 		List<UserTopBo> tops= redEnvelopeService.top(type,page,size);
 		PageInfo pageInfo = new PageInfo(tops);
 		pageInfo.setList(tops.stream().map(e->{
@@ -122,6 +129,7 @@ public class RedEnvelopeApiController {
 			}
 			return e;
 		}).collect(Collectors.toList()));
+		redisUtil.set(key,JSONUtil.toJsonStr(pageInfo));
 		return ResultGenerator.genSuccessResult(pageInfo);
 	}
 }

+ 1 - 1
cif-service/src/main/resources/mapper/RedEnvelopeMapper.xml

@@ -56,6 +56,6 @@
     GROUP BY
     user_id
     ORDER BY
-    L7DEarnings DESC;
+    L7DEarnings DESC
   </select>
 </mapper>