《Spring Boot从入门到实战》第六章习题答案

6.6 本章练习

1)设计并实现完整的Restful风格的人员管理模块的Web API,版本号为V1,并配置产生Swagger接口文档。

答案:

  1. 配置 Swagger 接口文档 创建一个 Swagger 配置类。

    package com.example.demo.config;

    import io.swagger.v3.oas.models.Components;
    import io.swagger.v3.oas.models.OpenAPI;
    import io.swagger.v3.oas.models.info.Info;
    import io.swagger.v3.oas.models.security.SecurityRequirement;
    import io.swagger.v3.oas.models.security.SecurityScheme;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    @Configuration
    public class Swagger2Config implements WebMvcConfigurer {

     @Bean
     public OpenAPI customOpenAPI() {
         return new OpenAPI()
                 .info(new Info().title("API Documentation")
                         .description("使用 Spring Boot 构建的 RESTful APIs")
                         .version("V1"))
                         .components(new Components().addSecuritySchemes("bearAuth",
                                 new SecurityScheme().name("Authorization")
                                         .type(SecurityScheme.Type.HTTP)
                                         .scheme("bear")
                                         .bearerFormat("JWT")
                                         )).addSecurityItem(new SecurityRequirement().addList("bearAuth"));
     }
    

    }

  2. 创建人员实体类 Person 实体类

    package com.example.demo.bean;

    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;

    @Entity
    public class Person {

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
     private String name;
     private Integer age;
     private String email;
    
     public Person() {
     }
    
     public Person(Long id, String name, Integer age, String email) {
         this.id = id;
         this.name = name;
         this.age = age;
         this.email = email;
     }
    
     public Long getId() {
         return id;
     }
    
     public void setId(Long id) {
         this.id = id;
     }
    
     public String getName() {
         return name;
     }
    
     public void setName(String name) {
         this.name = name;
     }
    
     public Integer getAge() {
         return age;
     }
    
     public void setAge(Integer age) {
         this.age = age;
     }
    
     public String getEmail() {
         return email;
     }
    
     public void setEmail(String email) {
         this.email = email;
     }
    

    }

  3. 创建人员控制器 接下来,创建一个控制器来处理 Web API 请求,遵循 RESTful 风格 PersonOneController

    package com.example.demo.controller;

    import com.example.demo.bean.Person;
    import io.swagger.v3.oas.annotations.Operation;
    import io.swagger.v3.oas.annotations.responses.ApiResponse;
    import io.swagger.v3.oas.annotations.responses.ApiResponses;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import java.util.ArrayList;
    import java.util.List;

    @RestController
    @RequestMapping("/api/v1/persons")
    public class PersonOneController {

     List<Person> personList = new ArrayList<>();
    
     public PersonOneController() {
         personList.add(new Person(1L, "Alice", 12, "123@163.com"));
         personList.add(new Person(2L, "Bob", 12, "123@163.com"));
     }
    
     @Operation(summary = "Get all persons", description = "Retrieve all persons from the database.")
     @GetMapping
     public List<Person> getAllPersons() {
         return personList;
     }
    
     @Operation(summary = "Get person by ID", description = "Retrieve a person by their unique ID.")
     @ApiResponses(value = {
             @ApiResponse(responseCode = "200", description = "Found the person"),
             @ApiResponse(responseCode = "404", description = "Person not found")
     })
     @GetMapping("/{id}")
     public Person getPersonById(@PathVariable Long id) {
         int personIndex = getPersonIndexUsingPersonId(id);
         Person person = personList.get(personIndex);
         return person;
     }
    
     private Integer getPersonIndexUsingPersonId(Long personId)
     {
         int personIndex = 0;
         for (Person personObj : personList) {
             if (personObj.getId() == personId) {
                 personIndex = personList.indexOf(personObj);
             }
         }
         return personIndex;
     }
    
     @Operation(summary = "Create a new person", description = "Create a new person in the database.")
     @PostMapping
     public ResponseEntity<Person> createPerson(@RequestBody Person person) {
         personList.add(person);
         return new ResponseEntity<>(person, HttpStatus.CREATED);
     }
    
     @Operation(summary = "Update an existing person", description = "Update an existing person by ID.")
     @PutMapping("/{id}")
     public ResponseEntity<Person> updatePerson(@PathVariable Long id, @RequestBody Person person) {
         int personIndex = getPersonIndexUsingPersonId(id);
         personList.set(personIndex, person);
         return new ResponseEntity<>(person, HttpStatus.OK);
     }
    
     @Operation(summary = "Delete a person", description = "Delete a person by their unique ID.")
     @DeleteMapping("/{id}")
     public ResponseEntity<Void> deletePerson(@PathVariable Long id) {
         int personIndex = getPersonIndexUsingPersonId(id);
         personList.remove(id);
         return ResponseEntity.noContent().build();
     }
    

    }

  4. 启动并测试 启动 Spring Boot 应用并访问 Swagger UI:

    http://localhost:8080/swagger-ui.html

