当前位置: 首页 > news >正文

西宁网站设计高端电子书推送网站怎么做

西宁网站设计高端,电子书推送网站怎么做,舟山网站seo,wordpress 备份一、简介 ​ 通常情况下#xff0c;前端在使用post请求提交数据的时候#xff0c;请求都是采用application/json 或 application/x-www-form-urlencoded编码类型#xff0c;分别是借助JSON字符串来传递参数或者keyvalue格式字符串#xff08;多参数通过进行连接#…一、简介 ​ 通常情况下前端在使用post请求提交数据的时候请求都是采用application/json 或 application/x-www-form-urlencoded编码类型分别是借助JSON字符串来传递参数或者keyvalue格式字符串多参数通过进行连接来传递参数确实足以覆盖大多数业务场景。但是在文件上传等特殊业务场景下这两种编码类型就有些捉襟见肘了例如选择JSON字符串传递参数在使用JSON.stringify()格式化参数数据时会将File和Blob对象转化成{}文件数据会丢失。所以此时我们就需要使用第三种编码类型multipart/form-data使用FormData对象来传递参数。 ​ FormData 提供了一种以 key/value键值对集合表示表单数据的数据构造方式通过该方式我们可以将file、blob等不易传输的数据通过 ajax 请求轻松的发送到服务器端。 ​ 当使用FormData 对象作为参数时无需手动设置请求的编码类型浏览器会自动将请求的编码类型Content-type设置为multipart/form-data。 浏览器兼容性 二、相关方法 1、FormData() ​ FormData([form]) 方法是FormData 对象的构造函数用来创建一个新的FormData 对象。 // 创建空的 FormData 对象 const formData new FormData()​ 该方法拥有一个可选参数form值为页面HTML中的一个表单元素当设置该参数时创建的FormData对象将自动的将form表单中的值包含进去包括file文件内容也会被编码之后包含进去。但是要注意给表单中所有的输入元素、设置name属性否则无法被FormData对象包含输入元素的name属性将会成为FormData对象中数据键值对的key输入元素的值将会成为对应的value。 !-- form表单元素 -- form action# idform1divlabel forname姓名/labelinput typetext idname namename/divdivlabel forage年龄/labelinput typetext idage nameage/divdivlabel forsex性别/label!-- 未设置name属性不会被 formData 包含 --input typetext idsex/div/formbr /button onclicklogFormData()输出formData对象/buttonscript // 输出 FormData 对象的数据 function logFormData () {// 获取表单元素const form document.getElementById(form1)// 创建带有预置数据的 FormData 对象const formData new FormData(form)// 输出formData对象中的所有键值对for (var pair of formData.entries()) {console.log(pair[0] ---- pair[1]);} } /script2、FormData.append() ​ FormData.append(name,value,[filename]) 方法用于向FormData 对象中添加一个新的值该方法拥有两个必选参数name和value以及一个可选参数filename。name对应FormData 对象中键值对数据的keyvalue对应键值对数据的值。如果name这个key在FormData中已经存在则会将新值value添加到原有值集合的后面先添加的值在前面后添加的值在后面多个值同时以集合的形式存在如果name这个key在FormData中不存在则会新增这个key并赋予对应的值value。 // 创建空的 FormData 对象 const formData new FormData() // 添加一个键值对 此时并不存在对应的key 会新增这个key formData.append(name, 张三) // 给同一个key 再次添加值 formData.append(name, 李四) // 输出这个key对应的所有value值 console.log(formData.getAll(name)----, formData.getAll(name));执行结果1 可选参数filename是当第二个参数value为Blob或file文件数据时设置传给服务器端的文件名称。如果不设置该参数则Blob类型默认文件名为blobfile类型的默认文件名为文件本身的名称。 // 创建空的 FormData 对象 const formData new FormData() // 添加一个file键值对数据 取默认文件名称 formData.append(file, file) // 添加一个file键值对数据 并设置文件名称 formData.append(file, file, test.png) // 输出这个key对应的所有value值 console.log(formData.getAll(file)----, formData.getAll(file));执行结果2 3、FormData.set() ​ FormData.set(name,value,[filename]) 方法与FormData.append()方法类似都是用于向FormData 对象中添加一个新的值如果name这个key在FormData中不存在则会新增这个key并赋予对应的值value但是如果name这个key在FormData中已经存在那么该方法会直接覆盖掉原来的value无论原有值集合有几个数据全都被覆盖。 ​ 其余用法与FormData.append()方法相同。 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 此时并不存在对应的key 会新增这个key formData.append(name, 张三) // 使用append()给同一个key 再次添加值 formData.append(name, 李四) // 输出这个key对应的所有value值 console.log(append()两次数据后----, formData.getAll(name)); // 使用set()给同一个key 设置值 会覆盖之前的值 formData.set(name, 王五) // 输出这个key对应的所有value值 console.log(set()一次数据后----, formData.getAll(name));4、FormData.delete() ​ FormData.delete(name) 方法用于从FormData对象中删除name这个key及其对应的所有value。 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 此时并不存在对应的key 会新增这个key formData.append(name, 张三) // 使用append()给同一个key 再次添加值 formData.append(name, 李四) // 输出这个key对应的所有value值 console.log(append()两次数据后----, formData.getAll(name)); // 使用delete()删除一个key及其所有的value formData.delete(name) // 再次输出这个key console.log(delete()删除一次后----, formData.getAll(name));5、FormData.entries() ​ FormData.entries() 方法用于获取一个由FormData对象中所有键值对组成的iterator迭代器对象然后通过该对象可以遍历访问所有的键值对数据。 ​ 该方法获取的iterator迭代器对象需要通过for…of…的形式来进行遍历每个遍历元素都是数组类型数组中有两个元素第一个为key另一个为value。如果FormData对象中的某个key有多个 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 formData.append(name, 张三) // 使用append()给同一个key 再次添加值 formData.append(name, 李四) // 使用append()添加另外一个键值对 formData.append(sex, 男) // 获取迭代器对象 const entries formData.entries() // 输出迭代器对象 console.log(entries-----, entries); // 遍历迭代器对象 for (var pair of entries) {// 输出遍历元素console.log(pair---, pair);// 输出元素的key和valueconsole.log(pair[0] ---- pair[1]); }除了该方法外我们还可以通过for…of…形式直接遍历FormData对象其作用与结果与该方法完全相同 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 formData.append(name, 张三) // 使用append()给同一个key 再次添加值 formData.append(name, 李四) // 使用append()添加另外一个键值对 formData.append(sex, 男) // 遍历formData对象 for (var pair of formData) {console.log(当前遍历元素---, pair);console.log(pair[0] ---- pair[1]); }6、FormData.keys() ​ FormData.keys() 方法用于获取一个由FormData对象中所有键值对中的key组成的iterator迭代器对象然后通过该对象可以遍历访问所有的key类型为String。与entries()方法相同的是如果FormData对象中的某个key有多个value则该key会被遍历多次每次对应一个value。 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 formData.append(name, 张三) // 使用append()给同一个key 再次添加值 formData.append(name, 李四) // 使用append()添加另外一个键值对 formData.append(sex, 男) // 获取key组成的迭代器对象 const keys formData.keys() // 输出迭代器对象 console.log(keys-----, keys); // 遍历迭代器对象 for (var key of keys) {console.log(key---, key); }7、FormData.values() ​ FormData.values() 方法用于获取一个由FormData对象中所有键值对中的value组成的iterator迭代器对象然后通过该对象可以遍历访问所有的value类型为String、File、Blob。如果FormData对象中的某个key有多个value则每个value都会遍历一次 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 formData.append(name, 张三) // 使用append()给同一个key 再次添加值 formData.append(name, 333444) // 使用append()添加另外一个键值对 formData.append(sex, 男) // 获取value组成的迭代器对象 const values formData.values() // 输出迭代器对象 console.log(values-----, values); // 遍历迭代器对象 for (var value of values) {console.log(value---, value); }8、FormData.has() ​ FormData.has() 该方法用于判断FormData 对象中是否含有某个key返回值为一个布尔值。 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 formData.append(name, 张三) // 使用append()添加另外一个键值对 formData.append(sex, 男) // 使用has()判断是否存在某个key console.log(has()判断是否存在name---, formData.has(name)); // 使用delete()删除一个key及其所有的value formData.delete(sex) // 使用has()判断一个已经被删除的key console.log(has()判断被delete()删除的sex---, formData.has(sex)); // 使用has()判断一个不存在的key console.log(has()判断不存在的age---, formData.has(age));9、FormData.get() ​ FormData.get(name) 方法用于获取FormData 对象中name这个key所对应的value集合里的第一个valuevalue集合中值的顺序按照添加的顺序进行排序。 // 创建空的 FormData 对象 const formData new FormData() // 使用append()添加一个键值对 formData.append(name, 张三) // 使用append()给同一个key 再次添加值 formData.append(name, 333) // 使用append()添加另外一个键值对 formData.append(sex, 男) // 使用get()获取name对应的第一个value console.log(get()获取name对应的第一个value---, formData.get(name)); // 使用get()获取sex对应的第一个value console.log(get()获取sex对应的第一个value---, formData.get(sex));一、进阶知识 在前一篇博客中我讲解了FormData对象的基础概念、相关方法和基本用法本篇博客我将讲解一些FormDate对象相关的进阶知识主要包含FormData与其他对象结合使用的各类场景以及一些使用技巧。 1、FormData对象、JSON字符串、keyvalue字符串 三种参数形式对比 ① 使用FormData对象传递参数 ​ 该参数形式对应请求头Content-type类型中的 multipart/form-data以键值对的形式存储参数数据参数中允许包含File、Blob类型的数据。 // 发送FormData对象参数 function ajaxFormData () {// 创建空的 FormData 对象const formData new FormData()// 创建Blob对象var aFileParts [a idab idbhey!/b/a]; // 一个包含 DOMString 的数组var blob new Blob(aFileParts, { type: text/html });// 添加一个Blob键值对数据 并设置文件名称formData.append(content, blob)// 添加一个字符串键值对数据formData.append(name, 张三)// 添加一个字符串键值对数据formData.append(age, 18)// 创建 XMLHttpRequest 实例对象const xhr new XMLHttpRequest();// 设置发送POST请求的URL地址const url http://example.com/api/user;// 配置请求对象xhr.open(POST, url);// 无需设置请求头信息 浏览器会自动设置 Content-type 为 multipart/form-data// 设置请求完成后的回调xhr.onreadystatechange function () {if (xhr.readyState 4 xhr.status 200) {console.log(xhr.responseText);}};// 发送请求并将参数加入进去xhr.send(formData); }浏览器查看请求头和请求参数 ② 使用JSON字符串传递参数 ​ 该参数形式对应请求头Content-type类型中的 application/json参数中的File、Blob类型的数据会被转换成{}数据会丢失。当然我们也可以通过将File、Blob对象转成base64格式的方式来传递数据但是不够优雅而且在转换格式的时候如果文件过大会占用大量内存影响浏览器性能因此并不推荐采用这种形式来传递File、Blob类型数据。 function ajaxJSON () {// 创建Blob对象var aFileParts [a idab idbhey!/b/a]; // 一个包含 DOMString 的数组var blob new Blob(aFileParts, { type: text/html });// 创建一个 FileReader 对象var reader new FileReader();// 当以 DataURL 格式读取成功后执行回调函数reader.onload (event) {// 将blob对象转换为bas64字符串var blobBase64 event.target.result// 创建要发送的参数对象var params {name: 张三,age: 18,content: blob,contentBase64: blobBase64}// 将参数对象转换为JSON字符串var JSONParams JSON.stringify(params)// 创建 XMLHttpRequest 实例对象const xhr new XMLHttpRequest();// 设置发送POST请求的URL地址const url http://example.com/api/user;// 配置请求对象xhr.open(POST, url);// 设置请求头信息xhr.setRequestHeader(Content-type, application/json)// 设置请求完成后的回调xhr.onreadystatechange function () {if (xhr.readyState 4 xhr.status 200) {console.log(xhr.responseText);}};// 发送请求并将参数加入进去xhr.send(JSONParams);};// 以 DataURL 的形式读取 Blob 数据reader.readAsDataURL(blob); }浏览器查看请求头和请求参数 ③ 使用keyvalue字符串传递参数 ​ 该参数形式对应请求头Content-type类型中的 application/x-www-form-urlencoded传递过程中只能传递字符串类型的参数参数组成keyvalue格式字符串多个参数之间通过进行连接。参数中的File、Blob类型的数据会被转换成[object File]、[object Blob]字符串数据会丢失。同理我们也可以通过将File、Blob对象转成base64格式的方式来传递数据但缺点也相同在转换格式的时候如果文件过大会占用大量内存影响浏览器性能因此并不推荐采用这种形式来传递File、Blob类型数据。 function ajaxString () {// 创建Blob对象var aFileParts [a idab idbhey!/b/a]; // 一个包含 DOMString 的数组var blob new Blob(aFileParts, { type: text/html });// 创建一个 FileReader 对象var reader new FileReader();// 当以 DataURL 格式读取成功后执行回调函数reader.onload (event) {// 将blob对象转换为bas64字符串var blobBase64 event.target.result// 创建要发送的参数字符串var params name张三age18content blob contentBase64 blobBase64// 创建 XMLHttpRequest 实例对象const xhr new XMLHttpRequest();// 设置发送POST请求的URL地址const url http://example.com/api/user;// 配置请求对象xhr.open(POST, url);// 设置请求头信息xhr.setRequestHeader(Content-type, application/x-www-form-urlencoded)// 设置请求完成后的回调xhr.onreadystatechange function () {if (xhr.readyState 4 xhr.status 200) {console.log(xhr.responseText);}};// 发送请求并将参数加入进去xhr.send(params);}// 以 DataURL 的形式读取 Blob 数据reader.readAsDataURL(blob); }浏览器查看请求头和请求参数 2、对FormData对象中的数据进行过滤 ​ 我们使用FormData对象向服务端传输数据时通常是为了进行文件上传以及一些相关数据的上传。为了数据安全我们需要对要加入到FormData中的数据进行校验过滤比如对文件名、文件类型、文件内容等等进行过滤只有过滤后的数据才能加入到FormData中并发送到服务端。 ① 文件名过滤 ​ 文件名过滤可以防止用户上传的文件名中包含违规内容和字符等具体实现可以结合正则表达式和字符串操作两者来实现。 ​ 例如上传文件的文件名不能包含、#和%三个特殊字符且文件名不能包含sb和2b两个违规词。 // 声明FormData const formData new FormData();// 省略...// 获取文件对象 var file e.target.files[0] // 获取文件名 var fileName file.name // 校验文件名中是否包含sb或2b 两个违规词 const pattern /^(?!.*([sS]b|2[Bb])).*$/i; // 进行文件名过滤 不能含有违规词 且不能含有特殊字符 if (pattern.test(fileName) fileName.indexOf() -1 fileName.indexOf(#) -1 fileName.indexOf(%) -1) {formData.append(file,file) } else {alert(文件名不符合规范请修改后再上传~); }② 文件类型过滤 ​ 文件类型过滤可以防止用户上传不支持的文件类型虽然前端可以通过标签的accept属性来限制用户选择的文件类型但是这并不严谨用户可以通过操作文件选择框的选项来解除限制所以在文件上传之前对文件类型进行过滤是有必要的。 ​ 例如上传文件的类型限制为图片类型且只能为.jpg、.png、.gif三种类型的文件。 // 创建空的 FormData 对象 const formData new FormData()// 省略...// 获取文件对象 var file e.target.files[0] // 获取文件名 var fileName file.name // 获取文件类型 var fileType file.type // 校验文件名是否以规定格式 jpg、png、gif 结尾 const pattern /\.jpg$|\.png$|\.gif$/i; // 进行文件类型过滤 if (pattern.test(fileName) fileType.indexOf(image) 0) {formData.append(file, file) } else {alert(文件类型不符请修改后再上传~); }③ 文件内容过滤 ​ 文件内容过滤可以防止用户上传包含恶意代码和违规内容的文件可以使用JS来过滤部分文件的内容也可以借助一些完善第三方的库来检查文件内容如js-xss、Filter.js等等。 ​ 例如对用户上传的.txt文件进行简单的敏感词汇校验过滤。 // 创建空的 FormData 对象 const formData new FormData() // 调起文件选择框 document.getElementById(file).click() // 监听文件选择框的change事件 document.getElementById(file).onchange function (e) {// 获取文件对象var file e.target.files[0]// 声明一个 FileReader 对象const reader new FileReader();// 当以文本形式读取成功后执行回调函数reader.onload (event) {const content event.target.result;console.log(原文件内容---, content);// 过滤敏感词汇const filteredContent content.replace(/sb|智障|2B/gi, **);// 显示过滤后的内容console.log(过滤后的文件内容---, filteredContent);// 将过滤后的内容写入FormDataformData.append(fileText, filteredContent)};// 以文本形式读取文件内容reader.readAsText(file);}④ 白名单过滤 ​ 白名单过滤是指根据过滤条件批量设置允许名单只有符合白名单的数据才能通过过滤。 ​ 例如设置文件类型白名单只允许jpg、png、gif类型的图片文件加入到FormData中。 // 创建空的 FormData 对象 var formData new FormData() // 声明一个白名单数组 const whitelist [image/jpg, image/png, image/gif]; // 调起文件选择框 document.getElementById(file).click() // 监听文件选择框的change事件 document.getElementById(file).onchange function (e) {// 获取文件对象var file e.target.files[0]// 获取文件类型var fileType file.type// 判断文件类型是否在白名单中if (whitelist.indexOf(fileType) -1) {formData.append(file, file)} else {alert(文件类型不符请修改后再上传~);} }⑤ 黑名单过滤 ​ 黑名单过滤是指根据过滤条件批量设置禁止名单凡是符合黑名单的数据都禁止通过。 ​ 例如设置文件类型黑名单禁止.exe和.bat类型的文件加入到FormData中。 // 创建空的 FormData 对象 var formData new FormData() // 声明一个黑名单数组 const blackList [application/x-msdownload, application/x-msdos-program,]; // 调起文件选择框 document.getElementById(file).click() // 监听文件选择框的change事件 document.getElementById(file).onchange function (e) {// 获取文件对象var file e.target.files[0]// 获取文件类型var fileType file.type// 判断文件类型是否在黑名单中if (blackList.indexOf(fileType) -1) {formData.append(file, file)} else {alert(文件类型不允许上传~);} }3、FormData对象结合同步token预防CSRF攻击 ​ CSRFCross-site Request Forgery跨站请求伪造攻击是一种常见的网络攻击攻击者通过伪造用户的身份利用用户在某些站点上的登录状态来构造并发送篡改数据的请求。防范CSRF攻击方式有很多在涉及表单提交的页面中我们常用的是FormData对象结合同步token又称CSRF token的防范策略来防范攻击者恶意伪造表单数据提交具体操作步骤如下 ​ ① 当用户请求访问表单页面时服务端生成一个随机且唯一的token服务端存储一份并将该token存储在cookie之中发送给前端。 ​ ② 前端从cookie中获取token然后将token添加到要提交的FormData对象中。 ​ ③ 前端触发表单提交接口发送FormData对象服务端收到请求后对比FormData对象中的token与服务端存储的token是否一致如果一致则认为是合法请求否则认为是CSRF攻击拒绝请求。 ​ 该防范策略的核心在于攻击者虽然在调用提交接口时能携带相关的cookie信息接口携带的cookie取决于接口的域名但是无法通过js获取相关cookie的值js只能获取当前页面域名下的cookie因而也就拿不到有效的token无法构造有效的表单数据请求就会被服务端所拒绝。 ​ 该防范策略的优点在于安全性高、操作简单、支持性好缺点在于需要增加额外的计算量和存储开销。 ​ 除此之外我们还可以给存储token的那个cookie设置SameSiteStrict或lax进一步防范CSRF攻击。 4、FormData对象结合input实现选择文件夹批量上传文件 ​ 之前我们批量上传文件时都是让用户一个个的去选择文件操作繁多或者就是让用户将文件放到文件夹下统一打包成压缩包作为一个文件上传但是文件的压缩格式有很多服务端基本不可能全部支持因此也有一定的局限性。所以我想到了另一种方案就是让用户直接去选择文件夹然后前端获取文件夹中的所有文件逐一加入到FormData对象中最后统一上传到服务端。我们还可以结合黑白名单过滤的方式对文件夹中的文件进行过滤只保留允许上传的文件发送到服务端。 ​ 想要通过实现文件夹上传需要借助该元素的webkitdirectory属性设置该属性后将限制用户只能选择文件夹而无法选择文件。但是该属性并非标准属性所以请慎用 浏览器兼容性 示例代码 input typefile idfolder namefolder webkitdirectory / div idshowBox文件夹内文件展示区域 /div// 创建空的 FormData 对象 var formData new FormData() // 声明一个白名单数组 表示可以上传的文件后缀名 const whiteList [ppt, pptx, txt, xlsx]; // 调起文件选择框 document.getElementById(folder).click() // 监听文件选择框的change事件 document.getElementById(folder).onchange function (e) {// 获取文件列表类数组对象let files e.target.files// 输出文件列表类数组对象console.log(files);// 将类数组对象转换为数组 且对文件后缀名进行过滤files Array.from(files).filter(item {// 过滤掉文件夹对象if (item.type ! item.name ! .DS_Store) {// 获取文件后缀名const suffix item.name.split(.).pop()// 过滤掉不足在白名单中的文件if (whiteList.indexOf(suffix) -1) {return true}}})// 输出过滤后的文件对象列表console.log(选择文件夹中的所有文件过滤后的结果---, files);// 用于显示的html字符串let html // 遍历文件对象列表files.forEach(item {// 将文件对象的信息拼接到html字符串中html html p文件名${item.name} br /文件路径${item.webkitRelativePath}/p// 将文件对象添加到FormData中formData.append(file, item)})// 将html字符串渲染到页面中document.getElementById(showBox).innerHTML html// 后续上传文件的逻辑...选择文件夹上传后首先浏览器会弹窗获取用户授权Safari浏览器在本地环境时无需授权线上环境未验证 用户授权之后我们可以监听标签的onchange事件然后通过event.target.files获取所选文件夹本身及其的所有子文件和子文件夹组成的文件类数组在进行相关处理时建议使用Array.from()转换真正的数组类型。 原始目录层级 获取的文件类数组以及过滤后的文件结果 页面渲染结果 从上面的示例中可以看出获取文件列表中包含一种name为.DS_Store并且type为的特殊文件这类特殊文件文件表示的就是文件夹我们可以通过该文件的webkitdirectory属性来获取文件夹的真实名称。 ​ 而且此时获取的各文件之间无法体现原始目录层级关系但是我们可以通过每个file文件的webkitRelativePath属性来得知每个文件的层级关系各级路径之间通过 / 连接我们可以通过/ 分割webkitRelativePath属性值从而还原文件夹的原始层级关系。 ​ 注意 文件名和文件夹名最好不要包含/、\等特殊字符因为获取的File中的name和webkitRelativePath属性会将他们转义很有可能会影响层级的拆分和判断。例如/在File中的name和webkitRelativePath中都会被转义为:\在File中的name中会被转义为\在webkitRelativePath中会被转义为/奇奇怪怪的规则(╯°□°╯︵┻━┻。 5、FormData对象结合dataTransfer实现拖拽文件夹批量上传文件 可以实现但其中涉及知识点太多暂时还没完全搞懂想了解的建议查阅最后一篇相关资料。
http://www.zqtcl.cn/news/700762/

