SpringBoot 实现用户登录,分布式Session功能

avatar 2019年7月22日16:32:56 3 988 views

之前介绍过不少关于登录功能的代码,本文介绍一下关于分布式Session 的功能实现,

完整代码(以后写博客,尽量给 git 地址)在 https://github.com/saysky/sensboot

 

通常,我们的项目都是多实例部署的,生产环境通常一个模块可能都会部署四五台服务器。

我们知道用户登录后,需要存储 session 信息,session 信息通常是存储在服务器的内存中的,不能持久化(服务器重启失效),多台服务器也不能共存。为了解决这个问题,我们可以将 session 存到几个服务器共享的地方里去,比如 Redis,只要在一个内网中,几台服务器可以共享 Redis (Redis本质也是装在某台服务器中)。

具体怎么实现呢?这里简单描述下:

  1. 用户登录成功,通过UUID生成一个随机唯一字符串,命名为 token,通过向 redis 中 set 一个值,key 为 token 字符串,value 为用户对象序列化后的字符串。
  2. 当用户访问其他页面,请求方法时,检验请求参数或 cookie 中是否有 token
  3. 如果有,则从 redis 查询 token,验证 token 是否有效
  4. 如果没有,则抛出异常 “用户未登录”

 

关于参数验证,这里可以通过 SpringMVC 的 resolveArgument 方法来统一解决,即所有方法参数验证时都会验证用户名是否登录。而不需要在每个方法里都写一段检查用户名是否登录,这样就太冗余了。

 

下面是具体实现,由上到下(重要到次要)贴代码,完整代码在 GitHub 中可以获取。

 

一、基本登录

  1. LoginController

登录的实现在 UserServiceImpl 中

  1. package com.liuyanzhao.sens.controller;
  2. import com.liuyanzhao.sens.result.Result;
  3. import com.liuyanzhao.sens.service.UserService;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.web.bind.annotation.PostMapping;
  8. import org.springframework.web.bind.annotation.RequestParam;
  9. import org.springframework.web.bind.annotation.ResponseBody;
  10. import javax.servlet.http.HttpServletResponse;
  11. /**
  12.  * @author 言曌
  13.  * @date 2019-07-22 14:07
  14.  */
  15. @Controller
  16. @Slf4j
  17. public class LoginController {
  18.     @Autowired
  19.     private UserService userService;
  20.     /**
  21.      * 登录功能
  22.      * 验证用户名和密码,登录成功,生成token,存入到redis中
  23.      * 登录成功
  24.      *
  25.      * @param response
  26.      * @param username
  27.      * @param password
  28.      * @return
  29.      */
  30.     @PostMapping("/doLogin")
  31.     @ResponseBody
  32.     public Result<String> doLogin(HttpServletResponse response,
  33.                                   @RequestParam("username") String username,
  34.                                   @RequestParam("password") String password) {
  35.         //登录
  36.         log.info("用户登录:username:{}, password:{}", username, password);
  37.         //判断用户名是否存在
  38.         String token = userService.login(response, username, password);
  39.         return Result.success(token);
  40.     }
  41. }

 

2. UserServiceImpl

