跳至主要內容

framework-core

杨轩-国实信息大约 5 分钟framework

git地址:

http://10.16.202.103:8089/component/component-ser/gosci-tech-framework-core

异常处理

异常基础信息接口

返回的异常由异常编码和具体消息组成。

public interface IErrorCode extends Serializable {
    /**
     * 返回的code码
     * @return code
     */
    int getCode();
    /**
     * 返回的消息
     * @return 消息
     */
    String getMessage();
}

系统内置异常枚举

通过定义枚举定义系统内置异常。

@Getter
@AllArgsConstructor
public enum BaseErrorCode implements IErrorCode {

    /** 公共错误 */
    PARAM_MISS_ERROR(BaseErrorCode.PARAM_MISS_CODE, "缺少必要的请求参数"),
    PARAM_TYPE_ERROR(BaseErrorCode.PARAM_TYPE_ERROR_CODE, "请求参数类型错误"),
    PARAM_BIND_ERROR(BaseErrorCode.PARAM_BIND_ERROR_CODE, "请求参数绑定错误"),
    PARAM_VALID_ERROR(BaseErrorCode.PARAM_VALID_ERROR_CODE, "请求参数格式不合规则"),
    PARAM_REPEAT_ERROR(BaseErrorCode.PARAM_REPEAT_ERROR_CODE, "请求参数重复"),
    METHOD_NOT_SUPPORTED_ERROR(BaseErrorCode.METHOD_NOT_SUPPORTED_CODE, "不支持当前请求方法"),
    MEDIA_TYPE_NOT_SUPPORTED_ERROR(BaseErrorCode.MEDIA_TYPE_NOT_SUPPORTED_CODE, "不支持当前媒体类型"),
    MEDIA_TYPE_NOT_ACCEPT_ERROR(BaseErrorCode.MEDIA_TYPE_NOT_ACCEPT_CODE, "不接受的媒体类型"),

    DATA_NOT_EXIST_ERROR(BaseErrorCode.DATA_NOT_EXIST_CODE, "数据不存在"),
    DATA_EXISTED_ERROR(BaseErrorCode.DATA_EXISTED_CODE, "数据已存在"),
    DATA_ADD_ERROR(BaseErrorCode.DATA_ADD_FAILED_CODE, "数据添加失败,请重试或联系管理员!"),
    DATA_UPDATE_ERROR(BaseErrorCode.DATA_UPDATE_FAILED_CODE, "数据更新失败,请重试或联系管理员!"),
    DATA_DELETE_ERROR(BaseErrorCode.DATA_DELETE_FAILED_CODE, "数据删除失败,请重试或联系管理员!"),
    DATA_IMPORT_ERROR(BaseErrorCode.DATA_IMPORT_FAILED_CODE, "数据导入失败,请重试或联系管理员!"),
    DATA_EXPORT_ERROR(BaseErrorCode.DATA_EXPROT_FAILED_CODE, "数据导出失败,请重试或联系管理员!"),
    ACCESS_DEFINED_ERROR(BaseErrorCode.ACCESS_DEFINED_FAILED_CODE, "非法访问,没有认证!"),
    UNAUTHORIZED_ERROR(BaseErrorCode.UNAUTHORIZED_FAILED_CODE, "权限不够!");

    /***********通用 异常 code*******/

    /**
     * 缺少必要的请求参数
     */
    public static final int PARAM_MISS_CODE = 10001;
    /**
     * 请求参数类型错误
     */
    public static final int PARAM_TYPE_ERROR_CODE = 10002;
    /**
     * 请求参数绑定错误
     */
    public static final int PARAM_BIND_ERROR_CODE = 10003;
    /**
     * 参数校验失败
     */
    public static final int PARAM_VALID_ERROR_CODE = 10004;
    /**
     * 请求参数绑定错误
     */
    public static final int PARAM_REPEAT_ERROR_CODE = 10005;

    /**
     * 不支持当前请求方法
     */
    public static final int METHOD_NOT_SUPPORTED_CODE = 10006;

    /**
     * 不接受的文件类型
     */
    public static final int MEDIA_TYPE_NOT_ACCEPT_CODE = 10007;

    /**
     * 不支持当前文件类型
     */
    public static final int MEDIA_TYPE_NOT_SUPPORTED_CODE = 10008;


    /***********通用 数据 code*******/

    /**
     * 数据不存在
     */
    public static final int DATA_NOT_EXIST_CODE = 20001;
    /**
     * 数据已存在
     */
    public static final int DATA_EXISTED_CODE = 20003;
    /**
     * 数据添加失败
     */
    public static final int DATA_ADD_FAILED_CODE = 20004;
    /**
     * 数据更新失败
     */
    public static final int DATA_UPDATE_FAILED_CODE = 20005;
    /**
     * 数据删除失败
     */
    public static final int DATA_DELETE_FAILED_CODE = 20006;
    /**
     * 数据导出失败
     */
    public static final int DATA_IMPORT_FAILED_CODE = 20007;
    /**
     * 数据导出失败
     */
    public static final int DATA_EXPROT_FAILED_CODE = 20008;

