热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Fluent

Fluent-Validator业务校验器背景在互联网行业中,基于Java开发的业务类系统,不管是服务端还是客户端,业务逻辑代码的更新往往是非常频繁的,这源于功能的快速迭代特
Fluent-Validator 业务校验器

背景

在互联网行业中,基于Java开发的业务类系统,不管是服务端还是客户端,业务逻辑代码的更新往往是非常频繁的,这源于功能的快速迭代特性。在一般公司内部,特别是使用Java web技术构建的平台中,不管是基于模块化还是服务化的,业务逻辑都会相对复杂。
这些系统之间、系统内部往往存在大量的API接口,这些接口一般都需要对入参(输入参数的简称)做校验,以保证:
1) 核心业务逻辑能够顺利按照预期执行。
2) 数据能够正常存取。
3) 数据安全性。包括符合约束以及限制,有访问权限控制以及不出现SQL注入等问题。
开发人员在维护核心业务逻辑的同时,还需要为输入做严格的校验。当输入不合法时,能够给caller一个明确的反馈,最常见的反馈就是返回封装了result的对象或者抛出exception。
一些常见的验证代码片段如下所示:

public Response execute(Request request) {
if (request == null) {
throw BizException();
}
List cars = request.getCars();
if (CollectionUtils.isEmpty(cars)) {
throw BizException();
}
for (Car car : cars) {
if (car.getSeatCount() <2) {
throw BizException();
}
}
// do core business logic
}

我们可以发现,它不够优雅而且违反一些范式:
1)违反单一职责原则(Single responsibility)。核心业务逻辑(core business logic)和验证逻辑(validation logic)耦合在一个类中。
2)开闭原则(Open/closed)。我们应该对扩展开放,对修改封闭,验证逻辑不好扩展,而且一旦需要修改需要动整体这个类。
3)DRY原则(Don’t repeat yourself)。代码冗余,相同逻辑可能散落多处,长此以往不好收殓。


1.简介

FluentValidato是一个适用于以Java语言开发的程序,让开发人员回归focus到业务逻辑上,使用流式(Fluent Interface)调用风格让验证跑起来很优雅,同时验证器(Validator)可以做到开闭原则,实现最大程度的复用的工具库。

2.特点



  1. 验证逻辑与业务逻辑不再耦合
    摒弃原来不规范的验证逻辑散落的现象。

  2. 校验器各司其职,好维护,可复用,可扩展
    一个校验器(Validator)只负责某个属性或者对象的校验,可以做到职责单一,易于维护,并且可复用。

  3. 流式风格(Fluent Interface)调用

  4. 使用注解方式验证
    可以装饰在属性上,减少硬编码量。

  5. 支持JSR 303 – Bean Validation标准
    或许你已经使用了Hibernate Validator,不用抛弃它,FluentValidator可以站在巨人的肩膀上。

  6. Spring良好集成
    校验器可以由Spring IoC容器托管。校验入参可以直接使用注解,配置好拦截器,核心业务逻辑完全没有验证逻辑的影子,干净利落。

  7. 回调给予你充分的自由度
    验证过程中发生的错误、异常,验证结果的返回,开发人员都可以定制。


3.上手


3.1引入maven依赖:


com.baidu.unbiz
fluent-validator


org.slf4j
slf4j-log4j12


1.0.5


3.2 业务领域模型

从广义角度来说DTO(Data Transfer Object)、VO(Value Object)、BO(Business Object)、POJO等都可以看做是业务表达模型。
创建一个学生类,包含 name(姓名)、age(年龄)、schoolName(学校名称)、(area)地区

package com.example.fluentvalidator;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author :jianyul
* @date : 2022/5/16 18:00
*/
@Data
@AllArgsConstructor
public class StudentDto {
private String name;
private Integer age;
private String schoolName;
private String area;
}

3.3 Validator样例

针对schoolName(学校名称)创建一个Validator,代码如下:

public class SchoolNameValidator extends ValidatorHandler implements Validator {
@Override
public boolean validate(ValidatorContext context, String schoolName) {
if (!"无锡中学".equals(schoolName)) {
context.addErrorMsg("学校名称不正确");
return false;
}
return true;
}
}

很简单,实现Validator接口,泛型T规范这个校验器待验证的对象的类型,继承ValidatorHandler可以避免实现一些默认的方法,validate()方法第一个参数是整个校验过程的上下文,第二个参数是待验证对象,也就是学校名称。
验证逻辑:假设学校名称必须是无锡中学,否则通过context放入错误消息并且返回false,成功返回true。

3.4 验证

StudentDto studentDto = new StudentDto("张三", 18, "苏州中学", "无锡");
Result result =
FluentValidator.checkAll()
.on(studentDto.getSchoolName(), new SchoolNameValidator())
.doValidate()
.result(toSimple());
System.out.println(result);
//打印结果:Result{isSuccess=false, errors=[学校名称不正确]}

首先我们通过FluentValidator.checkAll()获取了一个FluentValidator实例,紧接着调用了failFast()表示有错了立即返回,它的反义词是failOver,然后,、on()操作表示在指定属性上使用对应校验器进行校验,截止到此,真正的校验还并没有做,这就是所谓的“惰性求值(Lazy valuation)”,有点像Java8 Stream API中的filter()、map()方法,直到doValidate()验证才真正执行了,最后我们需要收殓出来一个结果供caller获取打印,直接使用默认提供的静态方法toSimple()来做一个回调函数传入result()方法,最终返回Result类。

4.深入了解


4.1 Validator详解

Validator接口代码如下:

public interface Validator {
/**
* 判断在该对象上是否接受或者需要验证
*


* 如果返回true,那么则调用{@link #validate(ValidatorContext, Object)},否则跳过该验证器
*
* @param context 验证上下文
* @param t 待验证对象
*
* @return 是否接受验证
*/
boolean accept(ValidatorContext context, T t);
/**
* 执行验证
*


* 如果发生错误内部需要调用{@link ValidatorContext#addErrorMsg(String)}方法,也即context.addErrorMsg(String)
*
来添加错误,该错误会被添加到结果存根{@link Result}的错误消息列表中。
*
* @param context 验证上下文
* @param t 待验证对象
*
* @return 是否验证通过
*/
boolean validate(ValidatorContext context, T t);
/**
* 异常回调
*


* 当执行{@link #accept(ValidatorContext, Object)}或者{@link #validate(ValidatorContext, Object)}发生异常时的如何处理
*
* @param e 异常
* @param context 验证上下文
* @param t 待验证对象
*/
void onException(Exception e, ValidatorContext context, T t);
}

ValidatorHandler是实现Validator接口的一个模板类,如果你自己实现的Validator不想覆盖上面3个方法,可以继承这个ValidatorHandler。

public class ValidatorHandler implements Validator {
@Override
public boolean accept(ValidatorContext context, T t) {
return true;
}
@Override
public boolean validate(ValidatorContext context, T t) {
return true;
}
@Override
public void onException(Exception e, ValidatorContext context, T t) {
}
}

内部校验逻辑发生错误时候,有两个处理办法,
第一,简单处理,如上述3.3中代码所示:
context.addErrorMsg("学校名称不正确");
第二,需要详细的信息,包括错误消息,错误属性/字段,错误值,错误码,都可以自己定义,放入错误的方法如下,create()方法传入消息(必填),setErrorCode()方法设置错误码(选填),setField()设置错误字段(选填),setInvalidValue()设置错误值(选填)。当然这些信息需要result(toComplex())才可以获取到。