Swagger UI 页面会显示所有 API 的文档,您可以在该页面直接测试人员管理的增、删、改、查操作。

2)实现人员管理模块的Web API控制版本,接口新版本为V2,修改V2版本的人员新增接口,并增加人员批量删除接口。

答案:

Sprinng boot对Restful的支持非常全面,因而实现Restful API非常简单,同样对于API版本控制也有相应的实现方案:

1)创建自定义的@ApiVersion注解。

2)自定义URL匹配规则ApiVersionCondition。

3)使用RequestMappingHandlerMapping创建自定义的映射处理程序,根据Request参数匹配符合条件的处理程序。

下面通过示例程序来演示Web API如何增加版本号。

  1. 创建自定义注解 创建一个自定义版本号标记注解@ApiVersion,实现代码如下:

    package com.example.demo.apiversion;

    import java.lang.annotation.*;

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ApiVersion {
    int value() default 1;
    }

在上面的示例中,创建了ApiVersion自定义注解用于API版本控制,并返回了对应的版本号。

  1. 自定义URL匹配逻辑 接下来定义URL匹配逻辑,创建ApiVersionCondition类并继承RequestCondition接口,将提取请求URL中的版本号与注解上定义的版本号进行对比,以此来判断某个请求应落在哪个控制器上。实现代码如下:

    package com.example.demo.apiversion;

    import org.springframework.web.servlet.mvc.condition.RequestCondition;

    import javax.servlet.http.HttpServletRequest;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

     private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+).*");
    
     private int apiVersion;
     public ApiVersionCondition(int apiVersion) {
         this.apiVersion = apiVersion;
     }
    
     public int getApiVersion() {
         return apiVersion;
     }
    
     public void setApiVersion(int apiVersion) {
         this.apiVersion = apiVersion;
     }
    
     @Override
     public ApiVersionCondition combine(ApiVersionCondition other) {
         return new ApiVersionCondition(other.getApiVersion());
     }
    
     @Override
     public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
         Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
         if(m.find()){
             Integer version = Integer.parseInt(m.group(1));
             if(version >= this.apiVersion){
                 return this;
             }
         }
         return null;
     }
    
     @Override
     public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
         return other.getApiVersion() - this.apiVersion;
     }
    

    }

在上面的示例中,通过ApiVersionCondition类重写RequestCondition定义的URL匹配逻辑。

当方法级别和类级别都有ApiVersion注解时,通过ApiVersionRequestCondition.combine方法将两者进行合并。最终将提取请求URL中的版本号,与注解上定义的版本号进行比对,判断URL是否符合版本要求。

  1. 自定义匹配的处理程序 接下来实现自定义匹配的处理程序。先创建ApiRequestMappingHandlerMapping类,重写部分RequestMappingHandlerMapping的方法,实现自定义的匹配处理程序 示例代码如下:

    package com.example.demo.apiversion;

    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.mvc.condition.RequestCondition;
    import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

    import java.lang.reflect.Method;

    public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

     private static final String VERSION_FLAG = "version";
    
     private static RequestCondition<ApiVersionCondition> createCondition(Class<?> clazz) {
         RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
         if (classRequestMapping == null) {
             return null;
         }
         StringBuilder mappingUrlBuilder = new StringBuilder();
         if(classRequestMapping.value().length > 0){
             mappingUrlBuilder.append(classRequestMapping.value()[0]);
         }
         String mappingUrl = mappingUrlBuilder.toString();
         if(!mappingUrl.contains(VERSION_FLAG)){
             return null;
         }
         ApiVersion apiVersion = clazz.getAnnotation(ApiVersion.class);
         return apiVersion == null? new ApiVersionCondition(1) : new ApiVersionCondition(apiVersion.value());
     }
    
     @Override
     protected RequestCondition<?> getCustomMethodCondition(Method method) {
         return createCondition(method.getClass());
     }
    
     @Override
     protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
         return createCondition(handlerType);
     }
    

    }

  2. 配置注册自定义的RequestMappingHandlerMapping。 重写WebMvcRegistrationsConfig类,重写getRequestMappingHandlerMapping()的方法,将之前创建的ApiRequestMappingHandlerMapping注册到系统中。

    package com.example.demo.apiversion;

    import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

    @Configuration
    public class WebMvcRegisterationsConfig implements WebMvcRegistrations {

     @Override
     public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
         return new ApiRequestMappingHandlerMapping();
     }
    

    }

  3. 配置实现接口。 配置完成之后,接下来编写测试的控制器(Controller),实现相关接口的测试。在Controller目录下分别创建OrderV1Controller和OrderV2Controller。示例代码如下:

