上一篇:Spring Boot 3.x Rest API最佳实践之API设计
下一篇:Spring Boot 3.x Rest API最佳实践之统一响应结构
接着上一篇《API设计》咱们对定义好的API
接口做简单的实现,会发现实现controller
组件时,我们会将关注点从路径怎么定义、参数怎么定义以及注解怎么编写等等,转向控制器逻辑实现上来。这就是关注点分离,专心的干一件事。如果觉得对你有帮助,记得点赞收藏,关注小卷,后续更精彩!
controller简单实现
对前面我们定义的三个API接口,实现Controller
很简单,只要实现相应的接口,并在类的头部加RestController
注解即可。这里我们先对接口做简单的mock实现。
package com.juan.demo.web.controller;
import ...
@RestController
public class HelloController implements HelloAPI {
@Override
public String hello() {
return "hello spring boot3.0";
}
}
对于后台商品管理API接口的实现,这里我们暂且对保存和删除操作简单打印日志,对查询操作返回构造的数据:
package com.juan.demo.web.controller;
import ...
@Slf4j
@RestController
public class ProductAdminController implements ProductAdminAPI {
@Override
public void addProduct(ProductSaveDTO saveDTO) {
log.info("add product: {}", saveDTO);
}
@Override
public void updateProduct(ProductSaveDTO saveDTO) {
log.info("update product: {}", saveDTO);
}
@Override
public ProductDetailDTO getProduct(long id) {
return new ProductDetailDTO(1L, "spring boot3入门", 30000);
}
@Override
public void deleteProduct(long id) {
log.info("delete product id = {}", id);
}
@Override
public List<ProductResultItemDTO> listProducts() {
return List.of(
new ProductResultItemDTO(1L, "spring boot3基础入门", 30000),
new ProductResultItemDTO(2L, "Vue3入门", 20000)
);
}
}
对于添加购物车API接口的实现逻辑,是我们后续要完善的:
package com.juan.demo.web.controller;
import ...
@Slf4j
@RestController
public class CartController implements CartAPI {
@Override
public void addCartItem(CartItemDTO cartItemDTO) {
log.info("添加购物车成功");
}
}
说下这里的实现逻辑,后台完成添加购物车逻辑后会将更新后的购物车数据列表查出来,序列化为json数据,并写入到客户端Cookie。这里用到了HttpServletResponse
对象,为了避免通过对请求响应对象注入的倾入式设计,后续我们会使用RequestContextHolder的静态方法getRequestAttributes()
,因此下面的接口定义是不可取的:
...
@RequestMapping("personal/cart")
public interface CartAPI {
@PostMapping
void addCartItem(@RequestBody CartItemDTO cartItemDTO, HttpServletResponse response);
}
购物车接口简单实现
前面我们提到对于HttpServletRequest
、HttpServletResponse
等对象通过API接口方法参数注入的形式是有倾入性的,为此我们可以在一个工具类中封装获取方式:
package com.juan.demo.util;
import ...
public class RequestContextUtil {
public static HttpServletResponse getResponse() {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs != null) return attrs.getResponse();
return null;
}
}
这样,我们再来实现下添加购物车的Controller
方法逻辑:
@Slf4j
@RestController
public class CartController implements CartAPI {
@Override
public void addCartItem(CartItemDTO cartItemDTO) {
// todo 先写死要写入cookie的json数据
String cartDataJson = "[{\"p\":10,\"q\":2}]";
log.info("更新后的用户购物车数据:{}", cartDataJson);
Cookie cookie = new Cookie("cart_data", URLEncoder.encode(cartDataJson, StandardCharsets.UTF_8));
Objects.requireNonNull(RequestContextUtil.getResponse()).addCookie(cookie);
log.info("添加购物车成功");
}
}
这里我们对构造出来的购物车json数据,通过URL编码后作为入参来构造Cookie
对象,并调用之前写好的工具类RequestContextUtil
来获取response
对象,并写入cookie数据。这里我们通过显式的调用Objects.requireNonNull(...)
,表明这里获取的对象不应该出现获取为空的情况。
实际添加逻辑,我们将采用一个CartService
接口的实现来封装,这里定义一个service
接口:
package com.juan.demo.service;
import ...
public interface CartService {
/**
* 添加购物车:对指定的商品添加一定的数量
* @param addItemDTO
* @return 更新后的购物车数据
*/
List<CartItemDTO> addCartItem(CartItemDTO addItemDTO);
}
实现类CartServiceImpl
被用@Service
注解为一个service
组件类,这里我们将要实现的逻辑写下来,方便后续进一步实现,这里先简单返回构造的数据:
package com.juan.demo.service.impl;
import ...
@Service
public class CartServiceImpl implements CartService {
@Override
public List<CartItemDTO> addCartItem(CartItemDTO addItemDTO) {
/* todo
* 1. 判断productId是否添加过,未添加则插入一条记录
* 2. 否则,更新购物车条目数量
* 3. 查询更新后的购物车条目列表
*/
return List.of(
new CartItemDTO(1L, 2),
new CartItemDTO(2L, 3)
);
}
}
在CartController
中注入相关组件,并完善添加购物车的调用和处理逻辑:
package com.juan.demo.web.controller;
import ...
...
public class CartController implements CartAPI {
@Resource
private CartService cartService;
@Resource
private ObjectMapper objectMapper;
@SneakyThrows
@Override
public void addCartItem(CartItemDTO cartItemDTO) {
// 实现后台添加购物车,返回添加后的购物车数据
List<CartItemDTO> cartData = cartService.addCartItem(cartItemDTO);
// 实现数据json序列化后,写入cookie
String cartDataJson = objectMapper.writeValueAsString(cartData);
// 写入cookie代码省略
...
}
}
这里我们注入了两个组件:CartService
和ObjectMapper
,用前者调用添加购物车的方法,再用后者进行购物车数据列表的json字符串序列化操作,注意,这里会抛出受检查异常,我们采用方法头部的@SneakyThrows
注解进行了优雅的抛出处理。
API客户端测试
对于前面编写好的API,要进行测试,传统的方式是采用类似于PostMan的工具进行填写和调用,这里更推荐idea自带的测试工具,支持.http
后缀的客户端工具。
我们在项目中新建一个test-http
目录,新建几个.http
后缀的模块测试文件
hello.http
### 测试hello api的字符串请求
GET http://localhost:8080/hello
cart.http
### 添加购物车
POST http://localhost:8080/personal/cart
Content-Type: application/json
{
"p": 2,
"q": 4
}
product.http
### 测试商品详情查询
GET http://localhost:8080/admin/products/1
### 查询商品列表
GET http://localhost:8080/admin/products
### 删除商品
DELETE http://localhost:8080/admin/products/1
### 新增商品
POST http://localhost:8080/admin/products
Content-Type: multipart/form-data;boundary=WebAppBoundary
--WebAppBoundary
Content-Disposition: form-data; name="name"
Content-Type: text/plain
Spring Boot 3.0实战
--WebAppBoundary
Content-Disposition: form-data; name="price"
Content-Type: text/plain
16800
--WebAppBoundary
### 修改商品
PUT http://localhost:8080/admin/products
Content-Type: multipart/form-data;boundary=WebAppBoundary
--WebAppBoundary
Content-Disposition: form-data; name="id"
Content-Type: text/plain
1
--WebAppBoundary
Content-Disposition: form-data; name="price"
Content-Type: text/plain
12800
--WebAppBoundary
测试截图:
读者可以基于上述http
调用脚本,对开发的服务进行自行测试,这里给出spring boot
控制台输出的日志:
测试发现的问题:
- 对于void,没有响应体
- 返回的结构不统一
下一小节,我们将采用spring boot
提供的统一响应API
的拦截处理特性来完成Rest API
正常响应格式的统一输出,大家加油!