SpringBoot统一参数校验开源项目:param-validate
param-validate
在开发过程中,用户传递的数据不一定合法,虽然可以通过前端进行一些校验,但是为了确保程序的安全性,保证数据的合法,在后台进行数据校验也是十分必要的。
后台的参数校验的解决方案
在controller方法中校验:
后台的参数是通过controller方法获取的,所以最简单的参数校验的方法,就是在controller方法中进行参数校验。在controller方法中如果进行参数校验会有大量重复、没有太大意义的代码。
使用拦截器、过滤器校验
为了保证controller中的代码有更好的可读性,可以将参数校验的工作交由拦截器(Interceptor)或者过滤器(Filter)来完成,但是此时又存在一个问题:非共性的参数需要每个方法都创建一个与之对应的拦截器(或者过滤器)。
实现对Entity的统一校验
鉴于上述解决方案的缺点,我们可以借助AOP的思想来进行统一的参数校验。思想是通过自定义注解来完成对实体类属性的标注,在AOP中扫描加了自定义注解的属性,对其进行注解属性标注的校验。对于不满足的参数直接抛出自定义异常,交由全局异常处理来处理并返回友好的提示信息。
认识Param-Validate
以上的解决方案各有优劣,param-validate采用了AOP+注解的形式解决了统一参数的校验。使用四个注解可以解决大部分的参数校验,有效的简化了代码、提高了程序的可读性和开发效率。
Param-Validate基于SpringBoot2.3.0.RELEASE,所以请保证你的SpringBoot版本和其一致,如果必须使用其他版本,可以将其内包含的SpringBoot依赖排除。
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.18338862369</groupId>
<artifactId>param-validate</artifactId>
<version>1.0.0</version>
<description>SpringBoot ParamValidate</description>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<aspectjweaver.version>1.9.5</aspectjweaver.version>
<lombok.version>1.18.12</lombok.version>
</properties>
<dependencies>
<!--web内置了jackson-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- aspect -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <optional>true</optional>-->
<version>${lombok.version}</version>
</dependency>
</dependencies>
</project>
项目结构
下图是项目的结构图。接下来,我们逐个包来介绍一下
response包
此包中定义的内容为用户相应的类
HttpStatus枚举
这个枚举是从Spring源码中拿到,并且拓展的一个枚举。其封装了很多常见的状态码和状态信息。
package cn.rayfoo.validate.response;
/**
* @author rayfoo@qq.com
* @version 1.0
* @date 2020/8/5 11:37
* @description
*/
public enum HttpStatus {
/**
* 登录成功
* {@code 600 Login Success}.
*/
LOGIN_SUCCESS(600, "Login Success" ),
/**
* 重新登录
* {@code 605 Login Success}.
*/
LOGIN_AGAIN(605, "Login AGAIN" ),
/**
* 登录超时
* {@code 601 Login Out Time}.
*/
LOGIN_OUT_TIME(601, "Login Out Time" ),
/**
* 用户无访问权限
* {@code 602 Not Roles}.
*/
NOT_ROLES(602, "Not Roles" ),
/**
* 用户未注册
* {@code 603 Not Register}.
*/
NOT_REGISTER(603, "Not Register未找到该账号" ),
/**
* 用户未注册
* {@code 604 AuthenticationExcption}.
*/
AUTHENTICATION_EXCPTION(604, "身份认证错误" ),
/**
* 未知的账号异常
* {@code 606 Unknown Account}.
*/
UNKNOWN_ACCOUNT(606, "账号已注销" ),
/**
* 请求中的参数有误
* {@code 705 Parameter Error}.
*/
PARAMETER_ERROR(705, "Parameter Error" ),
/**
* 验证码错误
* {@code 704 Invalid Captcha}.
*/
INVALID_CAPTCHA(704, "Invalid Captcha验证码错误" ),
/**
* 邮箱和手机号验证错误
*/
EMAIL_OR_PHONE_ERROR(703, "email or phone error" ),
/**
* 用户已激活
*/
HAS_BEEN_ACTIVATED(702, "has been activated" ),
/**
* 用户名或密码错误
*/
USERNAME_OR_PASSWORD_ERROR(700, "username or password error用户名或密码错误" ),
/**
* 用户未启用
*/
USER_NOT_ENABLED(699, "user not enabled" ),
/**
* 验证码错误
*/
ACTIVATION_CODE_ERROR(698, "Activation code error" ),
/**
* 用户名被占用
*/
USERNAME_IS_OCCUPIED(697, "Username is occupied" ),
/**
* 返回无数据
* {@code 706 not data}.
*/
NOT_DATA(706, "not data" ),
/**
* 流程操作成功
* {@code 710 Successful operation}.
*/
SUCCESSFUL_OPERATION(710, "Successful operation" ),
/**
* 数据冲突,无法存储!
*/
DATA_CONFLICT(711, "Data conflict" ),
/**
* 删除操作失败
*/
CANNOT_DELETE(712, "Cannot delete" ),
/**
* 操作失败
* {@code 720 operation failed}.
*/
FAILED_OPERATION(720, "operation failed" ),
NOT_VTASKSTATUS(709, "not vtaskstatus" ),
// 1xx Informational
/**
* {@code 100 Continue}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">HTTP/1.1</a>
*/
CONTINUE(100, "Continue" ),
/**
* {@code 101 Switching Protocols}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">HTTP/1.1</a>
*/
SWITCHING_PROTOCOLS(101, "Switching Protocols" ),
/**
* {@code 102 Processing}.
*
* @see <a href="http://tools.ietf.org/html/rfc2518#section-10.1">WebDAV</a>
*/
PROCESSING(102, "Processing" ),
/**
* {@code 103 Checkpoint}.
*
* @see <a href="http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting
* resumable POST/PUT HTTP requests in HTTP/1.0</a>
*/
CHECKPOINT(103, "Checkpoint" ),
// 2xx Success
/**
* {@code 200 OK}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">HTTP/1.1</a>
*/
OK(200, "OK" ),
/**
* {@code 201 Created}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">HTTP/1.1</a>
*/
CREATED(201, "Created" ),
/**
* {@code 202 Accepted}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">HTTP/1.1</a>
*/
ACCEPTED(202, "Accepted" ),
/**
* {@code 203 Non-Authoritative Information}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">HTTP/1.1</a>
*/
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information" ),
/**
* {@code 204 No Content}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">HTTP/1.1</a>
*/
NO_CONTENT(204, "No Content" ),
/**
* {@code 205 Reset Content}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">HTTP/1.1</a>
*/
RESET_CONTENT(205, "Reset Content" ),
/**
* {@code 206 Partial Content}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">HTTP/1.1</a>
*/
PARTIAL_CONTENT(206, "Partial Content" ),
/**
* {@code 207 Multi-Status}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-13">WebDAV</a>
*/
MULTI_STATUS(207, "Multi-Status" ),
/**
* {@code 208 Already Reported}.
*
* @see <a href="http://tools.ietf.org/html/rfc5842#section-7.1">WebDAV Binding Extensions</a>
*/
ALREADY_REPORTED(208, "Already Reported" ),
/**
* {@code 226 IM Used}.
*
* @see <a href="http://tools.ietf.org/html/rfc3229#section-10.4.1">Delta encoding in HTTP</a>
*/
IM_USED(226, "IM Used" ),
// 3xx Redirection
/**
* {@code 300 Multiple Choices}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">HTTP/1.1</a>
*/
MULTIPLE_CHOICES(300, "Multiple Choices" ),
/**
* {@code 301 Moved Permanently}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">HTTP/1.1</a>
*/
MOVED_PERMANENTLY(301, "Moved Permanently" ),
/**
* {@code 302 Found}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">HTTP/1.1</a>
*/
FOUND(302, "Found" ),
/**
* {@code 302 Moved Temporarily}.
*
* @see <a href="http://tools.ietf.org/html/rfc1945#section-9.3">HTTP/1.0</a>
* @deprecated In favor of {@link #FOUND} which will be returned from {@code HttpStatus.valueOf(302)}
*/
@Deprecated
MOVED_TEMPORARILY(302, "Moved Temporarily" ),
/**
* {@code 303 See Other}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">HTTP/1.1</a>
*/
SEE_OTHER(303, "See Other" ),
/**
* {@code 304 Not Modified}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">HTTP/1.1</a>
*/
NOT_MODIFIED(304, "Not Modified" ),
/**
* {@code 305 Use Proxy}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">HTTP/1.1</a>
*/
USE_PROXY(305, "Use Proxy" ),
/**
* {@code 307 Temporary Redirect}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">HTTP/1.1</a>
*/
TEMPORARY_REDIRECT(307, "Temporary Redirect" ),
/**
* {@code 308 Resume Incomplete}.
*
* @see <a href="http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting
* resumable POST/PUT HTTP requests in HTTP/1.0</a>
*/
RESUME_INCOMPLETE(308, "Resume Incomplete" ),
// --- 4xx Client Error ---
/**
* {@code 400 Bad Request}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">HTTP/1.1</a>
*/
BAD_REQUEST(400, "Bad Request" ),
/**
* {@code 401 Unauthorized}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">HTTP/1.1</a>
*/
UNAUTHORIZED(401, "Unauthorized" ),
/**
* {@code 402 Payment Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">HTTP/1.1</a>
*/
PAYMENT_REQUIRED(402, "Payment Required" ),
/**
* {@code 403 Forbidden}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">HTTP/1.1</a>
*/
FORBIDDEN(403, "Forbidden" ),
/**
* {@code 404 Not Found}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">HTTP/1.1</a>
*/
NOT_FOUND(404, "Not Found" ),
/**
* {@code 405 Method Not Allowed}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">HTTP/1.1</a>
*/
METHOD_NOT_ALLOWED(405, "Method Not Allowed" ),
/**
* {@code 406 Not Acceptable}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">HTTP/1.1</a>
*/
NOT_ACCEPTABLE(406, "Not Acceptable" ),
/**
* {@code 407 Proxy Authentication Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">HTTP/1.1</a>
*/
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required" ),
/**
* {@code 408 Request Timeout}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">HTTP/1.1</a>
*/
REQUEST_TIMEOUT(408, "Request Timeout" ),
/**
* {@code 409 Conflict}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">HTTP/1.1</a>
*/
CONFLICT(409, "Conflict" ),
/**
* {@code 410 Gone}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">HTTP/1.1</a>
*/
GONE(410, "Gone" ),
/**
* {@code 411 Length Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">HTTP/1.1</a>
*/
LENGTH_REQUIRED(411, "Length Required" ),
/**
* {@code 412 Precondition failed}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">HTTP/1.1</a>
*/
PRECONDITION_FAILED(412, "Precondition Failed" ),
/**
* {@code 413 Request entity Too Large}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">HTTP/1.1</a>
*/
REQUEST_ENTITY_TOO_LARGE(413, "Request entity Too Large" ),
/**
* {@code 414 Request-URI Too Long}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">HTTP/1.1</a>
*/
REQUEST_URI_TOO_LONG(414, "Request-URI Too Long" ),
/**
* {@code 415 Unsupported Media Type}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">HTTP/1.1</a>
*/
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type" ),
/**
* {@code 416 Requested Range Not Satisfiable}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">HTTP/1.1</a>
*/
REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable" ),
/**
* {@code 417 Expectation Failed}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">HTTP/1.1</a>
*/
EXPECTATION_FAILED(417, "Expectation Failed" ),
/**
* {@code 418 I'm a teapot}.
*
* @see <a href="http://tools.ietf.org/html/rfc2324#section-2.3.2">HTCPCP/1.0</a>
*/
I_AM_A_TEAPOT(418, "I'm a teapot" ),
/**
* @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a>
*/
@Deprecated INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource" ),
/**
* @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a>
*/
@Deprecated METHOD_FAILURE(420, "Method Failure" ),
/**
* @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a>
*/
@Deprecated DESTINATION_LOCKED(421, "Destination Locked" ),
/**
* {@code 422 Unprocessable entity}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.2">WebDAV</a>
*/
UNPROCESSABLE_ENTITY(422, "Unprocessable entity" ),
/**
* {@code 423 Locked}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.3">WebDAV</a>
*/
LOCKED(423, "帐号已锁定" ),
/**
* {@code 424 Failed Dependency}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.4">WebDAV</a>
*/
FAILED_DEPENDENCY(424, "Failed Dependency" ),
/**
* {@code 426 Upgrade Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2817#section-6">Upgrading to TLS Within HTTP/1.1</a>
*/
UPGRADE_REQUIRED(426, "Upgrade Required" ),
/**
* {@code 428 Precondition Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-3">Additional HTTP Status Codes</a>
*/
PRECONDITION_REQUIRED(428, "Precondition Required" ),
/**
* {@code 429 Too Many Requests}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-4">Additional HTTP Status Codes</a>
*/
TOO_MANY_REQUESTS(429, "Too Many Requests" ),
/**
* {@code 431 Request Header Fields Too Large}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-5">Additional HTTP Status Codes</a>
*/
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large" ),
// --- 5xx Server Error ---
/**
* {@code 500 Internal Server Error}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">HTTP/1.1</a>
*/
INTERNAL_SERVER_ERROR(500, "Internal Server Error" ),
/**
* {@code 501 Not Implemented}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">HTTP/1.1</a>
*/
NOT_IMPLEMENTED(501, "Not Implemented" ),
/**
* {@code 502 Bad Gateway}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">HTTP/1.1</a>
*/
BAD_GATEWAY(502, "Bad Gateway" ),
/**
* {@code 503 service Unavailable}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">HTTP/1.1</a>
*/
SERVICE_UNAVAILABLE(503, "service Unavailable" ),
/**
* {@code 504 Gateway Timeout}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">HTTP/1.1</a>
*/
GATEWAY_TIMEOUT(504, "Gateway Timeout" ),
/**
* {@code 505 HTTP Version Not Supported}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">HTTP/1.1</a>
*/
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported" ),
/**
* {@code 506 Variant Also Negotiates}
*
* @see <a href="http://tools.ietf.org/html/rfc2295#section-8.1">Transparent Content Negotiation</a>
*/
VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates" ),
/**
* {@code 507 Insufficient Storage}
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.5">WebDAV</a>
*/
INSUFFICIENT_STORAGE(507, "Insufficient Storage" ),
/**
* {@code 508 Loop Detected}
*
* @see <a href="http://tools.ietf.org/html/rfc5842#section-7.2">WebDAV Binding Extensions</a>
*/
LOOP_DETECTED(508, "Loop Detected" ),
/**
* {@code 509 Bandwidth Limit Exceeded}
*/
BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded" ),
/**
* {@code 510 Not Extended}
*
* @see <a href="http://tools.ietf.org/html/rfc2774#section-7">HTTP Extension Framework</a>
*/
NOT_EXTENDED(510, "Not Extended" ),
/**
* {@code 511 Network Authentication Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-6">Additional HTTP Status Codes</a>
*/
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required" ),
SMS_CLIENT_EXCEPTION(512, "输入手机号不合法,请重新输入" ),
SMS_SERVER_EXCEPTION(513, "发送短信出现异常,请稍后重试~" );
private final int value;
private final String reasonPhrase;
private HttpStatus(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
/**
* Return the integer value of this status code.
*/
public int value() {
return this.value;
}
/**
* Return the reason phrase of this status code.
*/
public String getReasonPhrase() {
return reasonPhrase;
}
/**
* Returns the HTTP status series of this status code.
*
* @see Series
*/
public Series series() {
return Series.valueOf(this);
}
/**
* Return a string representation of this status code.
*/
@Override
public String toString() {
return Integer.toString(value);
}
/**
* Return the enum constant of this type with the specified numeric value.
*
* @param statusCode the numeric value of the enum to be returned
* @return the enum constant with the specified numeric value
* @throws IllegalArgumentException if this enum has no constant for the specified numeric value
*/
public static HttpStatus valueOf(int statusCode) {
for (HttpStatus status : values()) {
if (status.value == statusCode) {
return status;
}
}
throw new IllegalArgumentException("No matching constant for [" + statusCode + "]" );
}
/**
* Java 5 enumeration of HTTP status series.
* <p>Retrievable via {@link HttpStatus#series()}.
*/
public static enum Series {
INFORMATIONAL(1),
SUCCESSFUL(2),
REDIRECTION(3),
CLIENT_ERROR(4),
SERVER_ERROR(5);
private final int value;
private Series(int value) {
this.value = value;
}
/**
* Return the integer value of this status series. Ranges from 1 to 5.
*/
public int value() {
return this.value;
}
public static Series valueOf(int status) {
int seriesCode = status / 100;
for (Series series : values()) {
if (series.value == seriesCode) {
return series;
}
}
throw new IllegalArgumentException("No matching constant for [" + status + "]" );
}
public static Series valueOf(HttpStatus status) {
return valueOf(status.value);
}
}
}
Result类
此类是一个通用的返回结果类,起初使用了Swagger文档标注,后期单独整合为工具包,为了减少jar包的依赖就删去了Swagger部分。
package cn.rayfoo.validate.response;
import lombok.Builder;
import lombok.Data;
/**
* @author rayfoo@qq.com
* @date 2020年8月6日
*/
@Data@Builder
public class Result<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息
*/
private String msg;
/**
* 数据记录
*/
private T data;
public Result() {
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
exception包
此包内封装了关于异常的定义和处理类
MyAssert类
从类名就可以看出,这个类是进行断言判断的类,其内定义了多个重载的断言判断方法,断言方法可以在特定条件下中止方法的运行。
package cn.rayfoo.validate.exception;
import HttpStatus;
import lombok.extern.slf4j.Slf4j;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>断言类</p>
* @date 2020/8/7 9:43
*/
@Slf4j
public class MyAssert {
/**
* 如果为空直接抛出异常 类似于断言的思想
* @param status 当status为false 就会抛出异常 不继续执行后续语句
* @param msg 异常描述
*/
public static void assertMethod(boolean status, String msg) throws Exception {
//为false抛出异常
if (!status) {
//记录错误信息
log.error(msg);
//抛出异常
throw MyException.builder().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).msg(msg).build();
}
}
/**
* 如果为空直接抛出异常 类似于断言的思想
* @param status 当status为false 就会抛出异常 不继续执行后续语句
* @param code 状态码
* @param msg 异常描述
*/
public static void assertMethod(boolean status,Integer code, String msg) throws Exception {
//为false抛出异常
if (!status) {
//记录错误信息
log.error(msg);
//抛出异常
throw MyException.builder().code(code).msg(msg).build();
}
}
/**
* 如果为空直接抛出异常 类似于断言的思想
* @param status 当status为false 就会抛出异常 不继续执行后续语句
*/
public static void assertMethod(boolean status) throws Exception {
//为false抛出异常
if (!status) {
//记录错误信息
log.error(HttpStatus.INTERNAL_SERVER_ERROR.name());
//抛出异常
throw MyException.builder().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).msg(HttpStatus.INTERNAL_SERVER_ERROR.name()).build();
}
}
}
MyException类
这个类是自定义异常的声明,由于AOP中只有RuntimeException及其子类的异常可以被全局异常处理器处理,所以其继承了RuntimeException。拓展了code和msg属性
package cn.rayfoo.validate.exception;
import lombok.*;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/7/20 9:26 下午
* @Description: 自定义的异常...
*/
@Getter@Setter@Builder@NoArgsConstructor@AllArgsConstructor
public class MyException extends RuntimeException{
private int code;
private String msg;
}
ServerExceptionResolver类
此类用来处理手动抛出的自定义异常。
package cn.rayfoo.validate.exception;
import HttpStatus;
import Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author rayfoo@qq.com
* @version 1.0
* @date 2020/8/5 14:55
* @description 全局异常处理
*/
@ControllerAdvice@Slf4j
public class ServerExceptionResolver {
/**
* 处理自定义的异常
* @param ex
* @return
*/
@ExceptionHandler(MyException.class)@ResponseBody
public Result<String> resolveMyException(MyException ex){
//打印完整的异常信息
ex.printStackTrace();
//创建result
Result<String> result = new Result<>();
//设置result属性
result.setData(ex.getMsg());
result.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
result.setMsg(ex.getMsg());
//保存自定义异常日志
log.error(ex.getMsg());
return result;
}
}
enums包
很明显这个包中存放的是一个枚举,这个枚举是为了@Verify注解创建的,其作为@Verify的属性提供正则校验表。
RegexOption枚举
如果需要新的校验条件只需要拓展这个枚举即可。
package cn.rayfoo.validate.enums;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>参数校验枚举</p>
* @date 2020/8/7 15:51
*/
public enum RegexOption {
/**
* 缺省,表示不进行正则校验
*/
DEFAULT(""),
/**
* 邮箱正则
*/
EMAIL_REGEX("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"),
/**
* 手机号正则
*/
PHONE_NUMBER_REGEX("^((13[0-9])|(14[0|5|6|7|9])|(15[0-3])|(15[5-9])|(16[6|7])|(17[2|3|5|6|7|8])|(18[0-9])|(19[1|8|9]))\\d{8}$"),
/**
* 身份证正则
*/
IDENTITY_CARD_REGEX("(^\\d{18}$)|(^\\d{15}$)"),
/**
* URL正则
*/
URL_REGEX("http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?"),
/**
* IP地址正则
*/
IP_ADDR_REGEX("(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)"),
/**
* 用户名正则
*/
USERNAME_REGEX("^[a-zA-Z]\\w{5,20}$"),
/**
* 密码正则
*/
PASSWORD_REGEX("^[a-zA-Z0-9]{6,20}$");
/**
* 正则
*/
private String regex;
/**
* 构造方法
*
* @param regex
*/
private RegexOption(String regex) {
this.regex = regex;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
}
}
annotation包
显而易见,这个包是用来存放注解的。这四个注解可以完成大部分情况下的参数校验工作。
@Verify注解
标注在参数、字段上,用于标注需要校验的数据以及其校验的规则。
- name:描述,当校验结果为false时,返回用户提示信息时使用
- maxLength:最大长度,用于判断字符串类型,如果时默认值代表不进行判断
- minLength:最小长度,用于判断字符串类型,如果时默认值代表不进行判断
- required:是否为必填属性
- notNull:是否允许为空
- regular:是否需要进行正则校验,如果需要正则内容是什么,默认为不进行正则校验
- isEntity:是否是一个entity,如果是,递归调用判断其内的属性
package com.github.validate.annotation;
import com.github.validate.enums.RegexOption;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/7 15:33
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface Verify {
/** 参数名称 */
String name();
/** 参数最大长度 */
int maxLength() default Integer.MAX_VALUE;
/*** 是否是实体,如果是,继续判断其内部的值 */
boolean isEntity() default false;
/** 是否必填 这里只是判断是否为null */
boolean required() default true;
/** 是否为非空 是否为null和空串都判断 */
boolean notNull() default true;
/** 最小长度 */
int minLength() default Integer.MIN_VALUE;
/** 正则匹配 */
RegexOption regular() default RegexOption.DEFAULT;
}
@RequestMap注解
用于标注Controller方法的参数,用于指定Map类型参数。
- baseEntityList制定Entity的全类名,如果Entity的属性名和key一致,就进行参数校验,校验规则由Entity中加了@Verify注解的属性来决定
package cn.rayfoo.validate.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>对Map</p>
* @date 2020/8/8 19:50
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestMap {
/**
* 实体类全类名列表
*/
String[] baseEntityList();
}
@RequestEntity注解
用法同上,不过其标注的是Entity类型。
package cn.rayfoo.validate.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/8 22:43
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestEntity {
String value() default "";
}
@EnableVerify注解
用于标注在SpringBoot启动类
- execution:指定execution表达式,只有execution标识的方法才会进行全局参数校验
package cn.rayfoo.validate.annotation;
import EnableVerifyConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 13:36
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({EnableVerifyConfig.class})
public @interface EnableVerify {
String execution();
}
Config包
EnableVerifyConfig类
@EnableVerify引用了EnableVerifyConfig类,下面我们介绍一下这个类是如何 编写的:
这个类被@EnableVerify所引用,当读取到此注解时,就会自动注册此类registerBeanDefinitions方法内的代码主要是进行Bean的注册(由于默认时没有扫描cn,rayfoo包的,所以需要注册到Spring中的Bean要手动的放入Bean容器中),所以我们需要手动的注册好Aspect类,这里借鉴了通用Mapper的源码。
package cn.rayfoo.validate.config;
import EnableVerify;
import ValidateAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 13:37
*/
public class EnableVerifyConfig implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/*
* 注册Aspect
*/
//获取启动器上的注解
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableVerify.class.getName()));
//获取自定义注解中的属性
String execution = annoAttrs.getString("execution");
//创建Bean
BeanDefinition validateAdvice = new GenericBeanDefinition();
//指定Bean的字节码
validateAdvice.setBeanClassName(AspectJExpressionPointcutAdvisor.class.getName());
MutablePropertyValues validateAdviceProp = validateAdvice.getPropertyValues();
//设置Bean的属性
validateAdviceProp.addPropertyValue("advice", new ValidateAdvice());
validateAdviceProp.addPropertyValue("expression", execution);
//注册bean
registry.registerBeanDefinition("validateAdvice",validateAdvice);
/*
* 注册全局异常处理 如果你的包恰好是cn.rayfoo就需要将其注释
*/
//创建Bean
// BeanDefinition serverExceptionResolver = new GenericBeanDefinition();
// //指定Bean的字节码
// serverExceptionResolver.setBeanClassName(ServerExceptionResolver.class.getName());
// //注册bean
// registry.registerBeanDefinition("serverExceptionResolver",serverExceptionResolver);
}
}
aspect包
ValidateAdvice类
最后就是此篇文章的重中之重了,所有的注解解析和参数校验都是在这个类中完成的,其主要使用了前置通知,在通知中获取目标方法,通过反射拿到目标方法的参数,根据方法参数的类型再选择进行何种判断。
package com.github.validate.aspect;
import com.github.validate.annotation.RequestEntity;
import com.github.validate.annotation.RequestMap;
import com.github.validate.annotation.Verify;
import com.github.validate.exception.MyAssert;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 12:42
*/
public class ValidateAdvice implements MethodInterceptor {
/**
* 校验的类型
*/
private static final String LINK_HASH_MAP_TYPE = "java.util.LinkedHashMap";
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//获取方法
Method method = invocation.getMethod();
//获取参数上的所有注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
//获取参数列表
Object[] args = invocation.getArguments();
//判断是否加了RequestMap注解
for (Annotation[] parameterAnnotation : parameterAnnotations) {
//获取当前参数的位置
int index = indexOf(parameterAnnotations, parameterAnnotation);
for (Annotation annotation : parameterAnnotation) {
//获取此注解修饰的具体的参数
Object param = args[index];
//如果有@RequestEntity注解
hasRequestEntity(annotation, param);
//如果有Verify注解 由于是参数上的注解 注意:此处传递的是具体的param 而非args
hasVerify(annotation, param);
//如果有RequestMap注解 由于是参数上的注解 注意:此处传递的是具体的param 而非args
hasRequestMap(annotation, param);
}
}
//执行方法
return invocation.proceed();
}
/**
* 如果参数存在RequestEntity注解
*
* @param annotation 参数上的注解
* @param param 具体的参数
*/
private void hasRequestEntity(Annotation annotation, Object param) throws Exception {
//获取注解的全类名
String requestEntityName = RequestEntity.class.getName();
//获取当前注解的全类名
String name = annotation.annotationType().getName();
//匹配是否相同
if (requestEntityName.equals(name)) {
//是否是实体类
isEntity(param);
}
}
/**
* 递归判断是否存在实体属性
* @param entity 判断的内容
*/
public void isEntity(Object entity) throws Exception {
//获取其内部的所有属性
Field[] fields = entity.getClass().getDeclaredFields();
//遍历属性
for (Field field : fields) {
//获取私有属性值
field.setAccessible(true);
//需要做校验的参数
if (field.isAnnotationPresent(Verify.class)) {
//获取注解对象
Verify verify = field.getAnnotation(Verify.class);
//校验的对象
Object fieldObj = field.get(entity);
//如果这个属性是一个实体
if (verify.isEntity()) {
//递归调用
isEntity(fieldObj);
}
//校验
validate(verify, fieldObj);
}
}
}
/**
* 如果参数上加的是Verify注解
*
* @param annotation 参数上的注解
* @param param 参数
*/
private void hasVerify(Annotation annotation, Object param) throws Exception {
//获取注解的全类名
String verifyName = Verify.class.getName();
//获取当前注解的全类名
String name = annotation.annotationType().getName();
//匹配是否相同
if (verifyName.equals(name)) {
//获取此注解修饰的具体的参数
//获取当前注解的具值
Verify verify = (Verify) annotation;
//进行校验
validate(verify, param);
}
}
/**
* 判断是否加了@RequestMap注解 加了再进行下一步的操作
*
* @param annotation 所有参数前的注解
* @param param 当前参数
*/
private void hasRequestMap(Annotation annotation, Object param) throws Exception {
//获取注解的全类名
String RequestMapName = RequestMap.class.getName();
//获取当前注解的全类名
String name = annotation.annotationType().getName();
//匹配是否相同
if (RequestMapName.equals(name)) {
//如果存在此注解,执行方法
isLinkedHashMap(annotation, param);
}
}
/**
* 判断是否为LinkedHashMap,如果是,进行进一步的操作
*
* @param annotation 参数上的注解
* @param param 注解所修饰的参数
*/
private void isLinkedHashMap(Annotation annotation, Object param) throws Exception {
//获取注解
RequestMap RequestMap = (RequestMap) annotation;
//获取要校验的所有entity
String[] entitys = RequestMap.baseEntityList();
//如果是map接收参数
if (LINK_HASH_MAP_TYPE.equals(param.getClass().getName())) {
//如果存在Verify注解
hasVerify(entitys, param);
}
}
/**
* 如果EntityList中的实体存在Verify注解
*
* @param entityList 实体列表
* @param param 加入@RequestMap的注解 的参数
*/
private void hasVerify(String[] entityList, Object param) throws Exception {
//迭代entityList
for (String entity : entityList) {
//获取所有字段
Field[] fields = Class.forName(entity).getDeclaredFields();
//迭代字段
for (Field field : fields) {
field.setAccessible(true);
//判断是否加入了Verify注解
if (field.isAnnotationPresent(Verify.class)) {
//如果有 获取注解的实例
Verify verify = field.getAnnotation(Verify.class);
//校验
fieldIsNeedValidate(param, verify, field.getName());
}
}
}
}
/**
* 字段是否需要校验
*
* @param param 增加@RequestMap注解的参数
* @param verify Verify注解的实例
* @param fieldName 加了Verify的属性name值
*/
private void fieldIsNeedValidate(Object param, Verify verify, String fieldName) throws Exception {
//获取集合
LinkedHashMap map = (LinkedHashMap) param;
//获取key列表
Set set = map.keySet();
//迭代key
for (Object key : set) {
//如果key和注解的fieldName一致
if (fieldName.equals(key)) {
//当前值
Object fieldObj = map.get(key);
//真正的进行校验
validate(verify, fieldObj);
}
}
}
/**
* 正则的校验方法
*
* @param verify 校验规则
* @param fieldObj 校验者
*/
private void validate(Verify verify, Object fieldObj) throws Exception {
//获取verify的name
String name = verify.name();
//是否时必传 断言判断
if (verify.required()) {
MyAssert.assertMethod(fieldObj != null, String.format("【%s】为必传参数", name));
}
//字符串的 非空校验
if (verify.notNull()) {
MyAssert.assertMethod(!(fieldObj == null || "".equals(fieldObj)), String.format("【%s】不能为空", name));
}
//是否有最大长度限制 断言判断
int maxLength = verify.maxLength();
if (Integer.MAX_VALUE != maxLength) {
MyAssert.assertMethod(maxLength > String.valueOf(fieldObj).length(), String.format("【%s】长度不合理,最大长度为【%s】", name, maxLength));
}
//是否有最小长度限制 断言判断
int minLength = verify.minLength();
if (Integer.MIN_VALUE != minLength) {
MyAssert.assertMethod(minLength < String.valueOf(fieldObj).length(), String.format("【%s】长度不合理,最小长度为【%s】", name, minLength));
}
//是否有正则校验
if (!"".equals(verify.regular().getRegex())) {
//初始化Pattern
Pattern pattern = Pattern.compile(verify.regular().getRegex());
//断言判断正则
MyAssert.assertMethod(pattern.matcher(String.valueOf(fieldObj)).matches(), String.format("参数【%s】的请求数据不符合规则", name));
}
}
/**
* hutool中的方法
*/
private <T> int indexOf(T[] array, Object value) {
if (null != array) {
for (int i = 0; i < array.length; ++i) {
if (equal(value, array[i])) {
return i;
}
}
}
return -1;
}
/**
* hutool中的方法
*/
private boolean equal(Object obj1, Object obj2) {
return Objects.equals(obj1, obj2);
}
}
使用Param-Validate
导入依赖
<dependency>
<groupId>com.github.18338862369</groupId>
<artifactId>param-validate</artifactId>
<version>1.0.0</version>
</dependency>
此依赖中包含了下述依赖,如果是其他SpringBoot版本,建议对依赖进行排除,并重新引入依赖。
本地引入依赖
创建一个文件夹将param-validate-1.0.0.jar放入,创建一个名为install.bat的bat文件,执行文件即可将jar安装到本地maven仓库。
mvn install:install-file -Dfile=%~dp0param-validate-1.0.0.jar -DgroupId=com.github.18338862369 -DartifactId=param-validate -Dversion=1.0.0 -Dpackaging=jar
使用
在启动类上加入@EnableVerify(execution = "execution(* cn.rayfoo.web..*(..))")
注解
package cn.rayfoo;
import EnableVerify;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 16:05
*/
@SpringBootApplication
@EnableVerify(execution = "execution(* cn.rayfoo.web..*(..))")
public class Runner {
public static void main(String[] args) {
SpringApplication.run(Runner.class, args);
}
}
在Controller测试
package cn.rayfoo.web;
import Verify;
import RegexOption;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 16:12
*/
@RestController
public class TestController {
@GetMapping("/test")
public void test(@Verify(name = "用户名",regular = RegexOption.USERNAME_REGEX)String username){
System.out.println("validate");
}
}
使用postMan测试
如果成功进行了参数校验,表示jar包执行正常
下载地址:https://github.com/18338862369/param-validate/releases/tag/1.0.0.RELEASE
如果对你有用,欢迎star支持一下~# param-validate
在开发过程中,用户传递的数据不一定合法,虽然可以通过前端进行一些校验,但是为了确保程序的安全性,保证数据的合法,在后台进行数据校验也是十分必要的。
后台的参数校验的解决方案
在controller方法中校验:
后台的参数是通过controller方法获取的,所以最简单的参数校验的方法,就是在controller方法中进行参数校验。在controller方法中如果进行参数校验会有大量重复、没有太大意义的代码。
使用拦截器、过滤器校验
为了保证controller中的代码有更好的可读性,可以将参数校验的工作交由拦截器(Interceptor)或者过滤器(Filter)来完成,但是此时又存在一个问题:非共性的参数需要每个方法都创建一个与之对应的拦截器(或者过滤器)。
实现对Entity的统一校验
鉴于上述解决方案的缺点,我们可以借助AOP的思想来进行统一的参数校验。思想是通过自定义注解来完成对实体类属性的标注,在AOP中扫描加了自定义注解的属性,对其进行注解属性标注的校验。对于不满足的参数直接抛出自定义异常,交由全局异常处理来处理并返回友好的提示信息。
认识Param-Validate
以上的解决方案各有优劣,param-validate采用了AOP+注解的形式解决了统一参数的校验。使用四个注解可以解决大部分的参数校验,有效的简化了代码、提高了程序的可读性和开发效率。
Param-Validate基于SpringBoot2.3.0.RELEASE,所以请保证你的SpringBoot版本和其一致,如果必须使用其他版本,可以将其内包含的SpringBoot依赖排除。
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.18338862369</groupId>
<artifactId>param-validate</artifactId>
<version>1.0.0</version>
<description>SpringBoot ParamValidate</description>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<aspectjweaver.version>1.9.5</aspectjweaver.version>
<lombok.version>1.18.12</lombok.version>
</properties>
<dependencies>
<!--web内置了jackson-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- aspect -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <optional>true</optional>-->
<version>${lombok.version}</version>
</dependency>
</dependencies>
</project>
项目结构
下图是项目的结构图。接下来,我们逐个包来介绍一下
response包
此包中定义的内容为用户相应的类
HttpStatus枚举
这个枚举是从Spring源码中拿到,并且拓展的一个枚举。其封装了很多常见的状态码和状态信息。
package cn.rayfoo.validate.response;
/**
* @author rayfoo@qq.com
* @version 1.0
* @date 2020/8/5 11:37
* @description
*/
public enum HttpStatus {
/**
* 登录成功
* {@code 600 Login Success}.
*/
LOGIN_SUCCESS(600, "Login Success" ),
/**
* 重新登录
* {@code 605 Login Success}.
*/
LOGIN_AGAIN(605, "Login AGAIN" ),
/**
* 登录超时
* {@code 601 Login Out Time}.
*/
LOGIN_OUT_TIME(601, "Login Out Time" ),
/**
* 用户无访问权限
* {@code 602 Not Roles}.
*/
NOT_ROLES(602, "Not Roles" ),
/**
* 用户未注册
* {@code 603 Not Register}.
*/
NOT_REGISTER(603, "Not Register未找到该账号" ),
/**
* 用户未注册
* {@code 604 AuthenticationExcption}.
*/
AUTHENTICATION_EXCPTION(604, "身份认证错误" ),
/**
* 未知的账号异常
* {@code 606 Unknown Account}.
*/
UNKNOWN_ACCOUNT(606, "账号已注销" ),
/**
* 请求中的参数有误
* {@code 705 Parameter Error}.
*/
PARAMETER_ERROR(705, "Parameter Error" ),
/**
* 验证码错误
* {@code 704 Invalid Captcha}.
*/
INVALID_CAPTCHA(704, "Invalid Captcha验证码错误" ),
/**
* 邮箱和手机号验证错误
*/
EMAIL_OR_PHONE_ERROR(703, "email or phone error" ),
/**
* 用户已激活
*/
HAS_BEEN_ACTIVATED(702, "has been activated" ),
/**
* 用户名或密码错误
*/
USERNAME_OR_PASSWORD_ERROR(700, "username or password error用户名或密码错误" ),
/**
* 用户未启用
*/
USER_NOT_ENABLED(699, "user not enabled" ),
/**
* 验证码错误
*/
ACTIVATION_CODE_ERROR(698, "Activation code error" ),
/**
* 用户名被占用
*/
USERNAME_IS_OCCUPIED(697, "Username is occupied" ),
/**
* 返回无数据
* {@code 706 not data}.
*/
NOT_DATA(706, "not data" ),
/**
* 流程操作成功
* {@code 710 Successful operation}.
*/
SUCCESSFUL_OPERATION(710, "Successful operation" ),
/**
* 数据冲突,无法存储!
*/
DATA_CONFLICT(711, "Data conflict" ),
/**
* 删除操作失败
*/
CANNOT_DELETE(712, "Cannot delete" ),
/**
* 操作失败
* {@code 720 operation failed}.
*/
FAILED_OPERATION(720, "operation failed" ),
NOT_VTASKSTATUS(709, "not vtaskstatus" ),
// 1xx Informational
/**
* {@code 100 Continue}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">HTTP/1.1</a>
*/
CONTINUE(100, "Continue" ),
/**
* {@code 101 Switching Protocols}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">HTTP/1.1</a>
*/
SWITCHING_PROTOCOLS(101, "Switching Protocols" ),
/**
* {@code 102 Processing}.
*
* @see <a href="http://tools.ietf.org/html/rfc2518#section-10.1">WebDAV</a>
*/
PROCESSING(102, "Processing" ),
/**
* {@code 103 Checkpoint}.
*
* @see <a href="http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting
* resumable POST/PUT HTTP requests in HTTP/1.0</a>
*/
CHECKPOINT(103, "Checkpoint" ),
// 2xx Success
/**
* {@code 200 OK}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">HTTP/1.1</a>
*/
OK(200, "OK" ),
/**
* {@code 201 Created}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">HTTP/1.1</a>
*/
CREATED(201, "Created" ),
/**
* {@code 202 Accepted}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">HTTP/1.1</a>
*/
ACCEPTED(202, "Accepted" ),
/**
* {@code 203 Non-Authoritative Information}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">HTTP/1.1</a>
*/
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information" ),
/**
* {@code 204 No Content}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">HTTP/1.1</a>
*/
NO_CONTENT(204, "No Content" ),
/**
* {@code 205 Reset Content}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">HTTP/1.1</a>
*/
RESET_CONTENT(205, "Reset Content" ),
/**
* {@code 206 Partial Content}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">HTTP/1.1</a>
*/
PARTIAL_CONTENT(206, "Partial Content" ),
/**
* {@code 207 Multi-Status}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-13">WebDAV</a>
*/
MULTI_STATUS(207, "Multi-Status" ),
/**
* {@code 208 Already Reported}.
*
* @see <a href="http://tools.ietf.org/html/rfc5842#section-7.1">WebDAV Binding Extensions</a>
*/
ALREADY_REPORTED(208, "Already Reported" ),
/**
* {@code 226 IM Used}.
*
* @see <a href="http://tools.ietf.org/html/rfc3229#section-10.4.1">Delta encoding in HTTP</a>
*/
IM_USED(226, "IM Used" ),
// 3xx Redirection
/**
* {@code 300 Multiple Choices}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">HTTP/1.1</a>
*/
MULTIPLE_CHOICES(300, "Multiple Choices" ),
/**
* {@code 301 Moved Permanently}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">HTTP/1.1</a>
*/
MOVED_PERMANENTLY(301, "Moved Permanently" ),
/**
* {@code 302 Found}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">HTTP/1.1</a>
*/
FOUND(302, "Found" ),
/**
* {@code 302 Moved Temporarily}.
*
* @see <a href="http://tools.ietf.org/html/rfc1945#section-9.3">HTTP/1.0</a>
* @deprecated In favor of {@link #FOUND} which will be returned from {@code HttpStatus.valueOf(302)}
*/
@Deprecated
MOVED_TEMPORARILY(302, "Moved Temporarily" ),
/**
* {@code 303 See Other}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">HTTP/1.1</a>
*/
SEE_OTHER(303, "See Other" ),
/**
* {@code 304 Not Modified}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">HTTP/1.1</a>
*/
NOT_MODIFIED(304, "Not Modified" ),
/**
* {@code 305 Use Proxy}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">HTTP/1.1</a>
*/
USE_PROXY(305, "Use Proxy" ),
/**
* {@code 307 Temporary Redirect}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">HTTP/1.1</a>
*/
TEMPORARY_REDIRECT(307, "Temporary Redirect" ),
/**
* {@code 308 Resume Incomplete}.
*
* @see <a href="http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting
* resumable POST/PUT HTTP requests in HTTP/1.0</a>
*/
RESUME_INCOMPLETE(308, "Resume Incomplete" ),
// --- 4xx Client Error ---
/**
* {@code 400 Bad Request}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">HTTP/1.1</a>
*/
BAD_REQUEST(400, "Bad Request" ),
/**
* {@code 401 Unauthorized}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">HTTP/1.1</a>
*/
UNAUTHORIZED(401, "Unauthorized" ),
/**
* {@code 402 Payment Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">HTTP/1.1</a>
*/
PAYMENT_REQUIRED(402, "Payment Required" ),
/**
* {@code 403 Forbidden}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">HTTP/1.1</a>
*/
FORBIDDEN(403, "Forbidden" ),
/**
* {@code 404 Not Found}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">HTTP/1.1</a>
*/
NOT_FOUND(404, "Not Found" ),
/**
* {@code 405 Method Not Allowed}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">HTTP/1.1</a>
*/
METHOD_NOT_ALLOWED(405, "Method Not Allowed" ),
/**
* {@code 406 Not Acceptable}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">HTTP/1.1</a>
*/
NOT_ACCEPTABLE(406, "Not Acceptable" ),
/**
* {@code 407 Proxy Authentication Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">HTTP/1.1</a>
*/
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required" ),
/**
* {@code 408 Request Timeout}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">HTTP/1.1</a>
*/
REQUEST_TIMEOUT(408, "Request Timeout" ),
/**
* {@code 409 Conflict}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">HTTP/1.1</a>
*/
CONFLICT(409, "Conflict" ),
/**
* {@code 410 Gone}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">HTTP/1.1</a>
*/
GONE(410, "Gone" ),
/**
* {@code 411 Length Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">HTTP/1.1</a>
*/
LENGTH_REQUIRED(411, "Length Required" ),
/**
* {@code 412 Precondition failed}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">HTTP/1.1</a>
*/
PRECONDITION_FAILED(412, "Precondition Failed" ),
/**
* {@code 413 Request entity Too Large}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">HTTP/1.1</a>
*/
REQUEST_ENTITY_TOO_LARGE(413, "Request entity Too Large" ),
/**
* {@code 414 Request-URI Too Long}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">HTTP/1.1</a>
*/
REQUEST_URI_TOO_LONG(414, "Request-URI Too Long" ),
/**
* {@code 415 Unsupported Media Type}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">HTTP/1.1</a>
*/
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type" ),
/**
* {@code 416 Requested Range Not Satisfiable}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">HTTP/1.1</a>
*/
REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable" ),
/**
* {@code 417 Expectation Failed}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">HTTP/1.1</a>
*/
EXPECTATION_FAILED(417, "Expectation Failed" ),
/**
* {@code 418 I'm a teapot}.
*
* @see <a href="http://tools.ietf.org/html/rfc2324#section-2.3.2">HTCPCP/1.0</a>
*/
I_AM_A_TEAPOT(418, "I'm a teapot" ),
/**
* @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a>
*/
@Deprecated INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource" ),
/**
* @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a>
*/
@Deprecated METHOD_FAILURE(420, "Method Failure" ),
/**
* @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a>
*/
@Deprecated DESTINATION_LOCKED(421, "Destination Locked" ),
/**
* {@code 422 Unprocessable entity}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.2">WebDAV</a>
*/
UNPROCESSABLE_ENTITY(422, "Unprocessable entity" ),
/**
* {@code 423 Locked}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.3">WebDAV</a>
*/
LOCKED(423, "帐号已锁定" ),
/**
* {@code 424 Failed Dependency}.
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.4">WebDAV</a>
*/
FAILED_DEPENDENCY(424, "Failed Dependency" ),
/**
* {@code 426 Upgrade Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc2817#section-6">Upgrading to TLS Within HTTP/1.1</a>
*/
UPGRADE_REQUIRED(426, "Upgrade Required" ),
/**
* {@code 428 Precondition Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-3">Additional HTTP Status Codes</a>
*/
PRECONDITION_REQUIRED(428, "Precondition Required" ),
/**
* {@code 429 Too Many Requests}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-4">Additional HTTP Status Codes</a>
*/
TOO_MANY_REQUESTS(429, "Too Many Requests" ),
/**
* {@code 431 Request Header Fields Too Large}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-5">Additional HTTP Status Codes</a>
*/
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large" ),
// --- 5xx Server Error ---
/**
* {@code 500 Internal Server Error}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">HTTP/1.1</a>
*/
INTERNAL_SERVER_ERROR(500, "Internal Server Error" ),
/**
* {@code 501 Not Implemented}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">HTTP/1.1</a>
*/
NOT_IMPLEMENTED(501, "Not Implemented" ),
/**
* {@code 502 Bad Gateway}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">HTTP/1.1</a>
*/
BAD_GATEWAY(502, "Bad Gateway" ),
/**
* {@code 503 service Unavailable}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">HTTP/1.1</a>
*/
SERVICE_UNAVAILABLE(503, "service Unavailable" ),
/**
* {@code 504 Gateway Timeout}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">HTTP/1.1</a>
*/
GATEWAY_TIMEOUT(504, "Gateway Timeout" ),
/**
* {@code 505 HTTP Version Not Supported}.
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">HTTP/1.1</a>
*/
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported" ),
/**
* {@code 506 Variant Also Negotiates}
*
* @see <a href="http://tools.ietf.org/html/rfc2295#section-8.1">Transparent Content Negotiation</a>
*/
VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates" ),
/**
* {@code 507 Insufficient Storage}
*
* @see <a href="http://tools.ietf.org/html/rfc4918#section-11.5">WebDAV</a>
*/
INSUFFICIENT_STORAGE(507, "Insufficient Storage" ),
/**
* {@code 508 Loop Detected}
*
* @see <a href="http://tools.ietf.org/html/rfc5842#section-7.2">WebDAV Binding Extensions</a>
*/
LOOP_DETECTED(508, "Loop Detected" ),
/**
* {@code 509 Bandwidth Limit Exceeded}
*/
BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded" ),
/**
* {@code 510 Not Extended}
*
* @see <a href="http://tools.ietf.org/html/rfc2774#section-7">HTTP Extension Framework</a>
*/
NOT_EXTENDED(510, "Not Extended" ),
/**
* {@code 511 Network Authentication Required}.
*
* @see <a href="http://tools.ietf.org/html/rfc6585#section-6">Additional HTTP Status Codes</a>
*/
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required" ),
SMS_CLIENT_EXCEPTION(512, "输入手机号不合法,请重新输入" ),
SMS_SERVER_EXCEPTION(513, "发送短信出现异常,请稍后重试~" );
private final int value;
private final String reasonPhrase;
private HttpStatus(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
/**
* Return the integer value of this status code.
*/
public int value() {
return this.value;
}
/**
* Return the reason phrase of this status code.
*/
public String getReasonPhrase() {
return reasonPhrase;
}
/**
* Returns the HTTP status series of this status code.
*
* @see Series
*/
public Series series() {
return Series.valueOf(this);
}
/**
* Return a string representation of this status code.
*/
@Override
public String toString() {
return Integer.toString(value);
}
/**
* Return the enum constant of this type with the specified numeric value.
*
* @param statusCode the numeric value of the enum to be returned
* @return the enum constant with the specified numeric value
* @throws IllegalArgumentException if this enum has no constant for the specified numeric value
*/
public static HttpStatus valueOf(int statusCode) {
for (HttpStatus status : values()) {
if (status.value == statusCode) {
return status;
}
}
throw new IllegalArgumentException("No matching constant for [" + statusCode + "]" );
}
/**
* Java 5 enumeration of HTTP status series.
* <p>Retrievable via {@link HttpStatus#series()}.
*/
public static enum Series {
INFORMATIONAL(1),
SUCCESSFUL(2),
REDIRECTION(3),
CLIENT_ERROR(4),
SERVER_ERROR(5);
private final int value;
private Series(int value) {
this.value = value;
}
/**
* Return the integer value of this status series. Ranges from 1 to 5.
*/
public int value() {
return this.value;
}
public static Series valueOf(int status) {
int seriesCode = status / 100;
for (Series series : values()) {
if (series.value == seriesCode) {
return series;
}
}
throw new IllegalArgumentException("No matching constant for [" + status + "]" );
}
public static Series valueOf(HttpStatus status) {
return valueOf(status.value);
}
}
}
Result类
此类是一个通用的返回结果类,起初使用了Swagger文档标注,后期单独整合为工具包,为了减少jar包的依赖就删去了Swagger部分。
package cn.rayfoo.validate.response;
import lombok.Builder;
import lombok.Data;
/**
* @author rayfoo@qq.com
* @date 2020年8月6日
*/
@Data@Builder
public class Result<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息
*/
private String msg;
/**
* 数据记录
*/
private T data;
public Result() {
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
exception包
此包内封装了关于异常的定义和处理类
MyAssert类
从类名就可以看出,这个类是进行断言判断的类,其内定义了多个重载的断言判断方法,断言方法可以在特定条件下中止方法的运行。
package cn.rayfoo.validate.exception;
import HttpStatus;
import lombok.extern.slf4j.Slf4j;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>断言类</p>
* @date 2020/8/7 9:43
*/
@Slf4j
public class MyAssert {
/**
* 如果为空直接抛出异常 类似于断言的思想
* @param status 当status为false 就会抛出异常 不继续执行后续语句
* @param msg 异常描述
*/
public static void assertMethod(boolean status, String msg) throws Exception {
//为false抛出异常
if (!status) {
//记录错误信息
log.error(msg);
//抛出异常
throw MyException.builder().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).msg(msg).build();
}
}
/**
* 如果为空直接抛出异常 类似于断言的思想
* @param status 当status为false 就会抛出异常 不继续执行后续语句
* @param code 状态码
* @param msg 异常描述
*/
public static void assertMethod(boolean status,Integer code, String msg) throws Exception {
//为false抛出异常
if (!status) {
//记录错误信息
log.error(msg);
//抛出异常
throw MyException.builder().code(code).msg(msg).build();
}
}
/**
* 如果为空直接抛出异常 类似于断言的思想
* @param status 当status为false 就会抛出异常 不继续执行后续语句
*/
public static void assertMethod(boolean status) throws Exception {
//为false抛出异常
if (!status) {
//记录错误信息
log.error(HttpStatus.INTERNAL_SERVER_ERROR.name());
//抛出异常
throw MyException.builder().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).msg(HttpStatus.INTERNAL_SERVER_ERROR.name()).build();
}
}
}
MyException类
这个类是自定义异常的声明,由于AOP中只有RuntimeException及其子类的异常可以被全局异常处理器处理,所以其继承了RuntimeException。拓展了code和msg属性
package cn.rayfoo.validate.exception;
import lombok.*;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/7/20 9:26 下午
* @Description: 自定义的异常...
*/
@Getter@Setter@Builder@NoArgsConstructor@AllArgsConstructor
public class MyException extends RuntimeException{
private int code;
private String msg;
}
ServerExceptionResolver类
此类用来处理手动抛出的自定义异常。
package cn.rayfoo.validate.exception;
import HttpStatus;
import Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author rayfoo@qq.com
* @version 1.0
* @date 2020/8/5 14:55
* @description 全局异常处理
*/
@ControllerAdvice@Slf4j
public class ServerExceptionResolver {
/**
* 处理自定义的异常
* @param ex
* @return
*/
@ExceptionHandler(MyException.class)@ResponseBody
public Result<String> resolveMyException(MyException ex){
//打印完整的异常信息
ex.printStackTrace();
//创建result
Result<String> result = new Result<>();
//设置result属性
result.setData(ex.getMsg());
result.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
result.setMsg(ex.getMsg());
//保存自定义异常日志
log.error(ex.getMsg());
return result;
}
}
enums包
很明显这个包中存放的是一个枚举,这个枚举是为了@Verify注解创建的,其作为@Verify的属性提供正则校验表。
RegexOption枚举
如果需要新的校验条件只需要拓展这个枚举即可。
package cn.rayfoo.validate.enums;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>参数校验枚举</p>
* @date 2020/8/7 15:51
*/
public enum RegexOption {
/**
* 缺省,表示不进行正则校验
*/
DEFAULT(""),
/**
* 邮箱正则
*/
EMAIL_REGEX("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"),
/**
* 手机号正则
*/
PHONE_NUMBER_REGEX("^((13[0-9])|(14[0|5|6|7|9])|(15[0-3])|(15[5-9])|(16[6|7])|(17[2|3|5|6|7|8])|(18[0-9])|(19[1|8|9]))\\d{8}$"),
/**
* 身份证正则
*/
IDENTITY_CARD_REGEX("(^\\d{18}$)|(^\\d{15}$)"),
/**
* URL正则
*/
URL_REGEX("http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?"),
/**
* IP地址正则
*/
IP_ADDR_REGEX("(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)"),
/**
* 用户名正则
*/
USERNAME_REGEX("^[a-zA-Z]\\w{5,20}$"),
/**
* 密码正则
*/
PASSWORD_REGEX("^[a-zA-Z0-9]{6,20}$");
/**
* 正则
*/
private String regex;
/**
* 构造方法
*
* @param regex
*/
private RegexOption(String regex) {
this.regex = regex;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
}
}
annotation包
显而易见,这个包是用来存放注解的。这四个注解可以完成大部分情况下的参数校验工作。
@Verify注解
标注在参数、字段上,用于标注需要校验的数据以及其校验的规则。
- name:描述,当校验结果为false时,返回用户提示信息时使用
- maxLength:最大长度,用于判断字符串类型,如果时默认值代表不进行判断
- minLength:最小长度,用于判断字符串类型,如果时默认值代表不进行判断
- required:是否为必填属性
- notNull:是否允许为空
- regular:是否需要进行正则校验,如果需要正则内容是什么,默认为不进行正则校验
- isEntity:是否是一个entity,如果是,递归调用判断其内的属性
package com.github.validate.annotation;
import com.github.validate.enums.RegexOption;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/7 15:33
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface Verify {
/** 参数名称 */
String name();
/** 参数最大长度 */
int maxLength() default Integer.MAX_VALUE;
/*** 是否是实体,如果是,继续判断其内部的值 */
boolean isEntity() default false;
/** 是否必填 这里只是判断是否为null */
boolean required() default true;
/** 是否为非空 是否为null和空串都判断 */
boolean notNull() default true;
/** 最小长度 */
int minLength() default Integer.MIN_VALUE;
/** 正则匹配 */
RegexOption regular() default RegexOption.DEFAULT;
}
@RequestMap注解
用于标注Controller方法的参数,用于指定Map类型参数。
- baseEntityList制定Entity的全类名,如果Entity的属性名和key一致,就进行参数校验,校验规则由Entity中加了@Verify注解的属性来决定
package cn.rayfoo.validate.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p>对Map</p>
* @date 2020/8/8 19:50
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestMap {
/**
* 实体类全类名列表
*/
String[] baseEntityList();
}
@RequestEntity注解
用法同上,不过其标注的是Entity类型。
package cn.rayfoo.validate.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/8 22:43
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestEntity {
String value() default "";
}
@EnableVerify注解
用于标注在SpringBoot启动类
- execution:指定execution表达式,只有execution标识的方法才会进行全局参数校验
package cn.rayfoo.validate.annotation;
import EnableVerifyConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 13:36
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({EnableVerifyConfig.class})
public @interface EnableVerify {
String execution();
}
Config包
EnableVerifyConfig类
@EnableVerify引用了EnableVerifyConfig类,下面我们介绍一下这个类是如何 编写的:
这个类被@EnableVerify所引用,当读取到此注解时,就会自动注册此类registerBeanDefinitions方法内的代码主要是进行Bean的注册(由于默认时没有扫描cn,rayfoo包的,所以需要注册到Spring中的Bean要手动的放入Bean容器中),所以我们需要手动的注册好Aspect类,这里借鉴了通用Mapper的源码。
package cn.rayfoo.validate.config;
import EnableVerify;
import ValidateAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 13:37
*/
public class EnableVerifyConfig implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/*
* 注册Aspect
*/
//获取启动器上的注解
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableVerify.class.getName()));
//获取自定义注解中的属性
String execution = annoAttrs.getString("execution");
//创建Bean
BeanDefinition validateAdvice = new GenericBeanDefinition();
//指定Bean的字节码
validateAdvice.setBeanClassName(AspectJExpressionPointcutAdvisor.class.getName());
MutablePropertyValues validateAdviceProp = validateAdvice.getPropertyValues();
//设置Bean的属性
validateAdviceProp.addPropertyValue("advice", new ValidateAdvice());
validateAdviceProp.addPropertyValue("expression", execution);
//注册bean
registry.registerBeanDefinition("validateAdvice",validateAdvice);
/*
* 注册全局异常处理 如果你的包恰好是cn.rayfoo就需要将其注释
*/
//创建Bean
// BeanDefinition serverExceptionResolver = new GenericBeanDefinition();
// //指定Bean的字节码
// serverExceptionResolver.setBeanClassName(ServerExceptionResolver.class.getName());
// //注册bean
// registry.registerBeanDefinition("serverExceptionResolver",serverExceptionResolver);
}
}
aspect包
ValidateAdvice类
最后就是此篇文章的重中之重了,所有的注解解析和参数校验都是在这个类中完成的,其主要使用了前置通知,在通知中获取目标方法,通过反射拿到目标方法的参数,根据方法参数的类型再选择进行何种判断。
package com.github.validate.aspect;
import com.github.validate.annotation.RequestEntity;
import com.github.validate.annotation.RequestMap;
import com.github.validate.annotation.Verify;
import com.github.validate.exception.MyAssert;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 12:42
*/
public class ValidateAdvice implements MethodInterceptor {
/**
* 校验的类型
*/
private static final String LINK_HASH_MAP_TYPE = "java.util.LinkedHashMap";
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//获取方法
Method method = invocation.getMethod();
//获取参数上的所有注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
//获取参数列表
Object[] args = invocation.getArguments();
//判断是否加了RequestMap注解
for (Annotation[] parameterAnnotation : parameterAnnotations) {
//获取当前参数的位置
int index = indexOf(parameterAnnotations, parameterAnnotation);
for (Annotation annotation : parameterAnnotation) {
//获取此注解修饰的具体的参数
Object param = args[index];
//如果有@RequestEntity注解
hasRequestEntity(annotation, param);
//如果有Verify注解 由于是参数上的注解 注意:此处传递的是具体的param 而非args
hasVerify(annotation, param);
//如果有RequestMap注解 由于是参数上的注解 注意:此处传递的是具体的param 而非args
hasRequestMap(annotation, param);
}
}
//执行方法
return invocation.proceed();
}
/**
* 如果参数存在RequestEntity注解
*
* @param annotation 参数上的注解
* @param param 具体的参数
*/
private void hasRequestEntity(Annotation annotation, Object param) throws Exception {
//获取注解的全类名
String requestEntityName = RequestEntity.class.getName();
//获取当前注解的全类名
String name = annotation.annotationType().getName();
//匹配是否相同
if (requestEntityName.equals(name)) {
//是否是实体类
isEntity(param);
}
}
/**
* 递归判断是否存在实体属性
* @param entity 判断的内容
*/
public void isEntity(Object entity) throws Exception {
//获取其内部的所有属性
Field[] fields = entity.getClass().getDeclaredFields();
//遍历属性
for (Field field : fields) {
//获取私有属性值
field.setAccessible(true);
//需要做校验的参数
if (field.isAnnotationPresent(Verify.class)) {
//获取注解对象
Verify verify = field.getAnnotation(Verify.class);
//校验的对象
Object fieldObj = field.get(entity);
//如果这个属性是一个实体
if (verify.isEntity()) {
//递归调用
isEntity(fieldObj);
}
//校验
validate(verify, fieldObj);
}
}
}
/**
* 如果参数上加的是Verify注解
*
* @param annotation 参数上的注解
* @param param 参数
*/
private void hasVerify(Annotation annotation, Object param) throws Exception {
//获取注解的全类名
String verifyName = Verify.class.getName();
//获取当前注解的全类名
String name = annotation.annotationType().getName();
//匹配是否相同
if (verifyName.equals(name)) {
//获取此注解修饰的具体的参数
//获取当前注解的具值
Verify verify = (Verify) annotation;
//进行校验
validate(verify, param);
}
}
/**
* 判断是否加了@RequestMap注解 加了再进行下一步的操作
*
* @param annotation 所有参数前的注解
* @param param 当前参数
*/
private void hasRequestMap(Annotation annotation, Object param) throws Exception {
//获取注解的全类名
String RequestMapName = RequestMap.class.getName();
//获取当前注解的全类名
String name = annotation.annotationType().getName();
//匹配是否相同
if (RequestMapName.equals(name)) {
//如果存在此注解,执行方法
isLinkedHashMap(annotation, param);
}
}
/**
* 判断是否为LinkedHashMap,如果是,进行进一步的操作
*
* @param annotation 参数上的注解
* @param param 注解所修饰的参数
*/
private void isLinkedHashMap(Annotation annotation, Object param) throws Exception {
//获取注解
RequestMap RequestMap = (RequestMap) annotation;
//获取要校验的所有entity
String[] entitys = RequestMap.baseEntityList();
//如果是map接收参数
if (LINK_HASH_MAP_TYPE.equals(param.getClass().getName())) {
//如果存在Verify注解
hasVerify(entitys, param);
}
}
/**
* 如果EntityList中的实体存在Verify注解
*
* @param entityList 实体列表
* @param param 加入@RequestMap的注解 的参数
*/
private void hasVerify(String[] entityList, Object param) throws Exception {
//迭代entityList
for (String entity : entityList) {
//获取所有字段
Field[] fields = Class.forName(entity).getDeclaredFields();
//迭代字段
for (Field field : fields) {
field.setAccessible(true);
//判断是否加入了Verify注解
if (field.isAnnotationPresent(Verify.class)) {
//如果有 获取注解的实例
Verify verify = field.getAnnotation(Verify.class);
//校验
fieldIsNeedValidate(param, verify, field.getName());
}
}
}
}
/**
* 字段是否需要校验
*
* @param param 增加@RequestMap注解的参数
* @param verify Verify注解的实例
* @param fieldName 加了Verify的属性name值
*/
private void fieldIsNeedValidate(Object param, Verify verify, String fieldName) throws Exception {
//获取集合
LinkedHashMap map = (LinkedHashMap) param;
//获取key列表
Set set = map.keySet();
//迭代key
for (Object key : set) {
//如果key和注解的fieldName一致
if (fieldName.equals(key)) {
//当前值
Object fieldObj = map.get(key);
//真正的进行校验
validate(verify, fieldObj);
}
}
}
/**
* 正则的校验方法
*
* @param verify 校验规则
* @param fieldObj 校验者
*/
private void validate(Verify verify, Object fieldObj) throws Exception {
//获取verify的name
String name = verify.name();
//是否时必传 断言判断
if (verify.required()) {
MyAssert.assertMethod(fieldObj != null, String.format("【%s】为必传参数", name));
}
//字符串的 非空校验
if (verify.notNull()) {
MyAssert.assertMethod(!(fieldObj == null || "".equals(fieldObj)), String.format("【%s】不能为空", name));
}
//是否有最大长度限制 断言判断
int maxLength = verify.maxLength();
if (Integer.MAX_VALUE != maxLength) {
MyAssert.assertMethod(maxLength > String.valueOf(fieldObj).length(), String.format("【%s】长度不合理,最大长度为【%s】", name, maxLength));
}
//是否有最小长度限制 断言判断
int minLength = verify.minLength();
if (Integer.MIN_VALUE != minLength) {
MyAssert.assertMethod(minLength < String.valueOf(fieldObj).length(), String.format("【%s】长度不合理,最小长度为【%s】", name, minLength));
}
//是否有正则校验
if (!"".equals(verify.regular().getRegex())) {
//初始化Pattern
Pattern pattern = Pattern.compile(verify.regular().getRegex());
//断言判断正则
MyAssert.assertMethod(pattern.matcher(String.valueOf(fieldObj)).matches(), String.format("参数【%s】的请求数据不符合规则", name));
}
}
/**
* hutool中的方法
*/
private <T> int indexOf(T[] array, Object value) {
if (null != array) {
for (int i = 0; i < array.length; ++i) {
if (equal(value, array[i])) {
return i;
}
}
}
return -1;
}
/**
* hutool中的方法
*/
private boolean equal(Object obj1, Object obj2) {
return Objects.equals(obj1, obj2);
}
}
使用Param-Validate
导入依赖
<dependency>
<groupId>com.github.18338862369</groupId>
<artifactId>param-validate</artifactId>
<version>1.0.0</version>
</dependency>
此依赖中包含了下述依赖,如果是其他SpringBoot版本,建议对依赖进行排除,并重新引入依赖。
本地引入依赖
创建一个文件夹将param-validate-1.0.0.jar放入,创建一个名为install.bat的bat文件,执行文件即可将jar安装到本地maven仓库。
mvn install:install-file -Dfile=%~dp0param-validate-1.0.0.jar -DgroupId=com.github.18338862369 -DartifactId=param-validate -Dversion=1.0.0 -Dpackaging=jar
使用
在启动类上加入@EnableVerify(execution = "execution(* cn.rayfoo.web..*(..))")
注解
package cn.rayfoo;
import EnableVerify;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 16:05
*/
@SpringBootApplication
@EnableVerify(execution = "execution(* cn.rayfoo.web..*(..))")
public class Runner {
public static void main(String[] args) {
SpringApplication.run(Runner.class, args);
}
}
在Controller测试
package cn.rayfoo.web;
import Verify;
import RegexOption;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/9 16:12
*/
@RestController
public class TestController {
@GetMapping("/test")
public void test(@Verify(name = "用户名",regular = RegexOption.USERNAME_REGEX)String username){
System.out.println("validate");
}
}
使用postMan测试
如果成功进行了参数校验,表示jar包执行正常
下载地址:https://github.com/18338862369/param-validate/releases/tag/1.0.0.RELEASE
如果对你有用,欢迎star支持一下~