javaweb BaseServlet 自动封装数据并调用service方法

17 篇文章 0 订阅
订阅专栏

这篇笔记是学习web开发时基于反射和泛型的产物,实际开发时不需要去造这种轮子,仅供学习

引入

整个项目要以数据库的表为基准,有多少张表,就要有多少个实体类,在之前的开发中,我们都是单表开发,根据功能去拆分出多个servlet、service和dao。

如图就是典型的单表开发,根据功能书写多个servlet、service和dao。

但是现在有很多表,如果每个表都要根据功能去写servlet,整个项目就会很冗余,也很不直观。

所以我们不应该根据功能去写dao\service\servlet,而是应该根据表写这些东西。

一张表对应一个实体类,对应一个dao和daoImpl,对应一个service和serviceImpl,对应一个servlet。

在dao中写五个基本的方法(增删改查、查所有)

然后在这个servlet中的doPost/doGet下用switch列出所有功能(增删改查、查所有)

如果这样做,我们就要在servlet中对浏览器的请求进行判断,是执行增删改查,还是查所有

浏览器端表单提交请求类型:

服务器端servlet接收请求:

1 Servlet封装目标说明 

封装目标:

        1.前端各个模块发送的请求会指向后端不同的servlet,并且通过请求参数type的值说明需要执行的操作。

        后端每个servlet上都通过使用switch-case调用对应的方法。

        以上的操作存在许多重复代码,而且每次新增功能后,还得去维护switch-case,因此要降低代码重复性,使用反射实现switch-case要实现的功能。

        2.每个servlet中都直接将请求头和响应头传给方法,在方法中完成请求头的解析,一方面造成代码重复,另一方面也与“servlet只允许给方法传递对象”的代码规范相违背

        因为方法一般都放在service里,而servlet调用service的方法时,应该只传递对象,而不能传递请求头、响应头。

        因此我们要降低重复性,消灭传递给方法的请求头和响应头,做到“只给方法传递对象”

        3.如果”只给方法传递对象“,那么就要求所有的方法在执行完毕后都要将结果返回给servlet,由servlet进行响应——这也会造成代码重复,这也要解决。

2 封装请求类型type 自动执行请求的操作 基础版 便于理解 

之前写servle时,前端每个模块对应一个servlet,前端发送的请求被各模块指定的servlet的doGet和doPost接收,从中取出type,使用switch-case进行判断。

比如前端的模块一需要执行查询操作,那么就需要后端的servlet_1取出请求参数type=show,然后进入case=show的分支,去调用指定的方法。

然后前端的模块二需要执行添加操作,那么就需要后端的servlet_2取出请求参数type=add,然后进入case=add的分支,去调用指定的方法。

这样后期维护起来太麻烦,每加入一个新模块就要重写一次doGet、doPost,并且各个模块里还需要维护switch-case

例图:ajax中实现级联菜单,取出请求参数type,然后进入不同的分支

因此我们现在要转变整个项目的编写思路。

首先前端保持不变,依然是根据不同的模块指向后端不同的servlet,请求参数依然是根据不同的需求给出不同的type值

而后端则设立一个BaseServlet去实现doGet和doPost以及编码设置,然后基于反射和泛型取出请求参数type的值,令servlet下的对应方法自动执行。

所有模块的servlet通过继承BaseServlet,实现代码的降重,而servlet中根据模块需求写各自的方法。

核心方法——通过反射获取类中的同名方法

语法

说明

Method 变量名 = Class对象 . getMethod ("方法名", 指向参数类型的Class对象)

获取类中某个带参的方法,由于存在方法重载的可能,因此需要给定方法的参数表

2.1 BaseServlet代码

代码写在父类上,这是为了代码降重,但实际上是在子类中被执行,此时this代指的是各种BaseServlet类的子类。

②下图代码中没有设置编码格式,是因为这部分代码被放在了过滤器Filter中

③作为基础类的BaseServlet继承HttpServlet,别的Servlet则继承该BaseServlet

④虽然目前没见过,但是有时候doGet和doPost需要执行不同的代码,到时候就在BaseServlet中修改一下doGet中的代码即可

2.2 子类代码

①子类直接继承BaseServlet,简化了doGet和doPost的操作

②子类中根据模块需求编写响应的方法,从父类继承的反射方法会自动解析前端发来的请求调用对应的方法

③子类中要对请求头进行数据解析,转换为对象,然后使用

3 封装请求类型type 自动执行请求的操作 正式版 实际使用

在上面的代码中,已经实现了自动根据type类型去执行同名的操作,并且写在了父类servlet上,令所有子类servlet都具备该功能。

