SpringBoot相关内容

如何在SpringBoot中自定义一个拦截器?

1、继承Spring中的的HandlerInterceptor,可以通过实现preHandle postHandle afterCompletion等方方法

@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
​
    @Autowired
    private JwtProperties jwtProperties;
​
    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }
        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getUserTokenName());
​
        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            log.info("当前客户id:{}", userId);
            //将获取到的当前员工id存入线程存储空间当中,使其可以在其他地方调用
            BaseContext.setCurrentId(userId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

2、在SpringBoot中的WebMVC配置类中实现自定义拦截器的注册

public class WebMvcConfiguration extends WebMvcConfigurationSupport {
​
    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
​
    @Autowired
    private JwtTokenUserInterceptor jwtTokenUserInterceptor;
​
    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");
​
        registry.addInterceptor(jwtTokenUserInterceptor)
                .addPathPatterns("/user/**")
                .excludePathPatterns("/user/user/login","/user/shop/status");
    }
}

SpringBoot中配置WebMVC的说明:

  • WebMvcConfigurer 为接口

  • WebMvcConfigurerAdapter 是 WebMvcConfigurer 的实现类大部分为空方法,是 WebMvcConfigurer的子类实现,由于Java8中可以使用default关键字为接口添加默认方法,为在源代码中spring5.0之后就已经弃用本类,如果需要我接着可以实现WebMvcConfigurer类。

  • WebMvcConfigurationSupport 是mvc的基本实现并包含了WebMvcConfigurer接口中的方法。

  • WebMvcAutoConfiguration 是mvc的自动装在类并部分包含了WebMvcConfigurer接口中的方法。

在SpringBoot中配置WebMVC的三种方法

  • 方式1:@EnableWebMvc

  • 方式2: 继承WebMvcConfigurationSupport 但是没有汇集项目中WebMvcConfigure接口实现类的功能的

  • 方式3: 继承 DelegatingWebMvcConfiguration 是WebMvcConfigurationSupport的拓展子类,如果项目中也存在其他实现WebMvcConfigurer接口来提供配置的类,则可以继承DelegatingWebMvcConfiguration来替代@EnableWebMvc,两者提供的功能是一样的。

在苍穹外卖的项目中使用的方式二

拦截器和过滤器?

过滤器 和 拦截器的 6个区别,别再傻傻分不清了_拦截器和过滤器的区别-优快云博客

拦截器和过滤器的区别 - 跨境电商杂货铺 - 博客园 (cnblogs.com)

SpringBoot中使用过滤器

写个类实现Filter或者OncePerRequestFilter接口,也可使用@WebFilter注解实现对特定的url进行过滤(在启动类上要加上@ServletComponentScan开启组件扫描),最主要的操作是在doFilter中进行的。

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
​
    @Autowired
    private RedisCache redisCache;
​
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userid;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

注册都是在继承WebMvcConfigurer类的子类中的addCorsMappings方法中进行

@Configuration
public class CorsConfig implements WebMvcConfigurer {
​
    @Override
    public void addCorsMappings(CorsRegistry registry) {
      // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}
JWT在SpringBoot中的配置,以及类似与SpringSecurity功能的实现

引入依赖

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

Jwt工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
​
public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
​
        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);
​
        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);
​
        return builder.compact();
    }
​
    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }
​
}

外卖中使用的是拦截器+jwt,实现用户登录、权限认证

package com.sky.interceptor;
​
import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
​
    @Autowired
    private JwtProperties jwtProperties;
​
    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }
        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getUserTokenName());
​
        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            log.info("当前客户id:{}", userId);
            //将获取到的当前员工id存入线程存储空间当中,使其可以在其他地方调用
            BaseContext.setCurrentId(userId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

BaseContext是什么?

BaseContext类介绍

简单来说,BaseContext是基于ThreadLocal封装的一个工具类。

package com.sky.context;
​
public class BaseContext {
​
    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
​
    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }
​
    public static Long getCurrentId() {
        return threadLocal.get();
    }
​
    public static void removeCurrentId() {
        threadLocal.remove();
    }
​
}

employee登录的方法

@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
    log.info("员工登录:{}", employeeLoginDTO);
​
    Employee employee = employeeService.login(employeeLoginDTO);
​
    //登录成功后,生成jwt令牌
    Map<String, Object> claims = new HashMap<>();
    claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
    String token = JwtUtil.createJWT(
            jwtProperties.getAdminSecretKey(),
            jwtProperties.getAdminTtl(),
            claims);
​
    EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
            .id(employee.getId())
            .userName(employee.getUsername())
            .name(employee.getName())
            .token(token)
            .build();
​
    return Result.success(employeeLoginVO);
}
SpringBoot中配置全局处理器

1、自定义异常

可以自定义,当然也可以使用提供的异常类,这里专门对自定义的异常类来进行说明。

可以先定义一个BaseException,继承RuntimeException.

package com.sky.exception;
​
/**
 * 业务异常
 */
public class BaseException extends RuntimeException {
​
    public BaseException() {
    }
​
    public BaseException(String msg) {
        super(msg);
    }
​
}

别的自定义类,可以直接继承异常基类,只需生成两个构造方法即可。

package com.sky.exception;
​
/**
 * 账号不存在异常
 */
public class AccountNotFoundException extends BaseException {
​
    public AccountNotFoundException() {
    }
​
    public AccountNotFoundException(String msg) {
        super(msg);
    }
​
}

传参的方式主要还是通过父类的构造器进行的。

2、全局处理异常

由于自定的异常都继承了BaseException类,这样就可以通过多态,统一的处理所有的异常了;异常是由SpringMVC处理、在handler中进行的。

需要使用@RestControllerAdivce和@ExceptionHandler(放到方法上)来实现异常处理.

package com.sky.handler;
​
import com.sky.constant.MessageConstant;
import com.sky.exception.BaseException;
import com.sky.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
​
import java.sql.SQLIntegrityConstraintViolationException;
​
/**
 * 全局异常处理器,处理项目中抛出的业务异常
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
​
    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }
​
    /**
     * 处理SQL异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        //Duplicate entry 'zhangsan' for key 'employee.idx_username'
        String message = ex.getMessage();
        if(message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            String msg = username + MessageConstant.ALREADY_EXISTS;
            return Result.error(msg);
        } else {
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }
}

@ExceptionHandler(value = 默认应该就是函数参数的class)当然也可以手动设置ExceptionType.class

@RestControllerAdvice注解加上后,出现错误,然后就会给前端响应请求了。

SpringBoot中的文档、日志配置

日志功能

主要是通过@slf4j这个注解,加上注解之后,就可以通过log.info()进行日志的输出。

文档、接口测试

主要是通过swwagger来实现。

swwagger使用

视频

使用文档的地址是在下面方法中设置的(WebMvcConfiguration)。

/**
 * 设置静态资源映射
 * @param registry
 */
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
    registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
yaml信息配置

可以事先在application.yaml中定义一些内容:

xfxh:
  apiHost: spark-api.xf-yun.com
  apiPath: ws(s)://spark-api.xf-yun.com/v3.1/chat
  appId: 1946577d
  apiKey: ZmRlNjkzM2VjZTQ4ZGZjNGFmZTQ3YWM0
  apiSecret: e323c012efc10b026f074964d67813d4

然后在写一个entity

public class config {
    private String apiHost;
    private String apiPath;
    private String appId;
    private String apiKey;
    private String apiSecret;
}

加上@Configuration 和 @ConfigurationProperties(prefix = "...")

邮箱注册验证

首先我们来理一下思路,发送验证码的整个过程,前端输入邮箱账号,点击获取验证码,请求后台接口,由我们的后台java程序将验证码存储在redis并设置相应的过期时间,以及实现发送验证码到邮箱的功能,用户如果填写的自己的邮箱,就能成功获取验证码,进行注册的时候,获取用户输入邮箱对应的验证码与用户输入的验证码进行匹配,匹配成功就进行注册,否则就提示验证码无效。

依赖

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

发送验证码、认证

@GetMapping("/getCode")
    @ApiOperation("获得验证码")
    public ResultJson<String> getRegisterCode(@RequestParam String email) throws Exception{
        //TODO:查看email是否符合注册的条件的业务代码
​
        //TODO:生成code,并发送邮箱,最后将code放到Session和Redis中(设置2min过期),发送给前端
        log.info("邮箱:{}",email);
        SimpleMailMessage message = new SimpleMailMessage();
        Random random = new Random();
        String code = String.valueOf(random.nextInt(899999) + 10000);
        message.setFrom("19846811030@163.com");
        message.setTo(email);
        message.setSubject("系统验证码");
        message.setText("邮箱验证码为: " + code +" ,请勿发送给他人,两分钟有效!");
        try {
            mailSender.send(message);
            //保存到Redis中
            redisUtil.setex(email+":code",code,1200);
            log.info("验证码邮件已发送。");
            return ResultJson.success(code);
        } catch (Exception e) {
            log.error("发送验证码邮件时发生异常了!", e);
            return ResultJson.error("验证码发送失误,请稍后再试!");
        }
    }

配置信息

spring:
    mail:
      host: smtp.163.com
      username: 
      password: 
      default-encoding: utf-8

注册的逻辑代码

@PostMapping("/register")
@ApiOperation("注册")
public ResultJson<String> register(@RequestBody RegisterVO registerVO){
    log.info("用户注册:{}",registerVO);
    //从redis中获得code
    String code = (String) redisUtil.get(registerVO.getEmail()+":code");
    if (!code.equals(registerVO.getCode()))
        return ResultJson.error("验证错误!");
    //TODO:注册的业务逻辑代码


    //最后返回
    return ResultJson.success("注册成功!");
}

 

RestTemplate

SpringBoot中已经集成了发送http请求的工具

使用之前必须在配置类中注册RestTemplate

@Configuration
public class WebConfiguration {
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        return builder
                //设置连接超时时间
                .setConnectTimeout(Duration.ofSeconds(5000))
                //设置读取超时时间
                .setReadTimeout(Duration.ofSeconds(5000))
                //设置认证信息
                .basicAuthentication("username","password")
                //设置根路径
                .rootUri("https://api.test.com/")
                //构建
                .build();
    }
}

简单注入也可以

@Configuration
public class WebConfiguration {
​
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值