Skip to content

介绍

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

快速开始

引入

xml

<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注解

java

@EnableZebra
@SpringBootApplication
public class ExampleApplication {
		public static void main(String[] args) {
				SpringApplication.run(ExampleApplication.class, args);
		}
}

常用配置

  1. 跨域配置

默认是启用跨域配置,如果想禁用采用以下方式配置

yml
zebra:
	web:
		cors:
			enabled: false
  1. swagger 配置

默认是启用 swagger 配置,如果想禁用采用以下方式配置

yml
zebra:
	web:
		swagger:
			enabled: false
  1. xss 配置

默认是启用 xss 配置,如果想禁用设置 enabled 为 false, excludes 是排除指定的uri,多个用,分割,示例: /usr/**,/usr

yml
zebra:
	web:
		xss:
			enabled: false
			excludes:
  1. 全局响应拦截白名单配置

框架对返回体做了全局的拦截包装处理,如果不想进行拦截,需要配置白名单。 以下为白名单配置,多个uri前缀用,分割

yml
zebra:
	web:
		global-advice:
			excludes: /app/**,/test1/**,/test2/**

状态码

0到-20为框架内使用

状态码描述
0成功
-1未知错误
-2参数校验失败
-3API接口已弃用
-4请求未找到
-5重复请求,请稍后重试
-6无效签名
-7数据库操作失败
-8解密失败
-9访问过于频繁,请稍后再试
-10未登录
-11登录超时

此代码在zebra-common-util里,放在这里介绍是为了能够融入贯通。

异常处理

提供全局异常处理对于使用者来说无感知,包括:

  • 请求未找到异常 NoHandlerFoundException
  • 参数验证异常 BindException ValidationException MethodArgumentNotValidException BindException
  • 自定义异常 ResultException和基于注解@Failed
  • 未知异常 Throwable

参数验证异常将异常提示转换成友好的RESTful响应 错误响应示例如下:

json
{
	"code": -3,
	"msg": "参数校验失败",
	"data": [
		{
			"errorKey": "userName",
			"errorMsg": "userName is null !"
		},
		{
			"errorKey": "userId",
			"errorMsg": "userId is null !"
		},
		{
			"errorKey": "age",
			"errorMsg": "age is null !"
		}
	]
}

自定义异常使用

  1. 使用R类的静态方法 R.failed() -1
  2. 使用R类的静态方法 R.failed(String msg) -1 可以定制msg
  3. 使用R类的静态方法 R.failed(FailCode failCode)

使用FailCode接口定制异常枚举

java
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注解

java
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对象返回值

响应示例:

json
{
	"code": 0,
	"msg": "ok",
	"data": ""
}

使用者不需要自行封装和处理响应体,框架实现了对有响应结果和无响应结果的处理,使用者只需要返回结果数据即可。
示例:

java

@RequestMapping("/example")
public class ExampleController {

		@GetMapping("/test6")
		public void test6(@RequestParam("id") @NotNull Long id) {
		}
}

返回结果为

json
{
	"code": 0,
	"msg": "成功",
	"data": true
}

如果不想框架对响应进行拦截和封装可以使用 @ExcludeResponse 注解来使用spring默认的方式。

枚举参数化及响应

请求
方法参数支持枚举类型,需要实现IEnum接口或者有getValue()方法。
响应

  1. 返回枚举所有字段 需要在枚举类上增加 @JsonFormat(shape = JsonFormat.Shape.OBJECT)注解
  2. 返回枚举单一字段 需要字段上增加 @JsonValue
java

@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 注解可优雅的实现接口版本控制,注解定义如下:

java

@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:

java

@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:

java

@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:

java

@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

yaml
zebra:
  web:
    sign:
	  secret-key: 123456

以下分别是get请求和post提交json内容请求

java

@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框架,则不需要引入以下仓库

xml

<dependency>
		<groupId>io.github.zhanghongbin</groupId>
		<artifactId>zebra-common-util</artifactId>
</dependency>

SignUtil.addSignParamsAndJoin 方法用来生成签名参数,第一个参数为密钥,第二个参数为请求参数,所有签名参数都需要放到url地址后边

java
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

java

@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 注解到类字段上,注解参数可以为以下枚举参数

java

@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表达式,一般情况下是调用其它的服务来控制脱敏规则,示例:

java
//调用服务名称为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 加解密配置

第一步:
引入

xml
<dependency>
		<groupId>io.github.zhanghongbin</groupId>
		<artifactId>zebra-common-util</artifactId>
</dependency>

并使用 org.zebra.common.crypto 包下的 AesCryptoProvider,RsaCryptoProvider,Sm2CryptoProvider类 生成密钥,或公钥私钥,以aes加解密为例:

java
AesCryptoProvider.generateSecretKey();

RsaCryptoProvider 和 Sm2CryptoProvider分别提供静态方法生成无证书的公钥和私钥
第二步: 把生成的密钥配置到服务器端的yaml文件中

yml
zebra:
  web:
    crypto:
	  algorithm: aes
	  secret-key: 212341231
	  public-key:
	  private-key:
字段描述
algorithm配置算法:aes,rsa,sm2
secret-keyaes 算法需要配置密钥
public-keyrsa和sm2需要配置公钥,公钥用来加密
private-keyrsa和sm2需要配置私钥,私钥用来解密

第三步:
使用 @ApiCrypto 注解在接口上进行标识,@ApiCrypto 有两个属性分别为 request,response 代表是否对请求进行解密,是否对响应进行加密 默认都为true,示例:

java

@RestController
@RequestMapping("/test")
public class UserTest2Controller {

		@PostMapping(value = "/c")
		@ApiCrypto
		public Long b(@RequestBody Map<String, Object> a) {
				return 1l;
		}
}

第四步:
调用者可以引入以下仓库

xml
<dependency>
		<groupId>org.zebra</groupId>
		<artifactId>zebra-common-util</artifactId>
		<version></version>
</dependency>

使用org.zebra.common.crypto包下的 AesCryptoProvider,RsaCryptoProvider,Sm2CryptoProvider 进行加解密操作,示例:

java

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请求进行访问接口,返回加密结果如下:

json
{
	"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的客户端连接中,如果长时间不断开连接,可能会出现以下情况:

  1. 连接超时。如果服务器端没有发送新的事件数据,而客户端也没有重新建立连接,可能会超过服务器的连接超时时间。这可能导致服务器关闭连接,客户端需要重新建立连接才能接收新的事件数据。
  2. 网络异常。长时间不断开连接可能会导致网络异常,例如连接中断、丢包等问题。在这种情况下,客户端可能需要重新建立连接,以恢复和服务器的通信。

假设服务端设置为30s超时连接,在客户端连接服务端后开始计时,如果在这30s内,服务端有向该客户端发送数据,那么在30s时间到了之后,服务端会先断开客户端的连接然后重新连接客户端,并开始新一轮计时;如果在这30s内,服务端没有向客户端发送数据,那么30s后服务端会断开客户端的连接,不再重连。

首先需要开启 sse 服务,默认 enabled 为 false

yml
zebra:
  web:
    sse:
	  enabled: true

前端使用 可以连接sse服务,sse服务接口为以下两个:

接口地址描述
Get 请求 /sse/subscribe/用于前端和sse服务连接,id为客户端标识
Get 请求 /sse/close/用于前端和sse服务关闭连接,id为客户端标识

后端使用 首先在前端连接到sse服务的时候,会发生连接事件,后端可以使用此事件做一些业务处理,示例如下: SseEmitterService 对象在应用启动的时候就注入到spring容器中,可以拿来使用,此事件是通过spring发送的异步事件, 事件对象 SseConnectionEvent 可以获取客户端id

java
@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连接对象

框架为后端提供了前端测试页面,只是为了测试,不代表真实的使用场景。 使用示例: 因为需要跳转到页面,需要增加以下依赖

xml
<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

增加@Controller注解并使用 @ExcludeResponse 注解 index 方法使框架不进行拦截,转向sse.html页面, 模拟发送消息的需要指定@ResponseBody这样给模拟调用者输出json格式。

java

@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) 而前端需要设置

js
eventSource.addEventListener("topic", function (event) {
console.log(event.data);
.....
});

websocket 服务

默认情况下是禁用 websocket ,如果启用配置如下:

yml
zebra:
  web:
    websocket:
	  enabled: true
  1. 接收 websocket 消息

需要实现 WebSocketMessageListener 接口,此接口为泛型接口,onMessage 方法为接收的消息事件,getType 方法用来注册消息类型,前端发送需要指定消息类型和消息内容,示例:

json
{
	"type": "l1",
	"content": {
		"id": 1,
		"name": "测试"
	}
}

json 格式 为 type 和 content两个属性 content 值由发送方和接收方协调一致。

java

@Component
public class L1 implements WebSocketMessageListener<UserLog> {

		@Override
		public void onMessage(WebSocketSession session, UserLog message) {

		}

		@Override
		public String getType() {
				return "l1";
		}
}
  1. 发送 websocket 消息

TokenHandler 接口是获取登录信息并和 websocket session 进行关联,发送消息的时候会使用 type 或 id 来进行指定。 默认实现为

java
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)

java

@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被注解的元素的大小必须在指定的范围内
@Email被注解的元素必须是电子邮箱地址
@Pattern被注解的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

注解作用
@Length被注解的字符串的大小必须在指定的范围内
@Range被注解的元素必须在合适的范围内
@URL验证带注解的字符串是否为URL
@Currency货币金额必须在正确的货币单位
@CreditCardNumber带注解的元素必须表示有效的信用卡号
@CodePointLength验证包含字符序列的代码点长度在min和max之间
@ConstraintComposition布尔运算符,应用于组合约束注解的所有约束
@SafeHtml验证用户提供的富文本值,以确保它不包含恶意代码
@UniqueElements验证所提供集合中的每个对象都是惟一的,即不能在集合中找到两个相等的元素
@EAN检查带注解的字符序列是否是有效的EAN 13号。验证数字的长度和校验数字
@ISBN检查带注解的字符序列是否是有效的ISBN。数字的长度和校验数字都经过验证
@LuhnCheckLuhn算法检查约束
@Mod10CheckModulo 10 检查约束
@Mod11CheckModulo 11 检查约束
@ParameterScriptAssert方法级约束,它根据带注解的方法或构造函数计算脚本表达式
@ScriptAssert类级约束,它根据带注解的元素计算脚本表达式

zebra Validator 附加的 constraint

注解作用
@Cellphone手机号校验
@Birthday生日校验
@Chinese中文校验
@CreditCode是否是有效的统一社会信用代码
@ZipCode验证是否为邮政编码(中国)
@IdCard身份证校验
@English英语校验
  • @Valid注解由java提供,验证时不支持指定校验分组。 用在方法、构造函数、方法参数和成员属性(field)上
  • @Validated注解由spring扩展提供,验证时支持指定校验分组,推荐使用。 用在类型、方法和方法参数上。但不能用于成员属性(field)

参数如果是非对象格式,需要在controller类上面添加@Validated注解。

其它

  • 接口返回非json问题

有些情况下 Controller 方法中返回String类型,调用者本来应该获取到的格式为

json
{
	"code": 0,
	"msg": "",
	"data": ""
}

但确是data里的内容,这是因为spring里转换器的问题造成的,可以有两种方式解决此问题 第一种如下: 强制设置输出 produces = "application/json" json格式

java

@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

  • 日期参数支持
java

@GetMapping("/getDate")
public Date get(@RequestParam(value = "date") Date date) {
		return new Date();
}
  • MapStructPlusUtil

在org.zebra.web.util包下提供 MapStructPlus 实用类