介绍
Web 模块,也是快速创建一个 Spring Boot Web 应用的可靠基石,主要特性包括
- api接口版本:通过注解方式配置接口的版本
- 跨域配置
- 全局异常、错误处理器配置
- 统一的long转字符串操作
- 接口签名,接口请求参数解密和响应结果加密
- 数据脱敏
- 众多的 Validator,如生日,英文,身份证等
- xss 攻击处理
- sse 和 websocket服务
依赖库
名称 | 描述 |
---|---|
hutool | |
knife4j-openapi2-spring-boot-starter | |
spring-boot-starter-web | |
spring-boot-starter-validation | |
spring-boot-starter-aop | |
zebra-common-util | |
guava | |
caffeine | |
spring-boot-starter-websocket | |
spring-tx | |
mapstruct-plus-spring-boot-starter |
快速开始
引入
<parent>
<groupId>io.github.zhanghongbin</groupId>
<artifactId>zebra-spring-boot-parent</artifactId>
<version>${version}</version>
</parent>
<dependency>
<groupId>io.github.zhanghongbin</groupId>
<artifactId>zebra-spring-boot-starter-web</artifactId>
</dependency>
在 @SpringBootApplication注解上边增加@EnableZebra注解
@EnableZebra
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
常用配置
- 跨域配置
默认是启用跨域配置,如果想禁用采用以下方式配置
zebra:
web:
cors:
enabled: false
- swagger 配置
默认是启用 swagger 配置,如果想禁用采用以下方式配置
zebra:
web:
swagger:
enabled: false
- xss 配置
默认是启用 xss 配置,如果想禁用设置 enabled 为 false, excludes 是排除指定的uri,多个用,分割,示例: /usr/**,/usr
zebra:
web:
xss:
enabled: false
excludes:
- 全局响应拦截白名单配置
框架对返回体做了全局的拦截包装处理,如果不想进行拦截,需要配置白名单。 以下为白名单配置,多个uri前缀用,分割
zebra:
web:
global-advice:
excludes: /app/**,/test1/**,/test2/**
状态码
0到-20为框架内使用
状态码 | 描述 |
---|---|
0 | 成功 |
-1 | 未知错误 |
-2 | 参数校验失败 |
-3 | API接口已弃用 |
-4 | 请求未找到 |
-5 | 重复请求,请稍后重试 |
-6 | 无效签名 |
-7 | 数据库操作失败 |
-8 | 解密失败 |
-9 | 访问过于频繁,请稍后再试 |
-10 | 未登录 |
-11 | 登录超时 |
此代码在zebra-common-util里,放在这里介绍是为了能够融入贯通。
异常处理
提供全局异常处理对于使用者来说无感知,包括:
- 请求未找到异常 NoHandlerFoundException
- 参数验证异常 BindException ValidationException MethodArgumentNotValidException BindException
- 自定义异常 ResultException和基于注解@Failed
- 未知异常 Throwable
参数验证异常将异常提示转换成友好的RESTful响应 错误响应示例如下:
{
"code": -3,
"msg": "参数校验失败",
"data": [
{
"errorKey": "userName",
"errorMsg": "userName is null !"
},
{
"errorKey": "userId",
"errorMsg": "userId is null !"
},
{
"errorKey": "age",
"errorMsg": "age is null !"
}
]
}
自定义异常使用
- 使用R类的静态方法 R.failed() -1
- 使用R类的静态方法 R.failed(String msg) -1 可以定制msg
- 使用R类的静态方法 R.failed(FailCode failCode)
使用FailCode接口定制异常枚举
public enum MyFailCode implements FailCode {
LOGIN_FAIL(-10001, "业务码必须唯一"),
PASSWORD_FAIL(-10002, "无此业务码");
private int code;
private String msg;
MyFailCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public int getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
public static void main(String[] args) {
//使用
R.failed(MyFailCode.LOGIN_FAIL);
}
}
4 编写异常类并使用@Failed注解
public class ExampleExceptions {
@Failed(code = -1024, msg = "UnCheckedException")
public static class UnCheckedException extends RuntimeException {
}
@Failed(code = -2048, msg = "CheckedException")
public static class CheckedException extends Exception {
}
}
建议采用第3或4方式
响应处理
响应体参数介绍
名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
code | 整型 | 是 | 状态码 |
msg | 字符串 | 是 | 描述 |
data | 对象 | 是 | 返回值 |
响应示例:
{
"code": 0,
"msg": "ok",
"data": ""
}
使用者不需要自行封装和处理响应体,框架实现了对有响应结果和无响应结果的处理,使用者只需要返回结果数据即可。
示例:
@RequestMapping("/example")
public class ExampleController {
@GetMapping("/test6")
public void test6(@RequestParam("id") @NotNull Long id) {
}
}
返回结果为
{
"code": 0,
"msg": "成功",
"data": true
}
如果不想框架对响应进行拦截和封装可以使用 @ExcludeResponse 注解来使用spring默认的方式。
枚举参数化及响应
请求
方法参数支持枚举类型,需要实现IEnum接口或者有getValue()方法。
响应
- 返回枚举所有字段 需要在枚举类上增加 @JsonFormat(shape = JsonFormat.Shape.OBJECT)注解
- 返回枚举单一字段 需要字段上增加 @JsonValue
@Getter
public enum TestEnum implements IEnum<Integer> {
UNKNOWN(0, "未知"),
MALE(1, "男");
TestEnum(Integer value, String description) {
this.value = value;
this.description = description;
}
private Integer value;
@JsonValue
private String description;
@Override
public Integer getValue() {
return value;
}
}
@RestController
@RequestMapping("/example")
@Slf4j
@Api(tags = "设备数据接口")
@ApiVersion
public class ExampleController {
@DeleteMapping
public Long get1(@RequestParam("testNum") TestEnum testNum) {
return 232L;
}
}
访问地址为 /example/v1.0.0?testNum=1
api版本控制
通过注解 @ApiVersion 注解可优雅的实现接口版本控制,注解定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiVersion {
String value() default "1.0.0";
/**
* 是否废弃版本接口
*/
boolean deprecated() default false;
/**
* api类型分别有 pass 代表平台服务 lan 代表内网服务 open 开发给第三方服务
* @return
*/
ApiType type() default ApiType.EMPTY;
}
@ApiVersion 可以使用在类上,所有接口都会采用版本号,也可以只使用在方法上只有此接口采用版本号,当方法和类同时存在时, 方法上的版本号会覆盖类上的版本号,方法上的@ApiVersion 注解优先级高, deprecated 参数为true此接口不能为访问, type 为url顶级前缀标识,会在url地址最前边增加 /pass,/lan 或 /open。 默认情况下版本号为放到类@RequestMapping地址后,当@RequestMapping路径有{version}占位符号,会采用占位符号的路径。
使用示例1:
@RestController
@RequestMapping("/basic")
@ApiVersion
public class ExampleController {
@DeleteMapping("/user")
public String get(@RequestParam("ids") List<String> idList) {
return "333";
}
}
访问地址为 /basic/v1.0.0/user?ids=1,2
使用示例2:
@RestController
@RequestMapping("/basic")
@ApiVersion
public class ExampleController {
@GetMapping("/get")
public UserInfoView get(@RequestParam("id") @NotNull Long id) {
return UserInfoView.builder().id(id).name("name").build();
}
@DeleteMapping("/user")
@ApiVersion(value = "1.0.1")
public String get(@RequestParam("ids") List<String> idList) {
return "333";
}
}
访问地址为/basic/v1.0.0/get?id=1/basic/v1.0.1/user?ids=1,2 使用示例3:
@RestController
@RequestMapping("/basic/{version}/user")
@ApiVersion
public class ExampleController {
@DeleteMapping
@ApiVersion(value = "1.0.1")
public String get(@RequestParam("ids") List<String> idList) {
return "333";
}
}
版本号会采用占位符号的位置 访问地址为 /basic/v1.0.1/user?ids=1,2
接口签名
使用@CheckSign注解对接口进行安全访问控制。
application.yml配置,secret-key为签名密钥,需要告知调用者,如果不进行配置默认secret-key为zebra-apisign-1234567890
zebra:
web:
sign:
secret-key: 123456
以下分别是get请求和post提交json内容请求
@RequestMapping("/example")
public class ExampleController {
@GetMapping("/get")
@CheckSign
public String get(@RequestParam("id") @NotNull Long id) {
return String.valueOf(id);
}
@PostMapping("/post")
@CheckSign
public String get(@RequestBody User user, @RequestParam("id") @NotNull long id) {
System.out.println(user.getName());
System.out.println(id);
return "test";
}
}
调用者示例,如果使用zebra框架,则不需要引入以下仓库
<dependency>
<groupId>io.github.zhanghongbin</groupId>
<artifactId>zebra-common-util</artifactId>
</dependency>
SignUtil.addSignParamsAndJoin 方法用来生成签名参数,第一个参数为密钥,第二个参数为请求参数,所有签名参数都需要放到url地址后边
public class ExampleTest {
public static void testGetRequest() {
Map<String, Object> aa = new HashMap<>();
aa.put("id", 123);
String params = SignUtil.addSignParamsAndJoin("zebra-apisign-1234567890", aa);
String content = HttpUtil.get("http://localhost:9090/example/get?id=123&" + params);
System.out.println(content);
}
public static void testPostJson() throws Exception {
Map<String, Object> params = new HashMap<>();
params.put("name", "xxx");
params.put("age", 1);
params.put("id", 123);
String s = SignUtil.addSignParamsAndJoin("zebra-apisign-1234567890", params);
params.remove("id");
String body = HttpRequest.post("http://localhost:9090/example/post?id=123&" + s)
.body(JSONUtil.toJsonStr(params))
.execute()
.body();
System.out.println(body);
}
public static void main(String[] args) throws Exception {
testPostJson();
}
}
feign接口签名示例:
参数 signCheck 为签名值,可以调用SignUtil.addSignParams();方法返回签名数据,数据类型为Map
@FeignClient(
name = "ExampleServiceStub",
url = "${rpc.url}"
)
public interface ExampleServiceStub {
@GetMapping("/example/v1.0.1/get")
UserInfoView get(@RequestParam("id") Long id, @RequestParam Map signCheck);
}
数据脱敏
使用@Sensitive对自动进行注解,技术实现使用hutool进行支持,分别提供以下脱敏方式
- 身份证脱敏
- 手机号脱敏
- 地址脱敏
- 邮箱脱敏
- 银行卡
- 用户姓名
使用 @Sensitive 注解到类字段上,注解参数可以为以下枚举参数
@AllArgsConstructor
@Getter
public enum SensitiveStrategy {
/**
* 身份证脱敏
*/
ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)),
/**
* 手机号脱敏
*/
PHONE(DesensitizedUtil::mobilePhone),
/**
* 地址脱敏
*/
ADDRESS(s -> DesensitizedUtil.address(s, 8)),
/**
* 邮箱脱敏
*/
EMAIL(DesensitizedUtil::email),
/**
* 银行卡
*/
BANK_CARD(DesensitizedUtil::bankCard),
/**
* 用户姓名
*/
USER_NAME(SensitiveStrategy::userName);
private final Function<String, String> desensitizer;
private static String userName(String userName) {
//return StringUtils.rightPad(StringUtils.left(userName, 1), userName.length(),"*");
return StrUtil.hide(userName, 1, userName.length());
}
}
如果想动态控制是否进行脱敏,需要实现 Condition 接口,并设置到 @Sensitive 注解的 condition 参数上。
其次还支持 spel 表达式注解 @SpelSensitive value属性编写el表达式,一般情况下是调用其它的服务来控制脱敏规则,示例:
//调用服务名称为abc的 test 方法,#versionName 为变量,名称必须和versionName一致,才能获取到 versionName 的值
@SpelSensitive(value = "#{@abc.test(#versionName)}")
private String versionName;
@Service("abc")
public class AbcService {
public Integer test(String u) {
return 1;
}
}
接口加解密
如果想保证数据传输的安全,对接口出参加密,入参解密,目前提供 AES、RSA,SM2 加解密配置
第一步:
引入
<dependency>
<groupId>io.github.zhanghongbin</groupId>
<artifactId>zebra-common-util</artifactId>
</dependency>
并使用 org.zebra.common.crypto 包下的 AesCryptoProvider,RsaCryptoProvider,Sm2CryptoProvider类 生成密钥,或公钥私钥,以aes加解密为例:
AesCryptoProvider.generateSecretKey();
RsaCryptoProvider 和 Sm2CryptoProvider分别提供静态方法生成无证书的公钥和私钥
第二步: 把生成的密钥配置到服务器端的yaml文件中
zebra:
web:
crypto:
algorithm: aes
secret-key: 212341231
public-key:
private-key:
字段 | 描述 |
---|---|
algorithm | 配置算法:aes,rsa,sm2 |
secret-key | aes 算法需要配置密钥 |
public-key | rsa和sm2需要配置公钥,公钥用来加密 |
private-key | rsa和sm2需要配置私钥,私钥用来解密 |
第三步:
使用 @ApiCrypto 注解在接口上进行标识,@ApiCrypto 有两个属性分别为 request,response 代表是否对请求进行解密,是否对响应进行加密 默认都为true,示例:
@RestController
@RequestMapping("/test")
public class UserTest2Controller {
@PostMapping(value = "/c")
@ApiCrypto
public Long b(@RequestBody Map<String, Object> a) {
return 1l;
}
}
第四步:
调用者可以引入以下仓库
<dependency>
<groupId>org.zebra</groupId>
<artifactId>zebra-common-util</artifactId>
<version></version>
</dependency>
使用org.zebra.common.crypto包下的 AesCryptoProvider,RsaCryptoProvider,Sm2CryptoProvider 进行加解密操作,示例:
public static void main(String[] args) throws Exception {
Map<String, String> aa = new HashMap<>();
aa.put("name", "rfff");
String json = JSONUtil.toJsonStr(aa);
AesCryptoProvider aesSecurityProvider = new AesCryptoProvider("dfQIa54eVbIyP3A3QXBxoQ==");
String content = aesSecurityProvider.encrypt(json);
String result = HttpRequest.post("http://localhost:9090/zhn/c")
.header("Content-Type", "application/json")
.body(content, "application/json")
.execute()
.body();
System.out.println(aesSecurityProvider.decrypt(JSONUtil.parseObj(result).getStr("data")));
}
以上使用hutool 提供的http请求进行访问接口,返回加密结果如下:
{
"code": 0,
"msg": "成功",
"data": "erLgS7uxWu96/YiRUAWHNg=="
}
sse 服务
SSE(Server Sent Event) ,是一种可以主动从服务端推送消息的技术。SSE的本质其实就是一个HTTP的长连接,只不过它给客户端发送的不是一次性的数据包,而是一个stream流,格式为text/event-stream。所以客户端不会关闭连接,会一直等着服务器发过来的新的数据流。 SSE连接超时,当SSE客户端连接后,如果长时间不断开,它会保持连接状态。SSE(Server-Sent Events)是一种基于HTTP的推送技术,允许服务器实时向客户端发送数据。当客户端连接到服务器的SSE端点时,它会创建一个EventSource对象,该对象将与服务器进行长期连接,并接收服务器发送的事件。只有在客户端手动关闭连接或连接发生错误时,才会断开SSE连接 在使用SSE的客户端连接中,如果长时间不断开连接,可能会出现以下情况:
- 连接超时。如果服务器端没有发送新的事件数据,而客户端也没有重新建立连接,可能会超过服务器的连接超时时间。这可能导致服务器关闭连接,客户端需要重新建立连接才能接收新的事件数据。
- 网络异常。长时间不断开连接可能会导致网络异常,例如连接中断、丢包等问题。在这种情况下,客户端可能需要重新建立连接,以恢复和服务器的通信。
假设服务端设置为30s超时连接,在客户端连接服务端后开始计时,如果在这30s内,服务端有向该客户端发送数据,那么在30s时间到了之后,服务端会先断开客户端的连接然后重新连接客户端,并开始新一轮计时;如果在这30s内,服务端没有向客户端发送数据,那么30s后服务端会断开客户端的连接,不再重连。
首先需要开启 sse 服务,默认 enabled 为 false
zebra:
web:
sse:
enabled: true
前端使用 可以连接sse服务,sse服务接口为以下两个:
接口地址 | 描述 |
---|---|
Get 请求 /sse/subscribe/ | 用于前端和sse服务连接,id为客户端标识 |
Get 请求 /sse/close/ | 用于前端和sse服务关闭连接,id为客户端标识 |
后端使用 首先在前端连接到sse服务的时候,会发生连接事件,后端可以使用此事件做一些业务处理,示例如下: SseEmitterService 对象在应用启动的时候就注入到spring容器中,可以拿来使用,此事件是通过spring发送的异步事件, 事件对象 SseConnectionEvent 可以获取客户端id
@Component
public class SseConnectionListener {
@Autowired
private SseEmitterService sseEmitterService;
@Async
@EventListener
public void connectEvent(SseConnectionEvent sseConnectionEvent) {
SseMessage<String> aa = new SseMessage<>("xxxxx");
sseEmitterService.send(sseConnectionEvent.getId(), aa);
}
}
SseEmitterService 对象为后端使用者提供了以下方法用来把消息发送到前端
方法名称 | 描述 |
---|---|
public boolean send(String id, SseMessage<?> message) | 发送消息到指定客户端 |
public void send(SseMessage<?> message) | 给所有连接的客户端发送消息 |
public void send(Set<String> ids, SseMessage<?> message) | 给多个客户端发送消息 |
public Set<String> getAllSseId() | 获取所有客户端id,不能保证连接有效 |
public SseEmitterUTF8 getSseEmitter(String id) | 根据客户端id获取sse连接对象 |
框架为后端提供了前端测试页面,只是为了测试,不代表真实的使用场景。 使用示例: 因为需要跳转到页面,需要增加以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
增加@Controller注解并使用 @ExcludeResponse 注解 index 方法使框架不进行拦截,转向sse.html页面, 模拟发送消息的需要指定@ResponseBody这样给模拟调用者输出json格式。
@Controller
@RequestMapping("/mock")
public class UserTest1Controller {
@Autowired
private SseEmitterService sseEmitterService;
@GetMapping("/index")
@ExcludeResponse
public String index() {
return "sse";
}
@GetMapping("/send")
@ResponseBody
public void send() {
SseMessage<String> aa = new SseMessage<>("ffff");
sseEmitterService.send("1", aa);
}
}
访问地址为: http://localhost:端口号/mock/index 效果如下:
注意: 一般情况下后端不会把所有数据一起发送给前端,而是会把页面分成多个模块,然后发给前端,此时前端需要区分哪一块数据对应哪一块页面。所以我们可以给各个模块的数据起个名字。也就是以下的代码 后端在构造SseMessage 的时候可以指定topic , new SseMessage(String topic, T data) 而前端需要设置
eventSource.addEventListener("topic", function (event) {
console.log(event.data);
.....
});
websocket 服务
默认情况下是禁用 websocket ,如果启用配置如下:
zebra:
web:
websocket:
enabled: true
- 接收 websocket 消息
需要实现 WebSocketMessageListener 接口,此接口为泛型接口,onMessage 方法为接收的消息事件,getType 方法用来注册消息类型,前端发送需要指定消息类型和消息内容,示例:
{
"type": "l1",
"content": {
"id": 1,
"name": "测试"
}
}
json 格式 为 type 和 content两个属性 content 值由发送方和接收方协调一致。
@Component
public class L1 implements WebSocketMessageListener<UserLog> {
@Override
public void onMessage(WebSocketSession session, UserLog message) {
}
@Override
public String getType() {
return "l1";
}
}
- 发送 websocket 消息
TokenHandler 接口是获取登录信息并和 websocket session 进行关联,发送消息的时候会使用 type 或 id 来进行指定。 默认实现为
public final class DefaultTokenHandler implements TokenHandler {
@Override
public LoginInfo getLoginInfo(String token) {
LoginInfo loginInfo = new LoginInfo();
loginInfo.setId(token);
loginInfo.setType(token);
return loginInfo;
}
}
通常情况下使用者需要实现 TokenHandler 接口,并注入到 spring 容器中。 WebSocketEmitterService 对象提供了发送消息的方法,可以从 spring 环境中获取此对象,发送方法为: send(String type, String id, String messageType, String messageContent) send(String type, String messageType, String messageContent)
@Component
public class L1 implements WebSocketMessageListener<UserLog> {
@Resource
private WebSocketEmitterService webSocketEmitterService;
@Override
public void onMessage(WebSocketSession session, UserLog message) {
webSocketEmitterService.send("abc", "l1", "rrrrrrr");
System.out.println(message);
}
@Override
public String getType() {
return "l1";
}
}
Validated校验
Validated 被注解的元素是一个POJO对象,用于检查此对象的所有被注解字段的值是否符合预期 Bean Validation 中内置的 constraint
注解 | 作用 |
---|---|
@Null | 被注解的元素必须为 null |
@NotBlank | 被注解的元素必须不为空,并且必须包含至少一个非空白字符 |
@NotEmpty | 被注解的元素必须非空 |
@AssertTrue | 被注解的元素必须为 true |
@AssertFalse | 被注解的元素必须为 false |
@Max | 被注解的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Min | 被注解的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax | 被注解的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin | 被注解的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Digits | 被注解的元素必须是一个数字,其值必须在可接受的范围内 |
@Positive | 被注解的元素必须是严格意义上的正数 |
@PositiveOrZero | 被注解的元素必须是正数或0 |
@Negative | 被注解的元素必须是一个严格意义上的负数 |
@NegativeOrZero | 被注解的元素必须是负数或0 |
@Past | 被注解的元素必须是过去的某个瞬间、日期或时间 |
@PastOrPresent | 被注解的元素必须是过去或现在的某个瞬间、日期或时间 |
@Future | 被注解的元素必须是将来的某个瞬间、日期或时间 |
@FutureOrPresent | 被注解的元素必须是当前或将来的某个瞬间、日期或时间 |
@Size | 被注解的元素的大小必须在指定的范围内 |
被注解的元素必须是电子邮箱地址 | |
@Pattern | 被注解的元素必须符合指定的正则表达式 |
Hibernate Validator 附加的 constraint
注解 | 作用 |
---|---|
@Length | 被注解的字符串的大小必须在指定的范围内 |
@Range | 被注解的元素必须在合适的范围内 |
@URL | 验证带注解的字符串是否为URL |
@Currency | 货币金额必须在正确的货币单位 |
@CreditCardNumber | 带注解的元素必须表示有效的信用卡号 |
@CodePointLength | 验证包含字符序列的代码点长度在min和max之间 |
@ConstraintComposition | 布尔运算符,应用于组合约束注解的所有约束 |
@SafeHtml | 验证用户提供的富文本值,以确保它不包含恶意代码 |
@UniqueElements | 验证所提供集合中的每个对象都是惟一的,即不能在集合中找到两个相等的元素 |
@EAN | 检查带注解的字符序列是否是有效的EAN 13号。验证数字的长度和校验数字 |
@ISBN | 检查带注解的字符序列是否是有效的ISBN。数字的长度和校验数字都经过验证 |
@LuhnCheck | Luhn算法检查约束 |
@Mod10Check | Modulo 10 检查约束 |
@Mod11Check | Modulo 11 检查约束 |
@ParameterScriptAssert | 方法级约束,它根据带注解的方法或构造函数计算脚本表达式 |
@ScriptAssert | 类级约束,它根据带注解的元素计算脚本表达式 |
zebra Validator 附加的 constraint
注解 | 作用 |
---|---|
@Cellphone | 手机号校验 |
@Birthday | 生日校验 |
@Chinese | 中文校验 |
@CreditCode | 是否是有效的统一社会信用代码 |
@ZipCode | 验证是否为邮政编码(中国) |
@IdCard | 身份证校验 |
@English | 英语校验 |
- @Valid注解由java提供,验证时不支持指定校验分组。 用在方法、构造函数、方法参数和成员属性(field)上
- @Validated注解由spring扩展提供,验证时支持指定校验分组,推荐使用。 用在类型、方法和方法参数上。但不能用于成员属性(field)
参数如果是非对象格式,需要在controller类上面添加@Validated注解。
其它
- 接口返回非json问题
有些情况下 Controller 方法中返回String类型,调用者本来应该获取到的格式为
{
"code": 0,
"msg": "",
"data": ""
}
但确是data里的内容,这是因为spring里转换器的问题造成的,可以有两种方式解决此问题 第一种如下: 强制设置输出 produces = "application/json" json格式
@RestController
@RequestMapping("/zhn")
public class UserTest2Controller {
@PostMapping(value = "/c", produces = "application/json")
public Long b(@RequestBody Map<String, Object> a) {
return 1l;
}
}
第二种: 在客户端调用时候请求报头增加 Accept 为 application/json
- 日期参数支持
@GetMapping("/getDate")
public Date get(@RequestParam(value = "date") Date date) {
return new Date();
}
- MapStructPlusUtil
在org.zebra.web.util包下提供 MapStructPlus 实用类