可以说已经实现了整个项目的代码降重以及使用反射替代switch-case的目的

3.1 基础版存在的问题

但是实际上存在三个问题:

①如果找不到同名的type,会报错,因为上面的代码中并没有进行同名判断以及结果为空时的处理

②使用invoke调用方法时传入的参数顺序是固定的,现在这种写法要求子类中的方法参数表只能是 "请求头,响应头",但实际上可能会不同

③每个子servlet都需要对请求头进行解析,将其中存储的json对象转为实体对象,再传给各自的方法。

   对请求头进行解析的代码也应该进行降重,让每个子servlet只需要编写方法。

3.2 解决type找不到方法的问题

在执行完下图代码后,我们可以获得一个指向确定的方法的Method对象,如果没有取到,则返回null,不会由于空指针而报错,解决了问题①

注意:代码在子类中执行,获取到的是子类Servlet的方法,建议子类servlet中的方法都设置为公开方法,避免这里还得暴力破解

3.3 解决使用Invoke执行方法时参数表的问题

此时如果想使用invoke调用该Method对象,需要给出该对象需要的参数,即解决Method对象.invoke(this,?)中的?

3.3.1 基础版

通过自动识别方法的参数表需要的参数类型以及参数类型的顺序,就能解决问题②,即?

通过自动封装请求头中的数据,就能解决问题③,即代码降重,关于图中的第五步操作——自动解析与封装请求头数据的进一步说明点击这里

  • 跳转链接中能说明清楚图中第五步对应实际开发中的哪个场景需求,又是如何实现的

3.3.2 基础版存在的问题

现状分析

上图解决了问题②和③,但是里面存在一个问题:

对方法参数表中参数类型的判断只局限于请求头、响应头、指定路径下的实体类,如果方法的参数表里存在字符串或者需求的是其他路径下的实体类就会出问题

具体说明

其他路径:第四步判断是否为实体类时借助了常量 "com.javasm.entity",如果我们传递的实体类对象是vo类或者包名叫bean而不是entity,那就会出问题

存在字符串或者其他数据类型:方法需求直接传入一个或多个字符串数据或者其他数据类型,由于这种情况没有写在循环判断中,就会导致整个object数组元素与参数表对不上

解决办法

针对存在字符串或者其他数据类型:目前没办法把这部分完美解决,需要spring的四个包进行辅助,这些包的底层机制目前也还不清楚

针对路径的情况,需要JAVA动态识别方法中参数表需要的实体对象的全类名,获取的具体实现原理 点击这里

3.3.3 正式版

下面给出正式版相比基础版的改动,其他没给出的与基础版一致,这些改动解决了实体类的路径问题。

父类servlet

①设置父类为泛型类

②声明一个Class对象 用于储存当前servlet类声明的泛型类型

   具体到应该声明一个对象还是一个数组得看父类声明泛型时设置了几个泛型,通常设置为1个,最多设置2个。

   如果只设置了一个泛型,那么此时子类的泛型个数就是一个,Class对象设置为一个对象即可

   如果设置了多个泛型,那么此时子类的泛型个数就是多个,此时Class对象就要设置为数组,并且下方对Class数组的使用就要逐个取出分别判断

③创建一个构造函数,在里面编写获取servlet声明的泛型的代码

   也可以创建一个非静态代码块,总之要求获取servlet声明的泛型的代码要在整个servlet类执行时就执行

④上图代码执行完毕后就能获得servlet在声明泛型时的具体类型,此时就能对Class对的全类名进行一个判定,如果相同,说明此时Class对指向的就是一个实体类而不是别的东西,此时就能实现条件都不满足时提示“你这方法需要的参数既不是请求头又不是响应头还不是实体类,我这个servlet封装搞不定这种复杂情况,方法停止”

子类Servlet

①子类继承父类时要声明泛型的实际类型,可以声明多个,但一般最多声明2个,就比如继承Map集合时声明K和V的数据类型,总之要包含子类中各种方法使用时的所有数据类型

4 封装响应代码 实现代码降重

上面的代码实现了自动读取请求参数type,然后执行对应的方法,并且在方法的参数表需要传入实体对象时,自动创建一个实体对象给到方法。

现在需要做的就是当servlet调用的方法执行完毕时,对前端的响应。

之前对前端的响应都是直接在各个子servlet下进行,各个子Servlet的响应方法都一样,这无疑是重复代码,因此可以放在父类servlet下统一编写。

子类Servlet

首先在子类Servlet的方法中,执行完毕后根据需求可以分别请求转发、重定向、异步请求,根据数据类型跳转可以分为页面、servlet、json数据,根据方法需求返回

