• 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支持一下~

  • 相关阅读:
    创建数据库链
    redis命令
    redis.conf文件配置信息
    mybatis调用存储过程实现
    oracle游标使用遍历3种方法
    Proud Merchants
    Bag Problem
    Watch The Movie
    Accepted Necklace
    Bone Collector II
  • 原文地址:https://www.cnblogs.com/zhangruifeng/p/13469398.html
Copyright © 2020-2023  润新知