为了代码简洁,UserService 接口这里就不贴了

  1. package com.liuyanzhao.sens.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.baomidou.mybatisplus.plugins.Page;
  4. import com.liuyanzhao.sens.entity.User;
  5. import com.liuyanzhao.sens.exception.GlobalException;
  6. import com.liuyanzhao.sens.mapper.UserMapper;
  7. import com.liuyanzhao.sens.result.CodeMsg;
  8. import com.liuyanzhao.sens.service.UserService;
  9. import com.liuyanzhao.sens.utils.RedisUtil;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.stereotype.Service;
  12. import org.springframework.util.StringUtils;
  13. import javax.servlet.http.Cookie;
  14. import javax.servlet.http.HttpServletResponse;
  15. import java.util.UUID;
  16. /**
  17.  * 用户业务逻辑实现类
  18.  * MyBatis Plus 版本
  19.  */
  20. @Service
  21. public class UserServiceImpl implements UserService {
  22.     public static final String COOKIE_NAME_TOKEN = "token";
  23.     /**
  24.      * token过期时间,2天
  25.      */
  26.     public static final int TOKEN_EXPIRE = 3600 * 24 * 2;
  27.     @Autowired
  28.     private UserMapper userMapper;
  29.     @Autowired
  30.     private RedisUtil redisUtil;
  31.     @Override
  32.     public User findByUsername(String username) {
  33.         return userMapper.findByUsername(username);
  34.     }
  35.     @Override
  36.     public String login(HttpServletResponse response, String username, String password) {
  37.         //判断用户名是否存在
  38.         User user = findByUsername(username);
  39.         if (user == null) {
  40.             throw new GlobalException(CodeMsg.USERNAME_NOT_EXIST);
  41.         }
  42.         //验证密码,这里为了例子简单,密码没有加密
  43.         String dbPass = user.getPassword();
  44.         if (!password.equals(dbPass)) {
  45.             throw new GlobalException(CodeMsg.PASSWORD_ERROR);
  46.         }
  47.         //生成cookie
  48.         String token = UUID.randomUUID().toString().replace("-""");
  49.         addCookie(response, token, user);
  50.         return token;
  51.     }
  52.     @Override
  53.     public User getByToken(HttpServletResponse response, String token) {
  54.         if (StringUtils.isEmpty(token)) {
  55.             return null;
  56.         }
  57.         User user = JSON.parseObject(redisUtil.get(COOKIE_NAME_TOKEN + "::" + token), User.class);
  58.         //重置有效期
  59.         if (user == null) {
  60.             throw new GlobalException(CodeMsg.USER_NOT_LOGIN);
  61.         }
  62.         addCookie(response, token, user);
  63.         return user;
  64.     }
  65.     private void addCookie(HttpServletResponse response, String token, User user) {
  66.         //将token存入到redis
  67.         redisUtil.set(COOKIE_NAME_TOKEN + "::" + token, JSON.toJSONString(user), TOKEN_EXPIRE);
  68.         //将token写入cookie
  69.         Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token);
  70.         cookie.setMaxAge(TOKEN_EXPIRE);
  71.         cookie.setPath("/");
  72.         response.addCookie(cookie);
  73.     }
  74. }

 

3. UserMapper Dao层 和 User 实体 这里也不贴了

我相信你都学到了分布式 Session 这里,MyBatis 的基本使用应该不成问题吧

GitHub里也有完整代码

 

登录成功,目前密码是没有加密的,登录成功,data里有 token 字符串,前端可以将 token

存起来,比如 APP 端,没有 cookie 这种东西的话,可以存在 localStorage,然后请求的时候携带 token 到参数上即可。

目前我们后端是将 token 存在 cookie 里,所以前端非APP端,无需携带参数。

 

 

