SpringBoot learning spike 4. Distributed session

The development of an e-commerce system, with the increase in business volume, must gradually develop from a stand-alone system to a clustered, distributed system. With the clustering of the system, it also faces a problem at this time, that is, the session problem. In the stand-alone era in the past, each request of the user falls on the same host, so the session is also on the same host, but in the cluster system, the user's request will randomly fall on any host, which will result in a session on the same host.
insert image description here
There are two common solutions to this problem, one is to synchronize the sessions of each host in real time, but this will also have a problem, assuming that the system is very large , the number of hosts is very large, so synchronizing sessions between hosts is a very resource-intensive behavior, and real-time performance is difficult to guarantee, so we adopt the second solution, using a third-party cache system, we can use redis to For caching, for the use of redis, please refer to: SpringBoot learning spike 1, SpringBoot integrated Redis (implemented by using Jedis) , here we use user login and user information acquisition to demonstrate, first after the user login is successful, we will get a User object, then we only need to store the User object in the cache, that is, in Redis, and use a token string as the key. Next, we can retrieve the User object from redis when we need User information. Here I Instead of implementing a specific login database, a simulated User is created for testing

 		User user = new User();
        user.setUserId(1);
        user.setMobile("13371081000");
        user.setPassword("123");

Next, we need to generate a key that will not be repeated. We use uuid directly, and remove the "-" in the uuid string: create a
new UUIDUtil:

public class UUIDUtil {
    public static String getUUID(){
        return UUID.randomUUID().toString().replace("-","");
    }
}

Next, we create a new prefix UserKey. Here we set the expiration time of the cache as one day, that is, 86400s.

public class Userkey extends BasePrefix {

    public static int EXPIRE = 86400;

    private  Userkey(String prefix) {
        super(prefix);
    }
    private Userkey(int expireSeconds,String prefix){
        super(expireSeconds, prefix);
    }

    public static Userkey getBySessionId = new Userkey(EXPIRE,"session_id");

} 

Next, we only need to store the User object in redis after successful login

		String uuid = UUIDUtil.getUUID();
        redisService.set(Userkey.getBySessionId,uuid,user);

Next, if we want to use the User object, we only need to take our token string to redis to retrieve the User object. We create a new getByToken method in UserService:

public User getByToken(String token) {
        if(StringUtils.isEmpty(token)) return null;
        User user = redisService.get(Userkey.getBySessionId,token,User.class);
        redisService.set(Userkey.getBySessionId,token,user);
        return user;
    }

In the above method, after taking out the User object, I re-set the User object in Redis. This is because we set the valid time for the User stored in Redis, but every time we use the User object, This valid time needs to be reset, just like when we log in to a webpage, if we do not perform any operations for one day, the system will prompt us that the login credentials have expired and we need to log in again when we are performing operations the next day, but if we If the operation is performed during this day, then our expiration time will be updated to one day after the time we operate at this time.

Well, through the above operations, every time we need User information, we only need to call our getByToken method with the token.
Next, we are making further optimizations. Every time we need to use User, we must first receive the token parameter, and then call the getByToken method of UserService, so that we have to write a bunch of the same code every time, so we can Can't we directly receive a User parameter in the Controller, and then we can operate it. In this way, our code will be very concise, and the answer is yes.
We need to rewrite a WebConfig, first create a new WebConfig, and then inherit the WebMvcConfigurerAdapter, our Controller will call back the addArgumentResolvers method of the WebMvcConfigurerAdapter when receiving parameters, and its formal parameter is List argumentResolvers, so we only need to add one with User information The HandlerMethodArgumentResolver class is enough. By looking at the source code of HandlerMethodArgumentResolver, we found that this is an interface, insert image description here
so we only need to create a new class to implement this interface. There are two methods under this interface, one is boolean supportsParameterto limit what our ArgumentResolver supports The analysis of the class, the other Object resolveArgumentis that we will get the specific implementation of this class

@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private UserService userService;

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        Class<?> clazz = methodParameter.getParameterType();
        return clazz == User.class;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        String token = request.getParameter("token");
        return userService.getByToken(token);
    }
}

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private UserArgumentResolver userArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(userArgumentResolver);
    }
}

Through the above one-pass operation, when we want to obtain User information, it becomes very easy

	@GetMapping("test")
    public Result<String> test(User user){
        System.out.println(user.getMobile());
        return Result.success("success");
    }

Guess you like

Origin blog.csdn.net/JiayaoXu/article/details/104740638