编辑
2023-10-29
开发
00
请注意,本文编写于 420 天前,最后修改于 419 天前,其中某些信息可能已经过时。

目录

前后端分离范式
什么是API
todo list:API设计
todo list:数据结构设计
创建数据库
创建Java springboot项目
结合数据库字段编写model层
编写service层
编写controller层
使用Postman进行API测试

前后端分离范式

这一节我想阐述现代前后端工程中,前后端分离范式的作用、常用做法以及工程实践。

当涉及现代前后端开发中的前后端分离范式时,它旨在将前端与后端的开发和部署过程解耦,以提高灵活性、可维护性和协作效率。在这种范式下,前端和后端可以独立开发,测试和部署,通过API进行通信。这样做的好处包括:

灵活性与独立部署:前后端可以独立进行开发和部署,不需要相互依赖。这意味着可以选择最适合项目需求的技术栈,并且容易进行横向扩展。

协作效率:团队可以并行开发,前端和后端开发人员可以专注于自己的领域,通过明确定义的API接口进行协作,提高开发效率。

技术栈灵活性:采用前后端分离范式,可以选择更适合特定需求的前端和后端技术,而不会受到对方技术栈的限制。

什么是API

参见知乎高赞文章:API是什么: 一篇讲透API - 吕攻城师的文章 - 知乎 https://zhuanlan.zhihu.com/p/347125981

总结一下,API = Application Programming Interface,是一种约定。编写API也就是通过编程语言(Java/Go/...)通过后端框架(Springboot/gin/...)向别的开发者(别的后端程序开发者或前端程序开发者)提供一组操作。

例如,win MFC提供了一组基于C++语言开发的操控windows界面的API,pytorch提供了一组基于python语言开发的操控GPU进行深度学习计算的API。这些都叫做API。

在上述API中,它们是通过源码/二进制进行分发的,也就是为了使用MFC提供的API,你必须有msvc相关的开发套件,为了使用pytorch提供的API,你必须在你的python环境中安装pytorch库。而通常在前后端开发的语境中,API指的是通过网络应用层协议HTTP方法https://en.wikipedia.org/wiki/HTTP#Request_methods)RPChttps://en.wikipedia.org/wiki/Remote_procedure_call )进行调用,而无需安装的应用程序接口。

接下来,我们将会以一个online todo list为例,使用Java SpringBoot框架完成后端API的构建。

todo list:API设计

根据一般todolist的功能,我们可以做出以下设计:

1. 创建待办事项 端点: POST /api/todos 描述: 创建一个新的待办事项 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回新创建的待办事项的信息 2. 获取所有待办事项 端点: GET /api/todos 描述: 获取所有待办事项列表 响应: 返回所有待办事项的列表 3. 获取单个待办事项 端点: GET /api/todos/:id 描述: 获取特定 ID 的待办事项 响应: 返回特定待办事项的信息 4. 更新待办事项 端点: PUT /api/todos/:id 描述: 更新特定 ID 的待办事项信息 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回更新后的待办事项信息 5. 删除待办事项 端点: DELETE /api/todos/:id 描述: 删除特定 ID 的待办事项 响应: 返回成功/失败的消息 6. 完成待办事项 端点: PUT /api/todos/:id/complete 描述: 标记特定 ID 的待办事项为已完成 响应: 返回已完成的待办事项信息 7. 获取已完成的待办事项 端点: GET /api/todos/completed 描述: 获取所有已完成的待办事项列表 响应: 返回已完成的待办事项列表

这里出现了一些新词汇。例如“端点”(endpoint),endpoint通俗来说,就是HTTP方法+除去服务器地址的url部分。端点中出现的"

"实际上是指特定的一个数字,例如获取单个待办事项时,会向/api/todos/3这个url发起GET请求。

todo list:数据结构设计

一个todolist会包含很多条待办事项(todo),使用Todo表存储每条todo,每个todo项包含一些字段(field),可以设计如下:

  • id: 一个唯一的标识符,用于识别每个待办事项。
  • description: 描述待办事项的信息。
  • deadline: 截止日期或计划完成日期。
  • priority: 优先级,如高、中、低。
  • completed: 标识待办事项是否已完成。

用SQL语句描述就是

sql
CREATE TABLE todos ( id INTEGER PRIMARY KEY, description TEXT, deadline DATE, priority TEXT, completed BOOLEAN );