二、封装参数验证

  1. UserArgumentResolver
  1. package com.liuyanzhao.sens.config;
  2. import com.liuyanzhao.sens.entity.User;
  3. import com.liuyanzhao.sens.exception.GlobalException;
  4. import com.liuyanzhao.sens.result.CodeMsg;
  5. import com.liuyanzhao.sens.service.UserService;
  6. import com.liuyanzhao.sens.service.impl.UserServiceImpl;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.core.MethodParameter;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.util.StringUtils;
  11. import org.springframework.web.bind.support.WebDataBinderFactory;
  12. import org.springframework.web.context.request.NativeWebRequest;
  13. import org.springframework.web.method.support.HandlerMethodArgumentResolver;
  14. import org.springframework.web.method.support.ModelAndViewContainer;
  15. import javax.servlet.http.Cookie;
  16. import javax.servlet.http.HttpServletRequest;
  17. import javax.servlet.http.HttpServletResponse;
  18. /**
  19.  * 用户参数验证,验证是否有token
  20.  */
  21. @Service
  22. public class UserArgumentResolver implements HandlerMethodArgumentResolver {
  23.     @Autowired
  24.     private UserService userService;
  25.     @Override
  26.     public boolean supportsParameter(MethodParameter parameter) {
  27.         Class<?> clazz = parameter.getParameterType();
  28.         return clazz == User.class;
  29.     }
  30.     @Override
  31.     public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
  32.                                   NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  33.         HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
  34.         HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
  35.         String paramToken = request.getParameter(UserServiceImpl.COOKIE_NAME_TOKEN);
  36.         String cookieToken = getCookieValue(request, UserServiceImpl.COOKIE_NAME_TOKEN);
  37.         if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
  38.             // return null;
  39.             throw new GlobalException(CodeMsg.USER_NOT_LOGIN);
  40.         }
  41.         String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
  42.         return userService.getByToken(response, token);
  43.     }
  44.     private String getCookieValue(HttpServletRequest request, String cookiName) {
  45.         Cookie[] cookies = request.getCookies();
  46.         if (cookies == null || cookies.length <= 0) {
  47.             // return null;
  48.             throw new GlobalException(CodeMsg.TOKEN_INVALID);
  49.         }
  50.         for (Cookie cookie : cookies) {
  51.             if (cookie.getName().equals(cookiName)) {
  52.                 return cookie.getValue();
  53.             }
  54.         }
  55.         return null;
  56.     }
  57. }

 

2. WebConfig

  1. package com.liuyanzhao.sens.config;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.cors.CorsConfiguration;
  6. import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  7. import org.springframework.web.filter.CorsFilter;
  8. import org.springframework.web.method.support.HandlerMethodArgumentResolver;
  9. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  10. import java.util.List;
  11. /**
  12.  * @author 言曌
  13.  * @date 2019-06-26 22:37
  14.  */
  15. @Configuration
  16. public class WebConfig implements WebMvcConfigurer {
  17.     @Autowired
  18.     UserArgumentResolver userArgumentResolver;
  19.     @Override
  20.     public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
  21.         //所有方法执行前,都会验证参数,检查token是否存在
  22.         argumentResolvers.add(userArgumentResolver);
  23.     }
  24.     /**
  25.      * 解决跨域
  26.      * @return
  27.      */
  28.     @Bean
  29.     public CorsFilter corsFilter() {
  30.         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  31.         source.registerCorsConfiguration("/**", buildConfig());
  32.         return new CorsFilter(source);
  33.     }
  34.     private CorsConfiguration buildConfig() {
  35.         CorsConfiguration corsConfiguration = new CorsConfiguration();
  36.         corsConfiguration.addAllowedOrigin("*");
  37.         corsConfiguration.setAllowCredentials(true);
  38.         corsConfiguration.addAllowedHeader("*");
  39.         corsConfiguration.addAllowedMethod("*");
  40.         return corsConfiguration;
  41.     }
  42. }

 

如上配置,可以实现 SpringMVC 验证参数时,会执行上面我们重写的 resolveArgument  方法,该方法的目的是验证请求参数或 cookie 中是否有 token,如果有则根据 token 查询用户,然后返回(如果返回了 user 对象,会自动注入到 参数中),如下 UserController 中 current 方法示例,User user 里已有用户信息。

 

3. 验证请求

UserController

  1. package com.liuyanzhao.sens.controller;
  2. import com.liuyanzhao.sens.entity.User;
  3. import com.liuyanzhao.sens.result.Result;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.web.bind.annotation.*;
  6. /**
  7.  * 后台用户管理控制器
  8.  * 异常不用捕获,用统一异常捕获处理
  9.  *
  10.  * @author liuyanzhao
  11.  */
  12. @Slf4j
  13. @RestController
  14. @RequestMapping(value = "/user")
  15. public class UserController {
  16.     /**
  17.      * 当前登录用户
  18.      *
  19.      * @param user 用户信息,由封装注入
  20.      * @return
  21.      */
  22.     @RequestMapping("/current")
  23.     @ResponseBody
  24.     public Result<User> current(User user) {
  25.         return Result.success(user);
  26.     }
  27. }

 