public class AreaValidator extends ValidatorHandler implements Validator {
// 实现Validator接口,泛型T规范这个校验器待验证的对象的类型
@Override
public boolean validate(ValidatorContext context, String area) {
if (!"无锡".equals(area)) {
context.addError(
ValidationError.create("地址不正确")
.setErrorCode(5000)
.setField("area")
.setInvalidValue(area));
// context.addErrorMsg("地址不正确");
return false;
}
return true;
}
}

如果需要可以使用复杂ComplexResult,内含错误消息,错误属性/字段,错误值,错误码,如下所示:

ComplexResult ret =
FluentValidator.checkAll()
.failOver()
.on(studentDto.getArea(), new AreaValidator())
.doValidate()
.result(toComplex());
System.out.println(ret);
//打印结果:Result{isSuccess=false, errors=[ValidationError{errorCode=5000, errorMsg='地址不正确', field='area', invalidValue=苏州}], timeElapsedInMillis=1}

上述都是针对单个属性值编写对应的Validator代码,实际开发中,我们需要对整个对象的多个属性进行业务校验,这时我们可以针对整个对象编写对应的Validator,最后用ComplexResult来接收校验结果,代码如下:

public class StudentValidator extends ValidatorHandler
implements Validator {
@Override
public boolean validate(ValidatorContext context, StudentDto studentDto) {
if (!"无锡".equals(studentDto.getArea())) {
context.addError(
ValidationError.create("地址不正确")
.setErrorCode(5000)
.setField("area")
.setInvalidValue(studentDto.getArea()));
}
if (!"无锡中学".equals(studentDto.getSchoolName())) {
context.addError(
ValidationError.create("学校名称不正确")
.setErrorCode(5000)
.setField("schoolName")
.setInvalidValue(studentDto.getSchoolName()));
}
//校验有没有Error信息
if (CollectionUtils.isNotEmpty(context.result.getErrors())) {
return false;
}
return true;
}
}

on()的一连串调用实际就是构建调用链,因此理所当然可以传入一个调用链。

ValidatorChain chain = new ValidatorChain();
List validators = new ArrayList();
validators.add(new StudentValidator());
chain.setValidators(validators);
ComplexResult rets =
FluentValidator.checkAll().on(studentDto, chain).doValidate().result(toComplex());
System.out.println(rets);
//打印结果:Result{isSuccess=false, errors=[ValidationError{errorCode=5000, errorMsg='地址不正确', field='area', invalidValue=苏州}, ValidationError{errorCode=5000, errorMsg='学校名称不正确', field='schoolName', invalidValue=苏州中学}], timeElapsedInMillis=7}

拓展

可根据项目自定义返回结果类型,实现ResultCollector即可

public interface ResultCollector {
/**
* 转换为对外结果
*
* @param result 框架内部验证结果
*
* @return 对外验证结果对象
*/
T toResult(ValidationResult result);
}

4.2 onEach

如果要验证的是一个集合(Collection)或者数组,那么可以使用onEach,FluentValidator会自动为你遍历:

ComplexResult result =
FluentValidator.checkAll()
.onEach(list, new StudentValidator())
.doValidate()
.result(toComplex());

4.3 fail fast or fail over

当出现校验失败时,也就是Validator的validate()方法返回了false,那么是继续还是直接退出呢?默认为使用failFast()方法,直接退出,如果你想继续完成所有校验,使用failOver()来skip掉。

ComplexResult result1 =
FluentValidator.checkAll()
.failFast()
.on(liS.getArea(), new AreaValidator())
.on(liS.getSchoolName(), new SchoolNameValidator())
.doValidate()
.result(toComplex());
ComplexResult result2 =
FluentValidator.checkAll()
.failOver()
.on(liS.getArea(), new AreaValidator())
.on(liS.getSchoolName(), new SchoolNameValidator())
.doValidate()
.result(toComplex());


4.4 when

on()后面可以紧跟一个when(),当when满足expression表达式on才启用验证,否则skip调用。

