Skip to content

前后端参数统一校验

提示

本功能需要结合acuity-web-pro + acuity-validator-starter + acuity-cloud或acuity-boot 一起使用。


我们这里以 新增岗位 为例,大致流程如下:

  1. 后端按照 hibernate validator 规范编写controller 和 entity,并标记相应的验证注解
  2. 前端进入新增页面时,就立即加载该界面的新增接口校验规则,并将后端的规则通过规则转换器formValidateService.ts 转换成前端校验组件认可的规则。

注意

formValidateService.ts 插件位于acuity-web-plus项目, 企业商用版才有。敬请见谅。**

步骤:

  1. 在server模块引入依赖:
xml
<dependency>
    <groupId>top.acuity.commons</groupId>
    <artifactId>acuity-validator-starter</artifactId>
</dependency>
<dependency>
    <groupId>top.acuity.commons</groupId>
    <artifactId>acuity-validator-starter</artifactId>
</dependency>
  1. 在 Org 类上加上 hibernate 的验证注解:
java
import org.hibernate.validator.constraints.Length;
public class Position implements Serializable {
    private static final long serialVersionUID = 1L;
   
    @Length(max = 255, message = "名称长度不能超过255")
    @NotEmpty(message = "名称不能为空")
    private String name;
 
    @Length(max = 255, message = "简称长度不能超过255")
    private String abbreviation;
  
    @NotEmpty(message = "民族不能为空")
    @Length(max = 2, message = "民族长度不能超过2")
    private String nation;
}
import org.hibernate.validator.constraints.Length;
public class Position implements Serializable {
    private static final long serialVersionUID = 1L;
   
    @Length(max = 255, message = "名称长度不能超过255")
    @NotEmpty(message = "名称不能为空")
    private String name;
 
    @Length(max = 255, message = "简称长度不能超过255")
    private String abbreviation;
  
    @NotEmpty(message = "民族不能为空")
    @Length(max = 2, message = "民族长度不能超过2")
    private String nation;
}
  1. 在controller中需要校验的方法加上 @Validated 注解:
java
@PostMapping("/position")
public R<Position> save(@RequestBody @Validated Position data) {
    return success(data);
}
@PostMapping("/position")
public R<Position> save(@RequestBody @Validated Position data) {
    return success(data);
}
  1. 写好 controller 方法后, 假设该方法的访问地址是

    POST http://127.0.0.1:8760/api/base/position
    POST http://127.0.0.1:8760/api/base/position
  2. 前端在进入新增页面时,会立即调用 http://127.0.0.1:8760/api/base/form/validator/position 接口, 获取保存组织接口的校验规则。

  1. 前端新增页面的代码如下
typescript
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
 	
  // 省略 处理参数回显逻辑 ...

  if (unref(type) !== ActionEnum.VIEW) {
    // 根据type值,获取接口
    let validateApi = Api[VALIDATE_API[unref(type)]];
    
    // 调用 getValidateRules 将请求后台获取参数验证规则
    await getValidateRules(validateApi, customFormSchemaRules(type)).then(async (rules) => {
      // 后台返回规则后,动态更新前端的验证规则
      rules && rules.length > 0 && (await updateSchema(rules));
    });
  }
});
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
 	
  // 省略 处理参数回显逻辑 ...

  if (unref(type) !== ActionEnum.VIEW) {
    // 根据type值,获取接口
    let validateApi = Api[VALIDATE_API[unref(type)]];
    
    // 调用 getValidateRules 将请求后台获取参数验证规则
    await getValidateRules(validateApi, customFormSchemaRules(type)).then(async (rules) => {
      // 后台返回规则后,动态更新前端的验证规则
      rules && rules.length > 0 && (await updateSchema(rules));
    });
  }
});

后端实现

提示

/api/base/form/validator/position 接口实际上是请求到了 FormValidatorController 类

java
@RequestMapping
public class FormValidatorController {
  
    @RequestMapping("/form/validator/**")
    @ResponseBody
    public R<Collection<FieldValidatorDesc>> standardByPathVar(HttpServletRequest request) throws Exception {
        String requestUri = request.getRequestURI();
        String formPath = StrUtil.subAfter(requestUri, FORM_VALIDATOR_URL, false);
        return R.success(localFieldValidatorDescribe(request, formPath));
    }
}
@RequestMapping
public class FormValidatorController {
  
