JavaRush /Java 博客 /Random-ZH /REST API 和另一个测试任务。
Денис
第 37 级
Киев

REST API 和另一个测试任务。

已在 Random-ZH 群组中发布
第一部分:开始 从哪里开始?奇怪的是,但从技术规格来看。确保在阅读提交的 TOR 后您完全理解其中所写的内容以及客户的期望非常重要。首先,这对于进一步实施很重要,其次,如果你没有实施对你的期望,这对你没有好处。为了避免浪费空气,我们来草拟一个简单的技术规格。因此,我想要一个可以向其发送数据的服务,数据将存储在该服务上并随意返回给我。如有必要,我还需要能够更新和删除这些数据。几句话似乎不太清楚吧?我想如何向那里发送数据?使用什么技术?该数据将采用什么格式?也没有传入和传出数据的示例。结论——技术规格已经很糟糕了。让我们尝试重新表述一下:我们需要一个可以处理 HTTP 请求并处理传输数据的服务。这将是人事记录数据库。我们会有员工,他们按部门和专业划分,员工可能有任务分配给他们。我们的任务是自动化雇用、解雇、调动员工的会计流程,以及使用 REST API 分配和取消任务的流程。作为第一阶段,我们目前仅与员工合作。该服务必须有多个端点才能使用: - POST /employee - POST 请求,该请求必须接受包含有关员工的数据的 JSON 对象。该对象必须保存到数据库中;如果数据库中已存在这样的对象,则必须用新数据更新字段中的信息。- GET /employee - GET 请求返回数据库中保存的完整员工列表 - DELETE - DELETE /employee 删除特定员工 员工数据模型:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  	//List of tasks, not needed for Phase 1
  ]
}
第二部分:工作工具 那么,工作范围或多或少已经明确了,但是我们该如何去做呢?显然,测试中的此类任务有几个应用程序目标,看看您如何编码,迫使您使用 Spring 并稍微使用数据库。好吧,让我们这样做吧。我们需要一个支持 REST API 的 SpringBoot 项目和一个数据库。在网站 https://start.spring.io/ 上您可以找到您需要的一切。 REST API 或其他测试任务。 - 1 您可以选择构建系统、语言、SpringBoot 版本、设置工件设置、Java 版本和依赖项。单击“添加依赖项”按钮将显示一个带有搜索栏的特色菜单。单词“rest”和“data”的第一个候选者是“Spring Web”和“Spring Data”——我们将添加它们。Lombok 是一个方便的库,允许您使用注释通过 getter 和 setter 方法摆脱数公里长的代码。通过单击“生成”按钮,我们将收到包含项目的存档,该项目已经可以在我们最喜欢的 IDE 中解压并打开。默认情况下,我们将收到一个空项目,带有构建系统的配置文件(在我的例子中它将是 gradle,但对于 Maven 来说没有根本区别,还有一个 spring 启动文件)细心的人可以注意两 REST API 或其他测试任务。 - 2 件事。首先,我有两个设置文件 application.properties 和 application.yml。默认情况下,您将获得确切的属性 - 一个可以在其中存储设置的空文件,但对我来说 yml 格式看起来更具可读性,现在我将显示一个比较: 尽管事实上左侧的图片看起来更 REST API 或其他测试任务。 - 3 紧凑,很容易看到属性路径中有大量重复。右图是一个常规的yml文件,具有树形结构,非常容易阅读。我稍后将在项目中使用该文件。细心的人可能会注意到的第二件事是我的项目已经有几个包了。目前还没有健全的代码,但值得仔细研究一下。申请书是怎样写的?有了具体的任务,我们必须将其分解——将其分解为小的子任务并开始它们的一致实施。对我们有什么要求?我们需要提供一个客户端可以使用的API;控制器包的内容将负责这部分功能。应用程序的第二部分是数据库——持久性包。在其中,我们将存储诸如数据库实体(Entities)以及存储库之类的东西 - 特殊的 spring 接口,允许您与数据库交互。服务包将包含服务类。下面我们就来说说什么是Spring类型的Service。最后但并非最不重要的一点是 utils 包。具有各种辅助方法的实用类将存储在那里,例如,用于处理日期和时间的类,或者用于处理字符串的类,以及谁知道还有什么。让我们开始实现该功能的第一部分。 第三部分:控制器
@RestController
@RequestMapping("${application.endpoint.root}")
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;

    @GetMapping("${application.endpoint.employee}")
    public ResponseEntity<List<Employee>> getEmployees() {
        return ResponseEntity.ok().body(employeeService.getAllEmployees());
    }
}
现在我们的 EmployeeController 类看起来像这样。这里有许多重要的事情值得关注。1.类上方的注释,第一个@RestController告诉我们的应用程序该类将是一个端点。2. @RequestMapping 虽然不是强制性的,但却是一个有用的注释;它允许您为端点设置特定路径。那些。为了敲击它,您需要将请求发送到 localhost:port/employee,而不是 localhost:8086/api/v1/employee 实际上,这些 api/v1 和员工来自哪里?从我们的 application.yml 中,如果仔细观察,您可以找到以下几行:
application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
正如你所看到的,我们有 application.endpoint.root 和 application.endpoint.employee 这样的变量,这些正是我在注释中写的,我建议记住这个方法 - 它将节省大量扩展或重写的时间功能 - 将所有内容都包含在配置中总是更方便,而不是对整个项目进行硬编码。3. @RequiredArgsConstructor是一个Lombok注解,一个方便的库,可以让你避免编写不必要的东西。在这种情况下,注释相当于该类将有一个公共构造函数,其中所有字段都标记为final
public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
但是如果一个注解就够了,为什么还要写这样的东西呢?:) 顺便说一句,恭喜你,这个最私密的final字段无非是臭名昭著的依赖注入。我们接着说,其实employeeService是一个什么样的领域呢?这将是我们项目中处理此端点请求的服务之一。这里的想法非常简单。每个类都应该有自己的任务,并且不应因不必要的操作而超载。如果这是一个控制器,让它负责接收请求和发送响应,但我们宁愿将处理委托给额外的服务。这个类中剩下的最后一件事是返回我们公司使用上述服务的所有员工列表的唯一方法。该列表本身包装在一个名为 ResponseEntity 的实体中。我这样做是为了将来,如果有必要,我可以轻松返回我需要的响应代码和消息,自动化系统可以理解这些代码和消息。因此,例如 ResponseEntity.ok() 将返回第 200 个代码,这表示一切都很好,但是如果我返回,例如
return ResponseEntity.badRequest().body(Collections.emptyList());
那么客户端将收到代码 400 - bad reuqest 和响应中的空列表。通常,如果请求不正确,则会返回此代码。但一个控制器不足以让我们启动应用程序。我们的依赖项不允许我们这样做,因为我们仍然必须有一个基础:) 好吧,让我们继续下一部分。 第四部分:简单的持久性 由于我们的主要任务是启动应用程序,因此我们现在将仅限于几个存根。您已经在 Controller 类中看到,我们返回 Employee 类型的对象列表,这将是我们的数据库实体。我们在demo.persistence.entity包中创建它 ,将来可以用数据库中的其他实体来补充该实体包。
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
这是一个像门一样简单的类,其注释准确地说如下:这是一个数据库实体@Entity,这是一个带有数据@Data的类-Lombok。乐于助人的 Lombok 将为我们创建所有必要的 getter、setter、构造函数 - 完整的填充。好吧,锦上添花的是@Accessors(chain = true) 事实上,这是 Builder 模式的隐藏实现。假设您有一个包含一堆字段的类,您希望不通过构造函数而是通过方法来分配这些字段。以不同的顺序,也许不是同时进行。您永远不知道您的应用程序中会有什么样的逻辑。此注释是完成此任务的关键。我们看看吧:
public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
假设我们的类中有所有这些字段😄你可以分配它们,你不能分配它们,你可以在不同的地方混合它们。在只有 3 个属性的情况下,这似乎并不是什么了不起的事情。但有些类的属性数量要多得多,例如 50 个。并编写类似的内容
public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
看起来不太漂亮,是吗?我们还需要严格按照构造函数添加变量的顺序。不过,我跑题了,让我们回到正题。现在我们有一个(强制)字段 - 唯一标识符。在本例中,这是一个 Long 类型的数字,保存到数据库时会自动生成。相应地,@Id注解清楚地向我们表明这是一个唯一的标识符;@GenerateValue负责它的唯一生成。值得注意的是,@Id可以添加到非自动生成的字段中,但随后需要手动处理唯一性问题。什么可以是唯一的员工标识符?嗯,例如,全名+部门……但是,一个人有全名,他们有可能在同一个部门工作,虽然很小,但确实有——这意味着这个决定是糟糕的。可以添加一堆其他字段,例如雇用日期、城市,但在我看来,所有这些都使逻辑过于复杂。您可能想知道,一堆字段怎么可能同时是唯一的?我回答——也许吧。如果你好奇,你可以在谷歌上搜索@Embeddable和@Embedded之类的东西。好吧,我们已经完成了本质。现在我们需要一个简单的存储库。它看起来像这样:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
是的,仅此而已。只是一个接口,我们称之为 EmployeeRepository,它扩展了 JpaRepository,它有两个类型参数,第一个负责它使用的数据类型,第二个负责键类型。在我们的例子中,它们是 Employee 和 Long。现在就够了。启动应用程序之前的最后一步将是我们的服务:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
有已经熟悉的RequiredArgsConstructor和新的@Service注释——这通常表示业务逻辑层。当运行 spring 上下文时,标有此注释的类将被创建为 Bean。当我们在EmployeeController类中创建了最终属性EmployeeService并附加了RequiredArgsConstructor(或者手动创建了一个构造函数)时,Spring在初始化应用程序时,会找到这个地方并将一个类对象放入这个变量中。这里的默认值是 Singleton - 即 所有此类链接都将有一个对象;在设计应用程序时考虑这一点很重要。其实就这样,应用程序就可以启动了。不要忘记在配置中输入必要的设置。 REST API 或其他测试任务。 - 4 我不会描述如何安装数据库、创建用户和数据库本身,但我只会注意到在 URL 中我使用了两个附加参数 - useUnicore=true 和 characterEncoding=UTF-8。这样做是为了使文本在任何系统上都或多或少平等地显示。但是,如果您懒得修改数据库并且确实想研究工作代码,那么有一个快速解决方案: 1. 将以下依赖项添加到 build.gradle:
implementation 'com.h2database:h2:2.1.214'
2. 在 application.yml 中,您需要编辑几个属性,为了简单起见,我将给出 spring 部分的完整示例:
spring:
  application:
    name: "employee-management-service"
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.H2Dialect
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:./mydb
    username: sa
    password:
该数据库将存储在项目文件夹中名为mydb 的文件中。但我建议安装一个成熟的数据库😉 有关该主题的有用文章:Spring Boot With H2 Database 以防万一,我将提供完整版本的 build.gradle 以消除依赖项中的差异:
plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'mysql:mysql-connector-java:8.0.30'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
系统已准备好启动: REST API 或其他测试任务。 - 5 您可以通过从任何合适的程序向我们的端点发送 GET 请求来检查它。在这种特殊情况下,普通浏览器就可以了,但将来我们将需要 Postman。 REST API 或其他测试任务。 - 6 是的,事实上,我们还没有实现任何业务需求,但是我们已经有了一个可以启动并可以扩展到所需功能的应用程序。续:REST API 和数据验证
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION