【SpringMVC】| 拦截器(含源码分析) 今天药忘吃喽~ 2024-03-16 13:50 16阅读 0赞 **目录** 拦截器 1. 拦截器的介绍 2. 拦截器的三个抽象方法 3. 拦截器的使用 4. 多个拦截器的执行顺序 Java核心技术大会 文末福利(Java核心技术卷) -------------------- ## 拦截器 ## **拦截器能拦截请求,前面学习的过滤器也能拦截请求,那两者有什么区别呢?** > **过滤器:**过滤器是过滤从浏览器发送的所有请求,所以过滤器就是作用在浏览器----》前端控制器DispatcherServlet之间**!** > > **拦截器:**前端控制器DispatcherServlet接收到请求后进行处理,去与Controller的RequestMapping请求映射进行匹配,所以拦截器就是作用在控制器Controller执行的前后! ![77b535386f184aa98c8a00a1b13c348b.png][] #### 1. 拦截器的介绍 #### > (1)SpringMVC中的拦截器用于拦截控制器方法的执行! > > (2)SpringMVC中的拦截器需要实现HandlerInterceptor或者继承HandlerInterceptorAdapter! > > (3)SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置。 拦截器的执行原理 > ①**preHandle():**在请求被处理之前进行操作;**预处理**。 > ②**postHandle():**在请求被处理之后,但结果还没有渲染前进行操作,可以改变响应结果;**后处理。** > ③**afterCompletion:**所有的请求响应结束后执行善后工作,清理对象、关闭资源 ;**最终处理。** ![5d461b7410834eb28c5a853df3d53e46.png][] 拦截器实现的两种方式 > ①**继承HandlerInterceptorAdapter【处理程序拦截适配器】**的父类。 > ②**实现HandlerInterceptor【处理程序拦截器】接口**,推荐使用实现接口的方式,因为继承是单继承的。 #### 2. 拦截器的三个抽象方法 #### **SpringMVC中的拦截器有三个抽象方法:** > (1)preHandle:控制器方法【controller】执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法。 > > (2)postHandle:控制器方法【controller】执行之后执行postHandle()。 > > (3)afterComplation:处理完视图和模型数据(ModelAndView),渲染视图完毕之后执行afterComplation()。 通过源码分析执行的顺序:首先通过前端控制器DispatcherServlet源码找到控制器方法的调用,返回的其实是一个mv(ModelAndView),在控制器方法之前执行preHandle()方法、在控制器方法之后执行postHandle()方法。 之后会去调用processDispatcherResult进行视图渲染;最后去调用afetrCompletion()方法! ![46bea96cd34444faabcc164c583c01a4.png][] processDispatcherResult处理ModelAndView,有一个render方法用来渲染视图的 ![e47f9ace01a44b8da33bfc3ee772d3f3.png][] 渲染完视图,执行拦截器的最终处理 ![136e0d89bc4f4d12879c7478631b3e37.png][] #### 3. 拦截器的使用 #### pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springmvc-thymeleaf007</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>springmvc-thymeleaf007 Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.10.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>false</filtering> </resource> </resources> </build> </project> web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--注册过滤器:解决post请求乱码问题--> <filter> <filter-name>encode</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!--指定字符集--> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> <!--强制request使用字符集encoding--> <init-param> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <!--强制response使用字符集encoding--> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <!--所有请求--> <filter-mapping> <filter-name>encode</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--发送put、delete请求方式的过滤器--> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--注册SpringMVC框架--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置springMVC位置文件的位置和名称--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--将前端控制器DispatcherServlet的初始化时间提前到服务器启动时--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!--指定拦截什么样的请求 例如:http://localhost:8080/demo.action --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> springmvc.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--配置包扫描--> <context:component-scan base-package="com.zl.controller"/> <!--视图控制器,需要搭配注解驱动使用--> <mvc:view-controller path="/" view-name="index"/> <!--专门处理ajax请求,ajax请求不需要视图解析器InternalResourceViewResolver--> <!--但是需要添加注解驱动,专门用来解析@ResponseBody注解的--> <!--注入date类型时,需要使用@DateTimeFormat注解,也要搭配这个使用--> <mvc:annotation-driven/> <!--开放对静态资源的访问,需要搭配注解驱动使用--> <mvc:default-servlet-handler/> <!-- 配置Thymeleaf视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="order" value="1"/> <property name="characterEncoding" value="UTF-8"/> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/templates/"/> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <property name="templateMode" value="HTML5"/> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> </beans> index.html <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>index</h1> <a th:href="@{/testInterceptor}">测试</a> </body> </html> success.html <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> success </body> </html> controller > **注:**此时实现的功能时通过访问index.xml,通过controller进行处理去访问success.html页面;但是此时有一个问题,如果我们知道了success.html的路径地址,就可以略过index.html直接进行访问! package com.zl.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class TestController { @RequestMapping("/testInterceptor") public String testInterceptor(){ return "success"; } } **怎么解决这个问题呢?通过拦截器!** index.html提交表单 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>index</h1> <!--<a th:href="@{/testInterceptor}">测试</a>--> <form th:action="@{/testInterceptor}"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="pwd"><br> <input type="submit" th:value="提交"> </form> </body> <h1 th:text="${msg}"></h1> </html> controller拿到数据,并放到session中去 package com.zl.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @Controller public class TestController { // 直接进行访问 @RequestMapping("/success") public String success(){ return "success"; } @RequestMapping("/testInterceptor") public String testInterceptor(HttpServletRequest request,String username,String pwd){ if ("root".equalsIgnoreCase(username) && "123".equalsIgnoreCase(pwd)){ // 登录成功,存储用户名到session中去 HttpSession session = request.getSession(); session.setAttribute("username",username); return "success"; }else { request.setAttribute("msg","账户或密码错误"); return "index"; } } } 定义拦截器,进行拦截 package com.zl.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 拿到存进session中的username if(request.getSession().getAttribute("username") == null){ // 表示没有登陆过,跳转到index.xml中去登录 request.setAttribute("msg","请先去登录"); request.getRequestDispatcher("/WEB-INF/templates/index.html").forward(request,response); return false; } // 表示登录过,放行 return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } } 在springmvc.xml中注册拦截器 > 注:拦截器主要放行两个页面,登录页面和登录验证的页面! <!--注册拦截器--> <mvc:interceptors> <mvc:interceptor> <!--映射要拦截的请求--> <mvc:mapping path="/**"/> <!--配置要放行的请求--> <mvc:exclude-mapping path="/"/><!--登录的页面--> <mvc:exclude-mapping path="/testInterceptor"/><!--登录验证的页面--> <!--配置具体的拦截器实现功能的类--> <bean class="com.zl.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors> #### **4. 多个拦截器的执行顺序** #### > (1)若**每个拦截器的preHandle()都返回true**,此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:**preHandle()会按照配置的顺序执行**,而**postHandle()**和**afterComplation()**会**按照配置的反序执行**。 定义两个拦截器 FirstInterceptor package com.zl.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FirstInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("FirstInterceptor--->preHandle"); return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("FirstInterceptor--->postHandle"); HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("FirstInterceptor--->afterCompletion"); HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } } SecondInterceptor package com.zl.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class SecondInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("SecondInterceptor--->preHandle"); return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("SecondInterceptor--->postHandle"); HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("SecondInterceptor--->afterCompletion"); HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } } 在springmvc.xml中注册拦截器 <!--注册拦截器,此时是对所有请求都拦截--> <mvc:interceptors> <!--方式一--> <bean class="com.zl.interceptor.FirstInterceptor"/> <!--方式二:拦截器类要写上Component注解,表示纳入Spring容器管理--> <ref bean="secondInterceptor"/> </mvc:interceptors> 执行结果如下:preHandle按照配置的顺序输出,而postHandle和afterComplation按照配置相反的顺序输出 ![a781d86a31b3437aa7fdc54eb8c23b02.png][] **源码分析:** ①首先在控制器controller方法上打一个断点 ![78e9d83b8d12410898c1755151c959b6.png][] ②进行访问时,会进入DispatcherServlet的方法栈,找到doDispatcher方法 > ha实际就是HandlerAdapter适配器控制器,调用handle方法,就相当于执行控制器上的方法 ![c1b70e3ee28a4fec96e9456c53d08b9f.png][] ③此时需要打四个断点:preHandle、执行控制器方法、postHandle、afterCompletion > **执行流程:** > > (1)先执行前处理器preHandel, > > (2)然后去执行控制器方法handle,此时就会去找控制器controller上面的方法去执行, > > (3)执行后处理器方法postHandle, > > (4)这些执行完毕后会调用processDispatcherResult方法中的render方法去渲染视图,渲染完毕后;最终会执行afterCimpletion方法! ![6ce5b59014634c73ae62915f4e5776ea.png][] 跳转到preHandel // if中的参数是false,就会直接执行return结束 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } > **mappedHandler实际上是一个执行链**,这个执行链存放的是:当前的控制器方法、和处理控制器方法的拦截器! > > **注:**此时我们可以看到有3个拦截器,我们只定义了2个,另一个实际上是SpringMVC自己创建的! ![d914f60bef204b30ac266617a20d0ade.png][] 点开+号查看内部的结构如下: > 主要包含三个部分:控制器方法、拦截器集合、拦截器的索引! ![df6a96bbc33441eea824f8c815b522f4.png][] > 进入到 applyPreHandle方法中进行源码分析:首先是遍历这个拦截器集合,并且是i++(就是按照配置的顺序执行)。 > > **注:**只要拦截器的preHandler全是true,那么当前的拦截器索引interceptorIndex就是当前最大的索引值;一旦出现了false,此时的拦截器索引interceptorIndex就是前一个拦截器的索引值! boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { // 根据下标拿到每个拦截器 HandlerInterceptor interceptor = interceptors[i]; // 判断当前的拦截的preHandle是true还是false if (!interceptor.preHandle(request, response, this.handler)) { // 是false就直接执行agterCompletion,然后结束 // 注:此时只能输出当前拦截器所有前面的拦截器的afterCimpletion方法 triggerAfterCompletion(request, response, null); return false; } // 是true,就修改拦截器的索引下标 this.interceptorIndex = i; } } return true; } 跳转到postHandle > 也是遍历拦截器集合,此时的i初始值也是当前的拦截器集合的个数相关联,但是是逆序(i--)打印的! void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } } 跳转到afterCompletion > 也是遍历拦截器集合,此时的i开始值是与拦截器索引interceptorIndex相关联的,也是逆序(i--)打印的!所以对于preHandle是按照配置的顺序打印的;而postHandle和afterCompletion是逆序打印的! void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } } } > (2)假如现在**SecondInterceptor拦截器的preHandle()返回了false**,**preHandle()返回false和它之前配置的拦截器的preHandle()都会执行**、**postHandle()都不会执行**、**返回false的拦截器之前的拦截器的afterComplation()会执行**。 ![ff67ff89ee194fb5af6db815152c7b6a.png][] 再次查看preHandle的源码: > 在for循环的if语句可以看出,当preHandle为false时才会去执行,此时并不会执行postHandle了,会执行afterCompletion,然后返回false直接结束当前方法!并且前面我们已经分析了afterCompletion遍历的结果是与拦截器索引interceptorIndex相关联,而这个值的大小又是当前拦截器为false时的前一个拦截器的索引值(相对于preHandle会少打印一个,少打印当前的)! boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; // preHandle为false要执行的语句 if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; } **例如:**当前有5个拦截器,one、two、three、four、five,此时three的preHandle返回了false;此时one、two、three的preHandle会执行、postHandle都不会执行、one、two的afterComplation会执行! ## Java核心技术大会 ## ## ![ca091ba5a68d4537a37bfd28967fb422.png][] ## **大会简介** > 人工智能在22年、23年的再次爆发让Python成为编程语言里最大的赢家;云原生的持续普及令Go、Rust等新生的语言有了进一步叫板传统技术体系的资本与底气。我们必须承认在近几年里,Java阵营的确受到了前所未有的挑战,出现了更多更强大的竞争者。 > > 但是,迄今Java仍然有着非常庞大的开发者生态,仍是使用人数最多的编程语言,仍是服务端应用、大数据应用、企业级产品的首选。 > > 本届技术大会由国内Java技术传播领军机构**机械工业出版社华章分社**发起,**周志明、李三红、杨晓峰**三位大会主席,与近30位国内外顶级专家将从Java语言、平台和趋势,Java应用开发和系统架构,以及Java性能优化等方面带来**8大专场**,**24场主题**分享。**2023年6月25日-7月1日**,让我们相约**「 Java核心技术大会 」**! **PART 1 特邀启动专场** ![06dfc98ffc92acf30d6bc8200bf37a12.png][] **PART 2 Java语言、平台和趋势专场** ![2ea29b5a9eb21e88e04bdbdefa73fb54.png][] **PART 3 Java应用开发专场** ![b5befd39f591c34cce9b1927225aa9e3.png][] **PART** **4 Java应用与系统架构专场** ![006bbad72779a215977d1cd0904fc272.png][] **PART 5 Java应用性能优化专场** ![a175282bd277d648805275bf6c2929f7.png][] **PART 6 大数据与数据库专场** ![80dd3a1ad50c609fd25ea218065d4296.png][] **PART 7 云原生与Serverless专场** ![b8d57ece92d35f1c4527899324f6f6be.png][] **PART** **8 AI驱动的Java编程专场** ![ff3df1b12c7e9a88dff256726249221d.png][] **「Java核心技术大会 2023」** > Core Java Week > 2023年6月25日-7月1日 > 邀您相约 > 共同深入探讨 Java 生态! > 直播预约:[视频号“IT阅读排行榜][IT] **现场参与更有** * 赢取Java核心技术 纸书&视频课 * 带走CoreJava限量周边 * 锁定购物袋超秒福利 * 加入交流群,向专家请教、学习 * 第一时间获取PPT等增值资源 ![在这里插入图片描述][2c158ec5bc314f33b621970b9560ef71.png] ## 文末福利(Java核心技术卷) ## > **《Java核心技术卷Ⅰ》和《Java核心技术卷Ⅱ》任选其一免费包邮送出!** > > 本次送书 **2** 本! > 活动时间:截止到 2023-06-29 00:00:00 > > 抽奖方式:利用程序进行抽奖。 > > 参与方式:关注博主(只限粉丝福利哦)、点赞、收藏,评论区随机抽取,最多三条评论! [77b535386f184aa98c8a00a1b13c348b.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/7b0f1b50e06e48469bcb7f9f87585a70.png [5d461b7410834eb28c5a853df3d53e46.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/8095c4f7d79a40bda2434e36512a36ba.png [46bea96cd34444faabcc164c583c01a4.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/086431f8b9b5407eb763504035e12444.png [e47f9ace01a44b8da33bfc3ee772d3f3.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/5e8c82145f8740d68cd17f16d00e34fe.png [136e0d89bc4f4d12879c7478631b3e37.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/487f294bef0d4c609efcbcb21ddde233.png [a781d86a31b3437aa7fdc54eb8c23b02.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/c56d0230c2e646b4951acc66abd7494c.png [78e9d83b8d12410898c1755151c959b6.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/482ae5ca7b354e7aa4004d56fdfa52c4.png [c1b70e3ee28a4fec96e9456c53d08b9f.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/13355c75c95e41c588ec6e90d0a9a71d.png [6ce5b59014634c73ae62915f4e5776ea.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/e1fada624fcc47d1af00fbc9b68abf05.png [d914f60bef204b30ac266617a20d0ade.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/57f18514b4dc4ee4bf591e1dd78ca931.png [df6a96bbc33441eea824f8c815b522f4.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/8426cd241f654672b59807f2509e0118.png [ff67ff89ee194fb5af6db815152c7b6a.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/4bfd10d97f4b4da8b7787d6194beac95.png [ca091ba5a68d4537a37bfd28967fb422.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/8e74a443e5e843018ec763a5763b141b.png [06dfc98ffc92acf30d6bc8200bf37a12.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/e216c32a7d9c440b9b3384645790bd0e.png [2ea29b5a9eb21e88e04bdbdefa73fb54.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/9fb2bcda63bc4683b188f5ad298e1e07.png [b5befd39f591c34cce9b1927225aa9e3.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/9a1eda2fdac6446daa38d23f7353ad66.png [006bbad72779a215977d1cd0904fc272.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/7952cd08fc3046f89a3cf4702a40797d.png [a175282bd277d648805275bf6c2929f7.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/99bd352d6b2a47eeaa106fce677c575b.png [80dd3a1ad50c609fd25ea218065d4296.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/8b192d12035d45bcaf564d358540b3fd.png [b8d57ece92d35f1c4527899324f6f6be.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/f8e9a76faf9945df91d4bb905b8bad1f.png [ff3df1b12c7e9a88dff256726249221d.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/8ab6ce12482a4f65beb5e9550711a2e0.png [IT]: https://mp.weixin.qq.com/s/5c7O11dr_iD0CM59yzhOwg [2c158ec5bc314f33b621970b9560ef71.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/16/e0cc4b867a7b40ea8c5328ffa7adc6d4.png
还没有评论,来说两句吧...