Day2 -- QingLuoPay基础功能搭建
Day2 -- QingLuoPay基础功能搭建
- 一,基础功能包含哪些部分?
- 二,功能实现
- 1. Http状态码定义
- 2. 自定义异常
- 3. 序列化返回数据
- 4. 数据校验
项目git地址:https://gitee.com/jixuonline/qing-luo-pay.git
一,基础功能包含哪些部分?
我们的基础工程也就是在项目开始之前我们需要将所需要的一下通用类如 状态码,自定义异常,数据校验,序列化返回数据等实现
- Http状态码
- 自定义异常
- 序列化返回数据
- 数据校验
该部分内容我把他放在了core包下实现
二,功能实现
可以看一下项目目录
1. Http状态码定义
这部分没必要多说,就是简单定义了我们的返回状态码
com.qingluo.pay.status.HttpStatus
package com.qingluo.pay.status;
import org.springframework.lang.Nullable;
/**
* 返回状态码
*
* @author dz
* @Date 2020-12-16
*/
public enum HttpStatus {
CONTINUE(100, HttpStatus.Series.INFORMATIONAL, "继续"),
SWITCHING_PROTOCOLS(101, HttpStatus.Series.INFORMATIONAL, "切换协议"),
PROCESSING(102, HttpStatus.Series.INFORMATIONAL, "处理中"),
CHECKPOINT(103, HttpStatus.Series.INFORMATIONAL, "检查点"),
OK(200, HttpStatus.Series.SUCCESSFUL, "请求成功"),
CREATED(201, HttpStatus.Series.SUCCESSFUL, "已创建"),
ACCEPTED(202, HttpStatus.Series.SUCCESSFUL, "已接受"),
NON_AUTHORITATIVE_INFORMATION(203, HttpStatus.Series.SUCCESSFUL, "非权威信息"),
NO_CONTENT(204, HttpStatus.Series.SUCCESSFUL, "无内容"),
RESET_CONTENT(205, HttpStatus.Series.SUCCESSFUL, "重置内容"),
PARTIAL_CONTENT(206, HttpStatus.Series.SUCCESSFUL, "部分内容"),
MULTI_STATUS(207, HttpStatus.Series.SUCCESSFUL, "多状态"),
ALREADY_REPORTED(208, HttpStatus.Series.SUCCESSFUL, "已报告"),
IM_USED(226, HttpStatus.Series.SUCCESSFUL, "IM 已使用"),
MULTIPLE_CHOICES(300, HttpStatus.Series.REDIRECTION, "多种选择"),
MOVED_PERMANENTLY(301, HttpStatus.Series.REDIRECTION, "永久重定向"),
FOUND(302, HttpStatus.Series.REDIRECTION, "已找到"),
/** @deprecated */
@Deprecated
MOVED_TEMPORARILY(302, HttpStatus.Series.REDIRECTION, "临时重定向"),
SEE_OTHER(303, HttpStatus.Series.REDIRECTION, "查看其他位置"),
NOT_MODIFIED(304, HttpStatus.Series.REDIRECTION, "未修改"),
/** @deprecated */
@Deprecated
USE_PROXY(305, HttpStatus.Series.REDIRECTION, "使用代理"),
TEMPORARY_REDIRECT(307, HttpStatus.Series.REDIRECTION, "临时重定向"),
PERMANENT_REDIRECT(308, HttpStatus.Series.REDIRECTION, "永久重定向"),
BAD_REQUEST(400, HttpStatus.Series.CLIENT_ERROR, "错误请求"),
UNAUTHORIZED(401, HttpStatus.Series.CLIENT_ERROR, "未授权"),
PAYMENT_REQUIRED(402, HttpStatus.Series.CLIENT_ERROR, "需要付款"),
FORBIDDEN(403, HttpStatus.Series.CLIENT_ERROR, "禁止访问"),
NOT_FOUND(404, HttpStatus.Series.CLIENT_ERROR, "未找到"),
METHOD_NOT_ALLOWED(405, HttpStatus.Series.CLIENT_ERROR, "方法不允许"),
NOT_ACCEPTABLE(406, HttpStatus.Series.CLIENT_ERROR, "不可接受"),
PROXY_AUTHENTICATION_REQUIRED(407, HttpStatus.Series.CLIENT_ERROR, "需要代理认证"),
REQUEST_TIMEOUT(408, HttpStatus.Series.CLIENT_ERROR, "请求超时"),
CONFLICT(409, HttpStatus.Series.CLIENT_ERROR, "冲突"),
GONE(410, HttpStatus.Series.CLIENT_ERROR, "已删除"),
LENGTH_REQUIRED(411, HttpStatus.Series.CLIENT_ERROR, "需要内容长度"),
PRECONDITION_FAILED(412, HttpStatus.Series.CLIENT_ERROR, "前提条件失败"),
PAYLOAD_TOO_LARGE(413, HttpStatus.Series.CLIENT_ERROR, "负载过大"),
/** @deprecated */
@Deprecated
REQUEST_ENTITY_TOO_LARGE(413, HttpStatus.Series.CLIENT_ERROR, "请求实体过大"),
URI_TOO_LONG(414, HttpStatus.Series.CLIENT_ERROR, "URI 过长"),
/** @deprecated */
@Deprecated
REQUEST_URI_TOO_LONG(414, HttpStatus.Series.CLIENT_ERROR, "请求 URI 过长"),
UNSUPPORTED_MEDIA_TYPE(415, HttpStatus.Series.CLIENT_ERROR, "不支持的媒体类型"),
REQUESTED_RANGE_NOT_SATISFIABLE(416, HttpStatus.Series.CLIENT_ERROR, "请求范围不可满足"),
EXPECTATION_FAILED(417, HttpStatus.Series.CLIENT_ERROR, "期望失败"),
I_AM_A_TEAPOT(418, HttpStatus.Series.CLIENT_ERROR, "我是一个茶壶"),
/** @deprecated */
@Deprecated
INSUFFICIENT_SPACE_ON_RESOURCE(419, HttpStatus.Series.CLIENT_ERROR, "资源空间不足"),
/** @deprecated */
@Deprecated
METHOD_FAILURE(420, HttpStatus.Series.CLIENT_ERROR, "方法失败"),
/** @deprecated */
@Deprecated
DESTINATION_LOCKED(421, HttpStatus.Series.CLIENT_ERROR, "目标已锁定"),
UNPROCESSABLE_ENTITY(422, HttpStatus.Series.CLIENT_ERROR, "无法处理的实体"),
LOCKED(423, HttpStatus.Series.CLIENT_ERROR, "已锁定"),
FAILED_DEPENDENCY(424, HttpStatus.Series.CLIENT_ERROR, "依赖失败"),
TOO_EARLY(425, HttpStatus.Series.CLIENT_ERROR, "过早"),
UPGRADE_REQUIRED(426, HttpStatus.Series.CLIENT_ERROR, "需要升级"),
PRECONDITION_REQUIRED(428, HttpStatus.Series.CLIENT_ERROR, "需要前提条件"),
TOO_MANY_REQUESTS(429, HttpStatus.Series.CLIENT_ERROR, "请求过多"),
REQUEST_HEADER_FIELDS_TOO_LARGE(431, HttpStatus.Series.CLIENT_ERROR, "请求头字段过大"),
UNAVAILABLE_FOR_LEGAL_REASONS(451, HttpStatus.Series.CLIENT_ERROR, "因法律原因不可用"),
INTERNAL_SERVER_ERROR(500, HttpStatus.Series.SERVER_ERROR, "服务器内部错误"),
NOT_IMPLEMENTED(501, HttpStatus.Series.SERVER_ERROR, "未实现"),
BAD_GATEWAY(502, HttpStatus.Series.SERVER_ERROR, "错误网关"),
SERVICE_UNAVAILABLE(503, HttpStatus.Series.SERVER_ERROR, "服务不可用"),
GATEWAY_TIMEOUT(504, HttpStatus.Series.SERVER_ERROR, "网关超时"),
HTTP_VERSION_NOT_SUPPORTED(505, HttpStatus.Series.SERVER_ERROR, "HTTP 版本不受支持"),
VARIANT_ALSO_NEGOTIATES(506, HttpStatus.Series.SERVER_ERROR, "变体也可协商"),
INSUFFICIENT_STORAGE(507, HttpStatus.Series.SERVER_ERROR, "存储空间不足"),
LOOP_DETECTED(508, HttpStatus.Series.SERVER_ERROR, "检测到循环"),
BANDWIDTH_LIMIT_EXCEEDED(509, HttpStatus.Series.SERVER_ERROR, "超出带宽限制"),
NOT_EXTENDED(510, HttpStatus.Series.SERVER_ERROR, "未扩展"),
NETWORK_AUTHENTICATION_REQUIRED(511, HttpStatus.Series.SERVER_ERROR, "需要网络认证");
private final int value;
private final Series series;
private final String reasonPhrase;
HttpStatus(int value, Series series, String reasonPhrase) {
this.value = value;
this.series = series;
this.reasonPhrase = reasonPhrase;
}
/**
* 获取状态码的值
*/
public int value() {
return this.value;
}
/**
* 获取状态码的系列
*/
public Series series() {
return this.series;
}
/**
* 获取状态码的描述
*/
public String getReasonPhrase() {
return this.reasonPhrase;
}
/**
* 根据状态码的值查找对应的枚举值
*/
public static HttpStatus valueOf(int statusCode) {
for (HttpStatus status : values()) {
if (status.value == statusCode) {
return status;
}
}
throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
}
/**
* 判断状态码是否为 1xx 信息响应
*/
public boolean is1xxInformational() {
return this.series == Series.INFORMATIONAL;
}
/**
* 判断状态码是否为 2xx 成功响应
*/
public boolean is2xxSuccessful() {
return this.series == Series.SUCCESSFUL;
}
/**
* 判断状态码是否为 3xx 重定向
*/
public boolean is3xxRedirection() {
return this.series == Series.REDIRECTION;
}
/**
* 判断状态码是否为 4xx 客户端错误
*/
public boolean is4xxClientError() {
return this.series == Series.CLIENT_ERROR;
}
/**
* 判断状态码是否为 5xx 服务器错误
*/
public boolean is5xxServerError() {
return this.series == Series.SERVER_ERROR;
}
/**
* HTTP 状态码系列
*/
public enum Series {
INFORMATIONAL(1),
SUCCESSFUL(2),
REDIRECTION(3),
CLIENT_ERROR(4),
SERVER_ERROR(5);
private final int value;
Series(int value) {
this.value = value;
}
/**
* 获取系列的值
*/
public int value() {
return this.value;
}
/**
* 根据状态码的值查找对应的系列
*/
public static Series valueOf(int statusCode) {
int seriesCode = statusCode / 100;
for (Series series : values()) {
if (series.value == seriesCode) {
return series;
}
}
throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
}
}
}
2. 自定义异常
先来看下面两张图,这是异常类的核心
在图一中我们自定义了一个异常类继承了RunTimeException,这样在抛出异常的时候就可以抛QLException
那么方法定义好了接受的参数怎么设置?在这里使用了枚举类来实现参数的介绍
我们来看一下枚举类的代码
public interface BaseEnum {
/**
* 业务状态码
*/
Integer getCode();
/**
* 业务说明
*/
String getValue();
}
public interface BaseExecptionEnum extends BaseEnum{
/**
* http响应状态码
*/
Integer getStatus();
}
通过上述两个接口我们就可以实现以上三个参数的获取(子类实现),这样就可以实现对枚举类的封装
public enum ExceptionEnum implements BaseExecptionEnum{
// 定义异常枚举类
PARAMS_NOT_NULL(1001,"参数不能为空");
private Integer code;
private Integer status;
private String value;
ExceptionEnum(Integer code,String msg){
}
ExceptionEnum(Integer code, Integer status,String msg){
}
/**
* 通过相应码获取对应枚举类
* @param code
* @return
*/
public static ExceptionEnum codeOf(Integer code) {
if (ObjectUtil.isEmpty(code)){
throw new QLException(PARAMS_NOT_NULL);
}
return EnumUtil.getBy(ExceptionEnum::getCode, code);
}
/**
* http响应状态码
*/
@Override
public Integer getStatus() {
return this.status;
}
/**
* 业务状态码
*/
@Override
public Integer getCode() {
return this.code;
}
/**
* 业务说明
*/
@Override
public String getValue() {
return this.value;
}
}
之后的核心就是我们的QLException自定义异常类的实现,该部分相当复杂,使用了异常链来实现父类异常与自定义异常的抛出,诸位感兴趣可以详细看看代码,其中包含注释
public class QLException extends RuntimeException{
// 序列化版本号
private static final long serialVersionUID = 1L;
// 错误信息
private String msg;
// 业务状态码,规则:4位数,从1001开始递增
private int code = 1001;
// Http 状态码
private int status;
/**
* 用于自定义异常的手动抛出
*
* throw new QLException();
* @param execptionEnum
*/
public QLException(BaseExecptionEnum execptionEnum){
// 调用父类设置错误信息
super(execptionEnum.getValue());
// 获取对应参数
this.status = execptionEnum.getStatus();
this.code = execptionEnum.getCode();
this.msg = execptionEnum.getValue();
}
public QLException(String msg){
// 获取对应参数
super(msg);
this.msg = msg;
}
public QLException(String msg,int code){
// 获取对应参数
super(msg);
this.msg = msg;
this.code = code;
}
public QLException(String msg,int code,int status){
// 获取对应参数
super(msg);
this.msg = msg;
this.code = code;
this.status = status;
}
/**
* s使用异常链的设计模式,通过自定义异常封装捕获的异常
* try {
* String content = FileReaderUtil.readFile(filePath);
* System.out.println("文件内容: " + content);
* } catch (FileReadException e) {
* // 捕获自定义异常,打印异常信息和原始异常信息
* System.err.println("捕获到自定义异常: " + e.getMessage());
* System.err.println("原始异常信息: " + e.getCause().getMessage());
* }
* @param execptionEnum
* @param err
*/
public QLException(BaseExecptionEnum execptionEnum,Throwable err){
// 上抛给父级构造处理
super(execptionEnum.getValue(),err);
// 获取对应参数
this.status = execptionEnum.getStatus();
this.code = execptionEnum.getCode();
this.msg = execptionEnum.getValue();
}
public QLException(String msg,Throwable err){
// 上抛给父级构造处理
super(msg,err);
// 获取对应参数
this.msg = msg;
}
public QLException(String msg,int code ,Throwable err){
// 上抛给父级构造处理
super(msg,err);
// 获取对应参数
this.msg = msg;
this.code = code;
}
public QLException(String msg,int code , int status ,Throwable err){
// 上抛给父级构造处理
super(msg,err);
// 获取对应参数
this.msg = msg;
this.code = code;
this.status = status;
}
}
做完这些就是对异常的拦截,在这里创建了一个全局异常处理器,通过@RestControllerAdvice注解拦截Controller层的异常信息,在这里要拦截的数据分为几部分
- 空参异常(未实现)
- 自定义异常
- 未知异常
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 拦截Assert抛出的异常
* @param e
* @return
*/
@ExceptionHandler
public static ResponseEntity<Object> handle(IllegalArgumentException e){
if (ObjectUtil.isNotEmpty(e.getMessage())){
log.error("Assert参数断言异常 --> ",ExceptionUtil.getMessage(e));
}
return ResponseEntity.status(BAD_REQUEST).body(
MapUtil.builder().put("code",BAD_REQUEST)
.put("msg",e.getMessage())
);
}
/**
* 对于ValidationException的异常进行处理
* @param e
* @return
*/
@ExceptionHandler
public static ResponseEntity<Object> handle(ValidationException e){
List<String> errors = null;
if (e instanceof ConstraintViolationException) {
ConstraintViolationException exs = (ConstraintViolationException) e;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
errors = violations.stream()
.map(ConstraintViolation::getMessage).collect(Collectors.toList());
}
if (ObjectUtil.isNotEmpty(e.getCause())) {
log.error("参数校验失败异常 -> ", e);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(MapUtil.<String, Object>builder()
.put("code", HttpStatus.BAD_REQUEST.value())
.put("msg", errors)
.build());
}
/**
* 捕获自定义异常
* @param e
* @return
*/
@ExceptionHandler
public static ResponseEntity<Object> handle(QLException e){
// 如果异常存在打印异常
if (ObjectUtil.isNotEmpty(e.getMessage())){
log.error("自定义异常 --> " , ExceptionUtil.getMessage(e));
}
/**
* ResponseEntity.status(e.getStatus()) --> 获取响应对象
* 设置Http相应正文
*
*
* 400 Bad Request
* {
* "code": 1001,
* "msg": "Invalid input parameters"
* }
*/
return ResponseEntity.status(e.getStatus()).body(
MapUtil.builder().put("code",e.getCode())
.put("msg",e.getMsg())
);
}
/**
* 处理未知异常
* @param e
* @return
*/
@ExceptionHandler
public static ResponseEntity<Object> handle(Exception e){
if (ObjectUtil.isNotEmpty(e.getMessage())){
log.error("未定义异常 --> " , ExceptionUtil.getMessage(e));
}
return ResponseEntity.status(BAD_REQUEST).body(
MapUtil.builder().put("code",BAD_REQUEST)
.put("msg", e.getMessage())
);
}
}
3. 序列化返回数据
用于序列化返回数据
/**
* @Author JiXu
* @Date 2025/1/3 下午3:24
* @ClassName: ResSerialization
* @Description: 用于序列化返回数据
*/
// 保证在序列化的过程当中如果value值为null,那么其key也为null
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResSerialization<T> implements Serializable {
// 声明序列化版本
private static final long serialVersionUID = 7735505903525411467L;
// 响应状态码
private int code;
// 响应消息
private String msg;
// 返回数据
private T data;
/**
* 创建构造函数
*/
public ResSerialization(int code, String msg) {
this.code = code;
this.msg = msg;
}
public ResSerialization(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public ResSerialization(int code, T data) {
this.code = code;
this.data = data;
}
/**
* 声明泛型方法
*/
public static <T> ResSerialization<T> success(int code, String msg){
return new ResSerialization<T>(code,msg);
}
public static <T> ResSerialization<T> success(T data){
return new ResSerialization<T>(HttpStatus.SUCCESS,null,data);
}
public static <T> ResSerialization<T> success(int code, String msg,T data){
return new ResSerialization<T>(code,msg,data);
}
public static <T> ResSerialization<T> error(int code, String msg,T data){
return new ResSerialization<T>(code,msg,data);
}
public static <T> ResSerialization<T> error(int code, String msg){
return new ResSerialization<T>(code,msg);
}
public static <T> ResSerialization<T> error(T data){
return new ResSerialization<T>(HttpStatus.SUCCESS,null,data);
}
public int getCode(){
return code;
}
public String getMsg(){
return msg;
}
public T getData(){
return data;
}
}
4. 数据校验
对于数据校验我们采用一下两种方法
- hibernate-validator所提供的注解
- 业务层面中使用Assert断言以及if语句进行判断
如何使用hibernate-validator
在com.qingluo.pay.aop;包下创建ValidatedAspect实现切面增强
@Aspect
@Slf4j
@EnableAspectJAutoProxy
@Component
public class ValidatedAspect {
@Resource
private Validator validator;
@Around("execution(* com.sl..controller.*Controller.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 获取@RequestBody映射的对象
Object body = AspectUtil.getBody(proceedingJoinPoint);
// 不为空的body进行拦截校验
if (!ObjectUtil.isEmpty(body)) {
// 进行校验
Set<ConstraintViolation<Object>> validateResult = validator.validate(body);
if (CollUtil.isNotEmpty(validateResult)) {
//没有通过校验,抛出异常,由统一异常处理机制进行处理,响应400
String info = JSONUtil.toJsonStr(validateResult.stream()
.map(ConstraintViolation::getMessage).collect(Collectors.toList()));
throw new SLException(info, HttpStatus.BAD_REQUEST.value());
}
}
//校验通过,执行原方法
return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
}
}