前言:

本文按照Spring官网构建REST服务的步骤测试,可以得到结论:

到底什么样的风格才是RESTful风格呢?

1,约束请求命令如下:

  • GET,获取资源。例如:/employees表示获取列表资源,/employees/{id}表示获取单个对象资源。
  • POST,新增。例如:/employees,body为json对象,表示新增。
  • PUT,更新。例如:/employees/{id},body为json对象,表示更新。
  • DELETE,删除。例如: /employees/{id},表示更新。

2,约束返回结果:  返回数据为列表,则每个对象资源附加自己的资源链接、列表资源链接以及可操作性链接。

参考链接:

官网demo按照如下步骤介绍如何使用SpringBoot构建REST服务,并强调

  • Pretty URLs like /employees/3 aren’t REST.

  • Merely using GETPOST, etc. aren’t REST.

  • Having all the CRUD operations laid out aren’t REST.

一、普通的http服务

pom.xml文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.htkm.demo</groupId>
<artifactId>demo-restful</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-restful</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

JPA类型的实体类:

@Data
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
private String name;
private String role;
public Employee() {}
public Employee(String name, String role) {
this.name = name;
this.role = role;
}
}

使用H2内存数据库,创建测试数据库:

@Configuration
@Slf4j
public class LoadDatabase {
@Bean
CommandLineRunner initDatabase(EmployeeRepository repository) {
return args -> {
log.info("Preloading " + repository.save(new Employee("Bilbo Baggins", "burglar")));
log.info("Preloading " + repository.save(new Employee("Frodo Baggins", "thief")));
};
}
}

JPA类型的DAO:

public interface EmployeeRepository extends JpaRepository<Employee, Long> {}

EmployeeContrller控制器:

@RestController
public class EmployeeController {
@Autowired
private EmployeeRepository repository; @GetMapping("/employees")
List<Employee> all() {
return repository.findAll();
}
@PostMapping("/employees")
Employee newEmployee(@RequestBody Employee newEmployee) {
return repository.save(newEmployee);
}
@GetMapping("/employees/{id}")
Employee one(@PathVariable Long id) {
return repository.findById(id)
.orElseThrow(() -> new EmployeeNotFoundException(id));
}
@PutMapping("/employees/{id}")
Employee replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
return repository.findById(id)
.map(employee -> {
employee.setName(newEmployee.getName());
employee.setRole(newEmployee.getRole());
return repository.save(employee);
})
.orElseGet(() -> {
newEmployee.setId(id);
return repository.save(newEmployee);
});
}
@DeleteMapping("/employees/{id}")
void deleteEmployee(@PathVariable Long id) {
repository.deleteById(id);
}
}

自定义异常:

public class EmployeeNotFoundException extends RuntimeException {
public EmployeeNotFoundException(Long id) {
super("Could not find employee " + id);
}
}

增加@ControllerAdvice注解,实现异常处理器:

@ControllerAdvice
public class EmployeeNotFoundAdvice {
@ResponseBody
@ExceptionHandler(EmployeeNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
String employeeNotFoundHandler(EmployeeNotFoundException ex) {
return ex.getMessage();
}
}

使用postman或者curl工具测试执行:

1,GET http://localhost:8080/employees

[
{
"id": 1,
"name": "Bilbo Baggins",
"role": "burglar"
},
{
"id": 2,
"name": "Frodo Baggins",
"role": "thief"
}
]

2,GET http://localhost:8080/employees/1

{
"id": 1,
"name": "Bilbo Baggins",
"role": "burglar"
}

3,DELETE http://localhost:8080/employees/1 资源被删除

二、restful的http服务

pom.xml增加hateoas依赖 :

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

控制器更改,在原来的返回json基础之上附加操作链接,红色加粗部分可以重构简化,在下一章节。

@GetMapping("/employees")
CollectionModel<EntityModel<Employee>> all() {
List<EntityModel<Employee>> employees = repository.findAll().stream()
.map(employee -> EntityModel.of(employee,
linkTo(methodOn(EmployeeController.
class).one(employee.getId())).withSelfRel(), // 附加自身链接
linkTo(methodOn(EmployeeController.class).all()).withRel("employees"))) // 附加all操作链接
.collect(Collectors.toList());
return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel()); // 附加自身链接
}
@GetMapping("/employees/{id}")
EntityModel<Employee> one(@PathVariable Long id) {
Employee employee = repository.findById(id) //
.orElseThrow(() -> new EmployeeNotFoundException(id)); return EntityModel.of(employee, linkTo(methodOn(EmployeeController.class).one(id)).withSelfRel(), // 附加自身链接
linkTo(methodOn(EmployeeController.class).all()).withRel("employees")); // 附加all操作链接
}

