• SpringBoot3之Web编程


    标签:Rest.拦截器.swagger.测试;

    一、简介

    基于web包的依赖,SpringBoot可以快速启动一个web容器,简化项目的开发;

    web开发中又涉及如下几个功能点:

    拦截器:可以让接口被访问之前,将请求拦截到,通过对请求的识别和校验,判断请求是否允许通过;

    页面交互:对于服务端的开发来说,需要具备简单的页面开发能力,解决部分场景的需求;

    Swagger接口:通过简单的配置,快速生成接口的描述,并且提供对接口的测试能力;

    Junit测试:通过编写代码的方式对接口进行测试,从而完成对接口的检查和验证,并且可以不入侵原代码结构;

    二、工程搭建

    1、工程结构

    2、依赖管理

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
        <version>${spring-boot.version}version>
    dependency>
    
    <dependency>
        <groupId>org.springdocgroupId>
        <artifactId>springdoc-openapi-starter-webmvc-uiartifactId>
        <version>${springdoc.version}version>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-thymeleafartifactId>
        <version>${spring-boot.version}version>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <version>${spring-boot.version}version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4jgroupId>
                <artifactId>slf4j-apiartifactId>
            exclusion>
        exclusions>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>${junit.version}version>
    dependency>
    

    三、Web开发

    1、接口开发

    编写四个简单常规的接口,从对资源操作的角度,也就是常说的:增Post、删Delete、改Put、查Get,并且使用了swagger注解,可以快速生成接口文档;

    @RestController
    @Tag(name = "Rest接口")
    public class RestWeb {
    
        @Operation(summary = "Get接口")
        @GetMapping("rest/get/{id}")
        public String restGet(@PathVariable Integer id) {
            return "OK:"+id;
        }
    
        @Operation(summary = "Post接口")
        @PostMapping("/rest/post")
        public String restPost(@RequestBody ParamBO param){
            return "OK:"+param.getName();
        }
    
        @Operation(summary = "Put接口")
        @PutMapping("/rest/put")
        public String restPut(@RequestBody ParamBO param){
            return "OK:"+param.getId();
        }
    
        @Operation(summary = "Delete接口")
        @DeleteMapping("/rest/delete/{id}")
        public String restDelete(@PathVariable Integer id){
            return "OK:"+id;
        }
    }
    

    2、页面交互

    对于服务端开发来说,在部分场景下是需要进行简单的页面开发的,比如通过页面渲染再去生成文件,或者直接通过页面填充邮件内容等;

    数据接口

    @Controller
    public class PageWeb {
    
        @RequestMapping("/page/view")
        public ModelAndView pageView (HttpServletRequest request){
            ModelAndView modelAndView = new ModelAndView() ;
            // 普通参数
            modelAndView.addObject("name", "cicada");
            modelAndView.addObject("time", "2023-07-12");
            // 对象模型
            modelAndView.addObject("page", new PageBO(7,"页面数据模型"));
            // List集合
            List pageList = new ArrayList<>() ;
            pageList.add(new PageBO(1,"第一页"));
            pageList.add(new PageBO(2,"第二页"));
            modelAndView.addObject("pageList", pageList);
            // Array数组
            PageBO[] pageArr = new PageBO[]{new PageBO(6,"第六页"),new PageBO(7,"第七页")} ;
            modelAndView.addObject("pageArr", pageArr);
            modelAndView.setViewName("/page-view");
            return modelAndView ;
        }
    }
    

    页面解析:分别解析了普通参数,实体对象,集合容器,数组容器等几种数据模型;

    <div style="text-align: center">
        <hr/>
        <h5>普通参数解析h5>
        姓名:<span th:text="${name}">span>
        时间:<span th:text="${time}">span>
        <hr/>
        <h5>对象模型解析h5>
        整形:<span th:text="${page.getKey()}">span>
        字符:<span th:text="${page.getValue()}">span>
        <hr/>
        <h5>集合容器解析h5>
        <table style="margin:0 auto;width: 200px">
            <tr>
                <th>Keyth>
                <th>Valueth>
            tr>
            <tr th:each="page:${pageList}">
                <td th:text="${page.getKey()}">td>
                <td th:text="${page.getValue()}">td>
            tr>
        table>
        <hr/>
        <h5>数组容器解析h5>
        <table style="margin:0 auto;width: 200px">
            <tr>
                <th>Keyth>
                <th>Valueth>
            tr>
            <tr th:each="page:${pageArr}">
                <td th:text="${page.getKey()}">td>
                <td th:text="${page.getValue()}">td>
            tr>
        table>
        <hr/>
    div>
    

    效果图展示

    四、拦截器

    1、拦截器定义

    通过实现HandlerInterceptor接口,完成对两个拦截器的自定义,请求在访问服务时,必须通过两个拦截器的校验;

    /**
     * 拦截器一
     */
    public class HeadInterceptor implements HandlerInterceptor {
        private static final Logger log  = LoggerFactory.getLogger(HeadInterceptor.class);
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                                 Object handler) throws Exception {
            log.info("HeadInterceptor:preHandle");
            Iterator headNames = request.getHeaderNames().asIterator();
            log.info("request-header");
            while (headNames.hasNext()){
                String headName = headNames.next();
                String headValue = request.getHeader(headName);
                System.out.println(headName+":"+headValue);
            }
            // 放开拦截
            return true;
        }
        @Override
        public void postHandle(HttpServletRequest request,HttpServletResponse response,
                               Object handler, ModelAndView modelAndView) throws Exception {
            log.info("HeadInterceptor:postHandle");
        }
        @Override
        public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
                                    Object handler, Exception e) throws Exception {
            log.info("HeadInterceptor:afterCompletion");
        }
    }
    
    /**
     * 拦截器二
     */
    public class BodyInterceptor implements HandlerInterceptor {
        private static final Logger log  = LoggerFactory.getLogger(BodyInterceptor.class);
        @Override
        public boolean preHandle(HttpServletRequest request,HttpServletResponse response,
                                 Object handler) throws Exception {
            log.info("BodyInterceptor:preHandle");
            Iterator paramNames = request.getParameterNames().asIterator();
            log.info("request-param");
            while (paramNames.hasNext()){
                String paramName = paramNames.next();
                String paramValue = request.getParameter(paramName);
                System.out.println(paramName+":"+paramValue);
            }
            // 放开拦截
            return true;
        }
        @Override
        public void postHandle(HttpServletRequest request,HttpServletResponse response,
                               Object handler, ModelAndView modelAndView) throws Exception {
            log.info("BodyInterceptor:postHandle");
        }
        @Override
        public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
                                    Object handler, Exception e) throws Exception {
            log.info("BodyInterceptor:afterCompletion");
        }
    }
    

    2、拦截器配置

    自定义拦截器之后,还需要添加到web工程的配置文件中,可以通过实现WebMvcConfigurer接口,完成自定义的配置添加;

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        /**
         * 添加自定义拦截器
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new HeadInterceptor()).addPathPatterns("/**");
            registry.addInterceptor(new BodyInterceptor()).addPathPatterns("/**");
        }
    }
    

    五、测试工具

    1、Swagger接口

    添加上述的springdoc依赖之后,还可以在配置文件中简单定义一些信息,访问IP:端口/swagger-ui/index.html即可;

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        /**
         * 接口文档配置
         */
        @Bean
        public OpenAPI openAPI() {
            return new OpenAPI()
                    .info(new Info().title("【boot-web】").description("Rest接口文档-2023-07-11")
                    .version("1.0.0"));
        }
    }
    

    2、Junit测试

    在个人的习惯上,Swagger接口文档更偏向在前后端对接的时候使用,而Junit单元测试更符合开发的时候使用,这里是对RestWeb中的接口进行测试;

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @AutoConfigureMockMvc
    public class RestWebTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @Test
        public void testGet () throws Exception {
            // GET接口测试
            MvcResult mvcResult = mockMvc
                    .perform(MockMvcRequestBuilders.get("/rest/get/1"))
                    .andReturn();
            printMvcResult(mvcResult);
        }
    
        @Test
        public void testPost () throws Exception {
            // 参数模型
            JsonMapper jsonMapper = new JsonMapper();
            ParamBO param = new ParamBO(null,"单元测试",new Date()) ;
            String paramJson = jsonMapper.writeValueAsString(param) ;
            // Post接口测试
            MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/rest/post")
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON).content(paramJson)).andReturn();
            printMvcResult(mvcResult);
        }
    
        @Test
        public void testPut () throws Exception {
            // 参数模型
            JsonMapper jsonMapper = new JsonMapper();
            ParamBO param = new ParamBO(7,"Junit组件",new Date()) ;
            String paramJson = jsonMapper.writeValueAsString(param) ;
            // Put接口测试
            MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/rest/put")
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON).content(paramJson)).andReturn();
            printMvcResult(mvcResult);
        }
    
        @Test
        public void testDelete () throws Exception {
            // Delete接口测试
            MvcResult mvcResult = mockMvc
                    .perform(MockMvcRequestBuilders.delete("/rest/delete/2"))
                    .andReturn();
            printMvcResult(mvcResult);
        }
    
        /**
         * 打印【MvcResult】信息
         */
        private void printMvcResult (MvcResult mvcResult) throws Exception {
            System.out.println("请求-URI【"+mvcResult.getRequest().getRequestURI()+"】");
            System.out.println("响应-status【"+mvcResult.getResponse().getStatus()+"】");
            System.out.println("响应-content【"+mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8)+"】");
        }
    }
    

    六、参考源码

    文档仓库:
    https://gitee.com/cicadasmile/butte-java-note
    
    源码仓库:
    https://gitee.com/cicadasmile/butte-spring-parent
    
  • 相关阅读:
    躲避小行星游戏
    mysql部署
    深度学习入门(五十三)循环神经网络——序列模型
    就业班 第四阶段(k8s) 2401--6.4 day2 Dashboard+国产kuboard(好用)+简单命令
    VScode 安装插件后依然不能理解lombok注释的问题
    循环队列(出队、入队、判空、长度、遍历、取头)(数据结构与算法)
    SQL Server 防病毒软件配置
    el-dialog 实现可以拖动的弹窗
    【Python学习笔记】函数
    如何用Redis作为Mybatis的二级缓存?
  • 原文地址:https://www.cnblogs.com/cicada-smile/p/17615881.html