重新认识Spring Boot(九)分层开发Web应用程序一 清疚 2022-12-19 00:51 110阅读 0赞 **目录** 1、应用程序分层开发模式-MVC 1.1了解MVC模式 1.2、MVC和三层架构的关系 2、使用视图技术Thymeleaf 2.1、认识ThyMeleaf 2.2、Thymeleaf基础语法 1、引用命名空间 2、常用的th标签 3、ThyMeleaf中的URL写法 4、用Thymeleaf进行 条件求值 5、Switch 6、ThyMeleaf中的字符串替换 7、ThyMeleaf的运算符 8、ThyMeleaf公用对象 2.3、处理循环遍历 2.4、处理公共代码块 2.5、处理分页 2.6、验证和提示错误数据 2.7、编写一个ThyMeleaf视图用于展示数据 -------------------- 前言 参考书是 龙中华 《Spring Boot 实战派》 相关链接: [https://blog.csdn.net/u013840066/article/details/104070447][https_blog.csdn.net_u013840066_article_details_104070447] -------------------- # 1、应用程序分层开发模式-MVC # ## 1.1了解MVC模式 ## Spring Boot 开发 MVC 应用程序主要使用MVC模式,MVC 是 model(模型)、View(视图)、Controller(控制器)的简写 <table> <tbody> <tr> <td>Model</td> <td>是java的实体Bean,代表存取数据的对象或POJO(简单的java对象),也可以带有逻辑。起作用时在内存中暂时存储数据,并在数据变化时更新控制器(如果要持久化,则需要把它写入数据库或者磁盘文件中)</td> </tr> <tr> <td>View</td> <td>主要用来解析、处理、显示内容,并进行模型的渲染</td> </tr> <tr> <td>Controller</td> <td>主要用来处理视图中的响应,他决定如何调用Model(模型)的实体Bean、如何调用业务层的数据增加、删除、修改、和查询的业务操作,以及如何将结果返回给视图进行渲染,</td> </tr> </tbody> </table> 这样的分层的好处是:将应用程序的用户界面和业务逻辑分离,使得代买具备良好的可拓展性,可复用性,可维护性和灵活性 目前的MVC模式大概是这样的: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70][] **如果对MVC了解不深入。会以为是用户通过浏览器访问MVC模型的页面就是访问View视图,实际上是访问DisPatcherServlet 处理的映射和调用视图渲染,然后返回用户的数据(我就是这样 嘤嘤嘤)** 整个工作流程如下: 1. 客户端(用户)发出的请求由Tomcat(服务器)接收,然后Tomcat将请求转交给DispatcherServlet处理。 2. DispatcherServlet匹配控制器中配置的映射路径,然后进行下一步 3. ViewResolver将ModelAndView或者Exception解析成View,然后View会调用render()方法。并根据ModelAndView中的数据渲染出页面 在MVC开发模式中,容易混淆的还有Model,往往会被认为是业务逻辑层或者DAO层,这种理解并不能说是错误的,但并不是严格意义的MVC模式 -------------------- ## 1.2、MVC和三层架构的关系 ## 三层架构,就是将整个业务应用程序划分为表现层(UI)、业务逻辑层(Service)、数据访问层(DAO\\Repository), 1. 表现层,用于展示界面,主要对用户的请求进行接收,以及对数据的返回,它为客户端(用户)提供应用程序的访问接口(界面) 2. 业务逻辑层:是三层架构的服务层、负责业务逻辑处理、主要是调用DAO层对数据进行增加、删除、修改和查询等操作。 3. 数据访问层:对数据库交互的持久层,被Service调用,在Spring Data JPA 中由hibernate来实现。 **Repository 和DAO层一样,都是可以进行数据的增加、删除、修改和查询、它们相当于仓库管理员。** **DAO层的工作就是存取对象,但是Repository层的工作是存取对象和管理对象** **简单理解:Repository = 管理对象(对象缓存和在Repository的状态)+ DAO** **并且严格的来说:MVC是三层架构中UI层,通过MVC把三层架构中的UI层再次进行了细化分层。** ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 1][] <p th:text="${message}?:'Hello World'">Hello World</p> **由此可见:三层架构是基于业务逻辑或者功能来划分的,MVC是根据页面或者功能来划分的。** -------------------- # 2、使用视图技术Thymeleaf # ## 2.1、认识ThyMeleaf ## Spring Boot 主要支持 Thymeleaf、freemaker、Mustache,Groovy、Templates等模板引擎 由于官方提供的大部分是关于Thymeleaf的案例,我也按照书上要求是用Thymeleaf Thymeleaf 是可以轻易的与Spring MVC 等WEB框架进行集成。 Thymeleaf的使用比较简单,要输出HelloWorld 可以在模板文件中加入代码: <p>hello world!</p> <p th:text="${message}?:'hello world'">hello world</p> 其中 > <p th:text="$\{message\}?:'hello world'">hello world</p> 用来接收控制器传入的参数message,如果控制器没有传入参数message 那么默认输出hello world, 如果传入了参数,Thymeleaf则会用 Message 替换掉 hello world **1、为什么需要是用引擎模板** Thymeleaf 解决了前端开发人员和后端开发人员配置一样的环境的尴尬和低效,通过属性进行模板渲染,不需要引入不能被浏览器是被的新的标签,页面直接作为HTML文件,用浏览器打开页面即可看到最终的效果,可以降低前后端人员的沟通成本 **2、使用thymeleaf** 1.引入依赖,在POM文件中添加: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> 2、在模板文件中添加解析 加入依赖后,还要在HTML文件中加入 <html lang="en" xmlns:th="http://www.thymeleaf.org"> 我为了摸鱼在模板出设置了这个 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 2][] 整个完整的Thymeleaf文件如下:起作用是为了展示数据库中实体中的文章标题和内容。 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text="${article.title}">Title</title> </head> <body> <div th:text="${article.title}">标题</div> <div th:text="${article.body}">文章</div> </body> </html> **3、配置视图解析器** Springboot默认的页面映射路径(即模板文件存放的位置是)**classpath:/templates/\*.html** 静态文件路径是 **"classpath:/static/"** ![20201104213113124.png][] 在application.properties 或者YAML文件中 配置Thymeleaf 模板解析器属性,可以如图下: spring.thymeleaf.mode=HTML5 spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.servlet.content-type=text/html #为了方便与测试关闭缓存 spring.thymeleaf.cache=false #代码解释如下: #spring.thymeleaf.mode=HTML5 :代表Thymleaf模式 #spring.thymeleaf.encoding=UTF-8 代表Thymeleaf编码格式 #spring.thymeleaf.servlet.content-type=text/html 代表文档类型 #spring.thymeleaf.cache=false 代表是否启用缓存 Thymeleaf检查HTML格式很严格,如果格式不对,会报错 如果要禁止这种严格模式 可以加入 spring.thymeleaf.mode=LEGACYHTML5 并且一般将ThyMeleaf的模板缓存设置为关闭,修改之后可能不会及时显示修改后的内容。 -------------------- ## 2.2、Thymeleaf基础语法 ## 语法在项目跑起来后才能成功,单独打开html页面是无法实现的。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 3][]![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 4][] ### 1、引用命名空间 ### 就是在模板文件中引用命名空间: ![20201105205556713.png][] 之后会在ThyMeleaf模板标签的渲染,如果有Spring Security 作为安全认证,则需要显示登录用户的信息,则可以现在视图中新增加额外的 > <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> 比如: <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <meta charset="UTF-8"> <title th:text="${article.title}">Title</title> </head> <!--.....省略部分HTML代码....--> <body> <span sec:authentication="name"></span> <span sec:authorize="hasRole('ROLE_ADMIN')">管理员</span> <span sec:authorize="hasRole('ROLE_USER')">普通用户</span> <!--.....省略部分HTML代码....--> </body> </html> **特别注意:spring-boot-starter-thymeleaf 和 thymeleaf-exteras-springsecutiry依赖的版本是否一致,如果不兼容,则无法调用用登录的信息** -------------------- ### 2、常用的th标签 ### <!--1、th:text--> <div th:text="${name}">name</div> <!--它可以显示控制器传入的name的值,如果name不存在,则可以显示默认值--> <div th:text="${name}?:'默认值'"></div> <!--2、th:object --> <div th:object="${user}"></div> <!--3、th:action--> <!--用来指定表单的提交地址--> <form th:action="@{/article/}+${article.id}" method="post"></form> <!--4、th:value--> <!--用对象将id的值替换为value的属性--> <input type="text" th:value="${article.id}" name="id"> <!--5、th:filed--> <!--用来绑定后台对象和表单数据,Thymeleaf 里的 “th:filed”等同于 “th:name”和“th:value”,其具体作用见一下代码--> <input type="text" id="title1" name="title1" th:filed="${artitle.title}"> <input type="text" id="title2" name="title2" th:filed="*{artitle.title}"> -------------------- ### 3、ThyMeleaf中的URL写法 ### ThyMeleaf是通过语法@{....}来处理URL的,需要使用 th:href 和 th:src等属性,比如 <a th:href="@{http://www.guoergou.cn}">我不是二狗</a><!--这是绝对路径--> <a th:href="@{/}">相对路径</a> <a th:href="@{css/bootstrap.min.css}">访问static下的css文件</a> -------------------- ### 4、用Thymeleaf进行 条件求值 ### ThyMeleaf 通过 th:if 和 th:unless 属性进行条件判断。 在下面的例子中, <a>标签只有在 th:if 中的条件成立时才显示。 比如: <a th:href="@{/login}" th:if=${session.user==null}>Login</a> <!-- 只有 if条件成立,也就是 user == null 时候 该标签才显示 --> <a>标签只有在 th:unless中的条件不成立时才显示。 <a th:href="@{/login}" th:unless=${session.user==null}>Login</a> <!-- 也就是说 user 不为 null 时 该 标签 才显示 --> -------------------- ### 5、Switch ### ThyMeleaf 支持 Switch 结构,如以下代码: <div th:switch="${user.role}"> <p th:case="admin">管理员</p> <p th:case="vip">VIP会员</p> <p th:case="*">普通用户</p> </div> <!--上述代码的意思是 如果用户角色role 是 admin ,则显示“管理员” 如果用户角色是VIP 则显示“vip会员”如果都不是 则显示 普通会员 即使用“*”来表示默认情况 --> -------------------- ### 6、ThyMeleaf中的字符串替换 ### 如果需要进行某一处地方替换,则可以通过字符串拼接操作完成 比如: <span th:text="'欢迎你! '+${user}+'!'"></span> <!--或者--> <span th:text="|欢迎您,${name}!|"></span> <!--第二种形式限制比较多,|....|中只能包含变量表达式${,,,,,,} 而不能包含其他常量、条件表达式等--> -------------------- ### 7、ThyMeleaf的运算符 ### 1、算数运算符 数字常量 <div th:text="10"></div> <div th:text="10 + 20"></div> 2、条件运算法 th:if 演示代码表示:如果 从控制器传来的role 是 admin 那么 则显示 欢迎你 管理员 ,如果是 role 显示vip 那么就显示 欢迎你 vip @Controller public class TestController { @RequestMapping("/test1") public String test(ModelMap modelMap) { modelMap.addAttribute("role","vip"); return "test1"; } } 前台代码: <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div th:if="${role} eq admin"> <span>欢迎你 管理员</span> </div> <div th:if="${role} eq vip"> <span>欢迎你 VIP瓜皮</span> </div> 显示为: ![20201105220809866.png][] 补充:使用到的表达式: <table> <tbody> <tr> <td>gt</td> <td>大于</td> </tr> <tr> <td> <p>ge</p> </td> <td>大于或者等于</td> </tr> <tr> <td>eq</td> <td>等于</td> </tr> <tr> <td>lt</td> <td>小于</td> </tr> <tr> <td>le</td> <td>小于或者等于</td> </tr> <tr> <td>ne</td> <td>不等于</td> </tr> </tbody> </table> 3、判断控制可以根据if 来判断是为否为空 <span th:if="${role}!=null">role不为空</span> <span th:if="${user}!=null">user不为空</span> ![20201105221644928.png][] -------------------- ### 8、ThyMeleaf公用对象 ### 提供一系列的的公用对象(utlity) 可以通过\#直接访问 <td th:text="${#dates.format(item.createTime,'yyyy-MM-dd HH:mm:ss')}">格式化时间</td> <span th:if="${#strings.isEmpty(name)}">判断是否为空的字符串</span> <span th:if="${#strings.contains(name,'long')}">是否包含long 分大小写</span> -------------------- ## 2.3、处理循环遍历 ## ** 1、遍历对象 object** ** **可以通过 “th:each=” Object:$\{object\} 比如: <div th:each="article:$[articles}"> <li th:text="${article.title}">文章标题</li> <li th:text=${article.body>文章内容</li> </div> 后台: @RequestMapping("/test1") public String test(ModelMap modelMap) { Article article = new Article(); article.setTitle("title1"); article.setBody("wocao"); modelMap.addAttribute("articles", article); /* List<Article> list = new ArrayList(); Article article = new Article(); article.setTitle("title1"); article.setBody("body1"); list.add(article); article = new Article(); article.setTitle("title2"); article.setBody("body2"); list.add(article); modelMap.addAttribute("articles", list);*/ return "test1"; } 即可输出: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 5][] **2、遍历分页:处理page对象 page** <div th:each="item:${page.content}"> <li th:text="${item.id}">id</li> <li th:text="${item.title}">title</li> </div> **3、遍历列表 list** **处理list来实现** @Controller public class TestController { @RequestMapping("/test1") public String test(ModelMap modelMap) { List<Article> list = new ArrayList(); Article article = new Article(); article.setTitle("title1"); article.setBody("body1"); list.add(article); article = new Article(); article.setTitle("title2"); article.setBody("body2"); list.add(article); modelMap.addAttribute("list", list); return "test1"; } } 前台: <div th:each="item : ${list}"> <li th:text="${item.title}">title</li> <li th:text="${item.body}">body</li> </div> **4、遍历数组:可以使用该标签进行 array** <div th:each="item : ${arrays}"> <li th:text="${item}"></li> </div> **5、遍历集合 map** <div th:each="item : ${map}"> <li th:text="${item.key}">遍历key值</li> <li th:text="${item.value}">遍历value值</li> <li th:text="${item}">遍历key-value值</li> </div> -------------------- ## 2.4、处理公共代码块 ## 一个网页的结构基本可以分为 header body footer (上、中、下)三个部分,一般header 和 footer 会重复使用,考虑到这点 我们使用到了三种标签来进行处理。 > th:fragment > > th:include > > th:replace 1、通过fragment来标记重复代码块: 比如这样: 1、创建common 页面存放 header 和 footer <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <div class="header" id="header" th:fragment="header"> 我是公用的Header </div> <div class="header" id="footer" th:fragment="footer"> 我是公用的footer </div> </html> 2、在单独页面进行引用: <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <link rel="stylesheet" type="text/css" href="../css/style.css"> <body> <div th:replace="~{common :: #header}"></div> <!----> <!--<td th:text="${#dates.format(item.createTime,'yyyy-MM-dd HH:mm:ss')}">格式化时间</td> <span th:if="${#strings.isEmpty(name)}">判断是否为空的字符串</span> <span th:if="${#strings.contains(name,'long')}">是否包含long 分大小写</span>--> <div th:include="~{common :: #footer}"></div> </body> </html> 最后得到:(引用了一个header的css, 把字改为红色) ![20201111213015706.png][] 由此可以看见两者的区别: > th:replace 会替换当前标签中的标签: > > <div class="header"> > > 公共header > > </div> > > th:include会只是加载模板的内容: > > <div> > > 公用的footer > > </div> -------------------- ## 2.5、处理分页 ## 在MVC开发过程中,分页也是常用的 ThyMeleaf 可以处理控制器传入的分页参数。 1、控制器传入 page 对象 @RequestMapping("/list") public String test3(ModelAndView modelAndView) { Pageable pageable = PageRequest.of(start, limit, sort); Page<Article> page = articleRepository.findAll(pageable); modelAndView.addObject("page",page); return "common"; } } //还未验证 2、用ThyMeleaf 接受 page 对象并处理 <div> <a th:href="@{/article/(start=0)}">首页</a> <a th:if="@{not page.isFirst()}" th:href="@{/article(start=${page.num-1})}">上一页</a> <!-- artilce路径下的参数 start --> <a th:if="@{not page.isLast()}" th:href="@{/article(start=${page.num+1})}">下一页</a> <a th:href="@{/article/(start= ${page.totalPage=1})}">末页</a> </div> <!--未验证--> 3、处理路径多参数 如果分页URI(URI 统一资源标识符,URL 统一资源定位符,每个URL都是URI ,但不一定每一个URI都是URL)中有多个参数,则一定要最后一格式,中间用“,”个开始而不是用“&”。 比如:路径:http:localhost:8080/search?key=10010&start=1 这样有两个参数key 和 start 在URL中是以“&”隔开的,但在ThyMeleaf中需要“,”隔开 比如:“@\{/article/search(key=$\{key\},start=$\{page.number+1\})” 以下代码: <div> <a th:href="@{/article/(key=${key},start=0)}">首页</a> <a th:if="@{not page.isFirst()}" th:href="@{/article/(key=${key},start=${page.num-1})}">上一页</a> <a th:if="@{not page.isLast()}" th:href="@{/article/(key=${key},start=${page.num+1})}">下一页</a> <a th:href="@{/article/(start= ${key=${key},page.totalPage=1})}">末页</a> </div> <!--未验证--> 代码解释如下: page.totalPages:总页数 page.isFirst():判断是否为第一页 page.isLast():判断是否为最后一页 page.number 当前页数 -------------------- ## 2.6、验证和提示错误数据 ## 大多数表单都需要字符串的验证,以及提供错误消息反馈。 thymeleaf 提供了几种错误信息的方法。 1、字段错误提示: 通过 "th:filed" 提供了“th:errors” 和 “th:errorclass”的属性。用来验证和提示错误消息,比如 邮箱验证粗粗哦,则会提示: 比如: <div> <span> 邮箱</span> <span><input type="text" th:filed="*{email}"></span> <span class="warn" th:if="${#fileds.hasError('email')}" th:errors="*{email}">邮箱错误</span> </div> <!--#filed.hasErrors()方法接受字段返回来的错误信息,并显示给用户,在Spring Boot 中 #Filed.hasError()方法和内置验证器(Validator)一起使用,直接通过实体定义的API返回信息。比如在试题中添加Email验证--> 实体中加入: @Email(message = "请输入邮箱") @NotBlank(message = "邮箱信息不能为空") private String email; 除反馈错误意外,还可以定义错误的CSS样式 <input type="text" th:filed="*{name}" class="small" th:errorclass="warn"> <!--当出现错误的时候可以提示错误信息 编程了 --> <input type="text" th:filed="*{name}" class="small warn"> 2、提示所有错误 可以使用\#Fileds.hasErrors()或者\#fileds.errors()其参数可以是"\*" 或者 “all” <ul th:if="#filed.hasErrors('*')"> <li th:each="err:${#fileds.errors("*")}" th:text=“${err}”>输入错误</li> </ul> -------------------- # 2.7、编写一个ThyMeleaf视图用于展示数据 # 1、编写控制器: @Controller public class HelloController { @RequestMapping("/helloWorld") public String helloWorld(Model model) { model.addAttribute("mav", "Hello,Spring Boot,I'm MVC"); /*视图的位置和名称是在resource文件下example文件下的hello.html*/ return "/example/hello"; } } 使用到的@controller 以此标注此控制器为MVC模式的控制器。然后用注解@RequestMapping标注URL,映射路径,通过return指定与渲染的视图。 2、添加模板 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Hello World</title> </head> <body> <span th:text="${mav}">展示的数据</span> </body> </html> 3、添加依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> 4、数据展示: ![20201116213711874.png][] 至此。一个完整的MVC模式的WEB开发完毕(一个小的demo) -------------------- 结尾: 做了两年开发,发现对Spring Boot 的认知上缺少了很多东西。因此想重新认识 Spring Boot 框架 仅此而已。 PS: 近半个月有其他事,也摸了下鱼,一直没发布,是我太菜了,还得继续学习啊。 [https_blog.csdn.net_u013840066_article_details_104070447]: https://blog.csdn.net/u013840066/article/details/104070447 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70]: /images/20221120/43ac2a75a9bb4c9581a041afdfa1446c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20201104210316930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/2020110421252298.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx,size_16,color_FFFFFF,t_70 [20201104213113124.png]: https://img-blog.csdnimg.cn/20201104213113124.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 3]: https://img-blog.csdnimg.cn/20201105215852567.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 4]: https://img-blog.csdnimg.cn/20201105215902932.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx,size_16,color_FFFFFF,t_70 [20201105205556713.png]: https://img-blog.csdnimg.cn/20201105205556713.png [20201105220809866.png]: https://img-blog.csdnimg.cn/20201105220809866.png [20201105221644928.png]: https://img-blog.csdnimg.cn/20201105221644928.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx_size_16_color_FFFFFF_t_70 5]: https://img-blog.csdnimg.cn/20201111204438303.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MTk4MTgx,size_16,color_FFFFFF,t_70 [20201111213015706.png]: https://img-blog.csdnimg.cn/20201111213015706.png [20201116213711874.png]: https://img-blog.csdnimg.cn/20201116213711874.png
还没有评论,来说两句吧...