springboot项目脚手架

如果本文对您有所帮助,动动小手,点赞不迷路~

每次新建一个项目时,大量工作需要重复,所以做了一个基于springboot的脚手架项目。

一个成熟的springboot项目应该包含哪些基本内容?

  • token校验:对接口的安全性进行一定的保障;
  • swagger文档:方便前后端联调;
  • 代码生成器:减轻开发中POJO类、CRUD工作量,加快开发速度;
  • 统一返回格式封装:包含基础返回数据格式和分页返回数据格式;
  • 常用工具类:看业务需求和工作中遇见比较好的工具类;
  • 全局异常配置:将错误信息返回给用户时便于理解。具体应包含:错误枚举和自定义异常;
  • 多环境配置文件
  • 日志配置:出现问题时便于查找。具体应包含:日志文件名按日期创建、统一打印接口出入参等。

JWT token

token主要是对访问资源做一定的保护,防止受到攻击,以下示例中只进行了登录校验,并未对资源访问角色做验证,部分配置和业务代码未贴出。

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthenticationInterceptor())
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/sendSms")
                // 开放swagger
                .excludePathPatterns("/swagger-ui.html")
                .excludePathPatterns("/webjars/**")
                .excludePathPatterns("/v2/**")
                .excludePathPatterns("/swagger-resources/**")
                .addPathPatterns("/**");
    }
}
@Slf4j
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    public static AuthenticationInterceptor interceptor;

    @Resource
    private UserService userService;

    @Resource
    private SysConfig sysConfig;

    @Resource
    private JwtUtil jwtUtil;

    @PostConstruct
    public void init() {
        interceptor = this;
        interceptor.sysConfig = this.sysConfig;
        interceptor.userService = this.userService;
        interceptor.jwtUtil = this.jwtUtil;
    }

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws BusinessException {
        String token = httpServletRequest.getHeader(interceptor.sysConfig.getHeaderToken());
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();

        if (method.isAnnotationPresent(ValidUser.class)) {
            ValidUser validUser = method.getAnnotation(ValidUser.class);
            if (!validUser.value()) {
                return true;
            }
        }
        // 执行认证
        if (token == null) {
            throw new BusinessException(ResponseCode.UN_AUTHENTICATION);
        }
        // 获取token中的userId
        Long userId;
        try {
            userId = interceptor.jwtUtil.getUserIdByToken(token);
        } catch (JWTDecodeException j) {
            throw new BusinessException(ResponseCode.UN_AUTHENTICATION);
        }
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(User::getStatus, 1);
        wrapper.eq(User::getId, userId);
        User user = interceptor.userService.getBaseMapper().selectOne(wrapper);
        if (user == null) {
            throw new BusinessException(ResponseCode.USER_NOT_EXIST);
        }
        if (user.getStatus() == 0) {
            throw new BusinessException(ResponseCode.USER_IS_DISABLED);
        }
        // 验证 token
        interceptor.jwtUtil.validateToken(user, token);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
    }
}
@Slf4j
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    public static AuthenticationInterceptor interceptor;

    @Resource
    private UserService userService;

    @Resource
    private SysConfig sysConfig;

    @Resource
    private JwtUtil jwtUtil;

    @PostConstruct
    public void init() {
        interceptor = this;
        interceptor.sysConfig = this.sysConfig;
        interceptor.userService = this.userService;
        interceptor.jwtUtil = this.jwtUtil;
    }

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws BusinessException {
        String token = httpServletRequest.getHeader(interceptor.sysConfig.getHeaderToken());
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();

        if (method.isAnnotationPresent(ValidUser.class)) {
            ValidUser validUser = method.getAnnotation(ValidUser.class);
            if (!validUser.value()) {
                return true;
            }
        }
        // 执行认证
        if (token == null) {
            throw new BusinessException(ResponseCode.UN_AUTHENTICATION);
        }
        // 获取token中的userId
        Long userId;
        try {
            userId = interceptor.jwtUtil.getUserIdByToken(token);
        } catch (JWTDecodeException j) {
            throw new BusinessException(ResponseCode.UN_AUTHENTICATION);
        }
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(User::getStatus, 1);
        wrapper.eq(User::getId, userId);
        User user = interceptor.userService.getBaseMapper().selectOne(wrapper);
        if (user == null) {
            throw new BusinessException(ResponseCode.USER_NOT_EXIST);
        }
        if (user.getStatus() == 0) {
            throw new BusinessException(ResponseCode.USER_IS_DISABLED);
        }
        // 验证 token
        interceptor.jwtUtil.validateToken(user, token);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
    }
}

swagger

swagger配置较为简单:引入依赖、初始化配置即可。

	//模块创建示例
	@Bean
    public Docket createUserRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("用户")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.abab.common.controller"))
                .paths(PathSelectors.regex("/user/.*"))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("springboot项目基础框架")
                .description("springboot项目脚手架")
                .version("1.0")
                .contact(new Contact("alex","url","123@idwarf.cn"))
                .build();
    }

我的项目中使用了两套swaggerUI,一套是springfox-swagger-ui,另一套是swagger-bootstrap-ui。界面如下:
springfox-swagger-ui
swagger-bootstrap-ui

代码生成器

项目中常使用的代码生成器有:lombok注解(构造方法,setter,getter,hashcode,toString等),mybatis-plus(可直接使用CRUD的SQL,无需自己开发)。
以上详情请自行百度。

统一返回格式封装

可根据业务需求自行定义,示例仅供参考。示例中ResponseCode.java为错误码枚举类。

