【JAVA WEB实用与优化技巧】如何自己封装一个自定义UI的Swagger组件,包含Swagger如何处理JWT无状态鉴权自动TOKEN获取

news/2024/7/22 13:25:33/文章来源:https://blog.csdn.net/yh4494/article/details/139092370

目录

  • 一、Swagger 简介
    • 1. 什么是 Swagger?
    • 2. 如何使用 Swagger
    • 3. Springboot 中swagger的使用示例
      • 1. maven 引入安装
      • 2. java配置
  • 二、Swagger UI存在的缺点
    • 1.不够方便直观
    • 2.请求的参数没有缓存
    • 3.不够美观
    • 4.如果是JWT 无状态登录,Swagger使用起来就没有那么丝滑了
  • 三、封装以及讨论
    • 封装组件
      • 代码封装
      • 解决无状态登录问题
        • 1. 我们往swagger-bootstrap-ui的右上角加一个按钮,点击出现弹框来配置自动获取登录token
        • 2.javascript 脚本部分实现:
  • 总结

一、Swagger 简介

Swagger 是一款用于 API 设计、构建、文档化和测试的开源工具。它提供了一整套的解决方案,帮助开发者更好地构建和管理 RESTful APIs。以下是对 Swagger 的简要介绍:

1. 什么是 Swagger?

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful Web 服务。它基于 OpenAPI 规范(以前称为 Swagger 规范),该规范定义了 API 的结构。

2. 如何使用 Swagger

  • 定义 API:使用 Swagger Editor 编写 OpenAPI 规范文件,描述 API 的端点、请求参数、响应格式等。
  • 生成文档:通过 Swagger UI 或 Swagger Codegen,生成可视化的 API 文档。
  • 集成到项目:在项目中集成 Swagger 的相关工具,比如在 Spring Boot 项目中使用 springfox-swagger2springfox-swagger-ui 依赖,自动生成和托管 API 文档。

3. Springboot 中swagger的使用示例

以下是一个使用 Swagger 注解的简单示例,展示了如何在 Spring Boot 应用中集成 Swagger:

1. maven 引入安装

<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version>
</dependency>

2. java配置

@EnableSwagger2
@Configuration
public class SwaggerConfig {@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.efficientnotes.boot.web")).paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("Efficient notes api platform").description("apis").termsOfServiceUrl("http://localhost:9000/").contact("yh4494@sina.com").version("1.0").build();}}

title title就是swagger页面上展示的title,比如交ERP api平台
description 详情介绍
termsOfServiceUrl 机构首页地址
contact 联系方式
version 版本号

Spring security 排除拦截:

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/swagger-ui.html").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/v2/*").permitAll().antMatchers("/csrf").permitAll().antMatchers("/").permitAll().anyRequest().authenticated().and().formLogin();}
}

控制器示例:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;@RestController
@Api(value = "示例API", description = "这是一个示例API")
public class ExampleController {@GetMapping("/hello")@ApiOperation(value = "问候接口", notes = "通过传递名字来获得问候语")public String hello(@ApiParam(value = "用户的名字", required = true) @RequestParam String name) {return "Hello, " + name + "!";}
}

在完成上述步骤后,启动 Spring Boot 应用并访问 http://localhost:8080/swagger-ui.html,即可查看自动生成的 API 文档。
Swagger 是一个强大且灵活的工具集,为开发者提供了方便、高效的 API 管理解决方案。通过标准化和自动化,它极大地简化了 API 开发和维护的过程。

在这里插入图片描述
图 ①

二、Swagger UI存在的缺点

1.不够方便直观

swagger ui 布局是上下瀑布式的,比如我访问完A接口,想访问B接口,访问完B接口想继续访问A接口就必须往上翻,接口少还好操作。接口多的话来回就很烦。

2.请求的参数没有缓存

比如我想掉一个post接口来伪造一条数据,第一次访问完成之后,刷新页面后第二次还要重新造数据,就很麻烦,命名我只需要改部分字段重新请求就行,结果每次都要重新填写报文。字段躲起来兼职就是折磨。

3.不够美观

不用多说,当然一个工具类产品美观并不重要,但是美观的产品还是能给人带来心情愉悦的体验,就像你旁边坐着一位漂亮女孩,你整天心情都会好很多。不管如何对我来说工具颜值还是挺重要的。

4.如果是JWT 无状态登录,Swagger使用起来就没有那么丝滑了