相关文章:

  • 瑞金网站建设推广合肥瑶海区地图
  • 静态网站建设国内免费域名
  • 网站建设设计公司电子商务网站开发与管理
  • 手机网站制作设计做国际网站有什么需要注意的
  • 机构网站源码如何分析一个网站
  • 免费营销软件网站网站建设与规划实训总结
  • 网站深度功能建筑人才网市场
  • 学校网站建设的意义和应用服务平台管理系统
  • 网站内容规划要包括什么内容wordpress5.2 php版本
  • 山西建设部网站超值的镇江网站建设
  • 做淘宝要网站网站推广外链怎么做
  • 深圳做网站推广哪家好自建网站优缺点
  • 网站建设询价函什么网站可以做会计题目
  • 电脑网站视频怎么下载珠海免费网站制作
  • wordpress menu icon咸阳seo
  • php制作网站网站开发与客户沟通
  • 百度网站建设平台微盟微商城官网
  • 三明网站seo上海中学分数线
  • 青岛谷歌网站建设网站建站公司排名
  • 成都旅游网站建设规划windows优化大师官方
  • 福永网站建设公司哪家好财务公司承兑汇票
  • 青岛快速建站模板制作公司网页什么价位
  • 网站建设公司的经营范围wordpress设置文本编辑器
  • 做网站用微软雅黑侵权吗wordpress 同类文章
  • 免费下载建设银行官方网站自己做网站犯法吗
  • 手机网站html代码附近做广告牌的店
  • 建设和优化网站的步骤wordpress 模板 含数据库
  • 太原制作网站的工作室wordpress弹幕播放器
  • 英语网站开发菏泽做网站优化的
  • 宜昌建设网站公司做网站语言服务器 空间