Postman测试执行

1,GET http://localhost:8080/employees,可以看到附加链接

{
"_embedded": {
"employeeList": [
{
"id": 1,
"name": "Bilbo Baggins",
"role": "burglar",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
},
"employees": {
"href": "http://localhost:8080/employees"

}
}

},
{
"id": 2,
"name": "Frodo Baggins",
"role": "thief",
"_links": {
"self": {
"href": "http://localhost:8080/employees/2"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/employees"
}
}
}

2,GET http://localhost:8080/employees/1

{
"id": 1,
"name": "Bilbo Baggins",
"role": "burglar",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}

三、扩展的restful服务

扩展实体,将name拆分为fristname和lastname,同时通过增加虚拟的get/set保留name属性:

@Data
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String role;
public Employee() {}
public Employee(String firstName, String lastName, String role) {
this.firstName = firstName;
this.lastName = lastName;
this.role = role;
}
public String getName() {
return this.firstName + " " + this.lastName;
}
public void setName(String name) {
String[] parts = name.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
}
}

重构简化代码,增加对象包装处理类 :

@Component
class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EntityModel<Employee>> { @Override
public EntityModel<Employee> toModel(Employee employee) {
return EntityModel.of(employee,
linkTo(methodOn(EmployeeController.class).one(employee.getId())).withSelfRel(),
linkTo(methodOn(EmployeeController.class).all()).withRel("employees"));
}
}

控制器更改:

@RestController
public class EmployeeController { @Autowired
private EmployeeRepository repository; @Autowired
private EmployeeModelAssembler assembler; @GetMapping("/employees")
CollectionModel<EntityModel<Employee>> all() {
List<EntityModel<Employee>> employees = repository.findAll().stream()
.map(assembler::toModel) // 包装对象
.collect(Collectors.toList());
return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel()); // 附加自身链接
} @PostMapping("/employees")
ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) {
EntityModel<Employee> entityModel = assembler.toModel(repository.save(newEmployee));
return ResponseEntity
.created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri())
.body(entityModel); // 返回状态码201,增加Location头 http://localhost:8080/employees/3 } @GetMapping("/employees/{id}")
EntityModel<Employee> one(@PathVariable Long id) {
Employee employee = repository.findById(id) //
.orElseThrow(() -> new EmployeeNotFoundException(id)); return assembler.toModel(employee); // 包装对象
} @PutMapping("/employees/{id}")
ResponseEntity<?> replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
Employee updatedEmployee = repository.findById(id) //
.map(employee -> {
employee.setName(newEmployee.getName());
employee.setRole(newEmployee.getRole());
return repository.save(employee);
}) //
.orElseGet(() -> {
newEmployee.setId(id);
return repository.save(newEmployee);
});
EntityModel<Employee> entityModel = assembler.toModel(updatedEmployee);
return ResponseEntity
.created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri())
.body(entityModel); // 返回状态码201,增加Location头 http://localhost:8080/employees/3 } @DeleteMapping("/employees/{id}")
ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
repository.deleteById(id);
return ResponseEntity.noContent().build();// 返回状态码204 }
}

