SpringBoot+Vue3项目实战——SpringBoot篇

项目准备与配置

数据库以及其他项目配置

server:
  port: 9090

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/lostarknote
    username: root
    password: guxiang
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    map-underscore-to-camel-case: true

遇到问题:找不到"url"....
可能原因:没有扫描到配置文件(yml),在pom.xml中的build标签中添加以下内容

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.yml</include>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>**/*.yml</include>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
    </resource>
</resources>

启动类相关

@SpringBootApplication:包扫描时,自动扫描启动类所在包及其子包,若需要扫描其他包,需要使用@ComponentScan进行包名指定

Lombok工具:

自动生成getter/setter/toString方法 ,需要引入lombok依赖,然后在实体类上添加@Data注解

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>

Result实体类

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {

    private Integer code;
    private String message;
    private T data;

    public static <E> Result<E> success(E data)
    {
        return new Result<>(0,"操作成功",data);
    }

    public static Result success(){
        return new Result(0,"操作成功",null);
    }

    public static Result error(String message) {
        return new Result(1,message,null);
    }


}

用户模块

注册功能:

1.密码加密

public class MD5Util {
    //生成MD5
    public static String getMD5(String message) {
        String md5 = "";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");  // 创建一个md5算法对象
            byte[] messageByte = message.getBytes("UTF-8");
            byte[] md5Byte = md.digest(messageByte);              // 获得MD5字节数组,16*8=128位
            md5 = bytesToHex(md5Byte);                            // 转换为16进制字符串
        } catch (Exception e) {
            e.printStackTrace();
        }
        return md5;
    }

    // 二进制转十六进制
    public static String bytesToHex(byte[] bytes) {
        StringBuffer hexStr = new StringBuffer();
        int num;
        for (int i = 0; i < bytes.length; i++) {
            num = bytes[i];
            if(num < 0) {
                num += 256;
            }
            if(num < 16){
                hexStr.append("0");
            }
            hexStr.append(Integer.toHexString(num));
        }
        return hexStr.toString().toUpperCase();
    }
}

2.参数校验——Spring Validation


3.全局异常处理器

@RestControllerAdvice
public class GobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e)
    {
        e.printStackTrace();
        return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "操作失败!");
    }
}

登录功能

登录认证:JWT令牌



JWT工具类

public class JWTUtil {
    private static final String SECRET = "guxiang";

    public static String generateToken(Map<String, Object> claims){
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis()* 1000 * 60 * 60 * 24))
                .sign(Algorithm.HMAC256(SECRET));
    }

    public static Map<String, Object> parseToken(String token){
        return JWT.require(Algorithm.HMAC256(SECRET))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }
}

JWT验证:从请求头中获取token

@RestController
@RequestMapping("/dungeon")
public class DungeonController {

    @GetMapping("/list")
    public Result<String> list(@RequestHeader("Authorization") String token, HttpServletResponse response) {
        //验证token
        //若能正常解析(不报错),则验证通过
        try {
            Map<String, Object> claims = JWTUtil.parseToken(token);
            return Result.success("副本数据获取成功!");
        }catch (Exception e) {
            response.setStatus(401);
            return Result.error("未登录!");
        }

    }
}

进阶:注册拦截器进行验证。

注册一个拦截器进行token的验证,就不用单独在每个业务代码里进行token验证

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //令牌验证
        String token = request.getHeader("Authorization");

        //若能正常解析(不报错),则验证通过
        try {
            Map<String, Object> claims = JWTUtil.parseToken(token);
            //放行
            return true;
        }catch (Exception e) {
            response.setStatus(401);
            return false;
        }
    }
}
@Configuration
public class WebConfig  implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //登录注册不用拦截
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}
@RestController
@RequestMapping("/dungeon")
public class DungeonController {

    @GetMapping("/list")
    public Result<String> list() {
        //这里不用再验证token,已经统一在拦截器中做验证
        return Result.success("副本数据获取成功!");
    }
}

登录优化——Redis



获取用户信息

ThreadLocal

  • 获取用户信息需要通过token中存储的username到数据库中查找,需要解析token。
  • 在其他业务可能也需要username的信息,又要解析token,为了避免代码重复,在之前拦截器中解析出的token统一放到线程中(ThreadLocal)。

public class ThreadLocalUtil {

    //提供ThreadLocal对象
    private static final ThreadLocal THREAD_LOCAL= new ThreadLocal();

    public static <T> T get() {
        return (T) THREAD_LOCAL.get();
    }

    public static void set(Object value) {
        THREAD_LOCAL.set(value);
    }

    public static void remove() {
        THREAD_LOCAL.remove();
    }
}

拦截器中将数据存入ThreadLocal中,请求完成后清除ThreadLocal,防止内存泄露

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //令牌验证
        String token = request.getHeader("Authorization");



        //若能正常解析(不报错),则验证通过
        try {
            Map<String, Object> claims = JWTUtil.parseToken(token);
            //将数据存储到ThreadLocal中
            ThreadLocalUtil.set(claims);
            //放行
            return true;
        }catch (Exception e) {
            response.setStatus(401);
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清空ThreadLocal中的数据,防止内存泄露
        ThreadLocalUtil.remove();
    }
}

接口中从ThreadLocal中获取数据

@GetMapping("/userInfo")
    public Result<User> userInfo(@RequestHeader("Authorization") String token) {
//        Map<String, Object> claims = JWTUtil.parseToken(token);
//        String username = (String) claims.get("username");

        Map<String, Object> map = ThreadLocalUtil.get();
        String username = (String) map.get("username");

        User loginUser = userService.findByUsername(username);

        return Result.success(loginUser);
    }

@JsonIgnore

更新用户信息

实体类中参数验证

更新用户密码

参数用一个map接收,因为接受的参数名与数据库中不一致
更新用户信息时,参数名与字段名一致,所以用user实体类接收


文章模块

新增文章分类

文章分类列表

@JsonFormat


文章分类详情

更新文章分类


新增文章

自定义校验


文章分页查询





文件上传