因为JWT无状态登录这种需要每次在请求的Header中带上TOKEN,Swagger可没那么只能给你登录接口返回的token带过去,这样就导致无状态session的情况下Swagger的调试功能等于瘫痪状态。

三、封装以及讨论

我们来讨论下如何解决上述一些缺陷给Swagger换一层皮,并且将Swagger封装成组件

很久以前我就关注过一些swaggerui的项目了,比较优秀的是swagger-bootstrap-ui。现在好像升级过不叫这个名字了,但我保存了一份之前的swagger-bootstrap-ui的代码。github的地址是:https://github.com/xiaoymin/swagger-bootstrap-ui,直接可以下载的地址: swagger-bootstrap-ui。

这份UI解决了上述中前三个问题不够方便直观、请求的参数没有缓存、不够美观,虽然不是太好看,但也还好吧!如果对外观还是不够满意的话可以自己修改下样式,盖起来也很方便,我会在下面的文章中进行讨论和介绍。那么我们先看下具体的ui长什么样子。
在这里插入图片描述
图②

看起来是不是比图 ①要舒服多了。那么我们在下面异步异步的讨论如何将这个ui封装成一个组件可以直接引用以及如何解决缺陷的第四点:JWT无状态登录如何自动拼装token。

封装组件

现在我们有了前端代码,可以参考swagger原生的ui就是做成的一个jar包,引入的这个jar包就拥有了swagger-ui。我们也参考下这种方式,前后端代码结合自动配置,项目引入我们的依赖就能直接拥有提供接口文档的能力。

代码封装

我们想让我们的组件提供完整的swagger能力,那么首先我们自己的封装的swagger组件中要引入所有的swagger的依赖。

<dependencies><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version><exclusions><exclusion><artifactId>spring-context</artifactId><groupId>org.springframework</groupId></exclusion><exclusion><artifactId>spring-aop</artifactId><groupId>org.springframework</groupId></exclusion><exclusion><artifactId>jackson-annotations</artifactId><groupId>com.fasterxml.jackson.core</groupId></exclusion><exclusion><artifactId>slf4j-api</artifactId><groupId>org.slf4j</groupId></exclusion><exclusion><artifactId>spring-beans</artifactId><groupId>org.springframework</groupId></exclusion></exclusions></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
</dependencies>

swagger2是通过EnableSwagger2来启用swagger,我们也可以写一个注解来做这件事情,引用我们自己定义的注解就可以自动配置启用我们的swagger组件。

EnableAllensSwagger

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(SwaggerConfig.class)
public @interface EnableAllensSwagger {
}

然后我们需要将所有的配置都配置好,提供我们自己自定义的配置出去,然需要引入的应用项目配置我们提供的配置来自定义Swagger。

SwaggerProperties

/*** SwaggerProperties** @author allens* @since 2024/5/21*/
@Component
@ConfigurationProperties(prefix = "allens.swagger")
@Setter
@Getter
public class SwaggerProperties {private String packages;private String title;private String description;private String termsOfServiceUrl;private String version;private String contact;
}

SwaggerConfig 通过这个类来做组件的autoconfig,当Springboot中启用@EnableAllensSwagger注解的时候,@Import(SwaggerConfig.class)就会自动加载注入 SwaggerConfig,SwaggerConfig回去扫描com.allens.swagger下的所有bean来自动加载组件。

@EnableSwagger2
@Configuration
@ComponentScan("com.allens.swagger")
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerConfig {@ResourceSwaggerProperties swaggerProperties;@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage(swaggerProperties.getPackages())).paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title(swaggerProperties.getTitle()).description(swaggerProperties.getDescription()).termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl()).contact(swaggerProperties.getContact()).version(swaggerProperties.getVersion()).build();}}

接着我们需要把前端的代码copy到项目中,同时项目打包的时候可以把swagger-bootstrap-ui打包到jar包中,同时我们需要访问/${servletContextPath}/doc.html的时候可以正常访问到前端界面。

① 拷贝swagger-boot-ui 到项目中
在这里插入图片描述

② 配置路由映射规则,如果是SpringSecurity也是要忽略doc.html//webjars/**这两个地址。

