Actual combat of Niuke backend project: optimizing login module

Posted by CarbonCopy on Fri, 18 Feb 2022 19:21:56 +0100

1. Use Redis to store verification code

  • The verification code needs frequent access and refresh, which requires high performance.
  • The verification code does not need to be saved permanently, and it usually becomes invalid after a short time.
  • During distributed deployment, there is a problem of Session sharing.

2. Use Redis to store login credentials

  • When processing each request, the user's login credentials must be queried, and the frequency of access is very high.

3. Use Redis to cache user information

  • When processing each request, the user information should be queried according to the credentials, and the frequency of access is very high.

cache

Use Redis to store verification code

LoginController writes and generates the verification code and stores the verification code in the session, but there will be performance problems:

  • The verification code needs frequent access and refresh, which requires high performance.
  • The verification code does not need to be saved permanently, and it usually becomes invalid after a short time.
  • During distributed deployment, there is a problem of Session sharing.
  • Data stored on the server is safer, but it will also increase the memory pressure on the server.

Therefore, store the verification code in Redis and obtain the verification code through the Key. However, when using the verification code, the user did not log in and could not obtain the user information, so the randomly generated string for a short time was stored in the cookie as a credential.

RedisKey generation

public class RedisKeyUtil {

    private static final String PREFIX_KAPTCHA = "kaptcha";
    // Login verification code
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

Rewrite generated verification code

@Controller
public class LoginController implements CommunityConstant {	

	...
    @Value("${server.servlet.context-path}")
    private String contextPath;

    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) {
        // Generate verification code
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);

        // Save the verification code into session
        // session.setAttribute("kaptcha", text);

        // Ownership of verification code
        String kaptchaOwner = CommunityUtil.generateUUID();
        Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
        cookie.setMaxAge(60);            // Set cookie lifetime
        cookie.setPath(contextPath);  // Set the effective range of cookie s
        response.addCookie(cookie);
        // Store the verification code in Redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

        // Output the image to the browser
        response.setContentType("image/png");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("Response verification code failed:" + e.getMessage());
        }
    }

1. Generate verification code, but no longer store it in Session
2. Randomly generated string kaptchaOwner stored in cookie
3. Generate the corresponding RedisKey according to the kaptchaOwner and store the verification code in Redis.
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS); String type is used here: 60 indicates the survival time, timeunit Seconds is the unit of time

Use Redis to store login credentials

The previous login information is to construct the LoginTicket entity class and store it in the database through logintickemapper.

1. LoginTicketMapper set to expire

@Mapper
@Deprecated
public interface LoginTicketMapper {

}

2. Refactoring UserService
1) When logging in, put the generated login credentials into Redis

         // Generate login credentials
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
//        loginTicketMapper.insertLoginTicket(loginTicket);

        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        redisTemplate.opsForValue().set(redisKey, loginTicket);

        map.put("ticket", loginTicket.getTicket());

2) Exit function
Take the value from Redis, forcibly convert it to loginTicket class, change the status of login voucher to 1, and then re pass it into Redis.

    public void logout(String ticket) {
//        loginTicketMapper.updateStatus(ticket, 1);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        loginTicket.setStatus(1);
        redisTemplate.opsForValue().set(redisKey, loginTicket);
    }

3) Query voucher

    public LoginTicket findLoginTicket(String ticket) {
//        return loginTicketMapper.selectByTicket(ticket);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

Use Redis to cache user information

1. First encapsulate the cache operation
Take the value from the cache first, initialize the cached data when it cannot be obtained, and clear the cached data when the data is changed

    // 1. Take priority from the cache
    private User getCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }

    // 2. Initialize the cache data when it cannot be fetched
    private User initCache(int userId) {
        User user = userMapper.selectById(userId);
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
        return user;
    }

    // 3. Clear cached data when data changes
    private void clearCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(redisKey);
    }

2. Refactoring findUserById

    public User findUserById(int id) {
//        return userMapper.selectById(id);
        User user = getCache(id);
        if (user == null) {
            user = initCache(id);
        }
        return user;
    }

Topics: Java