这是登录后的,调用查询查询当前登录用户信息接口,即对应上面的方法

注意:cookies 不用自己客户端(请求方)管理,如果 浏览器或者 PostMan 对应该域名有 cookie 的话,会自动携带的,后端能直接获取的。

 

如果我这里,浏览器(或PostMan)清除 cookie,或者 token 过期,再次请求,就会返回用户未登录的状态信息

 

三、返回体和响应码封装

  1. 封装返回对象
  1. package com.liuyanzhao.sens.result;
  2. public class Result<T> {
  3.     private int code;
  4.     private String msg;
  5.     private T data;
  6.     /**
  7.      * 成功时候的调用
  8.      */
  9.     public static <T> Result<T> success() {
  10.         return new Result<T>(200"成功");
  11.     }
  12.     /**
  13.      * 成功时候的调用
  14.      */
  15.     public static <T> Result<T> success(T data) {
  16.         return new Result<T>(200"成功", data);
  17.     }
  18.     /**
  19.      * 失败时候的调用
  20.      */
  21.     public static <T> Result<T> error(CodeMsg codeMsg) {
  22.         return new Result<T>(codeMsg);
  23.     }
  24.     private Result(T data) {
  25.         this.data = data;
  26.     }
  27.     private Result(int code, String msg) {
  28.         this.code = code;
  29.         this.msg = msg;
  30.     }
  31.     private Result(int code, String msg, T data) {
  32.         this.code = code;
  33.         this.msg = msg;
  34.         this.data = data;
  35.     }
  36.     private Result(CodeMsg codeMsg) {
  37.         if (codeMsg != null) {
  38.             this.code = codeMsg.getCode();
  39.             this.msg = codeMsg.getMsg();
  40.         }
  41.     }
  42.     public int getCode() {
  43.         return code;
  44.     }
  45.     public void setCode(int code) {
  46.         this.code = code;
  47.     }
  48.     public String getMsg() {
  49.         return msg;
  50.     }
  51.     public void setMsg(String msg) {
  52.         this.msg = msg;
  53.     }
  54.     public T getData() {
  55.         return data;
  56.     }
  57.     public void setData(T data) {
  58.         this.data = data;
  59.     }
  60. }

 

2. 状态码

  1. package com.liuyanzhao.sens.result;
  2. /**
  3.  * 状态码,错误码
  4.  * @author liuyanzhao
  5.  */
  6. public class CodeMsg {
  7.     private int code;
  8.     private String msg;
  9.     //通用的错误码
  10.     public static CodeMsg SUCCESS = new CodeMsg(200"成功");
  11.     public static CodeMsg SERVER_ERROR = new CodeMsg(500100"服务端异常");
  12.     public static CodeMsg BIND_ERROR = new CodeMsg(500101"参数校验异常:%s");
  13.     public static CodeMsg REQUEST_ILLEGAL = new CodeMsg(500102"请求非法");
  14.     public static CodeMsg ACCESS_LIMIT_REACHED = new CodeMsg(500103"访问太频繁!");
  15.     //登录模块 5002XX
  16.     public static CodeMsg USER_NOT_LOGIN = new CodeMsg(500200"用户未登录");
  17.     public static CodeMsg TOKEN_INVALID = new CodeMsg(500201"token无效");
  18.     public static CodeMsg USERNAME_NOT_EXIST = new CodeMsg(500202"用户名不存在");
  19.     public static CodeMsg PASSWORD_ERROR = new CodeMsg(500203"密码错误");
  20.     private CodeMsg() {
  21.     }
  22.     private CodeMsg(int code, String msg) {
  23.         this.code = code;
  24.         this.msg = msg;
  25.     }
  26.     public int getCode() {
  27.         return code;
  28.     }
  29.     public void setCode(int code) {
  30.         this.code = code;
  31.     }
  32.     public String getMsg() {
  33.         return msg;
  34.     }
  35.     public void setMsg(String msg) {
  36.         this.msg = msg;
  37.     }
  38.     public CodeMsg fillArgs(Object... args) {
  39.         int code = this.code;
  40.         String message = String.format(this.msg, args);
  41.         return new CodeMsg(code, message);
  42.     }
  43.     @Override
  44.     public String toString() {
  45.         return "CodeMsg ";
  46.     }
  47. }

 

 