@Configuration
public class SwaggerWebMvcConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("doc.html").addResourceLocations("classpath:/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/resources/webjars/");}
}
@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers(" "/doc.html","/webjars/**","/v2/api-docs/**","/swagger-resources/**").anonymous().anyRequest().authenticated();http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 添加CORS filterhttp.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);http.addFilterBefore(corsFilter, LogoutFilter.class);
}

然后我们就可以直接到项目中引入我们的组件并且配置了

<dependency><groupId>com.efficientnotes</groupId><artifactId>allens-swagger</artifactId><version>1.0-SNAPSHOT</version>
</dependency>

yml文件配置:

allens:swagger:packages: com.efficientnotes.boot.webtitle: Efficient notes api platformdescription: apistermsOfServiceUrl: http://localhost:9000/version: 1.0contact: allens@sina.com

启动服务并访问: http://localhost:9000/${servletContextPath}/doc.html
在这里插入图片描述

解决无状态登录问题

如果说你的项目是Session模式的方案,那么到这里就结束了。但假如说你是无状态登录的方案,那么就要继续往下看了。无状态登录我们需要把token塞进header中进行请求,那么我们就需要拦截截取登录接口的报文中的token。而且每次请求的时候都要把header中的token拼接上去。

注意只有无状态JWT模式才需要这样做!!!

1. 我们往swagger-bootstrap-ui的右上角加一个按钮,点击出现弹框来配置自动获取登录token

在这里插入图片描述
点击之后是一个弹窗,这个弹窗会要求输入登录接口路径、登录token提取路径以及Token名称。如果走自动获取HeaderJSON是不需要填的。如果想手动塞Header,那么可以使用JSON格式塞TOKEN塞进行去,这种方式不需要配置登录接口路径和登录token提取路径以及token名称。

  • 登录接口路径 你系统的登录接口路径
  • 登录接口提取路径 json path,比如登录接口返回的是{“data”: {token: “xxx”, “other”: “ok”},“msg”: “”},那么登录接口提取路径为:data.token
  • TOKEN名称 接口请求需要携带的Header Token名称
  • Header JSON 有两种情况,如果不填上述三个配置,header JSON就可以手动设置,设置完成之后,每次请求都会自动携带你配置的Header上去。如果配置了上述三个配置,那么每次自动获取完登录的TOKEN之后会自动带出来。

在这里插入图片描述
来看看代码怎么实现的:
doc.html

<div class="sbu-header-right" style="margin-top:12px"><div class="col-sm-6"><button style="background: #0d5aa7" onclick="showPromoteDefaultHeader()" type="button" class="btn btn-info">通用头设置</button></div>
</div>
2.javascript 脚本部分实现:
function showPromoteDefaultHeader() {var test = layer.open({type: 1, // page 层类型area: ['700px', '500px'],title: '配置头信息',shade: 0.6, // 遮罩透明度shadeClose: true, // 点击遮罩区域,关闭弹层maxmin: true, // 允许全屏最小化anim: 0, // 0-6 的动画形式,-1 不开启content:`<div class="allens-dialog"><div class="element"><span>登录接口路径</span><input id="allens-dialog-login-interface-name" name="loginInterfaceName" /></div><div class="element"><span>登录token提取路径</span><input id="allens-dialog-json-path" name="jsonPath" /></div><div class="element"><span>TOKEN名称</span><input id="allens-dialog-token-name" name="tokenName" /></div><div class="element"><span>Header JSON</span><textarea id="allens-dialog-header-json" style="width: 100%;" cols="10" rows="8" name="" id=""></textarea></div><button class="btn btn-primary btn-lg" id="allens-dialog-submit">提交</button></div>`});function notEmpty (variable) {if (variable === null || variable === undefined || variable === '') {// 变量为空return false;}return true;}var button = document.querySelector('#layui-layer' + test + ' #allens-dialog-submit');var loginInterFaceName = document.querySelector('#layui-layer' + test + ' #allens-dialog-login-interface-name');var jsonPath = document.querySelector('#layui-layer' + test + ' #allens-dialog-json-path');var headerJson = document.querySelector('#layui-layer' + test + ' #allens-dialog-header-json');var tokenName = document.querySelector('#layui-layer' + test + ' #allens-dialog-token-name');button.onclick = () => {if (notEmpty(loginInterFaceName)) {localStorage.setItem('LOGININTERFACENAME', loginInterFaceName.value)}if (notEmpty(jsonPath)) {localStorage.setItem('JSONPATH', jsonPath.value)}if (notEmpty(headerJson)) {localStorage.setItem('HEADERJSON', headerJson.value)}if (notEmpty(tokenName)) {localStorage.setItem('TOKENNAME', tokenName.value)}layer.close(test);}// text.setAttribute('placeholder', '格式为JSON:{"headerName": "headerValue"}')var data = localStorage.getItem('HEADERJSON');if (data) {const jsonObject = JSON.parse(data);const jsonString = JSON.stringify(jsonObject, null, 2);headerJson.value = jsonString;}loginInterFaceName.value = localStorage.getItem('LOGININTERFACENAME')jsonPath.value = localStorage.getItem('JSONPATH')tokenName.value = localStorage.getItem('TOKENNAME')
}

可以下载下源码看下具体的实现,暂时只支持CSDN下载,后续我会传到github。https://download.csdn.net/download/yh4494/89335880

总结

至此我们讨论的四个swagger的缺点就全部解决了,当然你也可以自己去修改样式,源码都有的怎么该都可以,只要不要有大的bug就行。甚至可以上传到中央仓库给大家使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1054366.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

node.js(express)+MongoDB快速搭建后端---新手教程

前言&#xff1a; Node.js是一个基于 Chrome V8引擎的JavaScript运行环境&#xff0c;是对于前端工程师来说学习成本最小的后端实现方法&#xff0c;本篇文章总结如何从0-1写一个后端的登录接口 一、检查node环境 先检查自己的node是否安装 一般来说前端工程师的电脑环境肯定…

(4)医疗图像处理:MRI磁共振成像-成像技术--(杨正汉)

目录 一、特殊成像技术 1.水成像技术 2.化学位移成像技术 二、成像辅助技术 1.脂肪抑制技术 2.磁化转移技术 3.流动补偿技术 4.空间饱和空间标记技术 5.生理门控及导航回波技术 所有的这些技术最终就是为了使得K空间通过傅里叶变化之后得到的图片变的更为清晰。 一、…

用于癌症免疫治疗的自佐剂聚胍纳米疫苗

近期&#xff0c;沈阳药科大学孙进教授团队、罗聪教授团队与新加坡国立大学陈小元教授团队共同合作在美国化学会旗下期刊《ACS nano》&#xff08;IF17.1&#xff09;上发表题为“Self-Adjuvanting Polyguanidine Nanovaccines for Cancer Immunotherapy”&#xff08;用于癌症…

【FPGA】Verilog语言从零到精通

接触fpga一段时间&#xff0c;也能写点跑点吧……试试系统地康康呢~这个需要耐心但是回报巨大的工作。正原子&&小梅哥 15_语法篇&#xff1a;Verilog高级知识点_哔哩哔哩_bilibili 1Verilog基础 Verilog程序框架&#xff1a;模块的结构 类比&#xff1a;c语言的基础…

React Hooks是如何保存的

React 函数式组件是没有状态的&#xff0c;需要 Hooks 进行状态的存储&#xff0c;那么状态是怎么存储的呢&#xff1f;Hooks是保存在 Fiber 树上的&#xff0c;多个状态是通过链表保存&#xff0c;本文将通过源代码分析 Hooks 的存储位置。 创建组件 首先我们在组件中添加两…

浅谈JMeter运行原理

浅谈JMeter运行原理 JMeter架构基础 JMeter基于Java平台开发&#xff0c;运行于Java虚拟机&#xff08;JVM&#xff09;之上。这意味着它可以在任何支持JVM的操作系统上运行&#xff0c;包括Windows、Linux、macOS等。其核心架构设计围绕着多线程执行机制&#xff0c;这使得它…

虚拟机报错:VMX 进程已提前退出。VMware Workstation 无法连接到虚拟机。

解决报错&#xff1a;VMware Workstation 无法连接到虚拟机。请确保您有权运行该程序、访问该程序使用的所有目录以及访问所有临时文件目录。 VMX 进程已提前退出。 解决方案&#xff1a;右键桌面图标进入VMware Workstation Pro的属性设置&#xff0c;兼容性–勾选“以管理员…

【python 进阶】 绘图

1. 将多个柱状绘制在一个图中 import seaborn as sns import matplotlib.pyplot as plt import numpy as np import pandas as pd# 创建示例数据 categories [A, B, C, D, E] values1 np.random.randint(1, 10, sizelen(categories)) values2 np.random.randint(1, 10, siz…

AI绘画Stable Diffusion XL 可商用模型!写实艺术时尚摄影级真实感大模型推荐(附模型下载)

大家好&#xff0c;我是设计师阿威 大家在使用AI绘画的时候&#xff0c;是不是遇到这种问题&#xff1a;收藏的模型确实很多&#xff0c;可商用的没几个&#xff0c;而今天阿威将给大家带来的这款写实艺术时尚摄影级真实感大模型-墨幽人造人XL&#xff0c; 对于个人来讲完全是…

Linux 删除SSH密钥(id_ed25519),重新生成

在Linux系统中&#xff0c;重新生成SSH密钥&#xff08;比如id_ed25519&#xff09;的过程包括删除现有的密钥文件并生成一个新的。 以下是具体的步骤&#xff1a; 0. 查看下是否有密钥 1. 删除原有的id_ed25519密钥 默认情况下&#xff0c;SSH密钥存储在用户的主目录下的 .…

【YOLOv10】使用yolov10训练自己的数据集/验证 /推理 /导出模型/ONNX模型的使用

YOLOv10: 实时端到端的目标检测。 性能 YOLOv10比最先进的YOLOv9延迟时间更低&#xff0c;测试结果可以与YOLOv9媲美&#xff0c;可能会成为YOLO系列模型部署的“新选择”。 目录 1 数据准备 2 配置文件 3 训练 4 验证 5 预测 6 导出模型 7 ONNX模型的使用 官方论文地址…

高铁Wifi是如何接入的?

使用PC端的朋友&#xff0c;请将页面缩小到最小比例&#xff0c;阅读最佳&#xff01; 在飞驰的高铁上&#xff0c;除了窗外一闪而过的风景&#xff0c;你是否好奇过&#xff0c;高铁Wifi信号如何连接的呢&#xff1f; 远动的火车可不能连接光纤吧&#xff0c;难道是连接的卫星…

简介有限面积和无限周长

前言 分形理论是一种非常重要的科学概念,它被广泛应用于物理学、数学、生物学等领域。分形理论描述了一种重复自相似的结构,这种结构在不同的尺度上都具有类似的形态。由于分形理论的应用广泛且深远,了解分形理论可以帮助人们更好地理解自然界和人造世界中的现象。 作为一…

orin部署tensorrt、cuda、cudnn、pytorch、onnx

绝大部分参考https://blog.csdn.net/qq_41336087/article/details/129661850 非orin可以参考https://blog.csdn.net/JineD/article/details/131201121 报错显卡驱动安装535没法安装、原始是和l4t-cuda的部分文件冲突 Options marked [*] produce a lot of output - pipe it t…

Llama模型家族之使用 Supervised Fine-Tuning(SFT)微调预训练Llama 3 语言模型(三)通过web页面方式微调

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

【面试干货】选择排序

【面试干货】选择排序 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、实现思想 选择排序的实现思想是每次从未排序的部分中选择最小的元素&#xff0c;然后将其放到已排序部分的末尾。 具体步骤如下&#xff1…

揭秘智慧校园:可视化技术引领教育新篇章

随着科技的飞速发展&#xff0c;我们的生活方式正在经历一场前所未有的变革。而在这场变革中&#xff0c;学校作为培养未来人才的重要基地&#xff0c;也在不断地探索与创新。 一、什么是校园可视化&#xff1f; 校园可视化&#xff0c;就是通过先进的信息技术&#xff0c;将学…

轻兔推荐 —— 一个好用的软件服务推荐平台

给大家推荐一个好用的的软件服务推荐平台&#xff1a;轻兔推荐 - https://app.lighttools.net/ 界面 网站界面简洁大方&#xff0c;没有太多杂七杂八的功能和页面&#xff0c;有明暗主题色可以选择&#xff0c;默认为亮色&#xff0c;可在网站上方手动切换。 内容 每工作日…

2024-05-29 服务器开发-c++线程池与task-思考

摘要: 无论是什么系统&#xff0c;线程池和task都是给上层所提供的基础的功能单元。本文记录一些核心的设计思想。 线程池要面对的场景: 调用下层接口时&#xff0c;被IO阻塞&#xff0c;导致整个服务无法对外提供服务更上层调用本模块接口时&#xff0c;是需要做到同步&#…

MT2076 小码哥处理订单

思路&#xff1a; 使用二分&#xff1a;题目中隐含条件&#xff1a;如果不满足&#xff0c;需要找到第一个不满足的订单。 二分法需要满足单调性or有一个界线使前后两部分性质相反。这里的”界线“为&#xff1a;是否满足条件。假设第i天无法满足&#xff0c;则后面的所有天都…