콩 검증 체결 : 당신의 모서리에주의를 지불해야합니다 (캐스케이드 제약, 제약 사용자 정의, 사용자 정의 유효성 검사기 및 국제 오류 메시지 ...)

각각

프로와 모든 것의 단점이있다, 기술적 솔루션은 은색 총알 없을 것입니다

관련 읽기

[자바] 데이터 유효성 검사의 작은 홈 깊이 이해 : 자바 빈 검증 2.0 (JSR303, JSR349, JSR380) 최대 절전 모드 유효성 검사 6.x에서의 사용 사례
[작은] 봄 홈 봄 메소드 레벨의 데이터 유효성 검사 : @Validated + MethodValidationPostProcessor 우아한 마무리 데이터를 확인 작업
[자바] 데이터 유효성 검사 (콩 검증)의 작은 홈 깊이 이해 : @Valid 역할 (캐스케이드 검사) 및 설명 파악하는 깊이에서 공통 제약은 설명 주석을


봄 WX 스캔 코드 그룹 추가에 관심이있을 수 있습니다 :`자바 수석 엔지니어, 건축가 '그룹 3 (마지막 두 개의 차원 코드)

머리말

일반적으로 우리가 사용하는 검증하는 데 필요한 웹 요청 매개 변수, 일부 프런트 엔드를위한 프로젝트가 JavaScript체크를하지만, 안전을 위해 검사의 후단이있다 필요 . 따라서, 단지 데이터 점검 web모든 측면 아래 중요한 점이다. 그 프론트 엔드 검증 JS 검증 프레임 워크 (예를 들어, 내가 전에 사용하고있다 jQuery Validation Plugin) 자연과 궁극적으로, 백 엔드.

방대한 데이터 유효성 검사 앞에서 된 Bean Validation프로젝트에 사용되는 경우가 매우 체계적으로 도입 할 필요가 없습니다 생산성, 여기 (안 요약 기사) 체결을 높이기 위해 바인딩, 많은 이야기 Bean Validation그를하지만, 과정에서 당신을 소개하는 것을 목표로 주변 세부 사항에 대해 걱정해야 -

전면 경우 用机,이 문서에서는 비트입니다 玩机~ 의미

BV(콩 검증)의 범위를 사용하여

재 강조 시점 (나는 아이디어가 특히 중요한 존재라고 생각합니다) 사용의 범위.
Bean Validation어떤 프로그래밍 응용 프로그램의 모델을 일정 수준 이상으로 한정되지 않고, 그것을 사용할 수 있습니다 어떤 수준 뿐만 아니라, web프로그램, 그것으로 할 수있다 Swing이 풍부한 클라이언트 ( GUI编程).

나는 주목할만한 인물 모두에게 업계를 복사 :
여기에 그림 삽입 설명
Bean Validation목표는 간단하게하는 것입니다 Bean적, 논리 추상화와 표준화를 확인 중복 것, 검증을 통합 API 사양을 형성하는 단계;

그것은 추상적 인 통합 API에 올 때, 그것은 혼란하지 않고, 당신이 가장 큰 범위 대중을 얻을 경우에만,이 작업은 일반적으로 사업과 아무 상관이 적어도 의미가 있습니다. 추상화는 프로그래머의 분류를위한 가장 중요한 기준 중 하나입니다

제약 상속

서브 클래스는 계승 바인딩 수퍼을 확인하면서, 서브 클래스를 검사 외에도 그의 부모로부터 상속되는 경우 (동일한 인터페이스에 적용).

// child和person上标注的约束都会被执行
public class Child extends Person {
    ...
}

주 : 서브 클래스는 슈퍼 클래스 메소드를 오버라이드 경우, 부모 클래스와 서브 클래스 제약 조건이 확인됩니다 .

제약 연결 (연접 패리티)

당신이 연관된 객체의 속성을 확인하려면, 당신은 속성에 추가해야 할 @Valid객체의 유효성을 검사 할 경우, 메모, 다음의 모든 표시 @Valid관련 객체가 이러한 개체가 확인됩니다 또한 배열 할 수있다, 컬렉션,지도 등 , 그들은이 보유 확인합니다 모든 요소를 .

Demo:

@Getter
@Setter
@ToString
public class Person {

    @NotNull
    private String name;
    @NotNull
    @Positive
    private Integer age;

    @Valid
    @NotNull
    private InnerChild child;
    @Valid // 让它校验List里面所有的属性
    private List<InnerChild> childList;

    @Getter
    @Setter
    @ToString
    public static class InnerChild {
        @NotNull
        private String name;
        @NotNull
        @Positive
        private Integer age;
    }

}

교정 절차 :

    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fsx");
        Person.InnerChild child = new Person.InnerChild();
        child.setName("fsx-age");
        child.setAge(-1);
        person.setChild(child);

        // 设置childList
        person.setChildList(new ArrayList<Person.InnerChild>(){{
            Person.InnerChild innerChild = new Person.InnerChild();
            innerChild.setName("innerChild1");
            innerChild.setAge(-11);
            add(innerChild);

            innerChild = new Person.InnerChild();
            innerChild.setName("innerChild2");
            innerChild.setAge(-12);
            add(innerChild);
        }});

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 输出错误消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

검증 실패 메시지를 인쇄 :

age 不能为null: null
childList[0].age 必须是正数: -11
child.age 必须是正数: -1
childList[1].age 必须是正数: -12

제약 조건 失败消息message지정

각 제약 조건 정의가 검증 결과 메시지 템플릿에 대한 프롬프트를 포함 message하고, 제약 조건을 선언 할 때, 당신은이 제약 주석 사용할 수있는 메시지 속성 기본 메시지 템플릿을 대체 할 ( 이것은 관습 message대부분은 간단한 방법 ).

당시 검사, 제약을 통과하지 못한 경우에는 구성 MessageInterpolator보간이 최종 확인 실패 메시지를 얻기 위해, 정의 된이 메시지 제약을 구문 분석하는 템플릿 파서로 사용됩니다.
보간은 기본적으로 사용되는 org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator의미, org.hibernate.validator.spi.resourceloading.ResourceBundleLocator스텐실을 채우기 위해 컨텐츠 파일 국제 자원 속성에 도착 ~

자원 파서는 기본 구현이 사용하는 PlatformResourceBundleLocator구성에 Configuration기본 할당 초기화 시간에 의하여 :

    private ConfigurationImpl() {
        this.validationBootstrapParameters = new ValidationBootstrapParameters();

        // 默认的国际化资源文件加载器USER_VALIDATION_MESSAGES值为:ValidationMessages
        // 这个值就是资源文件的文件名~~~~
        this.defaultResourceBundleLocator = new PlatformResourceBundleLocator(
                ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES
        );
        this.defaultTraversableResolver = TraversableResolvers.getDefault();
        this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();
        this.defaultParameterNameProvider = new DefaultParameterNameProvider();
        this.defaultClockProvider = DefaultClockProvider.INSTANCE;
    }

이 파서는 자리 템플릿 (그래서 모양 괄호 문자열을 구문 분석을 시도합니다 {xxx}).
그것은 구문 분석 message(예를 들어, 여기에 메시지 템플릿은 다음과 같이 핵심 코드는 {javax.validation.constraints.NotNull.message}예를 들어)

public abstract class AbstractMessageInterpolator implements MessageInterpolator {
    ...
    private String interpolateMessage(String message, Context context, Locale locale) throws MessageDescriptorFormatException {
        // 如果message消息木有占位符,那就直接返回  不再处理了~
        // 这里自定义的优先级是最高的~~~
        if ( message.indexOf( '{' ) < 0 ) {
            return replaceEscapedLiterals( message );
        }

        // 调用resolveMessage方法处理message中的占位符和el表达式
        if ( cachingEnabled ) {
            resolvedMessage = resolvedMessages.computeIfAbsent( new LocalizedMessage( message, locale ), lm -> resolveMessage( message, locale ) );
        } else {
            resolvedMessage = resolveMessage( message, locale );
        }   
        ...
    }

    private String resolveMessage(String message, Locale locale) {
        String resolvedMessage = message;

        // 获取资源ResourceBundle三部曲
        ResourceBundle userResourceBundle = userResourceBundleLocator.getResourceBundle( locale );
        ResourceBundle constraintContributorResourceBundle = contributorResourceBundleLocator.getResourceBundle( locale );
        ResourceBundle defaultResourceBundle = defaultResourceBundleLocator.getResourceBundle( locale );
        ...
    }
}

관해서는 message다음과 같이 요약 일반적으로 처리 단계 :

  1. 자리 표시하지 기호 경우 {처리 할 필요가 직접 반환 (예를 들면 우리의 사용자 지정 전체 텍스트 메시지로 직접 반환, 속성 값) -
  2. 자리 표시 자 또는 EL은 ,에 resolveMessage()~ 방법을 처리하기 위해 리소스 파일에서 내용을
  3. : 찾으려면 다음 세 단계를 수행 리소스 파일을 들고
    1 userResourceBundleLocator사용자 자신의 이동 : classpath(기본 이름은 리소스 파일을 찾기 위해 내부 ValidationMessages.properties물론, 당신은 또한 국제 가명을 사용할 수 있습니다)
    2 contributorResourceBundleLocator: 리소스 번들의 기여로드
    3 defaultResourceBundle: 기본 전략. 여기로 이동 于/org/hibernate/validator로드ValidationMessages.properties
  4. 자원을로드하는 순서로 다음 사항을 유의하십시오. 아무리,이 세 가지 리소스 파일 (단락 로직) 메모리에로드되지 않습니다. 일치하는 자리 표시 자 할 때, 여전히이 규칙을 따르십시오 :
    현재와 첫 번째 프로젝트 1. classpath자원과 ~의 다음 다음 단계와 일치하지 않는 경우 자원에서 자원, 개체 틀에 맞게
    법 위의 (2), 등등, 재귀 (안 자리가 일치 출력 인 경우, 출력이 아닌 모든 자리와 일치 null~ 오)

때문에, 그 참고 {여기에 특수 문자는 출력을 원하는 경우 {, 탈출하십시오 :\{

이 알면, 당신은 실패 메시지 사용자 정의 할 message, 당신은 단순히 다음과 같은 예는, OK, 너무 간단하지 않습니다를 :

    @Min(value = 10, message = "{com.fsx.my.min.message}")
    private Integer age;

로 명명 된 리소스 속성 파일 작성 ValidationMessages.properties다음과 같이 클래스 경로에서 다음을, 문서를 읽

// 此处可以使用占位符{value}读取注解对应属性上的值
com.fsx.my.min.message=[自定义消息]最小值必须是{value}

테스트 케이스를 실행, 오류 메시지는 다음을 인쇄 :

age [自定义消息]最小值必须是10: -1

완벽한 (힘의 정의)

참고 : 내 플랫폼은 중국어, 그래서 파일 이름 때문에 ValidationMessages_zh_CN.properties효과는 동일하다 Hibernate Validation제공 Locale국제화에 대한 지원을


국제 뉴스에서 봄의 사용자 환경

위에서 사용되는 Hibernate Validation대부분의 경우 우리가되기 때문에, 내장 된 국제화에 대한 지원 Spring데이터 유효성 검사 환경을 사용하여,이 경우 국가 관행의 봄 축복에 대해 이야기하는 것이 필요하다. 우리는 알고 Spring MVC국제 작업을 할 물론 가능하도록, 국제적인 할 수 있도록 설계된 모듈이 Spring스스로 그것을 할, 그리고 여기 나는 또한 줄 Demo권리를 :

설명 : 심지어 봄 환경에서, 당신은 평소 사용하지 Hibernate Validation~, 여전히 문제의 국제 프로그램을

(1), (자신의 국제 리소스 파일을 포함하는) 컨테이너에 인증 장치를 구성 :

@Configuration
public class RootConfig {

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();

        // 使用Spring加载国际化资源文件
        //ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        //messageSource.setBasename("MyValidationMsg");

        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("MyValidationMsg"); // 注意此处名字就随意啦,毕竟交给spring了`.properties`就不需要了哦
        messageSource.setCacheSeconds(120); // 缓存时长
        // messageSource.setFileEncodings(); // 设置编码 UTF-8

        localValidatorFactoryBean.setValidationMessageSource(messageSource);
        return localValidatorFactoryBean;
    }
}

하나의 테스트를 실행합니다 :

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {

    @Autowired
    private LocalValidatorFactoryBean localValidatorFactoryBean;

    @Test
    public void test1() {
        Person person = new Person();
        person.setAge(-5);

        Validator validator = localValidatorFactoryBean.getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 输出错误消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

}

(적용하려면 완벽한) 다음과 같이 검증 실패 메시지를 인쇄 :

age [自定义消息]最小值必须是10: -5

참고 : 경우 Spring당신은 또한 국제 고려할 필요가있는 경우 응용 프로그램이, 내가 개인적으로 사용하는 것이 좋습니다 Spring보다는 국제 다루는 Hibernate- (일종의 Spring기분이 나무는 물론, 거기에 뇌 잔류 분말이며, 이것은 필수되지 않습니다)


스프링 MVC 방법을 사용자 정의 글로벌 유효성 검사기에

Spring MVC다음과 같이 기본 구성 실행 코드 (사용) 교정은 다음과 같습니다

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {\
    ...
    @Bean
    public Validator mvcValidator() {
        Validator validator = getValidator();
        if (validator == null) {
            if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
                Class<?> clazz;
                try {
                    String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
                    clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
                } catch (ClassNotFoundException | LinkageError ex) {
                    throw new BeanInitializationException("Failed to resolve default validator class", ex);
                }
                validator = (Validator) BeanUtils.instantiateClass(clazz);
            } else {
                validator = new NoOpValidator();
            }
        }
        return validator;
    }
    ...
}

코드는 매우 짧은 대답, 라인 설명으로하지 라인입니다. 나는 다음과 같이 요약 :

  1. Spring MVC수표의 自动효과를, 당신은 가져와야합니다 javax.validation.Validator작업을하거나입니다 new NoOpValidator()그것은 나무 체크섬 행동
  2. Spring MVC최종 확인은 기본입니다 OptionalValidatorFactoryBean( LocalValidatorFactoryBean서브 클래스) -
  3. 물론, 발효를 확인하기 위해 @EnableWebMvc또한 필요하다 ( SpringBoot환경 다른 상기)

그럼 어떻게하는 글로벌 검증을 사용자 정의? 다음과 같은 최상의 방법은 다음과 같습니다 :

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    ...
    @Override
    public Validator getValidator() {
        // return "global" validator
        return new LocalValidatorFactoryBean();
    }
    ...
}

물론, 당신이 사용할 수있는 @InitBinder설정, 미세한 입도는 현재에 제공 될 수있다 Controller바운드 검사기 가능합니다 (예를 들어, 사용자 고유의 유효성 검사기의 다양한 달성 할 수있는, 더 복잡한 로직 결정)

== == 사용자 정의 제약

JSR그리고 Hibernate제약 지원이 충분히 강한되었습니다, 우리는 충족 할 수 있어야합니다 의 대부분 의 경우 기본 인증을. 여전히 비즈니스 요구를 충족 할 수없는 경우, 우리는 또한 제약 조건을 정의 할 수 있습니다,이 문제는 매우 간단합니다.

JSR그리고 Hibernate: 제약 주석 설명을 제공 @Valid 역할 (캐스케이드 검사) 및 공통 제약 주석 설명을 설명 파악 깊은 곳에서 : [자바] 데이터 유효성 검사 (콩 검증)의 작은 홈 깊이있는 이해를

세 단계의 제약 사항을 정의 (2 단계이라고합니다) :

  1. 사용자 정의 제약 주석
  2. 발리 데이터 구현 (인터페이스를 구현 : ConstraintValidator)
  3. 기본 유효성 검사 오류를 정의

제공 Demo: 여기 제약 조건 확인하기 위해 주석을 사용자 정의 세트의 길이 범위 :@CollectionRange

1, 사용자 정의 주석 (여기에 사용되는 고급이었다)

@Documented
@Constraint(validatedBy = {})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(value = CollectionRange.List.class)
@Size // 校验动作委托给Size去完成  所以它自己并不需要校验器~~~
@ReportAsSingleViolation // 组合组件一般建议标注上
public @interface CollectionRange {

    // 三个必备的基本属性
    String message() default "{com.fsx.my.collection.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    // 自定义属性  @OverridesAttribute这里有点方法覆盖的意思~~~~~~ 子类属性覆盖父类的默认值嘛
    @OverridesAttribute(constraint = Size.class, name = "min")
    int min() default 0;
    @OverridesAttribute(constraint = Size.class, name = "max")
    int max() default Integer.MAX_VALUE;

    // 重复注解
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        CollectionRange[] value();
    }
}

2, 발리 달성하기 위해
필요는 없다 본 실시 예의를 (다음이 될 것입니다)
3, 사용자 지정 오류 메시지가
메시지 속성이 사망, 물론, 당신은 쓸 수 있지만,에 본 실시 형태의 구성을 사용하여 ~

com.fsx.my.collection.message=[自定义消息]你的集合的长度必须介于{min}和{max}之间(包含边界值)

실행 케이스 :

@Getter
@Setter
@ToString
public class Person {
    @CollectionRange(min = 5, max = 10)
    private List<Integer> numbers;
}

    // 测试用例
    public static void main(String[] args) {
        Person person = new Person();
        person.setNumbers(Arrays.asList(1, 2, 3));

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 输出错误消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

검사 정보로 출력 (검증이 성공) :

numbers [自定义消息]你的集合的长度必须介于5和10之间(包含边界值): [1, 2, 3]

제약 조건의 조합

이것은 많은 경우에 필드에 대한 필요가있다, 비교적 간단 제약의 복수 (빈 0보다 큰되지 않음)된다. 이 시간 우리는 두 가지 방법이있다 :

  1. 의 재산에 여러 주석을 표시하려면 ( 권장 )
  2. 사용자 정의 주석, 이러한 패키지 함께 주석이 새로운 주석 제약 조건을 형성한다 (이용 장면은 상대적으로 작다)

맞춤 변수 MESSAGE 메시지를 사용할 수있다 == ==

우리는 실패 소식 제약 것을 알고 message사용할 수 있다는 점에서 {}동적 값에 자리가 디폴트가 취할 수 있는 제약 주석 모든 속성 값, 또한 만 속성의 값을 취할 .

하지만, 때때로 우리의 좋은 쇼를 위해, 우리는 사용자 정의해야 message그것을 어떻게 가능한 값을? 모든 사람이 사용자 지정 매개 변수 자리 표시자를 사용 방법을 알려, 예를 들어 줄 다음 (참고 : 필요 기반의 사용자 정의 주석) :

주문형 성별 제약 주석 :

@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {GenderConstraintValidator.class}) 
public @interface Gender {

    // 三个必备的基本属性
    String message() default "{com.fsx.my.gender.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    int gender() default 0; //0:男生  1:女生
}

메시지 자원은 다음과 같습니다 :

com.fsx.my.gender.message=[自定义消息]此处只能允许性别为[{zhGenderValue}]的

분명히, 우리가 읽어야 할 zhGenderValue이 사용자 정의 속성 값을, 그리고 중국되고 싶어요. 그래서 그것을이 검증을 달성 날 좀 봐 :

public class GenderConstraintValidator implements ConstraintValidator<Gender, Integer> {

    int genderValue;

    @Override
    public void initialize(Gender constraintAnnotation) {
        genderValue = constraintAnnotation.gender();
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        //添加参数  校验失败的时候可用
        HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class);
        hibernateContext.addMessageParameter("zhGenderValue", genderValue == 0 ? "男" : "女"); // 友好展示
        //hibernateContext.buildConstraintViolationWithTemplate("{zhGenderValue}").addConstraintViolation();

        if (value == null) {
            return false; // null is not valid
        }
        return value == genderValue;
    }
}

하나의 테스트를 실행합니다 :

@Getter
@Setter
@ToString
public class Person {
    @Gender(gender = 0)
    private Integer personGender;
}

    public static void main(String[] args) {
        Person person = new Person();
        person.setPersonGender(1);

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 输出错误消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

다음과 같이 인쇄입니다 :

personGender [自定义消息]此处只能允许性别为[男]的: 1

완벽한 (효과에 도달)

개요

기사가 기계의 앞에 사용하는 경우,이 기계는 재생 호출 할 수 있습니다 . Bean Validation그것은 지금 공식 자바 콩 검증 기준의 정의, 최신 버전 2.x입니다 hibernate validator표준 구현으로, 여전히 비즈니스 요구를 충족 할 수없는 경우, 우리는 또한 제약 조건을 정의 할 수 있습니다, 제약의 다양성을 포함하도록 확장되었습니다.
데이터 검증 Bean Validation이의이 덩어리의 내용이 종료됩니다, 나는 ~, 가장 원하는 도움을 가져다 당신에게 실무를 줄 수있는 모든 내용을 설명하도록하겠습니다

지식 교환

若文章格式混乱,可点击: 설명 링크 - 텍스트 링크 - 텍스트 링크 - 텍스트 링크 - 텍스트 링크

== 마지막 : 당신이 당신에게 도움이 기사를 생각한다면, 찬양의 노래를 가리 키도록 할 수 있습니다. 물론, 친구의 당신의 원은 더 작은 파트너도보고되도록 공유 作者本人许可的~==가

기술 내용에 관심이있는 경우 그룹 WX 교환에 참여할 수 있습니다 Java高工、架构师3群.
그룹이 두 차원 코드를 실패하면, WX 번호를 추가하십시오 fsx641385712(또는 2 차원 코드가 WX 아래 스캔). 그리고 참고 : "java入群"단어를 수동으로 그룹에 초대됩니다

추천

출처www.cnblogs.com/fangshixiang/p/11285677.html