说明: id 是主键,用于唯一标识每个待办事项。 description 是文本字段,存储待办事项的描述信息。 deadline 是日期字段,用于存储截止日期。 priority 是文本字段,存储待办事项的优先级。 completed 是布尔字段,用于标识待办事项是否已完成。可能的取值为 true 或 false。

这是一个简单的设计,可以根据实际需求进行调整和扩展。例如,你可以添加更多字段来记录创建时间、最后更新时间,或者将数据拆分为不同的表以支持更多的功能,比如用户身份验证和授权。

创建数据库

使用Navicat,连接postgresql image.png 并新建数据库与表 image.png image.png 可以看到,现在的数据库表是空的 image.png

创建Java springboot项目

使用IDEA创建springboot项目 image.png 然后添加这些依赖项 image.png 其中Spring Web用于创建RESTful API的应用程序,使用了Apache Tomcat作为默认的网页服务器 image.png Mybatis是一个持久层框架,用于方便地操作数据库 image.png Postgresql Driver是数据库驱动,JDBC的一种

(感兴趣的话可以查一查上面这些词有什么更深层的含义,例如RESTful、Apache Tomcat、持久层、JDBC等)

然后我们可以看到这样的文件结构

image.png 接着创建如下的目录结构

image.png 其中main目录下controller文件夹为控制器部分,service文件夹为服务层,model为模型层。resources目录下mapper为持久化映射层,存放mybatis xml文件。

结合数据库字段编写model层

首先要在项目配置文件中写入数据库相关配置 image.png

yml
# postgresql configuration spring.datasource.url=jdbc:postgresql://localhost:5432/todolist spring.datasource.username=xuqi spring.datasource.password= spring.datasource.driver-class-name=org.postgresql.Driver

然后在model层新建一个todo.java文件,内容为