    // -----------------通用 权限 code-----------------
    /**
     * 非法访问,没有认证
     */
    public static final int ACCESS_DEFINED_FAILED_CODE = 30001;

    /**
     * 权限不够
     */
    public static final int UNAUTHORIZED_FAILED_CODE = 30002;

    /**
     * code编码
     */
    final int code;
    /**
     * 中文信息描述
     */
    final String message;
}

自定义异常

抛出异常时使用throw new BizException()的方式,内置了多种构造方法。

public class BizException extends RuntimeException
{
    private static final long serialVersionUID = 1L;

    private Integer code;

    private final String message;

    public BizException(IErrorCode errorCode) {
        super(errorCode.getMessage());
        super.fillInStackTrace();
        this.code = errorCode.getCode();
        this.message = errorCode.getMessage();
    }
    public BizException(String message)
    {
        this.message = message;
    }

    public BizException(String message, Integer code)
    {
        this.message = message;
        this.code = code;
    }

    public BizException(String message, Throwable e)
    {
        super(message, e);
        this.message = message;
    }

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

    public Integer getCode()
    {
        return code;
    }
}

全局异常捕获

@RestControllerAdvice
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Slf4j
public class ValidationAdvice {

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseBody
    public Result unauthorizedException(UnauthorizedException e) {
        //获取异常信息,获取异常堆栈的完整异常信息
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        //日志输出异常详情
        log.error(sw.toString());
        String message = e.getMessage();
        if (!StrUtil.isBlank(message)) {
            return Result.failed(message);
        }
        return Result.failed(SystemCodeEnum.UNAUTHORIZED);
    }

    @ExceptionHandler(BizException.class)
    @ResponseBody
    public Result bizException(BizException e) {
        //获取异常信息,获取异常堆栈的完整异常信息
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        //日志输出异常详情
        log.error(sw.toString());
        return Result.failed(e.getMessage());
    }

    /*
    * 验证注解如果验证不通过会抛 ConstraintViolationException 异常
    */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public Result handler(ConstraintViolationException e) {
        StringBuffer errorMsg = new StringBuffer();
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
        return Result.failed(errorMsg.toString());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Result handler(MethodArgumentNotValidException e) {
        StringBuffer sb = new StringBuffer();
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
        return Result.failed(sb.toString());
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public Result handler(MissingServletRequestParameterException e) {
        String parameterName = e.getParameterName();
        return Result.failed("缺少请求参数:" + parameterName);
    }

    @ExceptionHandler(SignatureException.class)
    @ResponseBody
    public Result handler(SignatureException e) {
        return Result.failed("验签失败");
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result handler(Exception e) {
        //获取异常信息,获取异常堆栈的完整异常信息
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        //日志输出异常详情
        log.error(sw.toString());
        return Result.failed("服务异常,请稍后再试");
    }
}

使用演示:

@GetMapping(value="/get/{id}")
public Result<SystemUserVO> ex(
        @PathVariable("id") Integer id ) {
    throw new BizException("未找到用户",20000);
}

接口返回结果:

{
    "msg": "未找到用户",
    "code": 500,
    "data": null,
    "success": false
}

Knife4j

Knife4j 是为 Java MVC 框架集成 Swagger 生成 Api 文档的增强解决方案,前身是 swagger-bootstrap-ui,致力于 springfox-swagger 的增强 UI 实现。

knife4j 为了契合微服务的架构发展,由于原来 swagger-bootstrap-ui 采用的是后端Java 代码 + 前端 UI混合打包的方式,在微服务架构下显的很臃肿,因此项目正式更名为 knife4j,更名后主要专注的方面如下:

  • 后端 Java 代码以及前端 UI 模块进行了分离,在微服务架构下使用更加灵活
  • 提供专注于 Swagger 的增强解决方案,不同于只是单纯增强前端 UI 部分

framework-core包中已经集成好了knife4j,项目中就不需要再引入swagger了,只需要在项目的配置文件中配置一些必要信息,如:

knife:
  enable: true
  basePackage: com.gosci.tech.engine
  title: 海洋数据引擎API文档
  description: 海洋数据引擎API文档
  version: 1.0.0
  contact:
    name: Hydra
    url: http://10.16.202.109:7777/
    email: 765666922@qq.com

说明:

  • enable只有为true时才会启用knife4j
  • basePackage定义扫包路径,只有这个路径下的加了@Api注解的才会被knife4j生成接口文档

用户信息

使用TransmittableThreadLocal保存当前线程的用户信息,保存的信息如下:

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AuthUser {
    @NotNull(message = "用户id不可为空")
    private Long userId;
    @NotEmpty(message = "用户名不可为空")
    private String userName;
}

在项目中,可根据实际认证方式获取用户信息,然后注入。例如:

LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(KeyConstants.USER + userId);
//...
AuthUser authUser = new AuthUser();
authUser.setUserId(loginUser.getSysUser().getUserId());
authUser.setUserName(loginUser.getSysUser().getUserName());
AuthContextHolder.getInstance().setContext(authUser);

使用:

AuthUser authUser = AuthContextHolder.getInstance().authUser();

启动日志增强

打印激活的配置文件数量,项目运行地址以及接口文档地址信息: