美文网首页
Spring-web

Spring-web

作者: begonia_rich | 来源:发表于2019-10-10 02:23 被阅读0次

    spring-web,spring-webmvc模块笔记,这块的类非常多,采用包划分对主要类&接口进行说明

    Http部分

    常用的常量和枚举

    MediaType,HttpStatus,HttpHeaders,RequestMethod(有非常常用的key)

    常见的接口

    ClientHttpRequestFactory:请求对象生成工厂.常见的有HttpComponentsClientHttpRequestFactory,封装HttpClient对象

    ClientHttpRequest:请求对象.可以执行请求方法,返回响应对象

    ClientHttpResponse:响应对象.对于HttpComponentsClientHttpResponse而言就是封装HttpResponse

    InterceptingClientHttpRequest-这里引入请求拦截器采用iterator来实现的责任链模式,这种实现还是第一次见,和i++的套路一样

    HttpMessageConverterExtractor:消息转换提取器.类型转换

    HttpMessageConverter:这个接口非常重要.它是实际的Java对象+MediaType与Http消息(请求->参数读取,响应->返回值写入)之间的转换,如MappingJackson2HttpMessageConverter

    HttpEntity:表示一个Http实体对象,含有HttpHeaders信息和返回的body信息.后面分为RequestEntity和ResponseEntity

    RestTemplate:通过api编程式发起http请求.实现部分通过RequestCallback构造请求回调,处理头信息和参数转换.返回时同理,它引入请求工厂和响应消息提取器帮助处理

    remoting-httpinvoker

    HttpInvokerRequestExecutor:请求执行器.默认采用jdk的http发送,可以换成HttpComponentsHttpInvokerRequestExecutor使用apache的

    HttpInvokerProxyFactoryBean:客户端代理.配置url,本地接口代理,调用请求执行器获取执行结果

    HttpInvokerServiceExporter:服务端执行器.配置实际执行代理对象,序列化和反序列化返回

    web包

    SpringServletContainerInitializer:servlet3.0支持的容器初始化调用方式,spring将会对jar包中所有的WebApplicationInitializer类进行初始化

    WebApplicationInitializer:内部的容器初始化回调的接口

    HttpRequestHandler:主要是配合HttpRequestHandlerAdapter进行转换映射调用.可以比较通用的处理request和response,框架内使用较多

    RequestAttributes:定义了request的基本属性及操作.使用较多的是ServletRequestAttributes

    RequestScope/SessionScope:继承AbstractRequestAttributesScope,将资源绑定到request中.每次请求进来后通过RequestContextHolder获取

    context

    WebApplicationContext:web容器的顶层定义.它继承原ApplicationContext

    ConfigurableWebApplicationContext:可配置的WebApplicationContext接口定义

    ConfigurableWebEnvironment:定义web环境.主要为将ServletContext和ServletConfig内容加载到环境中,一般实现为StandardServletEnvironment

    ServletContextScope:与ServletContext对象绑定参数

    StandardServletEnvironment:将ServletConfigPropertySource与ServletContextPropertySource对应的配置参数注册到环境对象中

    AbstractContextLoaderInitializer:将ContextLoaderListener绑定到ServletContext

    拓展容器类

    AbstractRefreshableWebApplicationContext:web容器的基类.在postProcessBeanFactory阶段进行了初始化,包括ServletContextAware,ServletConfigAware的支持,web中scope的支持,ServletRequest,ServletResponse的属性注入支持,将ServletContext,ServletConfig加入容器等对应的web调整

    AnnotationConfigWebApplicationContext:重写loadBeanDefinitions方法.载入AnnotatedBeanDefinitionReader对象,将处理注解需要的配置类注册到容器

    XmlWebApplicationContext:重写loadBeanDefinitions方法.载入XmlBeanDefinitionReader对象,以xml的形式直接解析给定的资源集合

    GenericWebApplicationContext:基本web应用,包含ServletContext对象的Aware提供,web相关作用域注册,主题支持

    Listener相关

    ContextLoaderListener:容器初始化后加载context对象并刷新,绑定context对象到容器中(默认加载的是XmlWebApplicationContext)

    ContextCleanupListener:关闭容器时.对容器的属性中以”org.springframework.”开头的属性检查是否为DisposableBean,符合条件则调用destroy()方法

    RequestContextListener:实现了ServletRequestListener,当有请求进入时将request对象绑定到RequestContextHolder中

    ServletRequestHandledEvent:当请求完成后会推送的事件

    cors

    origin不支持ant风格 表示全部否则必须精准匹配域名,比如:http://domain1.com

    CorsProcessor:DefaultCorsProcessor对跨域请求的是否允许判定,以及响应头信息

    CorsConfiguration:跨域配置.如允许的origin/method等

    CorsConfigurationSource:管理url对应的CorsConfiguration

    CorsRegistration:标记了url和对应的CorsConfiguration配置

    CorsRegistry:提供了管理一组CorsRegistration的能力

    filter

    GenericFilterBean:实现Filter默认的init方法,尝试将过滤器初始化的参数设置到当前对象属性中.通过BeanWrapper形式设值的,可以参考下

    OncePerRequestFilter:保证一次请求只执行一次过滤器.其通过在request中增加FilterName属性进而判断,还有默认的不过滤异步,不过滤已产生错误行为.这是一个很好的基类,基本上所有的Filter都继承此基类.

    CommonsRequestLoggingFilter:在拦截器执行前和执行后进行日志输出

    CharacterEncodingFilter:字符编码设值过滤器.在执行前在request或response调用setCharacterEncoding(encoding)方法

    CorsFilter:跨域过滤器处理.判断是否跨域请求.进行判定处理,主要依托CorsProcessor和CorsConfigurationSource类

    HiddenHttpMethodFilter:在post请求中参数(默认_method)的值转为request.getMethod方法返回的值,用于有些请求中的方法指定

    HttpPutFormContentFilter:支持PUT和PATCH请求的表单提交(默认情况下只支持GET和POST的表单)

    RelativeRedirectFilter:通过RelativeRedirectResponseWrapper包装原Response对象,重写默认的sendRedirect重定向方法改为相对路径重定向

    RequestContextFilter:主要将request对象绑定到RequestContextHolder中,方便后续直接获取

    MultipartFilter:判断当前content-type为”multipart/*”的多文件类型,那么将request转为MultipartHttpServletRequest

    method

    ControllerAdviceBean:将@ControllerAdvice注解转换为类对象操作

    HandlerMethodArgumentResolver:参数解析器 对进行注解/指定类型的参数做转换

    HandlerMethodReturnValueHandler:方法返回处理器

    ModelAttributeMethodProcessor:默认非简单类型的处理转换.将表单数据读取通过DataBinder设值到参数对象,默认表单提交的参数转换器

    RequestResponseBodyMethodProcessor:将@ResponseBody注解的参数从body体读取换成对应类型的对象以及支持 @ResponseBody注解的返回值返回json,实际上通过HttpMessageConverter进行的转换,ReturnValueHandler更多是处理header信息设置,具体的转换还是委托出去通过转换器处理

    对参数和返回值做处理的2个常见处理器就是ModelAttributeMethodProcessor和RequestResponseBodyMethodProcessor

    WebDataBinderFactory:创建DataBinder的工厂

    DataBinder:进行数据绑定,转换和验证的聚合类.简单来看就是通过k-v的形式进行设值,并且将key对应value的对应属性类型进行转换处理

    multipart

    MultipartFile: 上传文件的定义.主要实现为StandardMultipartFile

    MultipartRequest:定义了Request操作获取MultipartFile的接口

    MultipartHttpServletRequest:多文件上传时request会被转为此接口定义,它继承了MultipartRequest接口.

    StandardMultipartHttpServletRequest:servlet3.0的标准实现.主要将javax.servlet.http.Part对象转为StandardMultipartFile对象

    MultipartResolver:判断当前Request是否为多文件请求,转换request到MultipartHttpServletRequest.一般实现为StandardServletMultipartResolver

    必须配合MultipartFilter过滤器使用,可以配合RequestPartMethodArgumentResolver参数解析支持@RequestPart注解

    util

    DefaultUriBuilderFactory:提供uri的基本转换,编码,参数设置.

    ContentCachingRequestWrapper:缓存request的内容,通过getContentAsByteArray方法可以多次获取

    ContentCachingResponseWrapper:缓存response的响应内容,通过getContentAsByteArray方法可以多次获取,最后可以通过copyBodyToResponse响应

    webmvc

    FrameworkServlet:主要定义初始化流程方法(initWebApplicationContext),以及HttpServlet的所有请求方法统一转到processRequest让子类doService处理,在顶层进行了Request和LocaleContext的线程绑定


    这里详细说下DispatcherServlet核心转发流程部分.

    初始化包含:

    initMultipartResolver(context);

    initLocaleResolver(context);

    initThemeResolver(context);

    initHandlerMappings(context);

    initHandlerAdapters(context);

    initHandlerExceptionResolvers(context);

    initRequestToViewNameTranslator(context);

    initViewResolvers(context);

    initFlashMapManager(context);

    这里的初始化很多都是直接指定到"beanName+类型"也就是beanName不对的话也不会成功,比如MultipartResolver的beanName就必须是multipartResolver

    主要执行流程:

    入口请求都会到doService,它先把自己流程中使用到的Bean通过request属性暴露出去,然后执行核心转发接口doDispatch,

    先检查是否为multipartRequest,如果是的话,通过multipartResolver把request换为MultipartHttpServletRequest

    通过初始化的HandlerMapping列表依次进行getHandler来获取本次uri可以支持的HandlerExecutionChain(常见RequestMappingHandlerMapping)

    继续通过初始化的HandlerAdapter列表进行support检查handler以找到对应的HandlerAdapter.

    HandlerExecutionChain中已经初始化过了拦截器,此时开始执行前置拦截器

    然后通过HandlerAdapter开始进行实际调用(常见RequestMappingHandlerAdapter)

    这里举例RequestMappingHandlerAdapter#invokeHandlerMethod流程
    
    1@InitBinder解析
    
        解析执行类和@ControllerAdvice注解类的@InitBinder注解方法. 通过使用统一的WebDataBinderFactory在初始化DataBinder时将所有参数调用@InitBinder注解的方法在参数中传入DataBinder对象,让方法可以调整所有的参数转换和赋值
    
    2@ModelAttribute解析
    
        解析执行类和@ControllerAdvice注解类的@ModelAttribute注解方法.  在执行目标方法前,先调用所有的@ModelAttribute注解方法
    
    3执行目标方法并返回
    
        生成ServletInvocableHandlerMethod对象来执行目标方法.将参数一一通过参数解析器转换,然后调用方法,最后如果需要的话对返回值进行转换处理
    

    然后执行后置拦截器,最后进行结果处理

    结果处理流程-如果有异常,那么通过之前初始化的HandlerExceptionResolver列表获取可以解析此异常的方法,并调用,获取返回值(通常无需返回值,直接通过response响应)替换原返回值,将获得的返回值ModelAndView进行处理,主要为通过之前初始化的ViewResolver列表获取View对象,然后调用将之前全流程组装的Model对象和request,response一起传入渲染方法render中进行响应处理(比如jsp,html页面,静态资源等)


    Handle映射处理器

    HandlerMapping: 映射处理器顶层接口.处理url->handler的关系,以及url->HandlerInterceptor的关系

    AbstractHandlerMapping:定义getHandler的基本流程,委托子类获取Handler对象,添加符合条件的过滤器到执行链中,生成HandlerExecutionChain返回

    AbstractHandlerMethodMapping:定义了getHandlerInternal基本流程,进行url匹配HandlerMethod,如果有多于1个HandlerMethod被匹配,抛出错误信息,否则设置request的attribute信息将HandlerMapping接口中的几个属性暴露出来,最后返回HandlerMethod.同时它也实现了Handler和Method的收集和url映射规则.它实现了InitializingBean,在初始化时调用子类判定bean和method是否需要映射,如果需要将映射信息保存到MappingRegistry内部类中

    RequestMappingInfoHandlerMapping:为Handler引入具化类型RequestMappingInfo,同时实现部分父类抽象方法

    RequestMappingHandlerMapping:引入@Controller和@RequestMapping注解的实现.实现父类需要具体判断的抽象方法

    BeanNameUrlHandlerMapping:BeanName以”/“开头的直接当做url注册为处理,不过需要注意bean要实现Controller接口,可以选择继承AbstractController

    其他关联类

    HandlerExecutionChain:保存handler对象(这里的handler对象会被对应的XXAdapter进一步处理),控制当次请求过滤器链HandlerInterceptor的访问

    HandlerMethod:保存Handler和Method的上下文和提供便捷操作方法

    InvocableHandlerMethod:提供了请求参数转换和调用方法的功能

    ServletInvocableHandlerMethod:增加了对返回值的处理转换能力

    HandlerMethodArgumentResolver:参数解析器 一般情况通过HandlerMethodArgumentResolverComposite包装组合引用

    HandlerMethodReturnValueHandler:返回值处理器 一般情况通过HandlerMethodReturnValueHandlerComposite包装组合引用

    Handle适配器-适配转换Handler的调用处理

    HandlerAdapter:接过HandlerMapping解析请求得到的handler对象.更精确的定位到能够执行请求的方法包装类

    HttpRequestHandlerAdapter:转换适配带HttpRequestHandler接口handle的方法调用

    SimpleControllerHandlerAdapter:转换适配带Controller接口handle的方法调用

    RequestMappingHandlerAdapter:对RequestMappingInfo这个Handler的具化处理.这是一个主要的映射处理类.初始化了默认的参数解析器,@InitBinder时调用的参数解析器,返回值参数解析器,支持了@ControllerAdvice注解类中@InitBinder方法的全局调用,@ModelAttribute注解的方法级调用和全局调用

    DataBinder-包装对象的访问/转换/验证封装

    ServletRequestDataBinder:在bind时支持将reqesut.getParameterNames中的属性绑定到类对象属性

    ExtendedServletRequestDataBinder:将URI信息也一起绑定到对象属性中

    WebDataBinderFactory: DataBinder工厂.其中InitBinderDataBinderFactory在创建DataBinder时将@InitBinder注解的方法均触发执行

    method

    RequestBodyAdvice:允许在读取消息前后进行操作消息内容

    ResponseBodyAdvice:允许在返回消息体前进行操作消息内容

    RequestResponseBodyAdviceChain:持有一组RequestBodyAdvice和ResponseBodyAdvice.在RequestResponseBodyMethodProcessor对参数进行解析前后,触发所有的回调.

    常见转换器转换器

    AbstractJackson2HttpMessageConverter:Jackson转换的顶层接口

    MappingJackson2HttpMessageConverter:通过ObjectMapper转换Json对象,用的非常多

    MappingJackson2XmlHttpMessageConverter:通过ObjectMapper转换xml对象

    AbstractJsonHttpMessageConverter:Json转换顶层接口

    GsonHttpMessageConverter:通过Gson转换json对象

    ByteArrayHttpMessageConverter:可以直接将body内容转为byte[]参数,也可以直接返回byte[]

    FormHttpMessageConverter:表单提交内容可以转为MultiValueMap参数

    resolver-常用的参数/返回值解析器

    很多参数解析器的功能都是通过HttpMessageConverter来实现,他们自身实现具体一种MediaType数据获取方式,然后将具体的参数转换方法让转换器完成

    以MethodArgumentResolver结尾一般代表方法参数解析

    RequestPartMethodArgumentResolver:支持参数注解@RequestPart解析

    RequestAttributeMethodArgumentResolver:支持参数注解@RequestAttribute解析

    PathVariableMethodArgumentResolver:支持非Map参数类型的@PathVariable注解解析

    PathVariableMapMethodArgumentResolver:支持Map参数类型的@PathVariable注解解析

    ServletRequestMethodArgumentResolver:支持servlet的一些api参数解析.如:ServletRequest,MultipartRequest,InputStream

    ServletResponseMethodArgumentResolver:支持servlet的一些api返回值解析.如:ServletResponse

    SessionAttributeMethodArgumentResolver:支持参数注解@SessionAttribute解析

    以ReturnValueHandler结尾一般代表返回值解析

    HttpHeadersReturnValueHandler:支持返回类型HttpHeaders

    ModelAndViewMethodReturnValueHandler:支持返回值类型ModelAndView

    ViewMethodReturnValueHandler:支持View类型的返回值解析

    ViewNameMethodReturnValueHandler:支持String类型的返回值当做viewName进行解析

    以Processor结尾一般代表既有参数值解析也有返回值解析

    HttpEntityMethodProcessor:支持参数和返回值为HttpEntity类型

    RequestResponseBodyMethodProcessor:支持@RequestBody参数注解和@ResponseBody的返回值注解

    MvcConfig配置

    基本上xxRegistry都是管理一组配置项 xxConfigurer就是全局统一的配置项

    InterceptorRegistry:拦截器注册表.管理一组InterceptorRegistration拦截器配置类,可以通过getInterceptor()方法得到MappedInterceptor对象

    CorsRegistry:跨域注册表.同理.持有一组CorsRegistration,然后可以直接获取CorsConfiguration

    WebMvcConfigurer:配置接口.子类实现后可以通过xxConfigurer和xxRegistry进行调整

    WebMvcConfigurationSupport:@EnableWebMvc注解提供的默认配置 注意,它不是配置接口,它是持有一组WebMvcConfigurer的配置类对象,和默认配置

    默认HandlerMapping有RequestMappingHandlerMapping,BeanNameUrlHandlerMapping以及SimpleUrlHandlerMapping(注册的有
    viewControllerHandlerMapping(对应的handler为ParameterizableViewController)
    resourceHandlerMapping(对应的handler为ResourceHttpRequestHandler)
    defaultServletHandlerMapping(对应的handler为DefaultServletHttpRequestHandler))

    默认的HandlerAdapter有RequestMappingHandlerAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter(必须有这些适配器,否则上面的各种Handler就没法起作用了)

    默认的异常解析器组合异常解析器HandlerExceptionResolverComposite
    其中最主要的为ExceptionHandlerExceptionResolver,其次还有ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver这俩

    可以配置很多WebMvcConfigurer的Bean,最后在WebMvcConfigurationSupport中会进行汇总回调配置

    Mvc

    Controller:接口,处理Request到ModelAndView转换

    UrlFilenameViewController:url解析后直接访问资源视图.可以简单的配置BeanName映射然后访问静态资源

    ModelAndView:方便将Model和View一起操作.在它之前的mav信息都是操作ModelAndViewContainer对象

    ViewResolver:视图解析器.通过viewName返回View对象.比较常见的是InternalResourceViewResolver(普通的jsp,html加载都是它)

    View:视图.只有一个渲染接口,响应内容

    Resource

    通过SimpleUrlHandlerMapping将url规则与ResourceResolver建立关联,最后通过HttpRequestHandlerAdapter调用ResourceHttpRequestHandler

    ResourceHttpRequestHandler :资源请求处理handler

    ResourceResolver是指资源解析过滤器链,包含有

    CachingResourceResolver:增加一层spring缓存

    GzipResourceResolver:gzip打包

    PathResourceResolver:根据uri进行资源访问

    DefaultResourceResolverChain:便于通过责任链模式操作ResourceResolver列表

    DefaultResourceTransformerChain:便于通过责任链模式操作ResourceTransformer列表

    View

    InternalResourceViewResolver:内部资源视图解析器.比较常用的视图解析器,可以配置前后缀方便代码简化,以前使用的非常多,现在前后端分离后几乎不使用了

    InternalResourceView:内部资源视图.通过RequestDispatcher的include(request, response);方法直接解析


    spring-web中常见注解

    @InitBinder:通过注解指定方法调整WebDataBinder对象(InitBinderDataBinderFactory).是整个流程所有参数都生效的

    @ModelAttribute:支持在方法上设置,即可以调整Model对象属性(ModelFactory),也可以在参数标注,即为表单映射数据解析(ModelAttributeMethodProcessor).不过实际上默认的非简单类型都使用的这个参数解析器,实际参数上用的不多(参考这个)

    @ExceptionHandler:标注方法可以处理的异常.配合@ControllerAdvice使用

    @ControllerAdvice:注解内部使用@ExceptionHandler,@InitBinder,@ModelAttribute注解的方法应用到所有的@RequestMapping注解的方法.其中异常处理使用的最多

    @CookieValue:将cookie里的值直接取到方法参数中(ServletCookieValueMethodArgumentResolver)

    @CrossOrigin:跨域请求配置,支持类级和方法级(RequestMappingHandlerMapping#initCorsConfiguration)

    @RequestMapping:用于将Web请求映射到请求处理类中的方法的注解(RequestMappingHandlerMapping)

    @GetMapping,@PostMapping,@PutMapping,@DeleteMapping,@PatchMapping 都是@RequestMapping注解指定了http方法的组合注解

    @MatrixVariable:便捷的提取指定格式参数(MatrixVariableMapMethodArgumentResolver,MatrixVariableMethodArgumentResolver)

    @PathVariable:处理URI中参数的提取(PathVariableMethodArgumentResolver)

    @RequestAttribute:类似request.getAttribute(key)一样获取Attribute中的值(RequestAttributeMethodArgumentResolver)

    @RequestBody:处理body体内数据转换.一般情况是json数据(RequestResponseBodyMethodProcessor)

    @ResponseBody:将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据,需要注意的呢,在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。

    @RequestHeader:提取Header信息(RequestHeaderMethodArgumentResolver)

    @RequestParam:默认的处理简单的类型参数映射.主要有get请求和post的表单提交(RequestParamMethodArgumentResolver)

    @RequestPart:接收文件类型(MultipartFile)或文件集合类型(比如List<MultipartFile>)可以指定文件名的注解,如果没有此注解将会使用参数名作为默认名称

    @ResponseStatus:在对应方法内发生异常时,将会按照方法或异常类含有的该枚举值进行设值提示

    @RestController:@Controller+@ResponseBody的组合注解

    @RestControllerAdvice:@ControllerAdvice+@ResponseBody的组合注解

    @SessionAttribute,@SessionAttributes:从session获取值

    @EnableWebMvc:引入默认配置DelegatingWebMvcConfiguration


    类型转换的对比 BeanWrapper、DataBinder、ConversionService、Formatter

    BeanWrapper

    BeanWrapper提供了对属性设置的支持。BeanWrapper通常不会被应用程序的代码直接使用,而是由DataBinder和BeanFactory使用。

    BeanWrapper的名字已经部分暗示了它的工作方式:它包装一个bean以对其执行操作,比如设置和获取属性。

    支持复杂对象路由,数组对象,map对象

    内置PropertyEditor,PropertyEditor隶属于Java Bean规范。PropertyEditor只提供了String <-> Object的转换。

    使用PropertyEditorRegistry,继承PropertyEditorRegistrySupport管理PropertyEditor

    ConversionService

    ConversionService及其相关一套类型转换机制是一套通用的类型转换SPI,相比PropertyEditor只提供String<->Object的转换,ConversionService能够提供任意Object<->Object的转换。

    所以我们可以猜测,Spring为何要使用ConversionService替代PropertyEditor有三个原因:

    1ConversionService功能更强大,支持的类型转换范围更广。

    2ConverterFactory支持一整个class hierarchy的转换(也就是多态),PropertyEditor则不行。

    3Java Bean这个规范最初是和Java GUI(Swing)一起诞生的,PropertyEditor接口里有大量和GUI相关的方法,显然已经过时了。顺便提一句,Java Bean和POJO不是一个概念,Java Bean不仅有setter、getter,还有一系列和Java GUI配套的东西。

    DefaultConversionService支持配置任意的ConverterRegistry

    Formatter

    Formatter SPI是另外一套和PropertyEditor类似的,String<->Object的转换机制,但是有两个优点:

    接口更干净,没有关于GUI的部分,只有print和parse两个方法。

    基于注解,支持同一类型的属性根据不同的格式来做String<->Object的转换。比如日期类型,一个字段的格式是yyyy-MM-dd,另一个格式是yyyyMMdd,如果利用PropertyEditor是比较麻烦,但是在这里就可以利用@DateTimeFormat来达到这个效果。

    Spring提供了DefaultFormattingConversionService来支持Formatter SPI,也就是说如果要使用Formatter SPI,依然可以利用ConversionService接口.这里采用适配器进行了转换

    注意:Formatter SPI必须基于注解才可以使用,这点和ConversionService基于类型不同。

    Formatter是PropertyEditor的另一种转换格式的补充

    DataBinder

    DataBinder主要提供了两个功能:

    1利用BeanWrapper,给对象的属性设值

    2在设值的同时做Validation

    关系图

    相关文章

      网友评论

          本文标题:Spring-web

          本文链接:https://www.haomeiwen.com/subject/vzeductx.html