Hibernate Validator校验请求参数

服务端收到通过拦截器的请求后,第一步,即为对请求参数的合法性校验。
合法性校验依次为三部分:

  • 参数是否存在
  • 参数类型是否合法
  • 其他复杂校验

请求校验

一般的参数校验举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public HashMap<String, Object> roleInfo(HttpServletRequest request) {
HashMap<String, Object> result = new HashMap<String, Object>();
String roleId;
try {
roleId = request.getParameter("id");
} catch (Throwable t) {
logger.error("ProxyController roleInfo args error:" + t);
result.put("result", StatusCode.ILLEGAL_PARAM.getValue());
result.put("description", StatusCode.ILLEGAL_PARAM.getDescription());
return result;
}
if (!LegalUtil.isLegalRoleId(roleId)) {
logger.error("ProxyController roleinfo roleId error:" + roleId);
result.put("result", StatusCode.ILLEGAL_PARAM.getValue());
result.put("description", StatusCode.ILLEGAL_PARAM.getDescription());
return result;
}
return proxyService.getRoleInfo(Integer.valueOf(request.getSession().getAttribute("agencyID").toString()), Integer.valueOf(roleId));
}

如果采用如下方式接受传入参数,看起来好像可以解决校验臃肿的问题:

1
2
3
4
5
6
7
8
9
public HashMap<String, Object> roleInfo(@RequestParam(value = "id") int id) {
if (!LegalUtil.isLegalRoleId(roleId)) {
logger.error("ProxyController roleinfo roleId error:" + roleId);
result.put("result", StatusCode.ILLEGAL_PARAM.getValue());
result.put("description", StatusCode.ILLEGAL_PARAM.getDescription());
return result;
}
return proxyService.getRoleInfo(Integer.valueOf(request.getSession().getAttribute("agencyID").toString()), Integer.valueOf(roleId));
}

但是如果参数过多呢,列出所有传参,代码显示较多,而且耦合较高,无法复用。
统一提出为Model,使用validation校验,采用bean注入方式调用,使代码看起来不那么臃肿,层次分明,Model大多时候可以复用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
public class RoleInfoReq {
@Pattern(regexp = "^[1-9]\\d{6,9}$", message = "用户id不合法")
@NotNull
private String id;
public Integer getId() {
return Integer.valueOf(id);
}
public void setId(String id) {
this.id = id;
}
}

1
2
3
4
//调用
public HashMap<String, Object> roleInfo(@Valid RoleInfoReq reqModel, HttpServletRequest request) {
return proxyService.getRoleInfo(Integer.valueOf(request.getSession().getAttribute("agencyID").toString()), reqModel.getId());
}

有木有觉得很清爽~, 稍复杂的例子如下
提出前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public HashMap<String, Object> chargeList(HttpServletRequest request, HttpSession session) {
HashMap<String, Object> result = new HashMap<String, Object>();
String idStr;
String begintime;
String endtime;
String currentpage;
String pagesize;
String totalnum;
String flag;
try {
idStr = request.getParameter("id");
begintime = request.getParameter("begintime");
endtime = request.getParameter("endtime");
currentpage = request.getParameter("currentpage");
pagesize = request.getParameter("pagesize");
totalnum = request.getParameter("totalnum");
flag = request.getParameter("flag");
} catch (Throwable t) {
logger.error("ChargeController player chargeList args error:" + t);
result.put("result", StatusCode.ILLEGAL_PARAM.getValue());
result.put("description", StatusCode.ILLEGAL_PARAM.getDescription());
return result;
}
if (!LegalUtil.isLegalIdIncludeZero(idStr) || !LegalUtil.isLegalDate(begintime) || !LegalUtil.isLegalDate(endtime)
|| !LegalUtil.isDigital(currentpage) || !LegalUtil.isDigital(pagesize) || !LegalUtil.isDigital(totalnum) || !LegalUtil.isDigital(flag)) {
result.put("result", StatusCode.ILLEGAL_PARAM.getValue());
result.put("description", StatusCode.ILLEGAL_PARAM.getDescription());
return result;
}
//吧啦吧啦。。。 咕噜咕噜。。。
}

提出后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
public class ChargeRecordsCardGivePlayerReq {
private final String dateformat = "^(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]|[0-9][1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)$";
private final String isDigit = "^[0-9]*$";
/**
* 7-10位正整数或0
*/
@Pattern(regexp = "^([1-9]\\d{6,9})|(0)$", message = "用户id不合法")
@NotNull
private String id;
/**
* 时间格式必须为YYYY-MM-DD
*/
@Pattern(regexp = dateformat, message = "时间格式不合法")
@NotNull
private String begintime;
@Pattern(regexp = dateformat, message = "时间格式不合法")
@NotNull
private String endtime;
@Pattern(regexp = isDigit, message = "当前页,格式不合法")
@Max(Integer.MAX_VALUE)
@NotNull
private String currentpage;
@Pattern(regexp = isDigit, message = "每页记录数,格式不合法")
@Max(Integer.MAX_VALUE)
@NotNull
private String pagesize;
@Pattern(regexp = isDigit, message = "总记录数,格式不合法")
@Max(Integer.MAX_VALUE)
@NotNull
private String totalnum;
@Pattern(regexp = "^1|2$", message = "房卡类型不合法")
@NotNull
private String flag;
public String getBegintime() {
return begintime;
}
public void setBegintime(String begintime) {
this.begintime = begintime;
}
public String getEndtime() {
return endtime;
}
public void setEndtime(String endtime) {
this.endtime = endtime;
}
public Integer getCurrentpage() {
return Integer.valueOf(currentpage);
}
public void setCurrentpage(String currentpage) {
this.currentpage = currentpage;
}
public Integer getPagesize() {
return Integer.valueOf(pagesize);
}
public void setPagesize(String pagesize) {
this.pagesize = pagesize;
}
public Integer getTotalnum() {
return Integer.valueOf(totalnum);
}
public void setTotalnum(String totalnum) {
this.totalnum = totalnum;
}
public Integer getFlag() {
return Integer.valueOf(flag);
}
public void setFlag(String flag) {
this.flag = flag;
}
public Integer getId() {
return Integer.valueOf(id);
}
public void setId(String id) {
this.id = id;
}
}

