page = mapper.page(request.plusPage(), wrapper);
+ String cursor = Optional.ofNullable(CollectionUtil.getLast(page.getRecords()))
+ .map(cursorColumn)
+ .map(String::valueOf)
+ .orElse(null);
+ Boolean isLast = page.getRecords().size() != request.getPageSize();
+ return new CursorPageBaseResp(cursor, isLast, page.getRecords());
+ }
+
+}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java
new file mode 100644
index 0000000..a7c00df
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java
@@ -0,0 +1,87 @@
+package com.abin.mallchat.common.common.utils;
+
+import cn.hutool.json.JSONUtil;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.auth0.jwt.interfaces.JWTVerifier;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * Description: jwt的token生成与解析
+ * Author: abin
+ * Date: 2023-04-03
+ */
+@Slf4j
+@Component
+public class JwtUtils {
+
+ /**
+ * token秘钥,请勿泄露,请勿随便修改
+ */
+ @Value("jwt.secret")
+ private String secret;
+
+ private static final String UID_CLAIM = "uid";
+
+ /**
+ * JWT生成Token.
+ *
+ * JWT构成: header, payload, signature
+ */
+ public String createToken(Long uid) {
+ // build token
+ String token = JWT.create()
+ .withClaim(UID_CLAIM, uid) // 只存一个uid信息,其他的自己去redis查
+ .sign(Algorithm.HMAC256(secret)); // signature
+ return token;
+ }
+
+ public static void main(String[] args) {
+ JwtUtils jwtUtils = new JwtUtils();
+ String token = jwtUtils.createToken(123L);
+ System.out.println(token);
+ }
+
+ /**
+ * 解密Token
+ *
+ * @param token
+ * @return
+ */
+ public Map verifyToken(String token) {
+ if (Objects.isNull(token)) {
+ return null;
+ }
+ try {
+ JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
+ DecodedJWT jwt = verifier.verify(token);
+ return jwt.getClaims();
+ } catch (Exception e) {
+ log.info("decode error,token:{}", token, e);
+ }
+ return null;
+ }
+
+
+ /**
+ * 根据Token获取uid
+ *
+ * @param token
+ * @return uid
+ */
+ public Long getUidOrNull(String token) {
+ return Optional.ofNullable(verifyToken(token))
+ .map(map -> map.get(UID_CLAIM))
+ .map(Claim::asLong)
+ .orElse(null);
+ }
+
+}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RedisUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RedisUtils.java
new file mode 100644
index 0000000..5124b44
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RedisUtils.java
@@ -0,0 +1,1081 @@
+package com.abin.mallchat.common.common.utils;
+
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import net.sf.json.util.JSONUtils;
+import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+import java.security.Key;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+
+@Slf4j
+@Component
+public class RedisUtils {
+ private static final ObjectMapper jsonMapper = new ObjectMapper();
+ public RedisTemplate redisTemplate;
+
+ public StringRedisTemplate stringRedisTemplate;
+
+ public RedisUtils(RedisTemplate redisTemplate, StringRedisTemplate stringRedisTemplate) {
+ this.redisTemplate = redisTemplate;
+ this.stringRedisTemplate = stringRedisTemplate;
+ }
+
+ private static final String LUA_INCR_EXPIRE =
+ "local key,ttl=KEYS[1],ARGV[1] \n" +
+ " \n" +
+ "if redis.call('EXISTS',key)==0 then \n" +
+ " redis.call('SETEX',key,ttl,1) \n" +
+ " return 1 \n" +
+ "else \n" +
+ " return tonumber(redis.call('INCR',key)) \n" +
+ "end ";
+
+ public Long inc(String key, int time, TimeUnit unit) {
+ RedisScript redisScript = new DefaultRedisScript<>(LUA_INCR_EXPIRE, Long.class);
+ return (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), unit.toSeconds(time));
+ }
+
+ /**
+ * 指定缓存失效时间
+ *
+ * @param key 键
+ * @param time 时间(秒)
+ */
+ public boolean expire(String key, long time) {
+ try {
+ if (time > 0) {
+ redisTemplate.expire(key, time, TimeUnit.SECONDS);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 指定缓存失效时间
+ *
+ * @param key 键
+ * @param time 时间(秒)
+ * @param timeUnit 单位
+ */
+ public boolean expire(String key, long time, TimeUnit timeUnit) {
+ try {
+ if (time > 0) {
+ redisTemplate.expire(key, time, timeUnit);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 根据 key 获取过期时间
+ *
+ * @param key 键 不能为null
+ * @return 时间(秒) 返回0代表为永久有效
+ */
+ public long getExpire(Object key) {
+ return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 根据 key 获取过期时间
+ *
+ * @param key 键 不能为null
+ * @return 时间(秒) 返回0代表为永久有效
+ */
+ public long getExpire(Object key, TimeUnit timeUnit) {
+ return redisTemplate.getExpire(key, timeUnit);
+ }
+
+ /**
+ * 查找匹配key
+ *
+ * @param pattern key
+ * @return /
+ */
+ public List scan(String pattern) {
+ ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+ RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+ RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+ Cursor cursor = rc.scan(options);
+ List result = new ArrayList<>();
+ while (cursor.hasNext()) {
+ result.add(new String(cursor.next()));
+ }
+ try {
+ RedisConnectionUtils.releaseConnection(rc, factory);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return result;
+ }
+
+ /**
+ * 分页查询 key
+ *
+ * @param patternKey key
+ * @param page 页码
+ * @param size 每页数目
+ * @return /
+ */
+ public List findKeysForPage(String patternKey, int page, int size) {
+ ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
+ RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+ RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+ Cursor cursor = rc.scan(options);
+ List result = new ArrayList<>(size);
+ int tmpIndex = 0;
+ int fromIndex = page * size;
+ int toIndex = page * size + size;
+ while (cursor.hasNext()) {
+ if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
+ result.add(new String(cursor.next()));
+ tmpIndex++;
+ continue;
+ }
+ // 获取到满足条件的数据后,就可以退出了
+ if (tmpIndex >= toIndex) {
+ break;
+ }
+ tmpIndex++;
+ cursor.next();
+ }
+ try {
+ RedisConnectionUtils.releaseConnection(rc, factory);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return result;
+ }
+
+ /**
+ * 判断key是否存在
+ *
+ * @param key 键
+ * @return true 存在 false不存在
+ */
+ public boolean hasKey(String key) {
+ try {
+ return redisTemplate.hasKey(key);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+
+ /**
+ * 删除缓存
+ *
+ * @param keys
+ */
+ public void del(String... keys) {
+ if (keys != null && keys.length > 0) {
+ if (keys.length == 1) {
+ boolean result = redisTemplate.delete(keys[0]);
+ log.debug("--------------------------------------------");
+ log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString());
+ log.debug("--------------------------------------------");
+ } else {
+ Set