ComplexResult result =
FluentValidator.checkAll()
.failOver()
.on(liS.getArea(), new AreaValidator())
.when("20".equals(liS.getAge()))
.on(liS.getSchoolName(), new SchoolNameValidator())
.doValidate()
.result(toComplex());
System.out.println(result);

4.5 验证回调callBack

doValidate()方法接受一个ValidateCallback接口

public interface ValidateCallback {
/**
* 所有验证完成并且成功后
*
* @param validatorElementList 验证器list
*/
void onSuccess(ValidatorElementList validatorElementList);
/**
* 所有验证步骤结束,发现验证存在失败后
*
* @param validatorElementList 验证器list
* @param errors 验证过程中发生的错误
*/
void onFail(ValidatorElementList validatorElementList, List errors);
/**
* 执行验证过程中发生了异常后
*
* @param validator 验证器
* @param e 异常
* @param target 正在验证的对象
*
* @throws Exception
*/
void onUncaughtException(Validator validator, Exception e, Object target) throws Exception;
}

我们可以根据业务需求,在校验回调接口中做其他逻辑处理,如下所示:

FluentValidator.checkAll()
.on(liS.getSchoolName(), new SchoolNameValidator())
.doValidate(
new DefaultValidateCallback() {
@Override
public void onSuccess(
ValidatorElementList validatorElementList) {
System.out.println("校验成功");
}
@Override
public void onFail(
ValidatorElementList validatorElementList,
List errors) {
System.out.println("校验失败");
}
})
.result(toComplex());

4.6 RuntimeValidateException

如果验证中发生了一些不可控异常,例如数据库调用失败,RPC连接失效等,会抛出一些异常,如果Validator没有try-catch处理,FluentValidator会将这些异常封装在RuntimeValidateException,然后再re-throw出去。

4.7 上下文传递

通过putAttribute2Context()方法,可以往FluentValidator注入一些键值对,在所有Validator中共享,有时候这相当有用。

Result result =
FluentValidator.checkAll()
.putAttribute2Context("school", "常州中学")
.on(liS.getSchoolName(), new SchoolNameValidator())
.doValidate()
.result(toSimple());

可在Validator中通过context.getAttribute拿到这个值

String name = context.getAttribute("school", String.class);
if (!name.equals(schoolName)) {
context.addErrorMsg("学校名称不正确");
return false;
}
return true;

4.8 闭包

通过putClosure2Context()方法,可以往FluentValidator注入一个闭包,这个闭包的作用是在Validator内部可以调用,并且缓存结果到Closure中,这样caller在上层可以获取这个结果。
典型的应用场景是,当需要频繁调用一个RPC的时候,往往该执行线程内部一次调用就够了,多次调用会影响性能,我们就可以缓存住这个结果,在所有Validator间和caller中共享。
下面展示了在caller处存在一个getAreas()方法,它假如需要RPC才能获取所有地区信息,显然是很耗时的,可以在validator中调用,然后validator内部共享的同时,caller可以利用闭包拿到结果,用于后续的业务逻辑。

StudentDto liS = new StudentDto("李四", 18, "常州中学", "常州");
Closure> closure =
new ClosureHandler>() {
private List allManufacturers;
@Override
public List getResult() {
return allManufacturers;
}
@SneakyThrows
@Override
public void doExecute(Object... input) {
// getAreas()模拟RPC远程接口调用
allManufacturers = getAreas();
}
};
FluentValidator.checkAll()
.putClosure2Context("area", closure)
.on(liS.getArea(), new AreaValidator())
.doValidate()
.result(toSimple());

Validator中获取接口查询的数据:

Closure> closure = context.getClosure("area");
List areas = closure.executeAndGetResult();
if (!areas.contains(area)) {
context.addError(
ValidationError.create("地址不正确")
.setErrorCode(5000)
.setField("area")
.setInvalidValue(area));
return false;
}
return true;
}

5.高级玩法


Hibernate Validator集成