java
import java.util.Date; public class Todo { private Integer id; private String description; private Date deadline; private String priority; private boolean completed; // 省略构造函数和getter/setter方法,使用IDEA自带的生成功能生成 }

然后在mapper文件夹中新建一个TodoMapper.xml,内容为

xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xuqi.todolist.model.TodoMapper"> <resultMap id="todoResultMap" type="com.xuqi.todolist.model.Todo"> <id property="id" column="id"/> <result property="description" column="description"/> <result property="deadline" column="deadline"/> <result property="priority" column="priority"/> <result property="completed" column="completed"/> </resultMap> <select id="getTodoById" parameterType="Integer" resultMap="todoResultMap"> SELECT * FROM todos WHERE id = #{id} </select> <select id="getAllTodos" resultMap="todoResultMap"> SELECT * FROM todos </select> <insert id="insertTodo" parameterType="com.xuqi.todolist.model.Todo"> INSERT INTO todos (id, description, deadline, priority, completed) VALUES (#{id}, #{description}, #{deadline}, #{priority}, #{completed}) </insert> <update id="updateTodo" parameterType="com.xuqi.todolist.model.Todo"> UPDATE todos SET description = #{description}, deadline = #{deadline}, priority = #{priority}, completed = #{completed} WHERE id = #{id} </update> <delete id="deleteTodo" parameterType="Integer"> DELETE FROM todos WHERE id = #{id} </delete> </mapper>

这个xml文件定义了mybatis如何通过sql语句生成对应的Java方法。要注意这里面的namespace="com.xuqi.todolist.model.TodoMapper"要和对应的Java类路径保持相同。parameterType="com.xuqi.todolist.model.Todo"也要和对应的model层的类路径相同。这里xml文件的编写要格外小心。

由于我们将xml文件放在resources目录下,我们需要在项目配置文件中添加如下配置:

yml
# mybatis configuration mybatis.mapper-locations=classpath:mapper/*.xml

显示指定xml文件的路径。(这也说明xml文件的路径是可以自定义的)

根据xml文件中定义的方法名(也就是xml的id项),编写对应的TodoMapper.java文件

java
package com.xuqi.todolist.model; import java.util.List; import org.apache.ibatis.annotations.*; @Mapper public interface TodoMapper { Todo getTodoById(Integer id); List<Todo> getAllTodos(); void insertTodo(Todo todo); void updateTodo(Todo todo); void deleteTodo(Integer id); }

这里使用了@Mapper注解,表示这个Java接口会被mybatis注入实现。注意这个接口的方法名和参数要与xml文件中的一一对应。

编写service层

在service文件夹创建TodoService.java文件,文件内容如下:

java
package com.xuqi.todolist.service; import com.xuqi.todolist.model.Todo; import com.xuqi.todolist.model.TodoMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class TodoService { @Autowired private TodoMapper todoMapper; // 这里需要注入TodoMapper public Todo getTodoById(Integer id) { return todoMapper.getTodoById(id); } public List<Todo> getAllTodos() { return todoMapper.getAllTodos(); } public void addTodo(Todo todo) { todoMapper.insertTodo(todo); } public void updateTodo(Todo todo) { todoMapper.updateTodo(todo); } public void deleteTodo(Integer id) { todoMapper.deleteTodo(id); } }

这里使用了@Service注解表示这是一个Service层代码,使用@Autowired注解完成字段注入。

这里的方法是可以自定义的,不必要与xml文件中的相同。controller层将会调用这里的方法。

编写controller层

在controller文件夹下创建TodoController.java文件,完成了API设计中的1-5项,内容如下:

java
package com.xuqi.todolist.controller; import com.xuqi.todolist.model.Todo; import com.xuqi.todolist.service.TodoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; import java.util.List; @RestController @RequestMapping("/api/todos") public class TodoController { @Autowired private TodoService todoService; // 这里需要注入TodoService @GetMapping("/{id}") public ResponseEntity<Todo> getTodoById(@PathVariable Integer id) { Todo todo = todoService.getTodoById(id); if (todo != null) { return ResponseEntity.ok().body(todo); } else { return ResponseEntity.notFound().build(); } } @GetMapping public ResponseEntity<List<Todo>> getAllTodos() { List<Todo> todos = todoService.getAllTodos(); return ResponseEntity.ok().body(todos); } @PostMapping public ResponseEntity<Void> addTodo(@RequestBody Todo todo) { todoService.addTodo(todo); return ResponseEntity.ok().build(); } @PutMapping("/{id}") public ResponseEntity<Void> updateTodo(@PathVariable Integer id, @RequestBody Todo todo) { if (todoService.getTodoById(id) != null) { todo.setId(id); todoService.updateTodo(todo); return ResponseEntity.ok().build(); } else { return ResponseEntity.notFound().build(); } } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteTodo(@PathVariable Integer id) { if (todoService.getTodoById(id) != null) { todoService.deleteTodo(id); return ResponseEntity.ok().build(); } else { return ResponseEntity.notFound().build(); } } }

注意这里的返回值用ResponseEntity包起来,请求体参数用@RequestBody注解标明,路径参数用@PathVariable注解标明。HTTP方法用对应的Mapping注解标明,这些注解中要标明端点,例如使用@RequestMapping("/api/todos")表示这个类下所有的HTTP方法都有共同的端点前缀/api/todos,@GetMapping("/{id}")表示这个Get方法具有一个路径端点(也就是上文提及的:id)

到这,整个Java springboot的代码已经编写完成了。

使用Postman进行API测试

1. 测试创建待办事项 端点: POST /api/todos 描述: 创建一个新的待办事项 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回新创建的待办事项的信息

这里注意这个POST方法的参数是@RequestBody,所以需要在Body中写入参数,而不是Params。

image.png

插入成功后数据库也更新为

image.png

2. 测试获取所有待办事项 端点: GET /api/todos 描述: 获取所有待办事项列表 响应: 返回所有待办事项的列表

通过上面的POST方法准备两条数据,再测试获取全部数据。

image.png

3. 测试获取单个待办事项 端点: GET /api/todos/:id 描述: 获取特定 ID 的待办事项 响应: 返回特定待办事项的信息

测试获取id为2的那条todo

image.png

4. 测试更新待办事项 端点: PUT /api/todos/:id 描述: 更新特定 ID 的待办事项信息 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回更新后的待办事项信息

更新id为2的todo的描述,注意要在body中填写更新的数据。

image.png 数据库也变更为

image.png 这里由于在body中没有填写deadline与priority的值,所以这两个字段被更新为NULL

5. 测试删除待办事项 端点: DELETE /api/todos/:id 描述: 删除特定 ID 的待办事项 响应: 返回成功/失败的消息

测试删除第二条todo

image.png 数据库也更新为

image.png

本文作者:insomnia

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!