    @RequestMapping("/form/validator/**")
    @ResponseBody
    public R<Collection<FieldValidatorDesc>> standardByPathVar(HttpServletRequest request) throws Exception {
        String requestUri = request.getRequestURI();
        String formPath = StrUtil.subAfter(requestUri, FORM_VALIDATOR_URL, false);
        return R.success(localFieldValidatorDescribe(request, formPath));
    }
}

前端实现

提示

getValidateRules方法位于前端项目的 formValidateService.ts 模块, 该模块适用于 async-validator 验证框架, 其他验证框架需要自行调整。

typescript
# formValidateService.ts
/**
 * 从后端获取某个接口基于 Hibernate Validator 注解生成的参数校验规则
 * @param Api url和method
 * @param customRules 自定义规则
 */
export const getValidateRules = async (
  Api: AxiosRequestConfig,
  customRules?: Partial<FormSchemaExt>[],
): Promise<Partial<FormSchema>[]> => {
  return new Promise(async (resolve, _reject) => {
    
    // 转换请求路径
    const formValidateApi = { url: '', method: Api.method };
    for (const sp in ServicePrefixEnum) {
      if (Api.url.startsWith(ServicePrefixEnum[sp])) {
        formValidateApi.url = Api.url.replace(
          ServicePrefixEnum[sp],
          `${ServicePrefixEnum[sp]}/form/validator`,
        );
      }
    }
    
    try {
      const key = formValidateApi.url + formValidateApi.method;
      if (ruleMap.has(key)) {
        return resolve(ruleMap.get(key));
      }

      // 请求后端的 /form/validator/xxx 接口获取 /xxx 接口的校验规则
      const res = await defHttp.request<FieldValidatorDesc[]>({ ...formValidateApi });
      if (res) {
        // 对后端返回的校验规则进行转换
        const formSchemaRules = transformationRules(res);
        // 后端的规则 + 前端写死的规则,出现重复的规则,以前端为准
        const allRules = enhanceCustomRules(formSchemaRules, customRules);
        ruleMap.set(key, allRules);
        return resolve(allRules);
      }
    } catch (error) {}
    return resolve([]);
  });
};
# formValidateService.ts
/**
 * 从后端获取某个接口基于 Hibernate Validator 注解生成的参数校验规则
 * @param Api url和method
 * @param customRules 自定义规则
 */
export const getValidateRules = async (
  Api: AxiosRequestConfig,
  customRules?: Partial<FormSchemaExt>[],
): Promise<Partial<FormSchema>[]> => {
  return new Promise(async (resolve, _reject) => {
    
    // 转换请求路径
    const formValidateApi = { url: '', method: Api.method };
    for (const sp in ServicePrefixEnum) {
      if (Api.url.startsWith(ServicePrefixEnum[sp])) {
        formValidateApi.url = Api.url.replace(
          ServicePrefixEnum[sp],
          `${ServicePrefixEnum[sp]}/form/validator`,
        );
      }
    }
    
    try {
      const key = formValidateApi.url + formValidateApi.method;
      if (ruleMap.has(key)) {
        return resolve(ruleMap.get(key));
      }

      // 请求后端的 /form/validator/xxx 接口获取 /xxx 接口的校验规则
      const res = await defHttp.request<FieldValidatorDesc[]>({ ...formValidateApi });
      if (res) {
        // 对后端返回的校验规则进行转换
        const formSchemaRules = transformationRules(res);
        // 后端的规则 + 前端写死的规则,出现重复的规则,以前端为准
        const allRules = enhanceCustomRules(formSchemaRules, customRules);
        ruleMap.set(key, allRules);
        return resolve(allRules);
      }
    } catch (error) {}
    return resolve([]);
  });
};

每个接口对应的校验规则请求地址有以下规则,如:

业务接口校验规则接口
post /api/base/user/savepost /api/base**/form/validator**/user/save
put /api/base/user/updateput /api/base**/form/validator**/user/update

由此可见,校验规则接口 就是在 业务接口的基础上加了 /form/validator,请求方式不变。

欢迎使用天源云Saas快速开发系统