张晗的个人博客

技术的价值是业务

⭐⭐⭐⭐

如果你打算技术创业,那应该全书阅读;如果你没有这样的打算,推荐针对性阅读。作者由于身处外国,很难针对国内读者提供实质帮助,更多是思路上的。

产品部分,打开了技术开发者的视野;技术部分,特别是代码整洁部分,主要是思路,更细致的落地还需要看别的参考书。例如,《代码大全》、《重构》等;团队部分,求职、招聘对招聘者和应聘者都有值得思考的地方。

总体来说,还是推荐阅读的。

阅读全文 »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
├─api							# 前后端交互
│ ├─advice # 全局异常处理
│ ├─controller # 控制器
│ ├─handler # 自定义返回值处理
│ └─rest # 请求与响应
│ ├─group # 分组验证
│ ├─req # 请求
│ └─vo # 响应
│ └─convert # vo 转换器
├─config # 配置
├─enums # 枚举
├─exception # 异常
├─model # 数据模型
│ └─handler # 与 db 有关的处理器
├─repository # 存储接口
│ ├─impl # 存储实现
│ └─mapper # 存储 mapper
├─service # 实现接口
│ ├─helper # 实现 helper
│ └─impl # 具体实现
└─util # 工具类

请求方式和参数注解可以自由组合。

@RequestParam

获取 query 参数。

1
2
3
4
5
// name 参数需要和 GET 请求中 query 参数的 key 匹配
@GetMapping("/get")
public String queryParam(@RequestParam("name") String name) {
return name;
}

@PathVariable

获取 path 参数。

1
2
3
4
5
// name 参数需要和 GET 请求中 path 匹配
@GetMapping("/path/{name}")
public String pathParam(@PathVariable("name") String name) {
return name;
}

@RequestBody

获取 body 参数。

1
2
3
4
5
// 请求体携带的参数和 req 属性匹配
@PostMapping("/body")
public UserReq body(@RequestBody UserReq req) {
return req;
}

最佳实践

  1. 不要使用 @RequestBody 来接收 String 类型的参数;
1
2
3
4
@PostMapping("/wrong")
public String wrong(@RequestBody String name) {
return name;
}
  1. 不要使用 Get 方式来接收 @RequestBody 请求;

代码

here

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

@RequestParam

Controller 类上添加 @Validate 注解。

1
2
3
4
@GetMapping("/param_notnull")
public String notnull(@RequestParam("username") @NotEmpty String username) {
return username;
}

@RequestBody

Java Bean 中添加校验,配合方法中的 @Validated 注解。

1
2
3
4
@PostMapping("/pojo_notnull")
public UserReq notnull(@RequestBody @Validated UserReq req) {
return req;
}
1
2
3
4
5
6
7
8
9
10
@Data
public class UserReq {
/**
* 用户名<br>
* username != null
*/
@NotNull
@NotEmpty
private String username;
}

分组校验

group

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Usage: {@link ValidationController#email(UserReq)} <br/>
* Date: 2023/5/24 16:14 <br/>
*
* @author <a href="mailto:hanzhang2566@foxmail.com">hanzhang</a>
*/
public interface EmailGroup {
}

/**
* Usage: {@link ValidationController#login(UserReq)} <br/>
* Date: 2023/5/24 16:18 <br/>
*
* @author <a href="mailto:hanzhang2566@foxmail.com">hanzhang</a>
*/
public interface LoginGroup {
}

controller

1
2
3
4
5
6
7
8
9
@PostMapping("/pojo_email")
public UserReq email(@RequestBody @Validated(value = {EmailGroup.class}) UserReq req) {
return req;
}

@PostMapping("/pojo_login")
public UserReq login(@RequestBody @Validated(value = {LoginGroup.class}) UserReq req) {
return req;
}

POJO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Data
public class UserReq {
/**
* 用户名
*/
@NotNull(groups = {LoginGroup.class})
@NotEmpty(groups = {LoginGroup.class})
private String username;

/**
* 密码
*/
@NotNull(groups = {LoginGroup.class})
@NotEmpty(groups = {LoginGroup.class})
private String password;

/**
* 邮箱
*/
@Email(groups = {EmailGroup.class})
@NotEmpty(groups = {EmailGroup.class})
private String email;
}

代码

here

模型设计

使用统一的 REST 方式定义响应模型。

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
// code 后端状态码
// message 简短消息
// data 后端数据
public class R extends HashMap<String, Object> {
private R(ErrorCode errorCode) {
put("code", errorCode.getCode());
put("message", errorCode.getMessage());
}

public static R ok(Object data) {
R r = new R(OK);
r.put("data", data);
return r;
}

public static R error(ErrorCode errorCode) {
return new R(errorCode);
}

private R(String message) {
put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
put("message", message);
}

public static R error(String message) {
return new R(message);
}
}

包装响应

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
// 隐式将返回值包装成 R 对象
public class MyHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private final HandlerMethodReturnValueHandler handler;

public MyHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler handler) {
this.handler = handler;
}

/**
* 返回值不是 {@link R},并且使用 Rest 方式返回 true
*
* @param returnType returnType
* @return boolean ? 处理 : 不处理
*/
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return handler.supportsReturnType(returnType) && !returnType.getParameterType().equals(R.class);
}

@Override
public void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
R ok = R.ok(returnValue);
handler.handleReturnValue(ok, returnType, mavContainer, webRequest);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
public class WebMvcConfiguration {
// 调整 RequestMappingHandlerAdapter#getReturnValueHandlers
@Resource
public void resetRequestMappingHandlerAdapter(RequestMappingHandlerAdapter adapter) {
List<HandlerMethodReturnValueHandler> originHandlers = adapter.getReturnValueHandlers();
if (CollectionUtils.isEmpty(originHandlers)) {
return;
}
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(originHandlers.size());
for (HandlerMethodReturnValueHandler originHandler : originHandlers) {
if (originHandler instanceof RequestResponseBodyMethodProcessor) {
// 未使用 R 包装返回值,隐式包装
newHandlers.add(new MyHandlerMethodReturnValueHandler(originHandler));
// 使用 R 包装返回值,跳过包装
newHandlers.add(originHandler);
} else {
newHandlers.add(originHandler);
}
}
adapter.setReturnValueHandlers(newHandlers);
}
}

统一异常处理

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
@RestControllerAdvice
public class ExceptionAdvice {
// 业务定义的非运行时异常
@ExceptionHandler(value = AppNonRuntimeException.class)
public ResponseEntity<R> appException(AppNonRuntimeException e) {
e.printStackTrace();
R error = R.error(e.getErrorCode());
return new ResponseEntity<>(error, HttpStatus.OK);
}

// 业务定义的运行时异常
@ExceptionHandler(value = AppRuntimeException.class)
public ResponseEntity<R> appRuntimeException(AppRuntimeException e) {
e.printStackTrace();
R error = R.error(e.getErrorCode());
return new ResponseEntity<>(error, HttpStatus.OK);
}

// 非业务定义的异常
@ExceptionHandler(value = Exception.class)
public ResponseEntity<R> exception(Exception e) {
e.printStackTrace();
R error = R.error(e.getMessage());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

代码

here

0%