19-实现RESTful风格的API
2025年9月13日大约 4 分钟SpringCloud Alibaba For Page项目
POM
主要在 page-commons
项目增加了如下依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
相关工具类
主要在 page-commons
项目中增加了如下工具类:
AbstractBaseResult
package com.page.commons.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serializable;
/**
* 通用的响应结果
* <p>Title: AbstractBaseResult</p>
* @author cangbao
* @date 2019/1/26 21:08
* @package com.page.commons.dto
*/
@Data
public abstract class AbstractBaseResult implements Serializable {
/**
* 此为内部类
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
protected static class Links {
private String self;
private String next;
private String last;
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
protected static class DataBean<T extends AbstractBaseDomain> {
private String type;
private Long id;
private T attributes;
private T relationships;
private Links links;
}
}
AbstractBaseDomain
package com.page.commons.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
import java.util.Date;
/**
* 通用的领域模型
* <p>Title: AbstractBaseDomain</p>
* @author cangbao
* @date 2019/1/26 21:08
* @package com.page.commons.dto
*/
@Data
public abstract class AbstractBaseDomain implements Serializable {
/**
* 该注解需要保留,用于 tk.mybatis 回显 ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 格式化日期,由于是北京时间(我们是在东八区),所以时区 +8
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date created;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updated;
}
SuccessResult
package com.page.commons.dto;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
/**
* 请求成功
* <p>Title: SuccessResult</p>
* @author cangbao
* @date 2019/1/26 21:09
* @package com.page.commons.dto
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SuccessResult<T extends AbstractBaseDomain> extends AbstractBaseResult {
private Links links;
private List<DataBean> data;
/**
* 请求的结果(单笔)
* @param self 当前请求路径
* @param attributes 领域模型
*/
public SuccessResult(String self, T attributes) {
links = new Links();
links.setSelf(self);
createDataBean(null, attributes);
}
/**
* 请求的结果(分页)
* @param self 当前请求路径
* @param next 下一页的页码
* @param last 最后一页的页码
* @param attributes 领域模型集合
*/
public SuccessResult(String self, int next, int last, List<T> attributes) {
links = new Links();
links.setSelf(self);
links.setNext(self + "?page=" + next);
links.setLast(self + "?page=" + last);
attributes.forEach(attribute -> createDataBean(self, attribute));
}
/**
* 创建 DataBean
* @param self 当前请求路径
* @param attributes 领域模型
*/
private void createDataBean(String self, T attributes) {
if (data == null) {
data = Lists.newArrayList();
}
DataBean dataBean = new DataBean();
dataBean.setId(attributes.getId());
dataBean.setType(attributes.getClass().getSimpleName());
dataBean.setAttributes(attributes);
if (StringUtils.isNotBlank(self)) {
Links links = new Links();
links.setSelf(self + "/" + attributes.getId());
dataBean.setLinks(links);
}
data.add(dataBean);
}
}
ErrorResult
package com.page.commons.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 请求失败
* <p>Title: ErrorResult</p>
* @author cangbao
* @date 2019/1/26 21:10
* @package com.page.commons.dto
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
// JSON 不显示为 null 的属性
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorResult extends AbstractBaseResult {
private int code;
private String title;
/**
* 调试信息
*/
private String detail;
}
BaseResultFactory
package com.page.commons.dto;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 通用响应结构工厂
* <p>Title: BaseResultFactory</p>
* @author cangbao
* @date 2019/1/26 21:11
* @package com.page.commons.dto
*/
@SuppressWarnings("all")
public class BaseResultFactory<T extends AbstractBaseDomain> {
/**
* 设置日志级别,用于限制发生错误时,是否显示调试信息(detail)
*
* @see ErrorResult#detail
*/
public static final String LOGGER_LEVEL_DEBUG = "DEBUG";
private static BaseResultFactory baseResultFactory;
private BaseResultFactory() {
}
// 设置通用的响应
private static HttpServletResponse response;
public static BaseResultFactory getInstance(HttpServletResponse response) {
if (baseResultFactory == null) {
synchronized (BaseResultFactory.class) {
if (baseResultFactory == null) {
baseResultFactory = new BaseResultFactory();
}
}
}
BaseResultFactory.response = response;
// 设置通用响应
baseResultFactory.initResponse();
return baseResultFactory;
}
/**
* 构建单笔数据结果集
*
* @param self 当前请求路径
* @return
*/
public AbstractBaseResult build(String self, T attributes) {
return new SuccessResult(self, attributes);
}
/**
* 构建多笔数据结果集
*
* @param self 当前请求路径
* @param next 下一页的页码
* @param last 最后一页的页码
* @return
*/
public AbstractBaseResult build(String self, int next, int last, List<T> attributes) {
return new SuccessResult(self, next, last, attributes);
}
/**
* 构建请求错误的响应结构
*
* @param code HTTP 状态码
* @param title 错误信息
* @param detail 调试信息
* @param level 日志级别,只有 DEBUG 时才显示详情
* @return
*/
public AbstractBaseResult build(int code, String title, String detail, String level) {
// 设置请求失败的响应码
response.setStatus(code);
if (LOGGER_LEVEL_DEBUG.equals(level)) {
return new ErrorResult(code, title, detail);
} else {
return new ErrorResult(code, title, null);
}
}
/**
* 初始化 HttpServletResponse
*/
private void initResponse() {
// 需要符合 JSON API 规范
response.setHeader("Content-Type", "application/vnd.api+json");
}
}
TestController
package com.page.service.reg.controller;
import com.page.commons.domain.TbUser;
import com.page.commons.dto.AbstractBaseResult;
import com.page.commons.dto.BaseResultFactory;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping(value = "records/{id}")
public AbstractBaseResult getById(HttpServletRequest request, @PathVariable long id) {
TbUser tbUser = new TbUser();
tbUser.setId(1L);
tbUser.setUsername("和谷桐人");
if (id == 1) {
return BaseResultFactory.getInstance().build(request.getRequestURI(), tbUser);
} else {
return BaseResultFactory.build(HttpStatus.UNAUTHORIZED.value(), "参数类型错误", "ID 只能为 1", applicationContext.getEnvironment().getProperty("logging.level.com.page"));
}
}
@GetMapping(value = "records")
public AbstractBaseResult getList(HttpServletRequest request) {
TbUser tbUser1 = new TbUser();
tbUser1.setId(1L);
tbUser1.setUsername("和谷桐人");
TbUser tbUser2 = new TbUser();
tbUser2.setId(2L);
tbUser2.setUsername("亚丝娜");
List<TbUser> tbUsers = Lists.newArrayList();
tbUsers.add(tbUser1);
tbUsers.add(tbUser2);
return BaseResultFactory.getInstance().build(request.getRequestURI(), 2, 10, tbUsers);
}
}
设置 Json 不返回 null 字段
@JsonInclude(JsonInclude.Include.NON_NULL)
附:SpringMVC 返回状态码
response.setHeader("Content-Type", "application/vnd.api+json");
response.setStatus(500);