为了在网欲音乐系统中实现登入功能,在此使用到了shiro这个安全框架,从而使用到了多Realm认证——用户名密码、手机短信验证。特整理这篇文章希望能让后来者少踩一点坑。
本篇文章环境:SpringBoot 2.0 + shiro+ redis
使用阿里云手机号发送短信验证码
效果展示
- 用户名密码登入
- 手机短语登入
核心代码刨析
1、用户密码登录realm
package com.music.system.shiro.realm;
import com.music.model.User;
import com.music.service.impl.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 用户密码登录realm
*/
@Slf4j
public class UserPasswordRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
public String getName() {
return LoginType.USER_PASSWORD.getType();
}
@Override
public boolean supports(AuthenticationToken token) {
if (token instanceof UserToken) {
return ((UserToken) token).getLoginType() == LoginType.USER_PASSWORD;
} else {
return false;
}
}
@Override
public void setAuthorizationCacheName(String authorizationCacheName) {
super.setAuthorizationCacheName(authorizationCacheName);
}
@Override
protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
log.info("---------------- 用户密码登录 ----------------------");
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String name = token.getUsername();
// 从数据库获取对应用户名密码的用户
User user = userService.findUserByName(name);
if (user != null) {
// 用户为禁用状态
if (!user.getShow()) {
throw new DisabledAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用户
user.getPassword(), //密码
getName() //realm name
);
return authenticationInfo;
}
throw new UnknownAccountException();
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
2、手机验证码登录realm
package com.music.system.shiro.realm;
import com.music.model.User;
import com.music.service.impl.UserService;
import com.music.utils.JedisClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
/**
* 手机验证码登录realm
*/
@Slf4j
public class UserPhoneRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Resource
JedisClient jedisClient;
@Override
public String getName() {
return LoginType.USER_PHONE.getType();
}
@Override
public boolean supports(AuthenticationToken token) {
if (token instanceof UserToken) {
return ((UserToken) token).getLoginType() == LoginType.USER_PHONE;
} else {
return false;
}
}
@Override
public void setAuthorizationCacheName(String authorizationCacheName) {
super.setAuthorizationCacheName(authorizationCacheName);
}
@Override
protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
log.info("---------------- 手机验证码登录 ----------------------");
UserToken token = (UserToken) authcToken;
String phone = token.getUsername();
// 手机验证码
String validCode = String.valueOf(token.getPassword());
System.out.println("validCode------>"+validCode);
// 这里从redis中获取了验证码为 123456,并对比密码是否正确
String redisCode = jedisClient.get(phone+"code");
System.out.println("redisCode------>"+redisCode);
//线上用redisCode取代123456
if(!"123456".equals(validCode)){
log.debug("验证码错误,手机号为:{}", phone);
throw new IncorrectCredentialsException();
}
User user = userService.findUserByPhone(phone);
if(user == null){
throw new UnknownAccountException();
}
// 用户为禁用状态
if(!user.getShow()){
throw new DisabledAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用户
validCode, //密码
getName() //realm name
);
return authenticationInfo;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
ShiroConfig
package com.music.system.config;
import com.music.system.shiro.CredentialsMatcher;
import com.music.system.shiro.MyModularRealmAuthenticator;
import com.music.system.shiro.SessionManager;
import com.music.system.shiro.filter.AuthcShiroFilter;
import com.music.system.shiro.filter.SessionControlFilter;
import com.music.system.shiro.realm.AuthorizationRealm;
import com.music.system.shiro.realm.LoginType;
import com.music.system.shiro.realm.UserPasswordRealm;
import com.music.system.shiro.realm.UserPhoneRealm;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.password}")
private String redisPassword;
@Value("${spring.redis.database}")
private int database;
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 由于已经重写了authc的拦截器,此处设置的loginUrl和unauthorizedUrl已经没有用了
// 没有登陆的用户只能访问登陆页面,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
//shiroFilterFactoryBean.setLoginUrl("/common/unauth");
// 登录成功后要跳转的链接
//shiroFilterFactoryBean.setSuccessUrl("/auth/index");
// 未授权界面;
//shiroFilterFactoryBean.setUnauthorizedUrl("common/unauth");
//自定义拦截器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
//自定义authc访问拦截器
filtersMap.put("authc", new AuthcShiroFilter());
//限制同一帐号同时在线的个数。
filtersMap.put("kickout", kickoutSessionControlFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
// 权限控制map.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 公共请求
filterChainDefinitionMap.put("/common/**", "anon");
// 静态资源
filterChainDefinitionMap.put("/static/**", "anon");
// 登录方法
filterChainDefinitionMap.put("/admin/*ogin*", "anon"); // 表示可以匿名访问
filterChainDefinitionMap.put("/admin/sentCode", "anon"); // 表示可以匿名访问
filterChainDefinitionMap.put("/admin/query", "anon"); // 表示可以匿名访问
//此处需要添加一个kickout,上面添加的自定义拦截器才能生效
filterChainDefinitionMap.put("/admin/**", "authc,kickout");// 表示需要认证才可以访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setAuthenticator(modularRealmAuthenticator());
List<Realm> realms = new ArrayList<>();
// 统一角色权限控制realm
realms.add(authorizingRealm());
// 用户密码登录realm
realms.add(userPasswordRealm());
// 用户手机号验证码登录realm
realms.add(userPhoneRealm());
securityManager.setRealms(realms);
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 自定义的Realm管理,主要针对多realm
*/
@Bean("myModularRealmAuthenticator")
public MyModularRealmAuthenticator modularRealmAuthenticator() {
MyModularRealmAuthenticator customizedModularRealmAuthenticator = new MyModularRealmAuthenticator();
// 设置realm判断条件
customizedModularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return customizedModularRealmAuthenticator;
}
@Bean
public AuthorizingRealm authorizingRealm(){
AuthorizationRealm authorizationRealm = new AuthorizationRealm();
authorizationRealm.setName(LoginType.COMMON.getType());
return authorizationRealm;
}
/**
* 密码登录realm
*
* @return
*/
@Bean
public UserPasswordRealm userPasswordRealm() {
UserPasswordRealm userPasswordRealm = new UserPasswordRealm();
userPasswordRealm.setName(LoginType.USER_PASSWORD.getType());
// 自定义的密码校验器
userPasswordRealm.setCredentialsMatcher(credentialsMatcher());
return userPasswordRealm;
}
/**
* 手机号验证码登录realm
* @return
*/
@Bean
public UserPhoneRealm userPhoneRealm(){
UserPhoneRealm userPhoneRealm = new UserPhoneRealm();
userPhoneRealm.setName(LoginType.USER_PHONE.getType());
return userPhoneRealm;
}
@Bean
public CredentialsMatcher credentialsMatcher() {
return new CredentialsMatcher();
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
//redis中针对不同用户缓存(此处的id需要对应user实体中的id字段,用于唯一标识)
redisCacheManager.setPrincipalIdFieldName("id");
redisCacheManager.setKeyPrefix("SPRINGBOOT_CACHE:"); //设置前缀
return redisCacheManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setKeyPrefix("SPRINGBOOT_SESSION:");
return redisSessionDAO;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public SessionManager sessionManager() {
SimpleCookie simpleCookie = new SimpleCookie("Token");
simpleCookie.setPath("/");
simpleCookie.setHttpOnly(false);
SessionManager sessionManager = new SessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdCookieEnabled(false);
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookie(simpleCookie);
return sessionManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisHost);
redisManager.setPort(redisPort);
redisManager.setTimeout(1800); //设置过期时间
redisManager.setPassword(redisPassword);
redisManager.setDatabase(database);
return redisManager;
}
/**
* 限制同一账号登录同时登录人数控制
*
* @return
*/
// 这里的@Bean不要启用了,自定义的filter不要交由Spring创建,否则会出现被标记为anon的url仍然会执行该自定义过滤器。 updated 2020.06.05
//@Bean
public SessionControlFilter kickoutSessionControlFilter() {
SessionControlFilter kickoutSessionControlFilter = new SessionControlFilter();
kickoutSessionControlFilter.setCache(cacheManager());
kickoutSessionControlFilter.setSessionManager(sessionManager());
kickoutSessionControlFilter.setKickoutAfter(false);
kickoutSessionControlFilter.setMaxSession(1);
kickoutSessionControlFilter.setKickoutUrl("/common/kickout");
return kickoutSessionControlFilter;
}
/***
* 授权所用配置
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/***
* 使授权注解起作用不如不想配置可以在pom文件中加入
* <dependency>
*<groupId>org.springframework.boot</groupId>
*<artifactId>spring-boot-starter-aop</artifactId>
*</dependency>
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
* 此方法需要用static作为修饰词,否则无法通过@Value()注解的方式获取配置文件的值
*
*/
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
LoginType文件
package com.music.system.shiro.realm;
public enum LoginType {
/**
* 通用
*/
COMMON("common_realm"),
/**
* 用户密码登录
*/
USER_PASSWORD("user_password_realm"),
/**
* 手机验证码登录
*/
USER_PHONE("user_phone_realm"),
/**
* 第三方登录(微信登录)
*/
WECHAT_LOGIN("wechat_login_realm");
private String type;
private LoginType(String type) {
this.type = type;
}
public String getType() {
return type;
}
@Override
public String toString() {
return this.type.toString();
}
}
LoginController
/*
Created by IntelliJ IDEA.
User: Kalvin
Date: 2020/10/26
Time: 12:29
*/
package com.music.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.music.model.User;
import com.music.service.IUserService;
import com.music.service.impl.UserService;
import com.music.system.enums.ResultStatusCode;
import com.music.system.shiro.realm.LoginType;
import com.music.system.shiro.realm.UserToken;
import com.music.system.vo.Result;
import com.music.utils.JedisClient;
import com.music.utils.OptionalLog;
import com.music.utils.PhoneCode;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import static com.music.system.shiro.realm.LoginType.USER_PASSWORD;
import static com.music.system.shiro.realm.LoginType.USER_PHONE;
@CrossOrigin
@RestController
@RequestMapping("admin")
public class LoginController {
@Autowired
private UserService userService;
@Resource
JedisClient jedisClient;
/**
* 用户密码登录
*/
@RequestMapping("/login")
public Result login(HttpServletRequest request){
String loginName = request.getParameter("name");
String password = request.getParameter("password");
request.getSession().setAttribute("loginName",loginName);
System.out.println("我是登录页的"+request.getSession().getAttribute("loginName"));
System.out.println(loginName+"---->"+password);
UserToken token = new UserToken(LoginType.USER_PASSWORD, loginName, password);
return shiroLogin(token,LoginType.USER_PASSWORD);
}
/**
* 用户点击获取验证码请求
* */
@RequestMapping("/sentCode")
public Result sentCode(@RequestBody Map map){
String phone = (String)map.get("phone");
System.out.println("phone----->"+phone);
// PhoneCode.getPhonemsg(phone,jedisClient); //上线打开注释
return new Result(1);
}
/**
* 手机验证码登录
*/
@RequestMapping("/loginByPhone")
public Result loginByPhone(@RequestBody Map map){
String phone = (String)map.get("phone");
String code = (String)map.get("code");
System.out.println(phone+"---->"+code);
UserToken token = new UserToken(LoginType.USER_PHONE, phone, code);
System.out.println("token---->"+token);
return shiroLogin(token,LoginType.USER_PHONE);
}
@OptionalLog(modules="操作日志", methods="查询操作日志")
@RequestMapping("/query")
public void listLogInfo(){
System.out.println("我进入来了。。。。");
}
public Result shiroLogin(UserToken token,LoginType loginType){
User user = null;
String userName = null;
String phone = null;
try {
//登录不在该处处理,交由shiro处理
Subject subject = SecurityUtils.getSubject();
System.out.println("subject-------->"+subject);
if(LoginType.USER_PASSWORD.equals(loginType)){
userName = token.getUsername();
user = userService.findUserByName(userName);
}else if(LoginType.USER_PHONE.equals(loginType)){
phone = token.getUsername();
user = userService.findUserByPhone(phone);
}
System.out.println(phone+"================="+userName);
//出现异常
subject.login(token);
if (subject.isAuthenticated()&&user!=null) {
JSON json = new JSONObject();
((JSONObject) json).put("token", subject.getSession().getId());
((JSONObject) json).put("user",user);
return new Result(ResultStatusCode.OK, json);
}else{
return new Result(ResultStatusCode.SHIRO_ERROR);
}
}catch (IncorrectCredentialsException | UnknownAccountException e){
e.printStackTrace();
return new Result(ResultStatusCode.NOT_EXIST_USER_OR_ERROR_PWD);
}catch (LockedAccountException e){
e.printStackTrace();
return new Result(ResultStatusCode.USER_FROZEN);
}catch (Exception e){
e.printStackTrace();
return new Result(ResultStatusCode.SYSTEM_ERR);
}
}
}
自定义多realm登录策略
package com.music.system.shiro;
import com.music.system.shiro.realm.LoginType;
import com.music.system.shiro.realm.UserToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import java.util.Collection;
import java.util.HashMap;
/**
* 自定义多realm登录策略
*/
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
// 判断getRealms()是否返回为空
assertRealmsConfigured();
// 所有Realm
Collection<Realm> realms = getRealms();
// 登录类型对应的所有Realm
HashMap<String, Realm> realmHashMap = new HashMap<>(realms.size());
for (Realm realm : realms) {
realmHashMap.put(realm.getName(), realm);
}
UserToken token = (UserToken) authenticationToken;
System.out.println("token------>"+token);
// 登录类型
LoginType loginType = token.getLoginType();
if (realmHashMap.get(loginType.getType()) != null) {
return doSingleRealmAuthentication(realmHashMap.get(loginType.getType()), token);
} else {
return doMultiRealmAuthentication(realms, token);
}
}
}
JedisClient Java操作redis工具(可扩展)
/*
Created by IntelliJ IDEA.
User: Kalvin
Date: 2020/5/13
Time: 14:21
*/
package com.music.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class JedisClient {
@Autowired
private StringRedisTemplate redisTemplate;
// Key(键),简单的key-value操作
/**
* 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
*
* @param key
* @return
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
/**
* 实现命令:expire 设置过期时间,单位秒
*
* @param key
* @return
*/
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:INCR key,增加key一次
*
* @param key
* @return
*/
public long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 实现命令:DEL key,删除一个key
*
* @param key
*/
public void del(String key) {
redisTemplate.delete(key);
}
// String(字符串)
/**
* 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
*
* @param key
* @param value
* @param timeout
* (以秒为单位)
*/
public void set(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:GET key,返回 key所关联的字符串值。
*
* @param key
* @return value
*/
public String get(String key) {
return (String)redisTemplate.opsForValue().get(key);
}
// Hash(哈希表)
/**
* 实现命令:HEXISTS key field,查找哈希表中是否包含指定键值对 key中给定域 field的值
* @param key
* @param field
* @return
*/
public Boolean hexists(String key, String field){
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
*
* @param key
* @param field
* @param value
*/
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* 实现命令:HGET key field,返回哈希表 key中给定域 field的值
*
* @param key
* @param field
* @return
*/
public String hget(String key, String field) {
return (String) redisTemplate.opsForHash().get(key, field);
}
/**
* 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
*
* @param key
* @param fields
*/
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
/**
* 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
*
* @param key
* @return
*/
public Map<Object, Object> hgetall(String key) {
return redisTemplate.opsForHash().entries(key);
}
// List(列表)
/**
* 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long lpush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* 实现命令:LPOP key,移除并返回列表 key的头元素。
*
* @param key
* @return 列表key的头元素。
*/
public String lpop(String key) {
return (String)redisTemplate.opsForList().leftPop(key);
}
/**
* 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long rpush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
}
有关使用阿里云手机发送短信验证码,下篇文章详细介绍。这里只是分享核心代码,帮助理解记忆。
有兴趣的小伙伴可以关注微信公众号:幽灵邀请函 回复:网欲音乐 获取更多项目资料及完整项目,一起学习可以加QQ:2817670312