由前端接口入门学习后端的controller层
本文是以一个前端工程师,后端小白的视角,详细介绍了关于controller的一些基本信息。大部分知识点还加上了简单的demo,真正做到了在实践中学习。
一、简单介绍一下controller层:
主要责任是接收来自用户界面(通常是Web浏览器)的请求,并根据请求的内容执行适当的操作。【即前端调用接口时就是在这里接收】
控制器通常是应用程序的入口点。它负责路由请求到正确的处理程序,并将处理后的结果返回给用户界面。控制器还可以负责验证用户输入、管理会话状态和处理异常情况等。
相当于是用户界面【前端web】和应用程序逻辑【后端代码逻辑】之间的中介。
二、前端调用后端接口时,一般会传递参数给后端,后端的控制层是如何接收的呢?
在后端的控制器层,接收前端传递的参数通常有以下几种方式,根据不同的方式,给出基于Spring Boot框架,使用了Spring Web模块提供的注解和API。你可以根据自己的实际需求和业务逻辑进行适当调整。记得在应用程序的启动类上添加@SpringBootApplication
注解以启用Spring Boot的自动配置和组件扫描功能。
-
查询参数(Query Parameters):【GET】
-
前端可以将参数作为URL的一部分通过查询字符串的形式传递,例如
/api/users?name=John&age=25
。const param1 = 'name'; const param2 = 'age'; const url = `/api/users?param1=${encodeURIComponent(param1)}¶m2=${encodeURIComponent(param2)}`; fetch(url) .then(response => response.json()) .then(data => { // 处理响应数据 }) .catch(error => { // 处理错误 });
-
在控制器中,可以通过读取请求对象的查询参数来获取这些值。
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping public ResponseEntity<List<User>> getUsers(@RequestParam("name") String name, @RequestParam("age") int age) { // 执行相应的逻辑,例如根据参数查询用户列表 List<User> users = userService.getUsersByNameAndAge(name, age); return ResponseEntity.ok(users); } // 其他控制器方法... }
-
-
路径参数(Path Parameters):【GET】
-
前端可以将参数作为URL的一部分通过路径参数的形式传递,例如
/api/users/123
,其中123为用户ID。下面是一个示例,展示了如何在前端使用路径参数传递多个参数给后端:
const param1 = 'id'; const url = `/api/users/${param1}`; fetch(url) .then(response => response.json()) .then(data => { // 处理响应数据 }) .catch(error => { // 处理错误 });
在上述示例中,我们使用 ${param1} 将两个参数拼接到 URL 的末尾,并将其作为请求的路径参数。最终的 URL 类似于 /api/users/id。
-
在控制器中,可以通过路由配置或路由参数的方式来提取路径参数的值。
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable("id") Long id) { // 执行相应的逻辑,例如根据参数获取特定用户 User user = userService.getUserById(id); return ResponseEntity.ok(user); } // 其他控制器方法... }
-
-
请求体参数(Request Body Parameters):【POST、PUT 或 PATCH】
-
对于一些HTTP方法(如POST、PUT等),前端可以将参数作为请求体的一部分发送。这通常用于传递较大或复杂的数据。下面是一个使用
fetch
发送带有 JSON 参数的 POST 请求的示例:const data = { name: 'John', email: 'john@example.com' }; fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) .then(response => response.json()) .then(data => { // 处理响应数据 }) .catch(error => { // 处理错误 });
-
在控制器中,可以通过读取请求对象的请求体来获取这些参数的值。
@RestController @RequestMapping("/api/users") public class UserController { @PostMapping public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) { // 执行相应的逻辑,例如根据参数创建新用户 User user = userService.createUser(request); return ResponseEntity.ok(user); } // 其他控制器方法... }
-
-
请求头参数(Request Header Parameters):
-
前端可以将参数作为请求头的一部分发送,例如在请求头中添加
Authorization
字段来传递身份验证令牌。下面是一个使用
fetch
发送带有请求头的 GET 请求的示例:fetch('/api/users', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer your_token', 'Custom-Header': 'custom_value' } }) .then(response => response.json()) .then(data => { // 处理响应数据 }) .catch(error => { // 处理错误 });
在上述示例中,通过设置
headers
选项来添加请求头。你可以根据实际需求添加自定义的请求头字段,如'Authorization'
和'Custom-Header'
,并为其指定相应的值。 -
在控制器中,可以通过读取请求对象的请求头来获取这些参数的值。
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping public ResponseEntity<List<User>> getUsers(@RequestHeader("Authorization") String authToken) { // 执行相应的逻辑,例如根据身份验证令牌获取用户列表 List<User> users = userService.getUsersByAuthToken(authToken); return ResponseEntity.ok(users); } // 其他控制器方法... }
-
三、更深入地介绍一下关于请求体参数
请求体参数可以是DTO 也可以是Map,通常来说,推荐使用 DTO 对象,因为它提供了更好的类型安全性和代码可读性,尤其是当参数具有明确的属性结构的时候。
DTO作为入参
DTO 是一个用于封装数据的对象,通常用于传输数据或承载多个属性的复杂对象。如果你的请求参数具有多个属性,那么使用 DTO 是一个不错的选择。使用 DTO 可以使代码更具可读性,并且在开发过程中可以方便地对数据进行验证和处理。例如:
// DTO类
public class UserDTO {
private String username;
private String password;
// 其他属性和方法
// Getters and setters
}
// 控制器方法
@PostMapping("/users")
public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO) {
// 使用 userDTO 对象进行处理
// ...
return ResponseEntity.ok(userDTO);
}
在上述示例中,UserDTO
是一个包含 username
和 password
属性的 DTO 类。前端使用 JSON数据作为请求参数,控制器方法 createUser
使用 @RequestBody
注解将请求的 JSON 数据映射到 UserDTO
对象中,然后可以直接使用 userDTO
对象进行处理。
DTO 需要定义和维护DTO类:使用 DTO 需要创建相应的类,并定义属性和方法。
优点:
- 明确定义类型和属性,减少类型错误;
- 代码可读性强和可维护性;
- DTO 对象可以包含验证逻辑和转换方法,用于对请求参数进行验证和处理,提供更严谨和一致的数据处理;
- 可扩展性:DTO 对象可以根据业务需求灵活扩展,添加新的属性和方法,而不会影响控制器方法的签名。
缺点:
-
很麻烦。如果请求参数很简单,还要特地建一个类,才一两个属性,因此如果请求参数很简单,尽量使用Map。
-
上述的例子使用的是
@RequestBody
注解将请求的 JSON 数据映射到UserDTO
对象。实际上,还有一些情况是没有办法使用这个注解 直接将 JSON 数据→UserDTO
对象。而是需要我们使用一些对象映射工具(如 ModelMapper、Dozer、MapStruct)或手动进行属性赋值。
-
例如,使用 ModelMapper:
@PostMapping("/users") public ResponseEntity<UserDTO> createUser(@RequestBody UserCreateRequest request) { ModelMapper modelMapper = new ModelMapper(); UserDTO userDTO = modelMapper.map(request, UserDTO.class); // 处理 userDTO 对象 // ... return ResponseEntity.ok(userDTO); }
常见的情况比如:
-
表单提交:如果请求是通过 HTML 表单提交的,而不是以 JSON 数据的形式发送,则无法直接使用
@RequestBody
注解将请求参数映射到 DTO 对象。在这种情况下,通常会使用@ModelAttribute
注解或直接将表单字段作为方法参数来获取请求参数。例如:
当使用 HTML 表单提交数据时,可以使用
@ModelAttribute
注解将请求参数映射到 DTO 对象。下面是一个使用@ModelAttribute
注解的简单示例:@Controller public class UserController { @PostMapping("/users") public String createUser(@ModelAttribute UserDTO userDTO) { // 处理 userDTO 对象 // ... return "redirect:/users"; } }
在上面的示例中,
@PostMapping
注解指定了处理 POST 请求的路径为/users
。而@ModelAttribute
注解应用于UserDTO
类型的参数,它告诉 Spring MVC 框架将请求参数映射到该对象。假设有一个 HTML 表单,包含以下字段:
<form action="/users" method="post"> <input type="text" name="name" /> <input type="email" name="email" /> <input type="submit" value="Submit" /> </form>
当用户填写表单并提交时,表单中的字段名将与
UserDTO
对象的属性名相匹配。Spring MVC 框架将自动将请求参数映射到UserDTO
对象的对应属性。例如,name
字段值将映射到UserDTO
的name
属性,email
字段值将映射到UserDTO
的email
属性。在
createUser
方法中,你可以对接收到的UserDTO
对象进行处理,例如将其保存到数据库中。然后,可以使用重定向 (redirect
) 将用户重定向到另一个页面,例如用户列表页面 (/users
)。需要注意的是,
@ModelAttribute
注解可以不写,因为它是 Spring MVC 默认的参数绑定方式。但为了增加代码的可读性和明确性,显式地添加@ModelAttribute
注解是一个好习惯。这是一个简单的示例,展示了如何使用
@ModelAttribute
注解将表单数据映射到 DTO 对象。根据实际需求,你可以在 DTO 对象中定义更多的属性,以便处理更多的请求参数。 -
文件上传:当请求涉及文件上传时,
@RequestBody
注解通常无法直接处理文件数据。文件上传通常需要使用MultipartFile
或类似的类型来处理文件数据,并可能需要其他额外的注解和配置来支持文件上传功能。 -
自定义请求处理:在某些情况下,你可能需要对请求进行自定义处理,使用特定的解析逻辑或处理器来解析请求的内容。这种情况下,你可能不会使用
@RequestBody
注解,而是手动处理请求的输入流或使用其他自定义的方式来映射请求数据到 DTO 对象。 这种方法适用于简单的场景,当 DTO 对象的属性较少时,手动赋值可能更加直观和简单。// 在控制器方法中,根据请求参数的名称,逐个从请求对象中获取值,并将其赋值给 DTO 对象的对应属性。 @PostMapping("/users") public ResponseEntity<UserDTO> createUser(@RequestBody UserCreateRequest request) { UserDTO userDTO = new UserDTO(); userDTO.setName(request.getName()); userDTO.setEmail(request.getEmail()); // 手动赋值其他属性 // ... // 处理 userDTO 对象 // ... return ResponseEntity.ok(userDTO); }
-
Map作为入参
如果你的请求参数是一组松散的键值对,并且你更关注请求中的数据而不是明确的对象结构,那么使用 Map 作为入参可能更合适。Map 可以接收各种参数,并以键值对的形式提供访问参数的灵活性。但是,使用 Map 作为入参可能会降低代码的可读性,因为你需要通过键来获取参数。例如:
@PostMapping("/users")
public ResponseEntity<UserDTO> createUser(@RequestBody Map<String, Object> requestMap) {
String username = (String) requestMap.get("username");
String password = (String) requestMap.get("password");
// 处理用户名和密码
// ...
return ResponseEntity.ok(userDTO);
}
在上述示例中,控制器方法 createUser
使用 @RequestBody
注解将请求的 JSON 数据映射到 Map<String, Object>
对象中。然后,通过键值对的方式从 requestMap
中获取 username
和 password
属性的值,并使用它们进行处理。注意需要手动进行类型转换,并且无法获得编译时的类型安全性。因此,确保请求的参数和键的名称保持一致,并进行适当的类型检查。
Map不用像DTO特地去维护一个类:
优点:
- 灵活和简单:Map 可以接收各种键值对形式的请求参数,适用于不确定或变化的请求结构,不需要在DTO类中去维护。
- 可以直接操作键值对:通过键值对的方式,可以直接获取和操作请求参数,适用于对请求数据的灵活处理。
缺点:
- 需要手动进行类型转换和验证,容易引入类型错误。
- 较低的可维护性:由于 Map 不提供明确的结构和属性,对于复杂的请求参数处理,可能需要在控制器方法中编写更多的逻辑来处理参数。
- 可读性较差:相对于 DTO,使用 Map 作为入参可能会降低代码的可读性,因为需要通过键来获取具体的参数值。