四、统一异常处理

  1. 自定义统一异常
  1. package com.liuyanzhao.sens.exception;
  2. import com.liuyanzhao.sens.result.CodeMsg;
  3. /**
  4.  * 统一异常
  5.  */
  6. public class GlobalException extends RuntimeException{
  7.     private static final long serialVersionUID = 1L;
  8.     private CodeMsg cm;
  9.     public GlobalException(CodeMsg cm) {
  10.         super(cm.toString());
  11.         this.cm = cm;
  12.     }
  13.     public CodeMsg getCm() {
  14.         return cm;
  15.     }
  16. }

 

2. 统一异常处理类

  1. package com.liuyanzhao.sens.exception;
  2. import java.util.List;
  3. import javax.servlet.http.HttpServletRequest;
  4. import com.liuyanzhao.sens.result.CodeMsg;
  5. import com.liuyanzhao.sens.result.Result;
  6. import org.springframework.validation.BindException;
  7. import org.springframework.validation.ObjectError;
  8. import org.springframework.web.bind.annotation.ControllerAdvice;
  9. import org.springframework.web.bind.annotation.ExceptionHandler;
  10. import org.springframework.web.bind.annotation.ResponseBody;
  11. /**
  12.  * 统一异常处理
  13.  */
  14. @ControllerAdvice
  15. @ResponseBody
  16. public class GlobalExceptionHandler {
  17.     @ExceptionHandler(value = Exception.class)
  18.     public Result<String> exceptionHandler(HttpServletRequest request, Exception e) {
  19.         e.printStackTrace();
  20.         if (e instanceof GlobalException) {
  21.             GlobalException ex = (GlobalException) e;
  22.             return Result.error(ex.getCm());
  23.         } else if (e instanceof BindException) {
  24.             BindException ex = (BindException) e;
  25.             List<ObjectError> errors = ex.getAllErrors();
  26.             ObjectError error = errors.get(0);
  27.             String msg = error.getDefaultMessage();
  28.             return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
  29.         } else {
  30.             return Result.error(CodeMsg.SERVER_ERROR);
  31.         }
  32.     }
  33. }

 