返回时根据"响应类型:响应的数据"手动拼接

父类Servlet

在父类Servlet编写的是所有servlet都能拿到的代码,代码的目的简单来说就是判断。

使用method对象.invoke之后获得的是一个Object对象

此时执行响应结果时,需要先把传入的object强制转换为字符串,再进行后面的判断

5 封装响应代码 封装方式 - 注解

现在基于注解去封装响应方法

5.1 之前的版本存在的问题

之前的版本在使用时要求方法返回时要在原数据前加上响应类型,约定请求转发加 f 、重定向加 r 、AJAX加 a

如果编写方法时不按照这个要求来做,就会产生问题,而实际开发中,这种对于返回类型的要求很不友好,很容易被忽略

5.2 解决办法

①创建注解类。

  1. 设置注解要加在serlvet类中的方法上
  2. 设置注解生成器为全周期
  3. 根据业务情况设置默认响应方式,如果前端基于VUE框架,那么基本都是AJAX

②注解所能选择的值则放在枚举类中,这样数据的安全性就更高了。

  1. 设置枚举的值以区分响应的方式(转发、重定向、out)
  2. 注意枚举的值全部都要大写

通过使用注解,能将响应方法的添加转移到注解上,这样方法的响应方式与方法的返回值实现了解耦

但是现在的问题是编写方法时忘记加上注解了,就会导致无法响应。

这种情况目前没办法解决,因为当前的封装手段只能尽量保证数据的完整性,不能检测是否添加了注解,或者添加的是否是需要添加的注解

5.3 子类Servlet上的变化

原来:

现在:

5.4 父类Servlet上的变化

