目录
一、统一结果返回
在基础模块设置(base)
接口统一返回类R.java
package com.lyj.service.common;
import com.lyj.service.enums.ErrorCodeEnum;
import lombok.Getter;
import java.io.Serializable;
/**
*接口统一返回值
*/
@Getter
public class R<T> implements Serializable {
private static final long serialVersionUID = 7498171848815486866L;
/**
* 系统定义的业务错误码(非HTTP标准状态码)
*
* @seecom.lyj.service.enums.ErrorCodeEnum
*/
private int code;
/**
* 系统定义的业务错误信息
*
* @see com.lyj.service.enums.ErrorCodeEnum
*/
private String msg;
/**
* 本次请求的返回值
*/
private T data;
/**
* 时间戳
*/
private long timestamp ;
public R(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
public R() {
}
private R(int code, String msg) {
this(code, msg, null);
}
/**
* 封装请求成功的返回值
*
* @param data 返回值
* @return R
*/
public static <T> R<T> ok(T data) {
return new R<>(ErrorCodeEnum.SUCCESS.getCode(), ErrorCodeEnum.SUCCESS.getMessage(), data);
}
/**
* 封装请求失败的返回值
*
* @param errorCode 系统定义的业务错误码和错误信息
* @return R
*/
public static <T> R<T> error(ErrorCodeEnum errorCode) {
return new R<>(errorCode.getCode(), errorCode.getMessage());
}
/**
* 封装请求失败的返回值
*
* @param code 自定义业务错误码
* @param message 自定义业务错误信息
* @return R
*/
public static <T> R<T> error(int code, String message) {
return new R<>(code, message);
}
/**
* 封装请求失败的返回值(系统定义的业务错误码+自定义业务错误信息)
*
* @param message 自定义业务错误信息
* @return R
*/
public static <T> R<T> error(String message) {
return new R<>(ErrorCodeEnum.BUSINESS_EXCEPTION.getCode(), message);
}
}
异常枚举类ErrorCodeEnum.java
package com.lyj.common.base.enums;
import lombok.Getter;
/**
* 异常枚举类
*/
@Getter
public enum ErrorCodeEnum {
// TODO 错误码需要进行规划调整
// 成功
SUCCESS(0, "成功"),
// 40xxx表示请求方原因导致的异常
/**
* 通用异常(400xx)
*/
BUSINESS_EXCEPTION(40000, "业务异常"),
PARAMETER_ERROR(40001, "参数异常"),
/**
* 参数异常(401xx)
*/
MISSING_PARAMETERS(40100, "参数缺失"),
/**
* 用户异常(4021X)
*/
USER_NOT_EXIST(40210, "用户不存在"),
USER_PHONE_NOT_EXIST(40210, "用户手机不存在"),
USER_NAME_DUPLICATE(40211, "用户名称重复"),
USER_DUPLICATE(40212, "用户重复添加"),
USER_ALREADY_BIND(40213, "用户已经绑定微信,重新绑定需退出原绑定微信"),
USER_NO_WORKGROUP(40214, "用户部门没有关联工作组"),
USER_NO_DEPT(40215, "用户没有部门信息"),
USER_DELETE_SELF_ERROR(40216, "您无法删除自己"),
USER_NOT_PHONE(40217, "未获取到有效手机号码,请联系智慧云门户配置"),
USER_NAME_EMPTY(40009, "用户名不能为空"),
USER_PASSWORD_EMPTY(40010, "密码不能为空"),
/**
* 角色异常(4022X)
*/
ROLE_NOT_EXIST(40220, "角色不存在"),
ROLE_NAME_DUPLICATE(40221, "角色名称重复"),
USER_ROLE_DUPLICATE(40222, "用户已关联此角色"),
USER_NO_ROLE(40223, "用户未分配角色,请联系管理员处理"),
REL_ROLE_ERROR(40224, "关联角色失败"),
ROLE_IN_USE(40225, "角色使用中"),
USE_DELETE_ERROR(40226, "无法删除"),
/**
* 部门异常(4023X)
*/
/**
* 认证异常(403xx)
*/
TOKEN_GENERATE_ERROR(40300, "生成token错误"),
LACK_TOKEN(40301, "token缺失"),
AUTHORIZED_TIMEOUT(40302, "token过期"),
LOGIN_ERROR(40303, "用户登录失败"),
NO_LOGGED_IN(40304, "用户未登录"),
UNAUTHORIZED(40305, "接口未授权"),
WITHOUT_AUTHORITY(40001, "用户没有权限"),
ILLEGAL_PARAMETER(40002, "参数非法"),
USER_FROZEN(40005, "用户已冻结"),
USER_EMAIL_DUPLICATE(40007, "用户邮箱重复"),
USER_WITHOUT_DEPARTMENT(40008, "用户没有所属部门"),
USER_PASSWORD_DUPLICATE(40011, "用户新密码与旧密码重复"),
USER_EMAIL_NOT_REGISTERED(40012, "该邮箱未注册"),
ROLE_ASSOCIATE_WITH_USER(40014, "当前角色下存在用户,请为用户分配新角色后删除"),
ROLE_CODE_DUPLICATE(40015, "角色码重复"),
DEPARTMENT_ASSOCIATE_WITH_USER(40017, "当前部门或子部门下存在用户,请为用户分配新部门后删除"),
ILLEGAL_TOKEN(40018, "token非法"),
AUTHENTICATED_TIMEOUT(40019, "认证超时"),
DATE_PARSE_ERROR(40021, "解析日期格式错误,请检查日期格式"),
DEPART_NOT_FOUND(40022, "部门不存在"),
USER_PASSWORD_IS_INCORRECT(40022, "用户或密码不正确"),
WORKING_STATUS(40023, "用户已离职或被冻结"),
ACCOUNT_LOCK(40024, "账号被锁定"),
// 50xxx表示响应方原因导致的异常
RUNTIME_EXCEPTION(50000, "运行时异常"),
SYSTEM_EXCEPTION(50001, "系统异常"),
REDIS_READ_TIMEOUT(50002, "redis数据读取超时"),
REDIS_WRITE_TIMEOUT(50003, "redis数据写入超时"),
MINIO_READ_TIMEOUT(50004, "minio数据读取超时"),
MINIO_WRITE_TIMEOUT(50005, "minio数据写入超时"),
MINIO_READ_ERROR(50006, "minio读取数据失败"),
MINIO_WRITE_ERROR(50007, "minio写入数据失败"),
DATA_CONNECT_ERROR(50008, "数据库连接失败"),
MINIO_CONNECT_ERROR(50008, "minio连接失败"),
MINIO_FILE_NOT_FOUND(500010, "未查询到对应文件记录"),
MINIO_FILE_NAME_TOO_LONG(50041, "文件名称过长,不能超过127个字符"),
MINIO_FILE_SUFFIX_ERROR(50042, "文件后缀名非法"),
MINIO_FILE_SIZE_ERROR(50043, "上传文件过大[单文件大小不得超过:%sMB],如需修改请联系管理员"),
MINIO_FILE_TOTAL_SIZE_ERROR(50044, "上传文件过大[总上传文件大小不得超过:%sMB],如需修改请联系管理员"),
FILE_SIZE_ERROR(50045, "上传文件失败"),
MINIO_PICTURE_FILE_SIZE_ERROR(50046, "上传图片失败,图片大小不得超过:%sMB,如需修改请联系管理员"),
BEAN_CLONE_ERROR(50010, "克隆bean失败"),
BEAN_COPY_ERROR(50011, "复制bean失败"),
DOWNLOAD_ERROR(500020, "下载失败"),
DOWNLOAD_FILE_EMPTY(500021, "下载附件记录为空"),
DOWNLOAD_FILE_NOTFOUND(500022, "未找到下载文件或下载文件已删除"),
// 文件类异常
ANALYSE_FILE_NAME_ERROR(50021, "解析文件名称失败"),
// 压缩类异常
ZIP_FILE_ERROR(50031, "压缩文件失败"),
SQL_ERROR(50201, "系统异常"),
MD5_ERROR(50202, "MD5加密失败"),
URL_PARSE_ERROR(50203, "url编码失败"),
/**
* 系统异常信息
*/
SYSTEM_INSERT_ID_EXIT(60501, "新增系统id必须为空"),
SYSTEM_UPDATE_ID_EXIT(60502, "更新系统id必须不能为空"),
SYSTEM_BASE_NOT_EXIT(60503, "新增|修改系统基础信息不存在"),
;
private final int code;
private final String message;
ErrorCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
public static String getMessageByCode(Integer code) {
for (ErrorCodeEnum codeEnum : ErrorCodeEnum.values()) {
if (codeEnum.getCode() == code) {
return codeEnum.getMessage();
}
}
return "";
}
}
二、统一异常处理
在基础模块设置(base)
自定义异常类BusinessException.java
package com.lyj.common.base.exceptions;
import com.lyj.common.base.enums.ErrorCodeEnum;
import lombok.Getter;
/**
*自定义异常
*/
@Getter
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 8860475639586178484L;
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(ErrorCodeEnum errorCode) {
this(errorCode.getCode(), errorCode.getMessage());
}
public BusinessException(String message) {
this(ErrorCodeEnum.BUSINESS_EXCEPTION.getCode(), message);
}
}
异常工具类BizExceptionUtil.java
package com.lyj.service.util;
import com.lyj.service.enums.ErrorCodeEnum;
import com.lyj.service.exceptions.BusinessException;
/**
* 异常工具类
*/
public class BizExceptionUtil {
private BizExceptionUtil() {
super();
}
/**
* 抛出异常
*
* @param code 异常码
* @param message 异常信息
*/
public static void bizException(int code, String message) {
throw new BusinessException(code, message);
}
/**
* 抛出异常
*
* @param errorCode 异常枚举值
*/
public static void bizException(ErrorCodeEnum errorCode) {
BizExceptionUtil.bizException(errorCode.getCode(), errorCode.getMessage());
}
/**
* 抛出异常
*
* @param message 异常信息
*/
public static void bizException(String message) {
BizExceptionUtil.bizException(ErrorCodeEnum.BUSINESS_EXCEPTION.getCode(), message);
}
}
统一异常处理GlobalExceptionHandler.java
package com.lyj.common.base.config;
import cn.hutool.core.util.StrUtil;
import com.lyj.common.base.common.R;
import com.lyj.common.base.enums.ErrorCodeEnum;
import com.lyj.common.base.exceptions.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import javax.validation.ConstraintViolationException;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 统一异常处理
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 其他业务异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public R handle(Exception e) {
log.error("unexpected Exception", e);
return R.error(ErrorCodeEnum.BUSINESS_EXCEPTION);
}
/**
* 参数验证失败异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(BindException.class)
public R handle(BindException e) {
log.error("unexpected BindException", e);
e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
FieldError fieldError = e.getBindingResult().getFieldError();
if (fieldError == null || StrUtil.isBlank(fieldError.getDefaultMessage())) {
return R.error(ErrorCodeEnum.PARAMETER_ERROR);
}
return R.error(ErrorCodeEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
}
/**
* 参数验证失败异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public R handle(ConstraintViolationException e) {
log.error("unexpected BindException", e);
return R.error(ErrorCodeEnum.PARAMETER_ERROR.getCode(), e.getMessage());
}
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handle(MethodArgumentNotValidException e) {
log.error("unexpected MethodArgumentNotValidException", e);
e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
FieldError fieldError = e.getBindingResult().getFieldError();
if (fieldError == null || StrUtil.isBlank(fieldError.getDefaultMessage())) {
return R.error(ErrorCodeEnum.PARAMETER_ERROR);
}
return R.error(ErrorCodeEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
}
/**
* 处理自定义异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(BusinessException.class)
public R handle(BusinessException e) {
log.error("expected BusinessException,errorMessage={}", e.getMessage());
return R.error(e.getCode(), e.getMessage());
}
/**
* 文件传输异常
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(MaxUploadSizeExceededException.class)
public R handle(MaxUploadSizeExceededException ex) {
if (ex.getCause().getCause() instanceof FileSizeLimitExceededException) {
double actual = BigDecimal.valueOf(((FileSizeLimitExceededException) ex.getCause().getCause()).getActualSize())
.divide(BigDecimal.valueOf(1024 * 1024), 3, RoundingMode.HALF_UP).doubleValue();
double permitted = BigDecimal.valueOf(((FileSizeLimitExceededException) ex.getCause().getCause()).getPermittedSize())
.divide(BigDecimal.valueOf(1024 * 1024), 3, RoundingMode.HALF_UP).doubleValue();
log.error("上传的文件大小: {}MB", actual);
log.error("允许上传的单个文件大小: {}MB", permitted);
return R.error(ErrorCodeEnum.MINIO_FILE_SIZE_ERROR.getCode(), String.format(ErrorCodeEnum.MINIO_FILE_SIZE_ERROR.getMessage(), permitted));
} else if (ex.getCause().getCause() instanceof SizeLimitExceededException) {
double actual = BigDecimal.valueOf(((FileSizeLimitExceededException) ex.getCause().getCause()).getActualSize())
.divide(BigDecimal.valueOf(1024 * 1024), 3, RoundingMode.HALF_UP).doubleValue();
double permitted = BigDecimal.valueOf(((FileSizeLimitExceededException) ex.getCause().getCause()).getPermittedSize())
.divide(BigDecimal.valueOf(1024 * 1024), 3, RoundingMode.HALF_UP).doubleValue();
log.error("上传的总文件大小: {}MB", actual);
log.error("允许的总上传文件大小: {}MB", permitted);
return R.error(ErrorCodeEnum.MINIO_FILE_TOTAL_SIZE_ERROR.getCode(), String.format(ErrorCodeEnum.MINIO_FILE_SIZE_ERROR.getMessage(), permitted));
}
log.error("文件上传错误信息: {}", ex.getMessage());
return R.error(ErrorCodeEnum.FILE_SIZE_ERROR);
}
}
三、测试
在管理应用模块测试(management)
测试接口testApi.java
package com.lyj.service.management.api;
import com.lyj.common.base.common.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotEmpty;
@RestController
@AllArgsConstructor //生成该类下全部属性的构造方法 代替@Autowired 属于lombok
@RequestMapping("/api/test/")
@Validated //有该@NotEmpty才生效
@Api(tags = "测试API")
public class testApi {
@GetMapping("/info")
@ApiOperation("测试info")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "id", required = true)
})
public R<Boolean> info(@NotEmpty(message = "id不能为空") @RequestParam("id") String id) {
return R.ok(Boolean.TRUE);
}
}
启动项目
访问Knife4j http://localhost:8080/doc.html
异常测试,手动抛出异常
public class testApi {
@GetMapping("/info")
@ApiOperation("测试info")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "id", required = true)
})
public R<Boolean> info(@NotEmpty(message = "id不能为空") @RequestParam("id") String id) {
BizExceptionUtil.bizException("系统异常!");
return R.ok(Boolean.TRUE);
}
}