curl测试执行

$ curl -v -X POST localhost:8080/employees -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "gardener"}'

请求相应状态为201,包含Location 响应头

> POST /employees HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 46
>
< Location: http://localhost:8080/employees/3
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Aug 2018 19:44:43 GMT
<
{
"id": 3,
"firstName": "Samwise",
"lastName": "Gamgee",
"role": "gardener",
"name": "Samwise Gamgee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}

$ curl -v -X PUT localhost:8080/employees/3 -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "ring bearer"}'

> PUT /employees/3 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 49
>
< HTTP/1.1 201
< Location: http://localhost:8080/employees/3
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Aug 2018 19:52:56 GMT
{
"id": 3,
"firstName": "Samwise",
"lastName": "Gamgee",
"role": "ring bearer",
"name": "Samwise Gamgee",
"_links": {
"self": {
"href": "http://localhost:8080/employees/3"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}

$ curl -v -X DELETE localhost:8080/employees/1

> DELETE /employees/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 204
< Date: Fri, 10 Aug 2018 21:30:26 GMT

四、附加可操作链接的restful服务

订单实体转换器,如果订单状态为可执行的订单则附加取消和完成链接 :

@Component
public class OrderModelAssembler implements RepresentationModelAssembler<Order, EntityModel<Order>> {
@Override
public EntityModel<Order> toModel(Order order) {
// Unconditional links to single-item resource and aggregate root
EntityModel<Order> orderModel = EntityModel.of(order,
linkTo(methodOn(OrderController.class).one(order.getId())).withSelfRel(),
linkTo(methodOn(OrderController.class).all()).withRel("orders"));
// Conditional links based on state of the order
if (order.getStatus() == Status.IN_PROGRESS) {
orderModel.add(linkTo(methodOn(OrderController.class).cancel(order.getId())).withRel("cancel")); // 附加cancel链接
orderModel.add(linkTo(methodOn(OrderController.class).complete(order.getId())).withRel("complete")); // 附加complete链接
}
return orderModel;
}
}

控制器中取消和完成操作

   @DeleteMapping("/orders/{id}/cancel")
ResponseEntity<?> cancel(@PathVariable Long id) { Order order = orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id)); if (order.getStatus() == Status.IN_PROGRESS) {
order.setStatus(Status.CANCELLED);
return ResponseEntity.ok(assembler.toModel(orderRepository.save(order)));
}
return ResponseEntity
.status(HttpStatus.METHOD_NOT_ALLOWED)
.header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE)
.body(Problem.create()
.withTitle("Method not allowed")
.withDetail("You can't cancel an order that is in the " + order.getStatus() + " status"));
}
@PutMapping("/orders/{id}/complete")
ResponseEntity<?> complete(@PathVariable Long id) { Order order = orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id)); if (order.getStatus() == Status.IN_PROGRESS) {
order.setStatus(Status.COMPLETED);
return ResponseEntity.ok(assembler.toModel(orderRepository.save(order)));
}
return ResponseEntity
.status(HttpStatus.METHOD_NOT_ALLOWED)
.header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE)
.body(Problem.create()
.withTitle("Method not allowed")
.withDetail("You can't complete an order that is in the " + order.getStatus() + " status"));
}

PostMan测试执行:

1,GET http://localhost:8080

{
"_links": {
"employees": {
"href": "http://localhost:8080/employees"
},
"orders": {
"href": "http://localhost:8080/orders"
}
}
}

2,GET http://localhost:8080/orders