OrderV1Controller;

package com.example.demo.controller;

import com.example.demo.apiversion.ApiVersion;
import com.example.demo.bean.JSONResult;
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;

@ApiVersion(value = 1)
@RestController
@RequestMapping("api/{version}/order")
public class OrderV1Controller {

    @GetMapping("/delete/{orderId}")
    public JSONResult deleteOrderById(@PathVariable String orderId) {
        System.out.println("V1删除订单成功:"+orderId);
        return JSONResult.ok("V1删除订单成功");
    }

    @GetMapping("/detail/{orderId}")
    public JSONResult queryOrderById(@PathVariable String orderId) {
        System.out.println("V1获取订单详情成功:"+orderId);
        return JSONResult.ok("V1获取订单详情成功");
    }

}

OrderV2Controller:

package com.example.demo.controller;

import com.example.demo.apiversion.ApiVersion;
import com.example.demo.bean.JSONResult;
import com.example.demo.bean.Person;
import org.springframework.web.bind.annotation.*;

import java.util.List;


@ApiVersion(value = 2)
@RestController
@RequestMapping("api/{version}/order")
public class OrderV2Controller {

    @GetMapping("/detail/{orderId}")
    public JSONResult queryOrderById(@PathVariable String orderId) {
        System.out.println("V2获取订单详情成功:"+orderId);
        return JSONResult.ok("V2获取订单详情成功");
    }

    @GetMapping("/list")
    public JSONResult list(){
        System.out.println("V2,新增list订单列表接口");
        return JSONResult.ok("V2,新增list订单列表接口");
    }

    @PostMapping("/save")
    public JSONResult saveOrder(@RequestBody Person person){
        System.out.println("V2,人员新增成功");
        return JSONResult.ok("V2,人员新增成功");
    }

    @DeleteMapping("/deleteBatch")
    public JSONResult deleteBathcOrder(@RequestBody List<Long> ids){
        System.out.println("V2人员批量删除成功");
        return JSONResult.ok("V2人员批量删除成功");
    }

}
  1. 验证测试,使用postman调用接口
相关推荐
袁庭新7 分钟前
LuaRocks如何安装数据库驱动?
java·数据库·redis·lua·luarocks·袁庭新
hummhumm16 分钟前
第 10 章 - Go语言字符串操作
java·后端·python·sql·算法·golang·database
nukix27 分钟前
Mac Java 使用 tesseract 进行 ORC 识别
java·开发语言·macos·orc
月光光心慌慌。30 分钟前
新日撸java三百行` 新手小白java学习记录 `Day1
java
蘑菇丁34 分钟前
ranger-kms安装
java·ide·eclipse
XiaoLeisj36 分钟前
【JavaEE初阶 — 多线程】内存可见性问题 & volatile
java·开发语言·java-ee
weixin_462428471 小时前
使用 Caffeine 缓存并在业务方法上通过注解实现每3到5秒更新缓存
java·缓存
程序媛小果1 小时前
基于java+SpringBoot+Vue的桂林旅游景点导游平台设计与实现
java·vue.js·spring boot
骑鱼过海的猫1231 小时前
【java】java通过s3访问ceph报错
java·ceph·iphone
杨充1 小时前
13.观察者模式设计思想
java·redis·观察者模式