The SpringBoot Mybatis -plus logic is removed and automatically filled with default values

One, foreword

  • Encapsulate returned results, error codes, prompt messages, and returned data.
  • Unified exception interception: Intercepts manually thrown exceptions or the exceptions thrown by the system and returns data to the front end in a unified manner.
  • JWT(Json Web Token) is an interface Token. After a successful login, the Token is returned to the front end, which saves the Token. All subsequent requests are sent back to the server, which verifies the Token to decide whether to release the Token. In addition, information such as user ID and user name can be saved in the token.
  • The code uses Mybatis-Plus, you can see my previous article

2. Directory structure

Maven dependency packages

Add the following dependencies to POM.xml Dependencies

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>5.3.3. RELEASE</version>
        </dependency>
Copy the code
  • JJWT on Java Json Web Token
  • Hutool-all encapsulates many useful Java tool classes, highly recommended, very easy to use, official website address: www.hutool.cn/
  • Spring-security-crypto password encryption and decoding tool

Four, encapsulation unified return results

CommonResult class

package com.llh.springbootdemo.config;


/ * * *@author llh
 */
public class CommonResult<T> {
    private Integer code;
    private String msg;
    private T data;

    public CommonResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public CommonResult(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> CommonResult<T> success(T t) {
        return new CommonResult<T>(200."Operation successful", t);
    }

    public static <T> CommonResult<T> error(T t) {
        return new CommonResult<T>(300."Operation failed", t);
    }

    public Integer getCode(a) {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg(a) {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData(a) {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString(a) {
        return "CommonResult{" +
                "code=" + code +
                ", msg='" + msg + '\' ' +
                ", data=" + data +
                '} '; }}Copy the code

  • use
    @PostMapping("/register")
    public CommonResult<Boolean> register(@RequestBody UserInfo userInfo) {
        return CommonResult.success(userInfoService.register(userInfo));
    }

    @PostMapping("/login")
    public CommonResult<String> login(@RequestBody UserInfo userInfo) {
        return CommonResult.success(userInfoService.login(userInfo));
    }

Copy the code

The result is as follows:

{
    "code": 200."msg": "Operation successful"."data": true
}
Copy the code

5. Unified exception interception

GlobalExceptionHandler class

package com.llh.springbootdemo.config;


import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/ * * *@author llh
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public CommonResult<String> exceptionHandler(Exception e) {
        returnCommonResult.error(e.getMessage()); }}Copy the code
  • Only Exception handling is done here, and you can add custom Exception handling.

Vi. Registration function

PasswordEncoder#encode encrypts the password. The encryption is asymmetric, that is, the same password is encrypted after the string is different.

@Override public Boolean register(UserInfo userInfo) { List<UserInfo> selectedList = list(new LambdaQueryWrapper<UserInfo>() .eq(UserInfo::getUsername, userInfo.getUsername())); if (! Selectedlist.isempty ()) {throw new RuntimeException(" Registration failed, the user name already exists "); } // PasswordEncoder PasswordEncoder = new BCryptPasswordEncoder(); String encodedPassword = passwordEncoder.encode(userInfo.getPassword()); userInfo.setPassword(encodedPassword); return save(userInfo); }Copy the code

7. Login function

PasswordEncoder#matches verifies the password

    @Override
    public String login(UserInfo userInfo) {
        List<UserInfo> selectedList = list(new LambdaQueryWrapper<UserInfo>()
                .eq(UserInfo::getUsername, userInfo.getUsername()));
        if (selectedList.isEmpty()) {
            throw new RuntimeException("Login failed. Account does not exist.");
        }
        UserInfo selected = selectedList.get(0);
        String encodedPassword = selected.getPassword();
        // Check whether the password is correct
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        boolean result = passwordEncoder.matches(userInfo.getPassword(), encodedPassword);
        if(! result) {throw new RuntimeException("Login failed, user password incorrect");
        }
        // Generate a token
        HashMap<String, Object> map = new HashMap<>(2);
        map.put("userId", selected.getId());
        String token = JwtUtil.generateToken(map);
        return token;
    }

Copy the code

JWT generation and validation tool classes

JwtUtil class

package com.llh.springbootdemo.utils; import cn.hutool.core.date.DateUtil; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.Map; /** * @author LLH */ public class JwtUtil {/** * private static final String SECRET = "token_secret"; /** * token prefix */ private static final String TOKEN_PREFIX = "Bearer"; /** * token expiration time */ private static final Integer EXPIRE_SECONDS = 60 * 60 * 24 * 7; Public static String generateToken(Map<String, Object> map) { String jwt = Jwts.builder() .setSubject("user info").setClaims(map) .signWith(SignatureAlgorithm.HS512, SECRET) .setExpiration(DateUtil.offsetSecond(new Date(), EXPIRE_SECONDS)) .compact(); return TOKEN_PREFIX + "_" + jwt; } public static Map<String, Object> resolveToken(String token) {if (token == null) {throw new RuntimeException(" token empty "); } try { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token.replaceFirst(TOKEN_PREFIX + "_", "")) .getBody(); } catch (ExpiredJwtException e) {throw new RuntimeException(" token has expired "); } catch (Exception e) {throw new RuntimeException(" token parsing Exception "); }}}Copy the code

9. Unified request interception

Intercepts all requests into the interceptor, retrieves the token from the request header, parses the token, and saves the user ID into the context object

TokenInterceptor class TokenInterceptor

package com.llh.springbootdemo.config;

import com.llh.springbootdemo.utils.JwtUtil;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/ * * *@author llh
 */
public class TokenInterceptor implements HandlerInterceptor {

    /** * request header */
    private static final String HEADER_AUTH = "Authorization";

    /** * secure URL, no token */ required
    private static final List<String> SAFE_URL_LIST = Arrays.asList("/userInfo/login"."/userInfo/register");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        response.setContentType("application/json; charset=utf-8");

        String url = request.getRequestURI().substring(request.getContextPath().length());
        System.out.println(url);
        // Requests such as login and registration do not require tokens
        if (SAFE_URL_LIST.contains(url)) {
            return true;
        }

        // Read the token from the request header
        String token = request.getHeader(HEADER_AUTH);
        if (token == null) {
            throw new RuntimeException("Request failed, token is empty");
        }

        // Parse the token
        Map<String, Object> map = JwtUtil.resolveToken(token);
        Long userId = Long.parseLong(map.get("userId").toString());
        ContextHolder.setUserId(userId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ContextHolder.shutdown(); }}Copy the code
  • All requests were approvedpreHandlemethods
  • ContextHolder.setUserId(userId);The request begins to place the parsed user ID into the context object.
  • ContextHolder.shutdown();End of request culls the user ID from the context object.

The WebMvcConfiguration class adds an interceptor to the MVC configuration

package com.llh.springbootdemo.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; /** * @author llh */ @Configuration public class WebMvcConfiguration extends WebMvcConfigurationSupport { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TokenInterceptor()); }}Copy the code

ContextHolder Class Context object class

package com.llh.springbootdemo.config;

/ * * *@author llh
 */
public class ContextHolder {
    public static ThreadLocal<Long> context = new ThreadLocal<>();