{
"_embedded": {
"orderList": [
{
"id": 3,
"description": "MacBook Pro",
"status": "COMPLETED",
"_links": {
"self": {
"href": "http://localhost:8080/orders/3"
},
"orders": {
"href": "http://localhost:8080/orders"
}
}
},
{
"id": 4,
"description": "iPhone",
"status": "IN_PROGRESS",
"_links": {
"self": {
"href": "http://localhost:8080/orders/4"
},
"orders": {
"href": "http://localhost:8080/orders"
},
"cancel": {
"href": "http://localhost:8080/orders/4/cancel"
},
"complete": {
"href": "http://localhost:8080/orders/4/complete"

}

}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/orders"
}
}
}

3,DELETE http://localhost:8080/orders/3/cancel

{
"title": "Method not allowed",
"detail": "You can't cancel an order that is in the COMPLETED status"
}

4,DELETE http://localhost:8080/orders/4/cancel

{
"id": 4,
"description": "iPhone",
"status": "CANCELLED",
"_links": {
"self": {
"href": "http://localhost:8080/orders/4"
},
"orders": {
"href": "http://localhost:8080/orders"
}
}
}

5,POST localhost:8080/orders

设置header为Content-Type:application/json

body为{"name": "Samwise Gamgee", "role": "gardener"}'

{
"id": 5,
"description": "新的订单",
"status": "IN_PROGRESS",
"_links": {
"self": {
"href": "http://localhost:8080/orders/5"
},
"orders": {
"href": "http://localhost:8080/orders"
},
"cancel": {
"href": "http://localhost:8080/orders/5/cancel"
},
"complete": {
"href": "http://localhost:8080/orders/5/complete"
}
}
}

五、总结

RESTful风格的http服务,包含以下特征(百度百科摘录):

  • 1、每一个URI代表1种资源;
  • 2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
  • 3、通过操作资源的表现形式来操作资源;
  • 4、资源的表现形式是XML或HTML;
  • 5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。

官网demo描述:

What’s important to realize is that REST, however ubiquitous, is not a standard, per se, but an approach, a style, a set of constraints on your architecture that can help you build web-scale systems.

到底什么样的风格才是为RESTful风格呢?

首先约束请求命令如下:

  • GET,获取资源。例如:/employees表示获取列表资源,/employees/{id}表示获取单个对象资源。
  • POST,新增。例如:/employees,body为json对象,表示新增。
  • PUT,更新。例如:/employees/{id},body为json对象,表示更新。
  • DELETE,删除。例如: /employees/{id},表示更新。

其次约束返回结果:  返回数据为列表,则每个对象资源附加自己的资源链接、列表资源链接以及可操作性链接。

以下例子是可能的返回结果:

{
"_embedded": {
"orderList": [
{
"id": 3,
"description": "MacBook Pro",
"status": "COMPLETED",
"_links": {
"self": {
"href": "http://localhost:8080/orders/3"
},
"orders": {
"href": "http://localhost:8080/orders"
}
}
},
{
"id": 4,
"description": "iPhone",
"status": "IN_PROGRESS",
"_links": {
"self": {
"href": "http://localhost:8080/orders/4"
},
"orders": {
"href": "http://localhost:8080/orders"
},
"cancel": {
"href": "http://localhost:8080/orders/4/cancel"
},
"complete": {
"href": "http://localhost:8080/orders/4/complete"
}

}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/orders"
}
}
}

使用SpringBoot构建REST服务-什么是REST服务的更多相关文章

  1. Springboot 构建http服务,返回的http行是'HTTP/1.1 200' 无状态码描述 客户端解析错误

    ————————————————————————————————————————— *** 响应的数据格式  HTTP/1.1 200 OK  Server: Apache-Coyote/1.1  A ...

  2. SpringBoot 搭建基于 MinIO 的高性能存储服务

    1.什么是MinIO MinIO是根据GNU Affero通用公共许可证v3.0发布的高性能对象存储.它与Amazon S3云存储服务兼容.使用MinIO构建用于机器学习,分析和应用程序数据工作负载的 ...

  3. Spring-Boot构建多模块项目

    Spring-Boot构建多模块项目 功能模块单独项目开发,可以将一个庞大的项目分解成多个小项目,便于细分开发 Maven多模块项目不能独立存在,必须有一个介质来包含. 1.创建一个Maven 项目, ...

  4. java springboot activemq 邮件短信微服务,解决国际化服务的国内外兼容性问题,含各服务商调研情况

    java springboot activemq 邮件短信微服务,解决国际化服务的国内外兼容性问题,含各服务商调研情况 邮件短信微服务 spring boot 微服务 接收json格式参数 验证参数合 ...

  5. SpringBoot 构建RestFul API 含单元测试

    相关博文: 从消费者角度评估RestFul的意义 SpringBoot 构建RestFul API 含单元测试 首先,回顾并详细说明一下在快速入门中使用的  @Controller .  @RestC ...

  6. 基于SpringBoot构建分模块项目

    前言 步骤过于详细,多图慎入!!! 假设一个场景,要开发一个4s店维修部的办公系统,其功能有:前台接待,维修抢单,财务结算,库存管理.于是我们创建一个项目balabalabala写完交工. 一段时间后 ...

  7. Azure 项目构建 – 构建直播教学系统之媒体服务篇

    本课程主要介绍如何在 Azure 平台上快速构建和部署基于 Azure 媒体服务的点播和直播教学系统, 实践讲解如何使用 Azure 门户创建媒体服务, 配置视频流进行传输,连接 CDN 加速等. 具 ...

  8. 【SpringCloud构建微服务系列】微服务网关Zuul

    一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...

  9. lucene构建restful风格的简单搜索引擎服务

    来自于本人博客: lucene构建restful风格的简单搜索引擎服务 本人的博客如今也要改成使用lucene进行全文检索的功能,因此在这里把代码贴出来与大家分享 一,文件夹结构: 二,配置文件: 总 ...

随机推荐

  1. Java实现 LeetCode 137 只出现一次的数字 II(二)

    137. 只出现一次的数字 II 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用额外空 ...

  2. Java实现图形化计算器

    package java计算器; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionL ...

  3. CMD指令和GIT指令

    CMD指令 dir 显示当前文件夹的所有的文件目录 mkdir 创建文件夹 cd> 创建文件 rd 删除文件夹 del 删除文件 cls 清屏 Linux指令 查看版本 node -v node ...

  4. go 语言中windows Linux 交叉编译

    记录一下. 在windows系统编译,然后再Linux系统运行. 在项目目录下运行: 命令: set GOARM=5 set GOARCH=arm set GOOS=linux go build xx ...

  5. SpringBoot后端系统的基础架构

    前言 前段时间完成了毕业设计课题--<基于Spring Boot + Vue的直播后台管理系统>,项目名为LBMS,主要完成了对直播平台数据的可视化展示和分级的权限管理.虽然相当顺利地通过 ...

  6. apache 2.4 httpd 2.4.6 反向代理后端的服务为HTTPS https 基于centos7

    需求场景:通过访问apache的http地址,反向代理访问后端的https服务,而且路径带有只能特定模块才反向代理 配置如下 listen <VirtualHost *:> #管理员邮箱 ...

  7. Java_图片转字符

    把高达头像转换成字符[-V-] 调节双循环里y与x的增量改变字符输出的细节.高和长 public class ImgToStr { public static void main(String arg ...

  8. Windows服务监控工具Perfmon

    原文:https://www.jianshu.com/p/f82c2b726ecf 一.Perfmon简介.性能监控指标.性能对象指标 Perfmon:提供了图表化的系统性能实时监视器.性能日志和警报 ...

  9. 《ElasticSearch入门》一篇管够,持续更新

    一.顾名思义: Elastic:灵活的:Search:搜索引擎 二.官方简介: Elasticsearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTf ...

  10. 034.Kubernetes集群安全-Secret

    一 secret概述 1.1 secret作用 Secret对象,主要作用是保管私密数据,比如密码.OAuth Tokens.SSH Keys等信息.将这些私密信息放在Secret对象中比直接放在Po ...