java程序中依据传入的参数自动调用对应的service处理,替代大量的if-else判断
m0_46722895的博客
07-13 1913
第一种方式:(较为局限) 在server层上标明对应server的简称,与参数相对应 factory类如下 @Service public class RequestHandlerFactory { @Autowired Map<String,BaseRequestHandlerService> requestHandlerServiceMap = new ConcurrentHashMap<>(); public BaseRequestHandlerSe
JavaWeb之查询数据库,显示到页面
Dawn
06-30 6707
需求: 将数据库中send_email表展示到页面上 分析 创建jsp页面 拿到list<Email>将数据显示在页面上 创建BeanEmail实体类 创建EmailServlet,BaseServlet类 响应编码设置 调用EmailService的findAll()方法,返回List<Email> 使用request进行转发到jsp页面,携带list集合 创建EmailService接口 调用EmailDao中的findAll()方法,返回List<Emai
Java Web之BaseServlet的抽取
weixin_34258078的博客
01-06 125
Java Web学习的初期,开发的小项目几乎都是JSP+Servlet+JDBC,长期开发下来,会发现当业务逻辑设计的接口一多的时候,充当控制器的Servlet也会越来越多,但是处理的业务逻辑相对单一。后来学习Struts2或者SpringMVC,发现它们处理起来优雅得多,但是配置起来也比纯的Servlet...
Javaweb——BaseServlet
刘伟佳的博客
10-30 176
BaseServlet 1.作用 我们知道,写一个项目可能会出现很多个Servlet,而且一般一个Servlet只有一个方法(doGet或doPost),如果项目大一些,那么Servlet的数量就会很惊人。 为了避免Servlet的“膨胀”,我们写一个BaseServlet。它的作用是让一个Servlet可以处理多种不同的请求。不同的请求调用Servlet的不同方法。我们写好了BaseServle...
JavaWebBaseServlet
田野上的风筝
06-27 433
JavaWeb中,使用最基础的Servlet用法实现一个功能操作是比较繁琐的,如实现两个功能就要创建两个Servlet,当业务操作过多时,这样会造成Servlet过于冗余,而web.xml 配置文件代码也过于臃肿。 过多地去创建Servlet明显是不可取的,这时可以在请求路径加一个方法参数,然后再在Servlet中进行判断,使用if语句判断方法参数从而调用哪个方法。如下图 这做法虽...
JavaWebBaseServlet程序
T_158327的博客
06-19 213
在面对同一模块不同的功能请求时,我们可以使用 反射 在 Servlet程序中优化 if else 代码结构,从而让注意力集中在功能上的开发。步骤如下:
JavaWeb实现简单对数据库的增删改查
热门推荐
Makonike的博客
03-13 1万+
通过JavaWeb实现简单的对数据库的增删改查
JavaWEB综合案例
Quella_ly的博客
07-17 422
以上是我们在综合案例要实现的功能。对数据的除了对数据的增删改查功能外,还有一些复杂的功能,如批量删除、分页查询、条件查询等功能批量删除功能每条数据前都有复选框,当我选中多条数据并点击批量删除按钮后,会发送请求到后端并删除数据库中指定的多条数据。分页查询功能当数据库中有很多数据时,我们不可能将所有的数据展示在一页里,这个时候就需要分页展示数据。条件查询功能数据库量大的时候,我们就需要精确的查询一些想看到的数据,这个时候就需要通过条件查询。这里的修改品牌和删除品牌。...
JavaWeb
chaunceny的博客
04-19 855
常见的软件系统结构B/S  C/S1.C/S  C/S结构是客户端/服务器端(client/server)比如QQ,LOL  需要编写服务器端的程序,以及客户端的,我们安装的qq包就是客户端程序  缺点:软件更新时客户端与服务器端都要更新,服务器端更新时客户端无法使用,比较麻烦,你想要运行对应的客户端程序,硬件必须支持  优点:安全心比较高【一对一的关系】2.B/S  B/S结构浏览器端/服务器端...
JavaWeb品牌页面的增删改查
linxin_ovo的博客
05-12 1083
主要功能:批量删除、新增、模糊查询、修改、删除、分页 使用的技术以及环境: 软件: idea、navicat、谷歌浏览器 技术: 前端:Vue、Element-Ui、Json、Axios 后端:Java、Maen、Servlet、MyBatis 中间件/服务器:Tomcat ...
Java Web 十五 BaseServlet
普通还不自信的程序员
08-05 217
BaseServlet详解 BaseServlet是通过重写Servletservice()方法,以达到可以控制所有Servlet的目的,同时简化了Servlet的一些操作。 ●为什么要重写service()方法? 因为客户端向服务端发送的每一次请求,服务器都会调用service()方法重写它可以控制所有的请求。 ●service()可以写什么? Servlet中常有的对象(比如session)可以在service()中进行获取。 ●BaseServlet的主要作用是什么? Ba
javaWeb实例之抽取BaseServlet
二木成林
05-14 318
首先说下为什么要抽取BaseServlet.java呢? 看图可以知道每当前端页面有一个请求时,后台就需要创建一个Servlet类实现相关的逻辑,如果需求多了,就会有很多Servlet.java类。 而抽取BaseServlet目的就是为了减少Servlet类数量。 即现在只有两个Servlet类了,而前端请求可以写作方法在UserServlet中,只需要在前端请求中写方法名即可反射。 如下图,发现Servlet类确实减少了: 其中BaseServlet.java // 不需要访问
javaWeb-BaseServlet封装
奋斗吧010
09-02 253
public class BaseServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String ...
JavaWeb-BaseServlet的抽取
郭松源的博客
11-12 478
1. 一个模块对应一个Servlet 在实际的项目开发中,一个模块一般只使用一个 Servlet 程序。 而我们以前往往根据一个功能写一个 Servlet,比如给用户注册功能写一个 RegistServlet,再给用户登录功能写一个 LoginServlet,事实上,我们完全可以将 用户注册功能 和 用户登录功能 封装为一个用户模块,然后给这个模块写一个单独的 UserServlet,我们接下来看看如何操作。 这里我们需要在前端使用 hidden 属性的 <input> 标签: 在 login
service的 封装
chenxiaoyu97的博客
09-20 391
/ 在这里可以统一修改请求头,例如 加入 用户 token 等操作。timeout: 600000, // 请求超时时间。/* request拦截器 */
基于ServletBaseServlet封装
weixin_44846862的博客
08-29 516
在web服务器中,每当客户端浏览器发送一个请求的时候,我们都要去创建一个servlet去完成相应的请求并返回响应,这样显得就很麻烦,有多少个请求就必须创建多少个servlet,其中的代码重复量太大了,所以,针对一个servlet可以处理多个请求,我们就封装了一个BaseServlet,他的作用就是:实现一个servlet可以处理多个客户端请求 下面就是整个封装的过程 package com.softeem.servlet; import javax.servlet.ServletException;
JavaWebBaseServletService事务
C.
07-20 615
BaseServlet 通常,写一个项目可能会出现N多个Servlet,而且一般一个Servlet只有一个方法(doGet或doPost),如果项目大一些,那么Servlet的数量就会很惊人。为了避免这种情况,我们写一个BaseServlet。它的作用是让一个Servlet可以处理多种不同的请求。不同的请求调用Servlet的不同方法。我们写好了BaseServlet后,让其他Servlet继承...
java处理不同的请求_spring根据controller中接收请求参数不同走不同service的实现方法...
weixin_33406123的博客
02-25 2427
前言前几天一个工程中,需要实现这样一个场景:根据前端发送过来的请求参数的不同,走不同的 service(可同事走多个),最初我的思路是尝试实现在 spring 中实现动态的依赖注入,也就是根据请求参数,动态的在 controller 中注入某个 service 接口的特定实现(接口有多个实现),但是发现这个实现不了,然后想了想,换了个思路,重新设计了一下,实现了需求中的场景。附controller...
JAVA 一个servlet处理多种请求,自动化前端数据封装成对象,简化数据库操作,你想要的都在这里!超级详细!
qq_41709360的博客
05-19 1343
JAVA 一个servlet处理多种请求,自动化前端数据封装成对象,简化数据库操作,你想要的都在这里!前言基础的很浅层次原理解析场景分析实现原理一个servlet处理多个请求自动化前端数据封装成对象通过上面的分析,自己实现一个看一下大佬实现的简化数据库操作,智能化数据库的操作大佬的实现导包-maven:数据库的核心方法操作的具体实现注意事项总结 前言 上一篇写了数据库连接池的操作【点击查看】,连接完数据库不就该操作数据库了(dao层和service层),操作数据库之后不就该从页面获取的数据中封装对象,然后s
如何在Javaweb项目后端的controller层调用Service方法进行登录验证
最新发布
03-27
JavaWeb项目中,通常会将业务逻辑处理放在Service层中,而将控制层的处理放在Controller层中。因此,在Controller层中调用Service方法进行登录验证,可以按照以下步骤进行: 1. 在Controller层中引入Service层...
写文章

热门文章

  • yanqiyetan V1.0 存档 20359
  • 少女异闻录:白鹭白百合的华丽日常 v1.02 存档 8797
  • 令IDEA控制台打印输出的文本对齐 以及 实现这个功能时个人的体会 4188
  • 关于前端 后端 数据库 时间的设置与传递 2964
  • 关于onenote2016遇到的页面大片空白无法正常收缩的bug 1882

分类专栏

  • 面向CSDN编程
  • 其他 3篇
  • JAVAWEB相关 17篇
  • JAVA相关 14篇

最新评论

  • 少女异闻录:白鹭白百合的华丽日常 v1.02 存档

    2301_77519614: 老哥很不错,能用

  • LinkedList有索引 为什么查找还会慢

    m0_73911208: 为啥LinkedList有索引,但LinkedHashSet没索引呢表情包

  • 关于onenote2016遇到的页面大片空白无法正常收缩的bug

    WD05ie: 谢谢大佬,很有帮助表情包

  • yanqiyetan V1.0 存档

    永遠亭Official: 《关于我在csdn甚至能找到黄油存档这件事》

  • 关于onenote2016遇到的页面大片空白无法正常收缩的bug

    Paml Jam: 感谢老板

大家在看

  • transformer对位置编码的理解 301
  • 如何下载旧版本app或者旧版本的电脑软件?下载旧版本手机app和电脑软件的方法 99
  • 【学习笔记】手写Tomcat 四
  • 联合和枚举
  • 今天晚上调了份准备好的Ideal的代码,终于是调好了,另外解决了一个奇特的Bug

最新文章

  • 少女异闻录:白鹭白百合的华丽日常 v1.02 存档
  • yanqiyetan V1.0 存档
  • service包的目录结构
2022年2篇
2021年30篇

目录

目录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家山东开业商场美陈生产厂家湖南玻璃钢雕塑人物空心的玻璃钢雕塑可以灌水泥吗重庆玻璃钢车雕塑商场集市美陈图片天津玻璃钢雕塑全国接单定制玻璃钢园林雕塑苏南广场玻璃钢雕塑技术淮安商场中庭美陈上海通道商场美陈研发公司玻璃钢雕塑表情富阳区玻璃钢金属雕塑介绍大安玻璃钢雕塑云南玻璃钢雕塑生产商上海卡通玻璃钢动物马雕塑广州玻璃钢雕塑 欧式园林福州佛像玻璃钢雕塑乳山玻璃钢牌匾雕塑五华区玻璃钢雕塑设计制作咨询通道商场美陈采购玉溪市玻璃钢雕塑怎么样云南仿铜玻璃钢雕塑方法玻璃钢雕塑牛图片玻璃钢雕塑有哪些类型常熟玻璃钢雕塑有限公司卡通玻璃钢雕塑制作多少钱河北佛像玻璃钢雕塑定制西藏玻璃钢艺术雕塑迪庆玻璃钢雕塑生产制造莱州玻璃钢气球雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化