五、Redis 封装类

  1. package com.liuyanzhao.sens.utils;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.data.redis.core.StringRedisTemplate;
  4. import org.springframework.stereotype.Component;
  5. import java.util.Map;
  6. import java.util.Set;
  7. import java.util.concurrent.TimeUnit;
  8. /**
  9.  * @author 言曌
  10.  * @date 2018/12/16 下午6:57
  11.  */
  12. @Component
  13. public class RedisUtil {
  14.     @Autowired
  15.     private StringRedisTemplate redisTemplate;
  16.     // Key(键),简单的key-value操作
  17.     /**
  18.      * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
  19.      *
  20.      * @param key
  21.      * @return
  22.      */
  23.     public long ttl(String key) {
  24.         return redisTemplate.getExpire(key);
  25.     }
  26.     /**
  27.      * 实现命令:expire 设置过期时间,单位秒
  28.      *
  29.      * @param key
  30.      * @return
  31.      */
  32.     public void expire(String key, long timeout) {
  33.         redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
  34.     }
  35.     /**
  36.      * 实现命令:INCR key,增加key一次
  37.      *
  38.      * @param key
  39.      * @return
  40.      */
  41.     public long incr(String key, long delta) {
  42.         return redisTemplate.opsForValue().increment(key, delta);
  43.     }
  44.     /**
  45.      * 实现命令: key,减少key一次
  46.      *
  47.      * @param key
  48.      * @return
  49.      */
  50.     public long decr(String key, long delta) {
  51.         if(delta<0){
  52. //            throw new RuntimeException("递减因子必须大于0");
  53.             del(key);
  54.             return 0;
  55.         }
  56.         return redisTemplate.opsForValue().increment(key, -delta);
  57.     }
  58.     /**
  59.      * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
  60.      */
  61.     public Set<String> keys(String pattern) {
  62.         return redisTemplate.keys(pattern);
  63.     }
  64.     /**
  65.      * 实现命令:DEL key,删除一个key
  66.      *
  67.      * @param key
  68.      */
  69.     public void del(String key) {
  70.         redisTemplate.delete(key);
  71.     }
  72.     // String(字符串)
  73.     /**
  74.      * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
  75.      *
  76.      * @param key
  77.      * @param value
  78.      */
  79.     public void set(String key, String value) {
  80.         redisTemplate.opsForValue().set(key, value);
  81.     }
  82.     /**
  83.      * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
  84.      *
  85.      * @param key
  86.      * @param value
  87.      * @param timeout (以秒为单位)
  88.      */
  89.     public void set(String key, String value, long timeout) {
  90.         redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
  91.     }
  92.     /**
  93.      * 实现命令:GET key,返回 key所关联的字符串值。
  94.      *
  95.      * @param key
  96.      * @return value
  97.      */
  98.     public String get(String key) {
  99.         return (String) redisTemplate.opsForValue().get(key);
  100.     }
  101.     // Hash(哈希表)
  102.     /**
  103.      * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
  104.      *
  105.      * @param key
  106.      * @param field
  107.      * @param value
  108.      */
  109.     public void hset(String key, String field, Object value) {
  110.         redisTemplate.opsForHash().put(key, field, value);
  111.     }
  112.     /**
  113.      * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
  114.      *
  115.      * @param key
  116.      * @param field
  117.      * @return
  118.      */
  119.     public String hget(String key, String field) {
  120.         return (String) redisTemplate.opsForHash().get(key, field);
  121.     }
  122.     /**
  123.      * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
  124.      *
  125.      * @param key
  126.      * @param fields
  127.      */
  128.     public void hdel(String key, Object... fields) {
  129.         redisTemplate.opsForHash().delete(key, fields);
  130.     }
  131.     /**
  132.      * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
  133.      *
  134.      * @param key
  135.      * @return
  136.      */
  137.     public Map<Object, Object> hgetall(String key) {
  138.         return redisTemplate.opsForHash().entries(key);
  139.     }
  140.     // List(列表)
  141.     /**
  142.      * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
  143.      *
  144.      * @param key
  145.      * @param value
  146.      * @return 执行 LPUSH命令后,列表的长度。
  147.      */
  148.     public long lpush(String key, String value) {
  149.         return redisTemplate.opsForList().leftPush(key, value);
  150.     }
  151.     /**
  152.      * 实现命令:LPOP key,移除并返回列表 key的头元素。
  153.      *
  154.      * @param key
  155.      * @return 列表key的头元素。
  156.      */
  157.     public String lpop(String key) {
  158.         return (String) redisTemplate.opsForList().leftPop(key);
  159.     }
  160.     /**
  161.      * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
  162.      *
  163.      * @param key
  164.      * @param value
  165.      * @return 执行 LPUSH命令后,列表的长度。
  166.      */
  167.     public long rpush(String key, String value) {
  168.         return redisTemplate.opsForList().rightPush(key, value);
  169.     }
  170. }

 

代码GitHub地址:https://github.com/saysky/sensboot

欢迎 star 和 fork

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

目前评论:3   其中:访客  3   博主  0

    • avatar 卡夫卡 1

      大佬能给文章排个序嘛 :evil:

      • avatar repostone 2

        非技术的路过。

        • avatar 膜拜 0

          支持,挺好的 :!: