首页 专题 文章 代码 归档

自用SpringBoot初始化通用配置

1. 前言

经历过一个项目后,基本可以把很多通用的配置保存起来,然后方便以后快速搭建一个完整可用的基础项目

整体是:SpringBoot+security+jwt(可选)+MybatisPlus

2. 配置

server:
  port: 4037

#mybatis
mybatis-plus:
  #  mapper-locations: classpath*:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

spring:
  profiles:
    active: dev
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false


---

#开发环境

spring:
  profiles: dev
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/java_navigation?useSSL=false
  artemis:
    port: 4037


mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
jwt:
  secret: fas13fdfdsf
  expiration: 86400000
  header: Authorization
  prefix: "Bearer "

cors:
  origins:
    - http://localhost:8080
  headers:
    - authentication
    - Cache-Control
    - X-User-Agent
    - Content-Type
    - Authorization
---

#生产环境

spring:
  profiles: prod
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/java_navigation?useSSL=false&serverTimeZone=UTC
    #    url: jdbc:p6spy:mysql://localhost:3306/java_navigation?useSSL=false

logging:
  file:
    name: logs/spring.log
jwt:
  secret: garfsdffdf!#fad
  expiration: 86400000
  header: Authorization
  prefix: "Bearer "

cors:
  origins:
    - http://localhost:8080
  headers:
    - authentication
    - Cache-Control
    - X-User-Agent
    - Content-Type
    - Authorization

3. MybatisPlus

配置

以下包括分页插件的配置、自定义填充器

@Configuration
@MapperScan("com.ujuji.navigation.mapper")
public class MybatisPlusConfig {

    @Bean
    public MyMetaObjectHandler myMetaObjectHandler() {
        return new MyMetaObjectHandler();
    }

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        paginationInterceptor.setOverflow(true);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInterceptor.setLimit(100);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}

Handler

主要就是填充处理createdAt和updatedAt两个字段

@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充.....");
        if (metaObject.hasSetter("createdAt")) {
            log.info("createdAt");
            this.strictInsertFill(metaObject, "createdAt", Date.class, new Date());
        }
        if (metaObject.hasSetter("updatedAt")) {
            log.info("updatedAt");
            this.strictInsertFill(metaObject, "updatedAt", Date.class, new Date());
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充.....");
        if (metaObject.hasSetter("updatedAt")) {
            log.info("updatedAt");
            this.strictUpdateFill(metaObject, "updatedAt", Date.class, new Date());
        }
    }
}

4. Security

配置

这里的配置内容,不算多,也不算少

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private JwtUtils jwtUtils;
    @Resource
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new MyAccessDeniedHandler();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
        //让Spring security放行所有preflight request,也即options预请求要放行
        registry.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();

        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/auth/**", "/").permitAll()
                .antMatchers("/public/**", "/job/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtLoginFilter(authenticationManager(), jwtUtils),
                        UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JwtAuthenticationTokenFilter(userDetailsService, jwtUtils),
                        UsernamePasswordAuthenticationFilter.class)
                .headers().cacheControl();
        http.cors();

    }

    @Resource
    CorsDataConfig corsDataConfig;
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        //指定允许跨域的请求(*所有):http://wap.ivt.guansichou.com
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        // configuration.setAllowedOrigins(corsDataConfig.getOrigins());
        configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
        // setAllowCredentials(true) is important, otherwise:
        // The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
        configuration.setAllowCredentials(true);
        // setAllowedHeaders is important! Without it, OPTIONS preflight request
        // will fail with 403 Invalid CORS request
        configuration.setAllowedHeaders(corsDataConfig.getHeaders());
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

accessDeniedHandler

处理验证失败后怎么返回,因为这是前后端分离项目,所以这里是返回JSON

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {

        AppResult<Object> fail = AppResultBuilder.fail(ResultCode.PERMISSION_NO_ACCESS);
        String string = new ObjectMapper().writeValueAsString(fail);

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print(string);//输出
        writer.close();
    }
}

JwtAuthenticationTokenFilter

这个主要是用来进行token认证处理的过滤器,简言之:获取header头中的token,然后判断是否有效、是否过期,如果验证成功且有效,那么构造一些信息,加入到security的全局上下文中(SecurityContextHolder)

@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {


    /*通过构造参数注入*/
    private final UserDetailsService userDetailsService;

    private final JwtUtils jwtutils;

    public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, JwtUtils jwtutils) {
        this.userDetailsService = userDetailsService;
        this.jwtutils = jwtutils;
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(jwtutils.getHeader());
        if (StringUtils.isNotEmpty(authHeader)) {

            String[] strings = authHeader.split("\\s");
            if (strings.length >= 2) {
                authHeader = strings[1];
            }

            String username = jwtutils.getUsernameFromToken(authHeader);
            log.info("加入凭证:{}", username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 判断如果username不为空,且上下文中没有数据,那么就尝试验证,
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                log.info("Details:{}", userDetails.toString());


                if (jwtutils.validateToken(authHeader, userDetails)) {
                    // 且验证成功后,在上下文中加入凭证
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

JwtLoginFilter

这个过滤器就是用来登录的,继承自传统的UsernamePasswordAuthenticationFilter,因为这是前后端分离项目,肯定不是传统的表单登录,所以需要重写登录处理的逻辑(attemptAuthentication

然后登录成功后的操作,登录失败后的操作

@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

    private final JwtUtils jwtUtils;
    private final AuthenticationManager authenticationManager;

    public JwtLoginFilter(AuthenticationManager authenticationManager, JwtUtils jwtUtils) {
        this.authenticationManager = authenticationManager;
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/login", "POST"));
        this.jwtUtils = jwtUtils;
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        log.info("Authentication-->>attemptAuthentication");

        // 从输入流中获取到登录的信息
        try {
            UserEntity loginUser = new ObjectMapper().readValue(request.getInputStream(), UserEntity.class);
            return this.authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        UserEntity user = (UserEntity) authResult.getPrincipal();
        System.out.println("user:" + user.toString());

        String role = "";
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();
        }

        String token = jwtUtils.generateToken(user);
        //String token = JwtTokenUtils.createToken(user.getUsername(), false);
        // 返回创建成功的token
        // 但是这里创建的token只是单纯的token
        // 按照jwt的规定,最后请求的时候应该是 `Bearer token`
        // 获取用户信息
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);//屏蔽某些东西

        final SecurityContext context = SecurityContextHolder.getContext();
        Map<String, Object> res = new HashMap<>();
        user.setPassword(null);
        res.put("userInfo", userVo);

        res.put("token", token);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        AppResult<Object> appResult = AppResultBuilder.success(res, ResultCode.USER_LOGIN_SUCCESS);
        String s = new ObjectMapper().writeValueAsString(appResult);
        PrintWriter writer = response.getWriter();
        writer.print(s);//输出
        writer.close();
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        final String message = failed.getMessage();

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        AppResult<String> appResult;
        if (failed instanceof InternalAuthenticationServiceException) {
            appResult = AppResultBuilder.fail(ResultCode.USER_LOGIN_ERROR);
        } else if (failed instanceof DisabledException) {
            appResult = AppResultBuilder.fail(ResultCode.USER_ACCOUNT_FORBIDDEN);
        } else if (StringUtils.isNoneBlank(message)) {
            appResult = AppResultBuilder.fail(message);
        } else {
            appResult = AppResultBuilder.fail(ResultCode.USER_LOGIN_FAIL);
        }
        String s = new ObjectMapper().writeValueAsString(appResult);
        PrintWriter writer = response.getWriter();
        writer.print(s);//输出
        writer.close();
    }
}

UserVo

@Data
@ToString(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
public class UserVo {
    private String username;
    private String email;
    private Boolean enable;
    @Null(message = "请勿传入非法参数")
    private String authority;
}

一个获取信息的静态类

public class AuthInfo {

    public static UserEntity getUserInfo() {
        SecurityContext context = SecurityContextHolder.getContext();
        return (UserEntity) context.getAuthentication().getPrincipal();
    }
}

CorsDataConfig.java

@Configuration
@ConfigurationProperties(prefix = "cors")
@Data
public class CorsDataConfig {
    List<String> origins;
    List<String> headers;
}

5. jwt

导入包:

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

这个包已经很久没更新了,当然,也没更新的必要,算法就那样,也没啥漏洞之类的..

jwt工具类

@Data
@ConfigurationProperties(prefix = "jwt")
@Component
@Slf4j
public class JwtUtils implements Serializable {

    private String secret;

    private Long expiration;

    private String header;

    private String prefix;



    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 生成令牌
     *
     * @param userDetails 用户
     * @return 令牌
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put("sub", userDetails.getUsername());
        claims.put("created", new Date());
        return generateToken(claims);
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌
     *
     * @param token       令牌
     * @param userDetails 用户
     * @return 是否有效
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        UserEntity user = (UserEntity) userDetails;
        String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }
}

6. 统一返回

ResultCode

//状态码
public enum ResultCode {
    /* 成功状态码 */
    SUCCESS(1000, "成功"),

    /* 参数错误:10001-19999 */
    PARAM_IS_INVALID(10001, "参数无效"),
    PARAM_IS_BLANK(10002, "参数为空"),
    PARAM_TYPE_BIND_ERROR(10003, "参数类型错误"),
    PARAM_NOT_COMPLETE(10004, "参数缺失"),

    /* 用户错误:20001-29999*/
    USER_NOT_LOGGED_IN(20001, "用户未登录"),
    USER_LOGIN_ERROR(20002, "账号不存在或密码错误"),
    USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"),
    USER_NOT_EXIST(20004, "用户不存在"),
    USER_HAS_EXISTED(20005, "用户已存在"),
    USER_PASSWORD_ERROR(20006, "用户密码错误"),
    USER_PASSWORD_MODIFY_SUCCESS(20007, "修改密码成功"),
    USER_PASSWORD_MODIFY_FAIL(20008, "修改密码失败"),
    USER_LOGIN_SUCCESS(20009, "登录成功"),
    USER_LOGIN_FAIL(20010, "登录失败,请检查用户名和密码"),
    USER_REGISTER_SUCCESS(20011, "注册成功"),
    USER_REGISTER_FAIL(20012, "注册失败"),
    USER_OLD_PASS_ERROR(20013, "旧密码错误"),
    EMAIL_EXISTED(20014, "邮箱已存在"),
    VERIFY_CODE_ERROR(20015, "验证码错误"),

    /*业务:30001-39999*/
    SERVICE_QUERY_FAIL(30001, "查询失败"),
    SERVICE_INSERT_SUCCESS(30002, "添加成功"),
    SERVICE_INSERT_FAIL(30003, "添加失败"),
    SERVICE_DELETE_SUCCESS(30004, "删除成功"),
    SERVICE_DELETE_FAIL(30005, "删除失败"),
    SERVICE_UPDATE_SUCCESS(30006, "修改成功"),
    SERVICE_UPDATE_FAIL(30007, "修改失败"),

    USER_HAS_NOTICE(31001, "用户已存在一个公告,不能再次创建"),
    USER_HAS_BOX(31002, "存在相同的一个盒子,不能创建"),
    PLEASE_INPUT_USER_ID(31003, "抱歉,请输入用户ID"),
    NOT_HAS_PERMISSION_OPERATOR(31004, "您没有操作此项操作的权限"),
    PLEASE_INPUT_ID(31005, "请输入ID"),
    BOX_NOT_EXISTED(31006, "盒子不存在,请检查"),
    LINK_NOT_EXISTED(31007, "链接不存在,请检查"),
    BOX_NOT_FOUND(31008, "未找到该导航"),
    SELECT_ONE_FAIL(31009, "查询单条记录失败"),
    SUFFIX_NOT_AVAILABLE(31010, "此后缀(标签)不可用,请更换"),
    NAME_EXISTED(31011, "名称已存在"),
    SUFFIX_NOT_EXISTED(31012, "此后缀不存在"),
    BOX_PWD_ERROR(31013, "盒子密码错误"),
    SUFFIX_LENGTH_ERROR(31014, "站点后缀长度小于规定"),
    LEAVE_MSG_NOT_EXISTED(31015, "留言不存在"),
    SET_MSG_FIXED_SUCCESS(31016, "留言置/取顶成功"),
    SET_MSG_FIXED_FAIL(31017, "留言置/取顶失败"),
    REPLY_MSG_SUCCESS(31018, "回复留言成功"),
    REPLY_MSG_FAIL(31019, "回复留言失败"),
    ITEM_NOT_EXISTS(31020, "该条目不存在"),
    SET_MSG_READ_FAIL(31021, "设置留言已读失败"),
    SET_MSG_READ_SUCCESS(31022, "设置留言已读成功"),


    /* 权限错误:70001-79999 */
    PERMISSION_NO_ACCESS(70001, "无访问权限"),
    PERMISSION_NO_OPERATION(70002, "无操作权限"),
    PERMISSION_NOT_MATCH_OPERATION(70003, "无操作权限");

    private final Integer code;
    private final String msg;

    ResultCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

AppResult

public class AppResult<T> {

    private int code;
    private String msg;
    private T data;// 数据

    public int getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

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

    public T getData() {
        return data;
    }

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

}

AppResultBuilder

public class AppResultBuilder {

    //成功,不返回具体数据
    public static <T> AppResult<T> successNoData(ResultCode code) {
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMsg(code.getMsg());
        return result;
    }

    //成功,返回数据
    public static <T> AppResult<T> success(T t, ResultCode code) {
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMsg(code.getMsg());
        result.setData(t);
        return result;
    }

    //失败,返回失败信息
    public static <T> AppResult<T> fail(ResultCode code) {
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMsg(code.getMsg());
        return result;
    }

    //失败,返回失败信息
    public static <T> AppResult<T> fail(String msg, ResultCode code) {
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMsg(msg);
        return result;
    }   //失败,返回失败信息

    public static <T> AppResult<T> fail(String msg) {
        AppResult<T> result = new AppResult<T>();
        result.setMsg(msg);
        return result;
    }

}

7. 异常处理

自定义异常MyException

public class MyException extends RuntimeException {
    private String message;
    private ResultCode code;

    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
        this.message = message;
    }

    public MyException(ResultCode code) {
        super(code.getMsg());
        this.message = code.getMsg();
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public ResultCode getCode() {
        return code;
    }

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

主要用来处理异常MyExceptionHandler

@RestControllerAdvice
@Slf4j
@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(value = MyException.class)
    public AppResult<BaseEntity> entityHandler(MyException e) {
        log.info("Exception Handler: {}", "entityHandler");
        return AppResultBuilder.fail(e.getMessage(), e.getCode());
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public AppResult<Object> argumentsHandler(MethodArgumentNotValidException e) {
        log.info("Exception Handler: {}", "argumentsHandler");
        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        assert fieldError != null;
        return AppResultBuilder.fail(fieldError.getDefaultMessage());
    }

    @ExceptionHandler(value = Exception.class)
    public AppResult<Object> globalHandler(Exception e) {

        if (e instanceof ServletException) {

            if (e instanceof NoHandlerFoundException) {
                log.info("Exception Handler: {}", "noHandlerFoundException");
                log.error("noHandlerFoundException:{}", e.getMessage());
                return AppResultBuilder.fail(ResultCode.NOT_FOUND);
            }

            log.info("Exception Handler: {}", "ServletException");
            log.error("ServletException:{}", e.getMessage());
            return AppResultBuilder.fail(ResultCode.SYSTEM_ERROR);
        }

        if (e instanceof DuplicateKeyException) {

            log.info("Exception Handler: {}", "duplicateKeyException");
            log.error("duplicateKeyException:{}", e.getMessage());
            return AppResultBuilder.fail(ResultCode.DATABASE_DOUBLE_KEY);

        }

        log.info("Exception Handler: {}", "globalHandler");
        log.error("系统错误:{}", e.getMessage());
        return AppResultBuilder.fail("系统错误,请联系管理员");
    }


    /**
     * 这里处理没有权限的异常,不能在SecurityConfig里面处理,因为要被Exception捕获了
     *
     * @param e 异常
     * @return 返回的信息
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public AppResult<Object> accessDeniedHandler(Exception e) {
        log.info("Exception Handler: {}", "accessDeniedHandler");
        log.error("accessDeniedHandler:{}", e.getMessage());
        return AppResultBuilder.fail(ResultCode.PERMISSION_NO_ACCESS);
    }


}

两点要说:

1、由于想通用,那么这里就捕获了Exception.class最大的异常,导致的一个后果就是security的异常处理(如没权限)哪里不能自动处理了,那么就只能也在该文件中单独处理AccessDeniedException.class

2、这里还处理了MethodArgumentNotValidException.class,这是实体类中的validation的验证失败处理,也就是返回验证失败的内容

解决filter抛出的异常

在filter中捕获,使用下面两条代码:

request.setAttribute("filter.error", e);
request.getRequestDispatcher("/error/throw").forward(request, response);

新建ErrorController

@RestController
public class ErrorController {
    /**
     * 重新抛出异常
     */
    @RequestMapping("/error/throw")
    public void rethrow(HttpServletRequest request) {
        throw ((MyException) request.getAttribute("filter.error"));
    }
}

相当于就是转发到Controller层,在Controller层抛出异常,这样的话就可以使用全局捕获了!

8. 实体类

BaseEntity

@Data
public class BaseEntity {
    @TableId(type = IdType.AUTO)
    private Integer id;

    @TableField(fill = FieldFill.INSERT)//INSERT代表只在插入时填充
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
    protected Date createdAt;

    @TableField(fill = FieldFill.INSERT_UPDATE)// INSERT_UPDATE 首次插入、其次更新时填充(或修改)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
    protected Date updatedAt;
}

UserEntity

@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("users")
public class UserEntity extends BaseEntity implements UserDetails {
    @NotBlank(message = "用户名不能为空")
    private String username;


    @NotBlank(message = "密码不能为空")
    @Length(min = 6, message = "密码长度最少6位")
    // @JsonIgnore
    private String password;

    @NotBlank(message = "邮箱不能为空")
    @Pattern(regexp = "\\w+@\\w+(\\.\\w{2,3})*\\.\\w{2,3}", message = "邮箱格式不准确")
    private String email;

    private Integer enable;

    @Null(message = "请勿传入非法参数")
    private String authority;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> list = new ArrayList<>();
        list.add(new SimpleGrantedAuthority(authority));
        return list;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enable == 1;
    }

}

9. 可能需要的坐标

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.1</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
此文阅读完毕,您可以:分享
二维码图片 扫描关注我们哟