    public static void setUserId(Long userId) {
        context.set(userId);
    }

    public static Long getUserId(a) {
        return context.get();
    }

    public static void shutdown(a) { context.remove(); }}Copy the code

  • The main use is ThreadLocal, where context objects can be retrieved from any thread requesting them.
  • For example, changing the passwordLong userId = ContextHolder.getUserId();Get the user ID.
  • Change the password to update the data according to the user ID. The user ID is directly taken from the context object, so that you do not need to pass it from the front. If you pass it from the front, it means that anyone can change the password of others, which is very insecure.
  • Take it from the context, that is, from the token, and you’re just protecting the interface, you can only manipulate your own data.
    @Override
    public Boolean changePassword(UserInfo userInfo) {
        // Password encryption
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encodedPassword = passwordEncoder.encode(userInfo.getPassword());

        UserInfo updateUserInfo = new UserInfo();
        updateUserInfo.setPassword(encodedPassword);
        // Get the user ID from the context object, not from the user
        Long userId = ContextHolder.getUserId();
        updateUserInfo.setId(userId);
        return updateById(updateUserInfo);
    }
Copy the code

X. Complete code

UserInfoService

package com.llh.springbootdemo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.llh.springbootdemo.entity.UserInfo;

/ * * *@author llh
 */
public interface UserInfoService extends IService<UserInfo> {
    /** * Register **@paramUserInfo Registration information *@returnSuccessful */
    Boolean register(UserInfo userInfo);