Hibernate Validator是JSR 303 – Bean Validation规范的一个最佳的实现类库,他仅仅是jboss家族的一员,和大名鼎鼎的Hibernate ORM是系出同门,属于远房亲戚关系。很多框架都会天然集成这个优秀类库,例如Spring MVC的@Valid注解可以为Controller方法上的参数做校验。
FluentValidator当然不会重复早轮子,这么好的类库,一定要使用站在巨人肩膀上的策略,将它集成进来。
想要了解更多Hibernate Validator用法,参考这个链接。
fluent-validator 集成 hibernate-validator 需要添加依赖


com.baidu.unbiz
fluent-validator-jsr303


org.slf4j
slf4j-log4j12


1.0.5


5.1 注解验证

上述都是通过显示的API调用来进行验证,FluentValidator同样提供简洁的基于注解配置的方式来达到同样的效果。
@FluentValidate可以装饰在属性上,内部接收一个Class[]数组参数,这些个classes必须是Validator的子类,这叫表明在某个属性上依次用这些Validator做验证。如下,我们改造下StudentDto这个类:

@Data
@AllArgsConstructor
public class StudentDto {
@NotNull private String name;
private Integer age;
@Length(max = 5)
@FluentValidate({SchoolNameValidator.class})
private String schoolName;
@FluentValidate({AreaValidator.class})
private String area;
}

然后还是利用on()或者onEach()方法来校验,这里只不过不用传入Validator或者ValidatorChain了。

ComplexResult ret =
FluentValidator.checkAll()
.failOver()
.configure(new SimpleRegistry())
.on(liS)
.doValidate()
.result(toComplex());

默认的,FluentValidator使用SimpleRegistry,它会尝试从当前的class loader中调用Class.newInstance()方法来新建一个Validator。

5.2 分组验证

当使用注解验证时候,会遇到这样的情况,某些时候例如添加操作,我们会验证A/B/C三个属性,而修改操作,我们需要验证B/C/D/E 4个属性
@FluentValidate注解另外一个接受的参数是groups,里面也是Class[]数组,只不过这个Class可以是开发人员随意写的一个简单的类,不含有任何属性方法都可以,例如:

@Data
@AllArgsConstructor
public class StudentDto {
@NotNull private String name;
private Integer age;
@Length(max = 5)
@FluentValidate(
value = {SchoolNameValidator.class},
groups = {Add.class})
private String schoolName;
@FluentValidate(
value = {AreaValidator.class},
groups = Update.class)
private String area;
}

那么验证的时候,只需要在checkAll()方法中传入想要验证的group,就只会做选择性的分组验证,例如下面例子,只有area(地区)会被验证。

ComplexResult result =
FluentValidator.checkAll(new Class[] {Update.class})
.on(liS)
.doValidate()
.result(toComplex());

5.3 级联验证

级联验证(cascade validation),也叫做对象图(object graphs),指一个类嵌套另外一个类的时候做的验证。
如下例所示,我们在车库(Garage)类中含有一个汽车列表(carList),可以在这个汽车列表属性上使用@FluentValid注解,表示需要级联到内部Car做onEach验证。

public class Garage {
@FluentValidate({CarNotExceedLimitValidator.class})
@FluentValid
private List carList;
}

注意,@FluentValid和@FluentValidate两个注解不互相冲突,如下所示,调用链会先验证carList上的CarNotExceedLimitValidator,然后再遍历carList,对每个car做内部的生产商、座椅数、牌照验证。

6.SpringBoot实战


6.1 添加依赖

fluent-validator 集成 spring 需要添加依赖


com.baidu.unbiz
fluent-validator-spring
1.0.9


6.2 注册 Fluent-validator

fluent-validate 与 spring 结合使用 annotation 方式进行参数校验,需要借助于 spring 的 AOP,fluent-validate 提供了处理类 FluentValidateInterceptor,但是 fluent-validate 提供的默认验证回调类 DefaultValidateCallback 对校验失败的情况并没有处理,所以需要自行实现一个