1
2
3
4
5
6
7
8
//调用
public HashMap<String, Object> chargeList(@Valid ChargeRecordsCardGivePlayerReq reqModel, HttpSession session) {
if (reqModel.getFlag() == 1) {
return chargeService.getGivePlayerRecords(Integer.valueOf(session.getAttribute("agencyID").toString()), reqModel.getId(), reqModel.getBegintime(), reqModel.getEndtime(), reqModel.getCurrentpage(), reqModel.getPagesize(), reqModel.getTotalnum());
} else {
return chargeService.getGiveManagerRecord(Integer.valueOf(session.getAttribute("agencyID").toString()), reqModel.getId(), reqModel.getBegintime(), reqModel.getEndtime(), reqModel.getCurrentpage(), reqModel.getPagesize(), reqModel.getTotalnum());
}
}

若参数校验不合法,返回状态码400,出错时间,错误原因等详细信息,对接良好。
注意,若为post请求,可加上注解@ModelAttribute

1
public HashMap<String, Object> chargeList(@Valid @ModelAttribute ChargeRecordsCardGivePlayerReq reqModel, HttpSession session) {}

若为get请求,仅注解@Valid即可

1
public HashMap<String, Object> chargeList(@Valid ChargeRecordsCardGivePlayerReq reqModel, HttpSession session) {}

模拟单纯发http请求,不走浏览器,使用curl或者postman,curl可以看到请求的详细过程,postman图形界面且有历史记录,开发中推荐使用curl,两者皆可。
如以上的chargeList函数的参数校验,模拟请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#正确输入
curl -v http://127.0.0.1:8091/guiyang/agentboot/charge/records/card/give/player?id=1234567"&"begintime=2017-01-01"&"endtime=2018-01-01"&"currentpage=1"&"pagesize=1"&"totalnum=1"&"flag=1
#缺少参数
curl -v http://127.0.0.1:8091/guiyang/agentboot/charge/records/card/give/player?id=1234567"&"begintime=2017-01-01"&"endtime=2018-01-01"&"pagesize=1"&"totalnum=1"&"flag=1
#用户id不合法
curl -v http://127.0.0.1:8091/guiyang/agentboot/charge/records/card/give/player?id=124534567"&"begintime=2017-01-01"&"endtime=2018-01-01"&"currentpage=1"&"pagesize=1"&"totalnum=1"&"flag=1
#时间格式不合法
curl -v http://127.0.0.1:8091/guiyang/agentboot/charge/records/card/give/player?id=1234567"&"begintime=2017-01-01"&"endtime=2018-0101"&"currentpage=1"&"pagesize=1"&"totalnum=1"&"flag=1
#总数量格式不合法
curl -v http://127.0.0.1:8091/guiyang/agentboot/charge/records/card/give/player?id=1234567"&"begintime=2017-01-01"&"endtime=2018-01-01"&"currentpage=-1"&"pagesize=1"&"totalnum=1"&"flag=1
#卡类型不合法
curl -v http://127.0.0.1:8091/guiyang/agentboot/charge/records/card/give/player?id=1234567"&"begintime=2017-01-01"&"endtime=2018-01-01"&"currentpage=1"&"pagesize=1"&"totalnum=1"&"flag=0

请求格式统一提出Model,同样地,返回格式也可封装为类,替代Map,这块不多介绍,感兴趣的尝试一下。

扩展:validation注解详解

  • @Null :元素必须为 null
  • @NotNull :元素必须不为 null
  • @AssertTrue :元素必须为 true
  • @AssertFalse :元素必须为 false
  • @Min(value) :元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value) :元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value) :元素必须是一个数字,其值必须大于等于指定的最小值,接受超出int长度的值,对应BigDecimal类型
  • @DecimalMax(value) :元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max, min) :限制字符长度必须在min到max之间
  • @Digits (integer, fraction) : 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
  • @Past : 验证注解的元素值(日期类型)比当前时间早
  • @Future : 限制必须是一个将来的日期
  • @Pattern(value) :必须符合指定的正则表达式
  • @Email :必须是电子邮箱地址,也可以通过正则表达式和flag指定自定义的email格式
  • @Length(min=, max=) :字符串的大小必须在指定的范围内
  • @NotEmpty :字符串必须非空
  • @Range(min=, max=) :元素必须在合适的范围内
  • @NotBlank :验证字符串非null,且长度必须大于0

注:@NotNull, @NotEmpty, @NotBlank 这3个注解的区别如下:

  • @NotNull :任何对象的value不能为null
  • @NotEmpty :集合对象的元素不为0,即集合不为空;也可用于字符串不为null且length大于0
  • @NotBlank :只能用于字符串不为null,并且字符串trim()以后length要大于0

约束力度为:@NotBlank > @NotEmpty > @NotNull