    /** * login **@paramUserInfo Login information *@returnTokens * /
    String login(UserInfo userInfo);

    /** * change the password **@paramUserInfo User information *@returnSuccessful */
    Boolean changePassword(UserInfo userInfo);
}

Copy the code

UserInfoServiceImpl

package com.llh.springbootdemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.llh.springbootdemo.config.ContextHolder;
import com.llh.springbootdemo.entity.UserInfo;
import com.llh.springbootdemo.mapper.UserInfoMapper;
import com.llh.springbootdemo.service.UserInfoService;
import com.llh.springbootdemo.utils.JwtUtil;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;

/ * * *@author llh
 */
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper.UserInfo> implements UserInfoService {
    @Override
    public Boolean register(UserInfo userInfo) {
        List<UserInfo> selectedList = list(new LambdaQueryWrapper<UserInfo>()
                .eq(UserInfo::getUsername, userInfo.getUsername()));
        if(! selectedList.isEmpty()) {throw new RuntimeException("Registration failed, the user name already exists");
        }
        // Password encryption
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encodedPassword = passwordEncoder.encode(userInfo.getPassword());
        userInfo.setPassword(encodedPassword);
        return save(userInfo);
    }

    @Override
    public String login(UserInfo userInfo) {
        List<UserInfo> selectedList = list(new LambdaQueryWrapper<UserInfo>()
                .eq(UserInfo::getUsername, userInfo.getUsername()));
        if (selectedList.isEmpty()) {
            throw new RuntimeException("Login failed. Account does not exist.");
        }
        UserInfo selected = selectedList.get(0);
        String encodedPassword = selected.getPassword();
        // Check whether the password is correct
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        boolean result = passwordEncoder.matches(userInfo.getPassword(), encodedPassword);
        if(! result) {throw new RuntimeException("Login failed, user password incorrect");
        }
        // Generate a token
        HashMap<String, Object> map = new HashMap<>(2);
        map.put("userId", selected.getId());
        String token = JwtUtil.generateToken(map);
        return token;
    }

    @Override
    public Boolean changePassword(UserInfo userInfo) {
        // Password encryption
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encodedPassword = passwordEncoder.encode(userInfo.getPassword());

        UserInfo updateUserInfo = new UserInfo();
        updateUserInfo.setPassword(encodedPassword);
        // Get the user ID from the context object, not from the user
        Long userId = ContextHolder.getUserId();
        updateUserInfo.setId(userId);
        returnupdateById(updateUserInfo); }}Copy the code

UserInfoController

package com.llh.springbootdemo.controller;

import com.llh.springbootdemo.config.CommonResult;
import com.llh.springbootdemo.entity.UserInfo;
import com.llh.springbootdemo.service.UserInfoService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/ * * *@author llh
 */
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {
    @Resource
    private UserInfoService userInfoService;


    @PostMapping("/register")
    public CommonResult<Boolean> register(@RequestBody UserInfo userInfo) {
        return CommonResult.success(userInfoService.register(userInfo));
    }

    @PostMapping("/login")
    public CommonResult<String> login(@RequestBody UserInfo userInfo) {
        return CommonResult.success(userInfoService.login(userInfo));
    }

    @PostMapping("/changePassword")
    public CommonResult<Boolean> changePassword(@RequestBody UserInfo userInfo) {
        returnCommonResult.success(userInfoService.changePassword(userInfo)); }}Copy the code

pom.xml


      
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.llh</groupId>
    <artifactId>spring-boot-demo</artifactId>
    <version>1.0.0</version>
    <name>spring-boot-demo</name>
    <description>springboot project description</description>

    <properties>
        <mybatis-spring-boot.version>2.1.4</mybatis-spring-boot.version>
        <mybatis-plus.version>3.4.2</mybatis-plus.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>5.3.3. RELEASE</version>
        </dependency>
    </dependencies>

</project>

Copy the code

Xi. Test

11.1 registered

Check that the database password is encrypted

11.2 the login

Token returned after successful login

11.3 Changing a Password

You need to add a token to the request header Authorization

Change the password from 123456->12345678

Xii. Conclusion

Source code address: github.com/tigerleeli/…

Synchronize wechat official account: Little Tiger’s technology blog