@Slf4j
public class MyValidateCallBack extends DefaultValidateCallback implements ValidateCallback {
@Override
public void onSuccess(ValidatorElementList validatorElementList) {
log.info("校验成功");
super.onSuccess(validatorElementList);
}
@Override
public void onFail(ValidatorElementList validatorElementList, List errors) {
log.info("校验失败");
throw new RuntimeException(errors.get(0).getErrorMsg());
}
@Override
public void onUncaughtException(Validator validator, Exception e, Object target)
throws Exception {
log.info("校验异常");
throw new RuntimeException(e);
}

6.3 注册IOC

注册 FluentValidateInterceptor拦截器及MyValidateCallBack回调方法,最后配置一个 AOP 规则

@Configuration
public class ValidateCallbackConfig {
@Bean
public FluentValidateInterceptor fluentValidateInterceptor() {
FluentValidateInterceptor fluentValidateInterceptor = new FluentValidateInterceptor();
fluentValidateInterceptor.setCallback(validateCallback());
return fluentValidateInterceptor;
}
public MyValidateCallBack validateCallback() {
return new MyValidateCallBack();
}
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
// 使用BeanNameAutoProxyCreator来创建代理
BeanNameAutoProxyCreator proxyCreator = new BeanNameAutoProxyCreator();
// 设置要创建代理的那些Bean的名字
proxyCreator.setBeanNames("*ServiceImpl");
proxyCreator.setInterceptorNames("fluentValidateInterceptor");
return proxyCreator;
}
}

6.4 使用校验

为了方便,在StudentServiceImpl实现类上增加参数校验

@Service
public class StudentServiceImpl implements StudentService {
@Override
public Integer getAge(@FluentValid StudentDto studentDto) {
return studentDto.getAge();
}
}

总结

fluent-validate 可以全方位兼容 hibernate-validate,基于 spring 的 AOP 可以提供基于注解的方法入参校验,同时也可以提供流式编程的工具类业务校验,替代 hibernate-validate 的同时提供了更多扩展性

参考文档:http://neoremind.com/2016/02/java%E7%9A%84%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E9%AA%8C%E8%AF%81%E6%A1%86%E6%9E%B6fluent-validator/


欢迎大家访问 个人博客 Johnny小屋


推荐阅读
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 开发笔记:UEditor调用上传图片上传文件等模块
    1、引入ue相关文件,写好初始代码为了更好的封装整一个单独的插件,这里我们要做到示例化ue后隐藏网页中的编辑窗口,并移除焦点。 ... [详细]
  • DOM事件大全
    1.事件:js与html的交互就是通过事件的,观察者模式2.事件流:从页面中接收事件的顺序IE::事件冒泡流,事件冒泡,事件从最具体的元素接收,然后逐级向上传播,主流浏览器都支持N ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 标题: ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • 微信官方授权及获取OpenId的方法,服务器通过SpringBoot实现
    主要步骤:前端获取到code(wx.login),传入服务器服务器通过参数AppID和AppSecret访问官方接口,获取到OpenId ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • Apache Shiro 身份验证绕过漏洞 (CVE202011989) 详细解析及防范措施
    本文详细解析了Apache Shiro 身份验证绕过漏洞 (CVE202011989) 的原理和影响,并提供了相应的防范措施。Apache Shiro 是一个强大且易用的Java安全框架,常用于执行身份验证、授权、密码和会话管理。在Apache Shiro 1.5.3之前的版本中,与Spring控制器一起使用时,存在特制请求可能导致身份验证绕过的漏洞。本文还介绍了该漏洞的具体细节,并给出了防范该漏洞的建议措施。 ... [详细]
  • Bro是一款强大的网络安全工具,以及协议识别与统计的工具。Broisapowerfulnetworkanalysisframeworkthatismuchdifferentfro ... [详细]
author-avatar
小甜甜龌龊的华丽
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有