@Data
@ApiModel("统一封装返回数据格式")
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {

    @ApiModelProperty("错误码")
    private Integer code;


    @ApiModelProperty("错误信息")
    private String message;


    @ApiModelProperty("返回数据")
    private Object data;

    public static Result<Void> error(String message) {
        return new Result(ResponseCode.SERVER_EXCEPTION.getCode(), message, null);
    }

    public static Result<Void> error(Integer code, String message) {
        return new Result(code, message, null);
    }

    public static Result<Void> error(ResponseCode responseCode) {
        return new Result(responseCode.getCode(), responseCode.getMessage(), null);
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMessage(), data);
    }

    public static <T> Result<T> success() {
        return new Result<T>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMessage(), null);
    }

    @JsonIgnore
    public boolean isFailed() {
        return !ResponseCode.SUCCESS.getCode().equals(this.code);
    }

    @JsonIgnore
    public boolean isSucceed() {
        return ResponseCode.SUCCESS.getCode().equals(this.code);
    }
}

分页返回格式:

@Data
public class Page<T> {

    /**
     * 分页数据
     */
    @ApiModelProperty(value = "返回数据")
    private List<T> records;

    /**
     * 总条数
     */
    @ApiModelProperty(value = "返回数据总数")
    private Integer total;

    /**
     * 总页数
     */
    @ApiModelProperty(value = "总页数")
    private Integer pages;

    /**
     * 当前页
     */
    @ApiModelProperty(value = "当前页码")
    @Min(value = 1, message = "请输入正确的页码")
    private Integer current;

    /**
     * 每页显示数量
     */
    @ApiModelProperty(value = "每页显示数量")
    @Min(value = 1, message = "请输入正确的数量")
    private Integer size;

    /**
     * 设置MySQL查询中 limit offset
     */
    @ApiModelProperty(hidden = true)
    @JsonIgnore
    public Integer getStart() {
        return (this.current - 1) * size;
    }

    /**
     * 设置总记录数和页面总数
     *
     * @param total 总记录数
     */
    @ApiModelProperty(hidden = true)
    public void setTotal(Integer total) {
        this.total = total;
        this.setPages(this.total % this.size > 0 ? this.total / this.size + 1 : this.total / this.size);
    }
}

常用工具类

靠开发中的积累。

全局异常配置

自定义异常拦截处理

此节需配合统一返回格式封装一起看,此处只例举部分异常,根据自定义异常和业务需求可自行删减,需注意:子异常需放在父异常上面

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @Resource
    private HttpServletRequest request;
    
    @ExceptionHandler(TokenExpiredException.class)
    @ResponseBody
    public Result<Void> tokenExpiredHandler(TokenExpiredException e) {
        log.info("accessToken已失效:", e.getMessage());
        return Result.error(ResponseCode.INVALID_TOKEN);
    }

    @ExceptionHandler(JWTVerificationException.class)
    @ResponseBody
    public Result<Void> jwtVerificationException(JWTVerificationException e) {
        log.info("accessToken校验失败:", e.getMessage());
        return Result.error(ResponseCode.UN_AUTHENTICATION);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result<Void> unchangedExceptionHandler(Exception e) {
        log.error("请求url[{}],发生[{}]异常: ", request.getRequestURL(), e.getClass().getName(), e);
        return Result.error(ResponseCode.SERVER_EXCEPTION.getCode(), e.getMessage());
    }

}

多环境配置文件

针对不同的环境需要有不同的配置,最为显著的就是数据库配置(上面的swagger也只在devuat中配置),springboot中可以创建多个配置文件,根据环境去读取指定配置。
在application.properties中指定环境(启动脚本中也可指定):

spring.profiles.active=dev

多环境配置文件

日志配置

对日志输出配置可自行百度,本次配置主要是使用AOP对用户的出参和入参进行打印,以便查找并修复线上问题。

@Slf4j
@Aspect
@Component
public class AspectLog {

    /**
     * 换行符
     */
    private static final String LINE_SEPARATOR = System.lineSeparator();

    @Around("execution( * com.abab.common.controller..*.*(..))")
    public Object accountController(ProceedingJoinPoint joinPoint) throws Throwable {
        return doAround(joinPoint);
    }

    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取 @WebLog 注解的描述信息
        /*String methodDescription = getAspectLogDescription(joinPoint);*/
        // 打印请求相关参数
        log.info("========================================== Start ==========================================");
        // 打印请求 url
        log.info("URL            : {}", request.getRequestURL().toString());
        // 打印描述信息
        /*log.info("Description    : {}", methodDescription);*/
        // 打印 Http method
        log.info("HTTP Method    : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        log.info("IP             : {}", request.getRemoteAddr());
        // 打印请求入参
        log.info("Request Args   : {}", getParams(joinPoint));

        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = joinPoint.proceed();
            // 打印出参
            log.info("Response Data  : {}", JSONObject.toJSONString(result));
        } catch (Throwable throwable) {
            //throwable.printStackTrace();
            log.info("Response ERROR : {}", throwable);
            throw throwable;
        } finally {
            // 执行耗时
            log.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
            log.info("=========================================== End ===========================================" + LINE_SEPARATOR);
        }

        return result;
    }

    private String getParams(JoinPoint joinPoint) {
        JSONObject params = new JSONObject();
        // 参数名
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if ((arg instanceof HttpServletResponse) || (arg instanceof HttpServletRequest)
                        || (arg instanceof MultipartFile) || (arg instanceof MultipartFile[])) {
                    continue;
                }
                try {
                    params.put(argNames[i], args[i]);
                } catch (Exception e1) {
                    log.error(e1.getMessage());
                }
            }
        }
        return params.toJSONString();
    }

}
  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值