微服务下基于RESTFUL风格实现统一的接口设计(JSON API)
RESTFUL JSON APIJSON API解决的问题服务架构参考文档效果展示返回单个对象返回集合对象名词解析实现步骤相关技术栈需求简述项目结构简图统一的依赖管理POMRESTFUL JSON API 项目搭建POMapplicationapplication.yml核心代码核心代码的关系图JSON API解决的问题在微服务架构下,前后端分离的项目如何通过统一的数据结构进行数据交互。服务架构适
·
RESTFUL JSON API
JSON API
解决的问题
在微服务架构下,前后端分离的项目如何通过统一的数据结构进行数据交互。
服务架构
适用范围:微服务架构 + 前后端分离
参考文档
效果展示
返回单个对象
{
"links": {
"self": "/restful/records/1"
},
"data": [
{
"type": "User",
"id": 1,
"attributes": {
"id": 1,
"userId": 1,
"username": "kino",
"password": "aejics2329ijdsjhuas##44"
}
}
]
}
返回集合对象
{
"links": {
"self": "/restful/records",
"next": "/restful/records?page=2",
"last": "/restful/records?page=10"
},
"data": [
{
"type": "User",
"id": 1,
"attributes": {
"id": 1,
"userId": 1,
"username": "kino",
"password": "aejics2329ijdsjhuas##44"
},
"links": {
"self": "/restful/records/1"
}
},
{
"type": "User",
"id": 2,
"attributes": {
"id": 2,
"userId": 2,
"username": "kino iq",
"password": "aejics2329ijdsjhuas##44"
},
"links": {
"self": "/restful/records/2"
}
}
]
}
名词解析
links 资源连接,比如连接到详情页
data 资源对象
data attributes 资源对象中的数据
relationshiops attributes的关联对象
实现步骤
相关技术栈
Spring Boot(2.2.5.RELEASE)
Spring Cloud(Hoxton.SR3)
Spring Cloud Alibaba(2.2.1.RELEASE)
因为该项目的职责是为了让前后端通过统一的RESTFUL风格的JSON API通信,所以不是按照完整的项目架构去搭建的,只在项目中集成了nacos,也没有提取统一的工具类库。
需求简述
按照统一的格式将数据通过JSON格式返回给前端
具体格式参照JSON API文档
能够返回单个对象
能够返回集合对象
对象属性为空时不返回null
请求错误时返回统一的错误信息
生成环境不显示错误信息中的detail信息
开发环境中显示错误的detail信息
detail错误信息的展示效果可以通过动态配置来实现
项目结构简图
统一的依赖管理
POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kino</groupId>
<artifactId>kino-dependencies</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<properties>
<java.version>13</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
RESTFUL JSON API 项目搭建
POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>kino-provider</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>com.kino</groupId>
<artifactId>kino-dependencies</artifactId>
<version>1.0.0</version>
<relativePath>../kino-dependencies/pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>kino.provider.KinoProviderApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
application
package kino.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class KinoProviderApplication {
public static void main(String[] args) {
SpringApplication.run(KinoProviderApplication.class,args);
}
}
application.yml
spring:
application:
name: kino-provider
cloud:
nacos:
discovery:
server-addr: 192.168.23.1:8848
server:
port: 8083
management:
endpoints:
web:
exposure:
include: "*"
# Configure the log level of the package
# 作用解析:通过日志级别来实现error detail信息显示的动态配置
logging:
level:
kino:
provider: INFO
核心代码
核心代码的关系图
(1)展示了如何相应请求
(2)如何按照要求构建相应数据(SUCCESS)
AbstractBaseDomain
package kino.provider.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public abstract class AbstractBaseDomain implements Serializable {
private Long id;
}
AbstractBaseResult
package kino.provider.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
public abstract class AbstractBaseResult {
@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> {
private String type;
private Long id;
private T attributes;
private T relationships;
private Links links;
}
}
BaseResultFactory
package kino.provider.dto;
import java.util.List;
// 工厂模式
public class BaseResultFactory<T extends AbstractBaseDomain> {
private static String LOGGER_LEVEL_DEBUG = "DEBUG";
private static BaseResultFactory baseResultFactory;
// 私有化构造函数,外部不能通过new获取对象
private BaseResultFactory() {
}
public static BaseResultFactory getInstance() {
// 双重锁机制(绝对单例)
if (baseResultFactory == null) {
synchronized (BaseResultFactory.class) {
if (baseResultFactory == null) {
baseResultFactory = new BaseResultFactory();
}
}
}
return baseResultFactory;
}
public AbstractBaseResult build(String self, T attribute) {
return new SuccessResult(self, attribute);
}
public AbstractBaseResult build(String self, int next, int last, List<T> attributes) {
return new SuccessResult(self, next, last, attributes);
}
public AbstractBaseResult build(int code, String title, String detail, String level) {
if (LOGGER_LEVEL_DEBUG.equals(level)) {
return new ErrorResult(code, title, detail);
} else {
return new ErrorResult(code, title, null);
}
}
}
ErrorResult
package kino.provider.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/***
* <p>
* 逻辑说明:
* 生成环境不显示detail详细
* 只有在DEBUG模式下显示detail信息
* </p>
*/
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
@EqualsAndHashCode(callSuper = false) // 解决build时的警告
public class ErrorResult extends AbstractBaseResult {
private int code;
private String title;
private String detail;
}
SuccessResult
package kino.provider.dto;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false) // 解决build时的警告
public class SuccessResult<T extends AbstractBaseDomain> extends AbstractBaseResult {
private Links links;
private List<DataBean> data;
public SuccessResult(String self, T attribute) {
links = new Links();
links.setSelf(self);
createDataBean(null, attribute);
}
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));
}
private void createDataBean(String self, T attribute) {
if (data == null) {
// guava 的链式编程
data = Lists.newArrayList();
}
DataBean dataBean = new DataBean();
dataBean.setId(attribute.getId()); // AbstractBaseDomain
dataBean.setType(attribute.getClass().getSimpleName());
dataBean.setAttributes(attribute);
if (StringUtils.isNoneBlank(self)) {
// 解决集合中每个对象都带links(page)
Links links = new Links();
links.setSelf(self + "/" + attribute.getId());
dataBean.setLinks(links);
}
data.add(dataBean);
}
}
RestfulApiController
package kino.provider.controller;
import com.google.common.collect.Lists;
import kino.provider.domain.User;
import kino.provider.dto.AbstractBaseResult;
import kino.provider.dto.BaseResultFactory;
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 = "restful")
public class RestfulApiController {
// 动态刷新参数
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping(value = "records/{id}")
public AbstractBaseResult getById(HttpServletRequest request, @PathVariable long id) {
// mock data
User user = new User();
user.setId(1L); //AbstractBaseDomain
user.setUserId(1L);
user.setUsername("kino");
user.setPassword("aejics2329ijdsjhuas##44");
if (id == 0) { // 检验detail数据不显示
return BaseResultFactory.getInstance().build(HttpStatus.UNAUTHORIZED.value(), "参数类型错误", "ID 不能为0", applicationContext.getEnvironment().getProperty("logging.level.kino.provider"));
} else {
return BaseResultFactory.getInstance().build(request.getRequestURI(), user);
}
}
@GetMapping(value = "records")
public AbstractBaseResult getList(HttpServletRequest request) {
// mock data
User user = new User();
user.setId(1L); //AbstractBaseDomain
user.setUserId(1L);
user.setUsername("kino");
user.setPassword("aejics2329ijdsjhuas##44");
User user2 = new User();
user2.setId(2L); //AbstractBaseDomain
user2.setUserId(2L);
user2.setUsername("kino iq");
user2.setPassword("aejics2329ijdsjhuas##44");
List<User> list = Lists.newArrayList();
list.add(user);
list.add(user2);
return BaseResultFactory.getInstance().build(request.getRequestURI(), 2, 10, list);
}
}
更多推荐
已为社区贡献1条内容
所有评论(0)