Directory
1. Why use Redis instead of Session?
1.1 If you are using session, there are the following problems:
1.2 Why use Redis for storage?
2. SMS login process
3. Realization of SMS verification code login and registration code
4. Verify the login status code of the current user
5. Optimization of the interceptor
6. Precautions when Redis replaces Session
1. Why use Redis instead of Session?
1.1 If you are using session, there are the following problems:
The session data is a variable, placed in the nodejs process
There are multiple processes in the official online operation, and the data between the processes cannot be shared: for example, there are three processes that all have a session. When I log in successfully for the first time, I hit the first process. He puts my login information in Go in your own session, if the second login hits the second process, the login fails, this is the sharing problem in the session
1.2 Why use Redis for storage?
Because redis data is stored in memory, there is no data sharing problem; at the same time, Redis has a certain persistence layer function and can also be used as a caching tool. For NoSQL databases, as a persistence layer, the data it stores is semi-structured, which means that the computer has fewer rules when reading it into memory, and the reading speed is faster. For those database systems with structured, multi-paradigm rules, it has more performance advantages. As a cache, it can support large data storage in memory. As long as the hit rate is high, it can respond quickly, because data read/write in memory is dozens to hundreds of times faster than database read/write disk.
2. SMS login process
3, SMS verification code login, registration code implementation
Constant configuration classes defined in 3.1
public class RedisConstants { public static final String LOGIN_CODE_KEY = "login:code:"; public static final Long LOGIN_CODE_TTL = 2L; public static final String LOGIN_USER_KEY = "login:token:"; public static final Long LOGIN_USER_TTL = 36000L; public static final Long CACHE_NULL_TTL = 2L; public static final Long CACHE_SHOP_TTL = 30L; }
3.2 Submit the mobile phone verification code and set the validity period to prevent others from maliciously swiping the verification code, resulting in a surge in redis data storage
//1. Check the phone number if(RegexUtils. isPhoneInvalid(phone)) { //1.1. If it is incorrect, prompt information return Result.fail("The mobile phone number verification is wrong, please re-enter!"); } //2. If it is correct, generate a verification code corresponding to the mobile phone number String code = RandomUtil.randomNumbers(6); //Represents random generation of six-digit verification codes //TODO 2.1 save the generated verification code to redis, and set the validity period of the verification code stringRedisTemplate.opsForValue() .set(LOGIN_CODE_KEY + phone,code,RedisConstants.LOGIN_CODE_TTL , TimeUnit.MINUTES); //Here use String type for storage
3.3 Obtain the saved verification code from redis according to the corresponding phone (key value) and compare it with the currently entered verification code
String resCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone); if(resCode==null||!resCode.equals(code)){ //2.1 If it is incorrect, prompt information return Result.fail("Verification code is wrong, please re-enter!"); }
3.4 If the verification code passes, check whether the current user exists in the database according to the mobile phone number, if not, automatically create a new user and save it in the database
//3. If they are consistent, check whether the corresponding user exists according to the mobile phone number LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getPhone,phone); User user = userService. getOne(queryWrapper); if(user==null){ //3.1 If it does not exist, automatically create a new user to the database user = createUserByPhone(phone); }
private User createUserByPhone(String phone) { User user = new User(); user.setPhone(phone); user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); //here use hutool dependency to generate random string userService. save(user); return user; }
If it exists, save the user’s information to redis in the form of token-map hash, and the map is the user’s information, saved in the form of key-value, and set the validity period
Attention! ! ! If you directly convert the userDTO object to the map type in this way, a type conversion exception may be reported! ! ! Because redis is stored using the StringRedisTemplate type here, all the required field types are required to be String; and the attribute types in my userDTO here are not all String types, and the id in it is Long type, obviously so direct Conversion is not advisable
//4.1 Randomly generate a token as a login token String token = UUID.randomUUID().toString(true); //Here, use hutool to generate a display format without "-" dashes, as the key /4.2 store the user object as a hash structure UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); //Type conversion here <strong>Map<String, Object> map = BeanUtil.beanToMap(userDTO);</strong> //convert userDTO object to hash type as value //4.3 store information in redis stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, map); //4.4 Set the validity period of the token here stringRedisTemplate.expire(token,CACHE_SHOP_TTL,TimeUnit.MINUTES); //Set the corresponding token value here to be valid for 30 minutes
Here’s how to fix it:
CopyOptions is used here to solve this problem, please refer to https://blog.csdn.net/moshowgame/article/details/82826535 for related usage
<strong>Map<String, Object> map = BeanUtil.beanToMap(userDTO,new HashMap<>(), </strong><strong>//convert userDTO object to hash type as value</strong> <strong> CopyOptions. create() .setIgnoreNullValue(true) </strong><strong>//Ignore null values/only copy non-null properties</strong><strong> .setFieldValueEditor((fieldName, fieldValue)-> fieldValue.toString())); </strong><strong> //Here is the modifier of the field value, converting the field type to String type</strong>
4. Verify the current user’s login status code implementation
The Interceptor interceptor is used here to judge
4.1 Since the token has been returned to the front end in the login method, the token needs to be obtained from the front end
//TODO 1. Get the value corresponding to the token key in redis String token = request.getHeader("authorization"); //Get the token in the front-end request header if(StrUtil.isBlank(token)){ //1.1 If it does not exist, intercept it and give a prompt message response. setStatus(403); return false; }
4.2 Obtain the user information corresponding to the token from redis, if it exists, perform type conversion and store it
//TODO 2. Obtain user information from redis according to token Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token); //2.1 Determine whether the user exists if(map.isEmpty()){ //2.2 If it does not exist, give a prompt message response. setStatus(403); return false; //Intercept } //TODO 3. Reconvert the hash data in the queried map into a userDTO object UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);//The false here means not to ignore the exception generated during the conversion process //3.1 Save information to ThreadLocal UserHolder.saveUser(userDTO);
4.3 At the same time, it should be noted here that the validity period of the token corresponding to the user has been set before. Only the previous setting is not enough, because it means that no matter whether the user is operating on the page or not, as long as the set validity period has passed, the corresponding token will be deleted, which is very unfriendly;
Requirement: When the user does not perform any operations on the page, it will be deleted after the specified time. On the contrary, if the user has a corresponding operation on the page, the corresponding token will be refreshed.
Workaround:
In the interceptor, it is necessary to verify whether the current user has logged in before, and when the token is still within the validity period, perform the refresh operation of the current token validity period
//TODO 4. Refresh the validity period of the token stringRedisTemplate.expire(LOGIN_USER_KEY + token,CACHE_SHOP_TTL,TimeUnit.MINUTES); return true; //Release
5. Optimization of interceptors
Reason: Because the previous Interceptor interceptor intercepted the path of the current login state, but the path that can be accessed without logging in was not intercepted, which caused the user to access the unblocked path after logging in. Path, its token validity period suddenly expires, the unfriendly impact
Workaround:
Use two interceptors, one is to intercept all paths; the other is to intercept the path that needs to be logged in, and check whether the user of the current thread exists to determine whether to release/intercept
Here is the interceptor that blocks all paths:
package com.hmdp.MyInterceptor; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.hmdp.dto.UserDTO; import com.hmdp.utils.UserHolder; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; import java.util.concurrent.TimeUnit; import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL; import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY; /** * Intercept all request paths here */ public class EveryInterceptor implements HandlerInterceptor { private StringRedisTemplate stringRedisTemplate; //Because EveryInterceptor does not belong to the Spring container management, the redis class cannot be directly injected public EveryInterceptor(StringRedisTemplate stringRedisTemplate) { //Here, redis object injection is performed in the MvcConfig class, and the reverse injection container this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //TODO 1. Get the value corresponding to the token key in redis String token = request.getHeader("authorization"); //Get the token in the front-end request header if(StrUtil.isBlank(token)){ return true; //Here is the path that can be accessed without logging in } //TODO 2. Obtain user information from redis according to token Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token); //2.1 Determine whether the user exists if(map.isEmpty()){ return true; //Here is the path that can be accessed without logging in } //TODO 3. Reconvert the hash data in the queried map into a userDTO object UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);//The false here means not to ignore the exception generated during the conversion process //3.1 Save information to ThreadLocal UserHolder. saveUser(userDTO); //TODO 4. Refresh the validity period of the token stringRedisTemplate.expire(LOGIN_USER_KEY + token,CACHE_SHOP_TTL,TimeUnit.MINUTES); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); //Remove user information from the current thread } }
Here is the interceptor that intercepts the path that needs to be logged in:
package com.hmdp.MyInterceptor; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.hmdp.dto.UserDTO; import com.hmdp.entity.User; import com.hmdp.utils.UserHolder; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Invocation; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; import static com.hmdp.utils.RedisConstants.*; /** * Here is the login interceptor configuration class, which intercepts the path that requires login to access */ public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1. Determine whether there is user information in the current thread UserDTO user = UserHolder. getUser(); if(user==null){ response.setStatus(401); //Set status code return false; } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); //Remove user information from the current thread } }
In the interceptor configuration class, configure the execution path and execution order of the corresponding interceptor:
package com.hmdp.config; import com.hmdp.MyInterceptor.EveryInterceptor; import com.hmdp.MyInterceptor.LoginInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /** * Here is the configuration class of the interceptor */ @Configuration public class MvcConfig implements WebMvcConfigurer { @Resource private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { //1. Here is the interceptor that intercepts all paths registry.addInterceptor(new EveryInterceptor(stringRedisTemplate)) .addPathPatterns("/**").order(-1); //2. Here is the interceptor that requires login to access registry. addInterceptor(new LoginInterceptor()) .excludePathPatterns( //The path that does not need to be intercepted is configured here "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ).order(1); } }
6. Precautions when Redis replaces Session
- Choose the appropriate data type
- Choose the right key
- Choose the right storage granularity https://blog.csdn.net/weixin_43959436/article/details/123856398