web前端性能优化之合并静态资源请求
除过在前端编码的时候将css、js等静态资源文件合并压缩之外,我们还可以在页面中将多个css、js的请求合并为一个请求。
比如我们在淘宝的首页源码中可以看到以下的css引用:
<link rel="stylesheet" href="/??tb/global/2.6.0/global-min.css,tb/tb-fp/1.7.2/style-min.css?t=20131231">
这段代码通过一个网络请求完成两个css文件的请求,是怎么做到的呢?
原来是一个叫nginx-http-concat的模块,nginx-http-concat模块是淘宝开发的基于Nginx减少HTTP请求数量的扩展模块,主要是用于合并减少前端用户Request的HTTP请求的数量。
nginx-http-concat是一个nginx扩展模块,需要在安装nginx的服务器上重新编译nginx并加入nginx-http-concat,这个模块可以在github上下载。
安装之后需要重新修改nginx配置:
location /static/css/ {
concat on; #是否开启concat,默认是关闭的
concat_max_files 20; #允许concat的最大文件数,默认是10
}
location /static/js/ {
concat on;
concat_max_files 30;
}
nginx重新reload之后,就可以使用类似淘宝的方法将多个css或js文件合并为一个请求。比如:
/css/??global.min.css,index.min.css?t=20140107
以上concat的配置在http、server、location中都能生效,除此之外还有几个参数,不设置也罢。
concat_unique on; #是否只对同一类型的MIME types文件进行合并,默认是开启的
concat_types text/css application/x-javascript; #指定可以合并的MIME types,默认是text/css application/x-javascript
concat_delimiter; #指定合并文件之间的分隔符。该分隔符会被插入被合并的文件之间。
concat_ignore_file_error off; #是否忽略文件错误(403或404等),默认是关闭的。
很多时候,我们都想确认页面内容:html标签、css、js、图片等元素的加载顺序。
现在的浏览器基本都自带调试工具,下面就介绍下用chrome浏览器正确查看页面内容加载顺序的方法:
F12打开开发人员工具,点击【Network】(网络)的tab切换到页面元素加载内容查看标签,这里需要说明的是:在该面板未激活时的任何请求都不会被显示,这就是为什么 我们初始看到面板展示的是一片空白啦!要想查看页面内容加载顺序,必须重新加载下页面(f5,或保险起见ctrl+f),这样就可以快速查看到页面各内容加载顺序啦!
Network面板下还有很多分类tab,如:name、domain、cookies、 time(查看元素加载用时),点击各title还可按tab所代表的降序或升序排序标准重新对页面所加载的内容进行排序。当然这种排序,chrome在多次按f12即重新打开开发人员工具时会被重置,恢复至默认,这就很方便我们查看页面内容的加载顺序啦!!
firefox 的firebug查看页面内容加载顺序,方法同chrome类似,至于喜欢用ie的同学,可用httpwatch pro, 继续享受ie的摧残吧,骚年!!
另外需要强调的是:
页面是按“由上到下”的加载顺序来加载图片、js等内容的。但对于样式中加载的图片,它们的加载顺序是根据标签元素在html页面的先后位置决定的,如:body>子元素>子孙元素!更形象的实例是:
假如body 跟 div.test 元素都有定义背景图,则不管两者css是以行内式样式(标签style属性)、嵌入式样式表(<style></style>标签)还是外部样式表(<link>标签) 加载的,加载的顺序始终都是先加载body的背景图片再加载子元素div.test的背景图片,因为body 在html页面中位于div.test之前!
上面强调图片加载顺序完全可用文章前端介绍的“快速查看到页面各内容加载顺序”的方法得到验证!!
一位站长译的一个国外的如何判断用户是否访问过某个网址文章,个人感觉写得非常不错,下面分享一下。我们经常有这样的需求:想知道用户之前有没有访问过某个网址。有没有什么方法或技术能实现这一点呢?
初步探索
注意到,在大部分浏览器默认设置里,用户访问过的链接和没访问过的链接颜色是不同的,如下图:
即用户访问过的链接,computed color默认为紫色(或其他在CSS中指定的颜色):
而没访问过的链接,computed color默认为蓝色(或其他在CSS中指定的颜色):
那是不是说,我们可以在页面上加上我们感兴趣的链接,然后用JavaScript取得这些链接文本实际的颜色,即可知道用户是否访问过指定网址呢?
这个方法真的有效过。2010年有一篇安全文章上即介绍了这种方法,并将这类方法称为“历史嗅探”(history sniffing)。
遗憾的是,各大浏览器厂商都已经注意到了这个问题,根据我的测试,目前最新的浏览器中都对这个问题进行了防范,获取超链接的Computed Style时,无论这个链接是否被访问过,取得的颜色都是未访问过时的那种颜色。
看起来根据颜色获取的方案目前行不通了,不过神奇的是,现在我们有了另外一种方案。
requestAnimationFrame
Context Information Security公司最近出了一份名为《PIXEL PERFECT TIMING ATTACKS WITH HTML5》的报告,其中提到了一种很有创意的方案:使用HTML5中的requestAnimationFrame,根据浏览器渲染已访问过及未访问过的链接的时间差,判断指定链接是否访问过。
现代浏览器绘图时每一帧的流程大致如下图所示:
大致流程为:JS修改某个元素的样式,浏览器重新计算对应元素的外观及位置,然后将它们绘制出来,这个过程即是一帧。而requestAnimationFrame的作用则是可以注册一个函数,在下一帧开始绘制之前进行调用。
requestAnimationFrame的初衷是让开发人员可以更好地管理动画,绘制更平滑的动画,如这篇博客中所说的。不过,这个接口也让获取不同元素的渲染时间成为了可能。
工作原理
在开始之前,我们需要了解的是浏览器是如何渲染访问过的链接和未访问过的链接的。
当浏览器渲染一个页面时,浏览器必须区分出某个链接是否曾访问过。每个浏览器都有一个记录访问过的链接的数据库,此时它要做的就是从这个数据库中查询
指定的URL是否存在。
IE与Firefox中,如果链接已经渲染到页面上了,查询还没完成,浏览器会先使用“未访问过”的样式来渲染,查询结果返回时,如果指定链接是已访问过,那么浏览器就重绘指定的链接。而这个“重绘”是需要时间的,可以使用requestAnimationFrame来监测。
Chrome浏览器和Firefox、IE不同,它会一直等到数据库URL查询完成才将链接渲染到屏幕上。
除了初始渲染之外,使用JavaScript修改链接的href也有可能引发浏览器重绘。测试显示,在Firefox中,修改一个链接的href,如果改变了它的“已访问”状态,则会引发重绘。但在IE中这不能工作,一个链接一旦创建,改变href永远也不会同时改变它的“已访问”状态。
Chrome中有点例外,只改变href并不会引发重绘,不过,如果在改变href的同时也“触碰”一下链接的样式(但不修改),则当新href需要改变“已访问”状态时,浏览器会重绘对应的链接。
所谓的“触碰”指的是类似这样的操作:
1
2
3
4
5
6
7
8
9
10
|
< word">a href = "http://www.google.com" id = "link1" >############</ a > < script > var el = document.getElementById('link1'); el.href = 'http://www.yahoo.com'; // below lines are required for Chrome to force style update el.style.color = 'red'; el.style.color = ''; </ script > |
简单来说,基本原理就是这样:创建链接,改变它的href,使用requestAnimationFrame来监测接下来若干帧的耗时,判断是否发生了重绘,如果发生了重绘,说明指定链接的“已访问”状态发生了变化,即可判断出指定链接是否被访问过。
当然,实际操作过程中还有不少问题需要考虑,比如,浏览器渲染通常都非常快,重绘的时间可能也会非常短,导致完全无法区分。解决方案主要有两个,一是增加链接数,创建多个A链接,指向同一个URL,需要时使用JS同时改变这些A链接的href属性为另一个值。另一个方案是给元素加一些耗时的样式,比如文字阴影,并且让模糊半径尽可能大,这样在重绘时需要的时间就会多很多了。
实践
我写了一个针对Chrome浏览器的demo,你可以使用Chrome访问
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Browser History Sniffing</title>
<script src="http://codeorigin.jquery.com/jquery-1.10.2.min.js"></script>
<style>
html, body {
padding: 0;
margin: 0;
}
#wrapper {
padding: 16px 32px;
}
ul#test-area {
opacity: 0.1;
float: right;
}
ul#test-area a {
text-shadow: 5px 5px 500px #999;
}
#operation {
display: none;
padding-left: 4em;
}
#links {
line-height: 20px;
width: 60%;
}
h1 {
font-size: 24px;
margin: 0;
padding: 0;
line-height: 48px;
}
#desc {
font-size: 12px;
line-height: 20px;
background: #f5f5f5;
padding: 8px;
margin-bottom: 40px;
}
.inform {
background: #ff0;
}
span.time {
color: #ccc;
padding-left: 2em;
font-size: 12px;
}
</style>
<script type="text/javascript">
// var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");
// document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3Fe99baf24f819fcd0a35adeec04e55b97' type='text/javascript'%3E%3C/script%3E"));
</script>
</head>
<body>
<div id="wrapper">
<h1>Browser History Sniffing</h1>
<div id="desc">
<p>浏览器历史嗅探 demo,<span class="inform">仅支持 Chrome</span>,在
Mac 10.8.4/Chrome 28.0、
Windows XP SP3/Chrome 28.0、
Ubuntu 12.4/Chrome 28.0
上测试通过。
</p>
<p>关于:<a href="http://oldj.net/article/browser-history-sniffing/">http://oldj.net/article/browser-history-sniffing/</a>
</p>
</div>
<ul id="test-area"></ul>
<ul id="links"></ul>
<div id="operation">
<input id="url" placeholder="http://" size="50"/>
<input id="add" type="button" value="TEST"/>
</div>
</div>
<script>
(function () {
// var known_visited_url = "http://oldj.net/static/history-sniffing/test.html";
var known_visited_url = location.href;
var links = [
known_visited_url,
"http://www.taobao.com",
http://www.111cn.net",
"http://a.com",
"http://www.tmall.com",
"http://www.alipay.com",
"http://www.111cn.net",
"http://list.tmall.com/search_product.htm?spm=1.1000386.a2145lv.2.b5GVjI&from=sn_1_prop&area_code=310000&auction_tag=835&vmarket=0&style=g&sort=s&n=60&s=0&cat=50024400#J_crumbs",
"http://www.facebook.com",
"http://www.google.com"
];
var last_time = 0;
var current_is_visited = false;
var min_large_time = -1;
function checkIsLinkVisted(url, callback) {
var i_count = 10;
var times = [];
// $("#test-area a").each(function () {
// this.href = known_visited_url;
// });
function check(time) {
var delay = parseInt(time - last_time);
times.push(delay);
last_time = time;
if (i_count > 0) {
requestAnimationFrame(check);
} else {
checkEnd();
}
i_count--;
}
function checkEnd() {
var large_time_count = 0;
var large_time = 60;
for (var i = 0; i < times.length; i++) {
if (times[i] > large_time) large_time_count++;
}
console.log(times);
if (min_large_time == -1) {
min_large_time = large_time_count;
if (min_large_time < 2) min_large_time = 2;
current_is_visited = true;
} else if (large_time_count >= min_large_time) {
current_is_visited = !current_is_visited;
}
$("#links").append("<li class='"
+ (current_is_visited ? "visited" : "unvisited") + "'>"
+ (current_is_visited ? "[v]" : "[ ]") + " "
+ "<a href='" + url + "'>"
+ url + "</a>"
+ "<span class='time'>"
+ times.join(", ")
+ "</span>"
+ "</li>");
if (callback && typeof callback == "function") callback();
}
setTimeout(function () {
$("#test-area a").each(function () {
this.href = url;
this.style.color = "red";
this.style.color = "";
});
requestAnimationFrame(check);
}, 500);
}
function initTestArea() {
var htmls = [];
for (var i = 0; i < 500; i++) {
htmls.push("<li><a href='" + known_visited_url + "'>###########</a></li>");
}
$("#test-area").append(htmls.join("\n"));
}
function checkByTurn(list) {
var current = list.shift();
if (current) {
checkIsLinkVisted(current, function () {
checkByTurn(list);
});
} else {
// $("#test-area").html("");
$("#operation").slideDown();
}
}
$(document).ready(function () {
initTestArea();
checkByTurn(links);
// checkIsLinkVisted(links[3]);
});
$("input#url").keydown(function (e) {
if (e.keyCode == 13) {
checkIsLinkVisted(this.value);
}
});
$("input#add").click(function () {
var url = $("input#url").val();
checkIsLinkVisted(url);
});
})();
</script>
</body>
</html>
测试。
这个测试需要一个已知的已访问过的URL作为基准链接,既然你在读这篇博客,并且可能会点击上面的测试链接,那我就把上面的那个测试链接作为基准链接了。接下来,依次测试各个链接,看是否会引发重绘,据此判断你是否曾经访问过指定链接。
效果如下图:
已访问过的链接除了会使用紫色显示(这是浏览器自带的功能)外,我还在前面加了一个“[v]”标记,未访问过的链接前面则是“[ ]”。另外,你也可以在下面的输入框中输入新的URL,看看能不能正确判断。
原理即是上面提到的,修改链接地址后,根据接下来各帧的时间,判断是否发生了重绘(redraw),如果有,则认为链接的已访问状态发生了改变。
这是一个Chrome的例子,根据contextis的白皮书,稍加改造,即可适用于IE及Firefox。
更多
这种通过判断渲染时间来获取信息的方式非常可怕,它的强大之处在于可以获取第三方网站的访问记录,并且完全不需要用户知情。比如,可以通过这个方式了解用户是否访问过竞争对手的网站,或者了解用户是否访问过指定的限制级网站。
Princeton大学一篇2000年的论文《Timing Attacks on Web Privacy》就已经指出了这类计时攻击可能会泄露用户隐私。这篇论文里也提出了一种判断用户是否访问过指定网址的方法,做法是在当前页面加载指定网站的固定资源(访问过那个网站的用户都会下载的资源),比如logo或js文件,如果用户之前访问过,则浏览器会从本地缓存中读取对应的资源,速度会快很多,否则会重新下载。使用JavaScript或Java applet(2000年的论文,那时Java applet还很流行)可以度量这个时间,进而判断用户是否曾访问过指定网址。
这个十几年前提出的方案目前仍然是有效的,不过相对来说,本文中提出的方案更为先进,效率也更高。
另外,除了客户端时间外,服务器端时间也有可能泄露意想不到的数据。比如来自Stanford的论文《Exposing Private Information by Timing Web Applications》中提到,由于后台在处理不同类型的数据时使用的逻辑不同,耗费的时间也不同,因此,只要构造适当的请求,反复执行并度量时间,就可能获得很多隐私信息。
如何防范此类计时攻击呢?目前似乎没有很好的办法。对服务器端来说,最好的方案或许是使用一些手段,让所有请求的返回时间常量化,比如统一为500毫秒,如果某个请求早于500毫秒完成,就休眠一会儿,直到500毫秒时再返回。但这样的问题是,整个站点的响应速度将取决于最慢的那个页面,对于效率至上的团队来说,这可能是无法忍受的。如果不是常量化,而是在每个请求之后休眠一段随机的时间也是不行的,因为完全随机意味着统计学上有迹可循,只要有足够多的样本,攻击者完全可以将随机因素剔除出去。
对客户端的计时攻击来说,防范的办法就更少了,或许只有等待浏览器厂商注意到这类问题,并对浏览器特性做出修改,就像无法取得已访问链接的computed color一样。
在网页制作中HTML中的使用图像是一个非常见的问题了,我们看到很多漂亮的图文结合的页面都是由html广西与图片组成的。
HTML文档中插入图像是web前端开发中常见,也是必要的应用方式。通过在页面中插入相应的图像,可以给浏览者带来良好的体验,也能够使页面内容更直观,更具有说服力。
本节主要讲述如何在HTML文档中使用img标签,并结合各种属性设置图片在HTML文档中的展现形式,以确保图像能够更好的服务于HTML页面。
HTML中使用图像的方法
HTML中需要插入图像的话,需要通过<img>标签进行定义。在<img>标签中通过设置src属性来指定该img标签需要加载图像的url地址。图像的url地址可以是绝对地址,也可以是相对地址。
注意img标签只能够添加属性,没有闭合标签。例如常见的img标签为:<img src=”xxx.jpg”/>
图像还可以通过css样式,以背景的形式加载到页面中。本节暂时不讲述如何通过css的背景调用显示图片,该内容将会在以后的css课程中进行讲解。
<img>标签的属性
alt属性 | alt属性可以在浏览器显示或无法载入图像时,显示一段预先设置好的文本来告诉浏览者,该图像原本显示的大致内容。这样可以在某些无法加载图片的情况下帮助用户了解原本这个图片的大致内容。例如现在常见的手机浏览器在3G模式下都会屏蔽网页图片,因此书写alt文本是非常必要的。alt标签内容还会影响seo搜索引擎对图片的识别,所以在书写alt时请填入与图片更相关的文字描述。
例子:<img alt=”这是一个对atl无法显示图像时的预留文本” />
显示结果:
height属性、width属性 | 可以强制定义图像的高度与宽度,并且图像会失去自身原有的高度与宽度,而通过拉伸的形式按照设定的高度与宽度进行显示。默认情况下,浏览器会自动获取图像文件原有的高宽。但是某些情况下,不指定图像的高度与宽度会影响浏览器的显示效率,因此最好为图像设定正确的高度值。
例子:<img alt=”1539455109-0″ src=”/wp-content/uploads/2013/09/1539455109-0.jpg” width=”100″ height=”100″ />
显示:
为图像设置链接
通过为图像设定链接,可以使整个图像成为锚点。在前端开发中,可以让用户更容易关注,并点击超链接。常见的应用为某些图形按钮,可以直接通过点击图像区域来进行页面跳转。
为图像设置连接的方法为,在<img>标签的外面套用一层<a>标签,a标签的锚点会被设置到图像上。
例子:<a title=”《前端开发从零学起》从基础到深入实战教程” href=”/front_tutorial”><img src=”/upload/news/201609/20131125065005394.jpg” width=”100″ height=”100″ /></a>
显示:
图像热区
HTML中可以为单个图像设定不同的热区,每个热区都可以指向不同的url地址。单独的热区可以通过坐标的或属性的形式设定热区的形状,用户在热区的范围内通过鼠标点击可以触发对应的跳转事件。
例如下面引用一个实例,热区设定到了对应的星球部分,通过点击不同的星球,会跳转到相应的星球大图页面中:
为图像设定热区的步骤为:
1.通过在<img>标签中设置usemap属性,并设定一个值,该值是用于热区编码识别是哪一个图片应用热区。例如:usemap=”#planetmap”
2.在页面中使用<map>标签定义热区编码,标签以<map>开始</map>结束。需要为map标签指定<img>的图片名称,例如:<map name=”planetmap” id=”planetmap”> 这样就与上面图片的id关联起来了。
3.使用<area>标签设定热区的形状shape、位置坐标coords、超链接href、超链接的打开方式target。由于定义热区的坐标比较复杂,而且在浏览器中反复测试坐标比较麻烦,建议使用dreameweaver的设计视图中的图像热区功能。
例:在dreameweave的设计视图中,点击页面中的图像,属性面板就会激活热区功能。
如图:
结语:HTML中使用图像是贯彻整个前端开发的核心应用。通过在HTML中设置不同的图像,来控制页面的UI显示,优化用户体验。通过对图像添加锚点,也可以更方便的引导用户来关注你站点的主要内容。
Vim是一个类似于Vi的文本编辑器,不过在Vi的基础上增加了很多新的特性,Vim普遍被推崇为类Vi编辑器中最好的一个
编辑 .vimrc文件
vim ~/.vimrc添加以下代码:
代码如下 | 复制代码 |
function CompleteSymbol() inoremap ( ()<ESC>i inoremap [ []<ESC>i inoremap { {}<ESC>i inoremap < <><ESC>i inoremap " ""<ESC>i inoremap ' ''<ESC>i |
endfunction在函数下方调用:
代码如下 | 复制代码 |
call CompleteSymbol() |
补全符号后,光标在符号内。
PS:调用一定要在函数下方。
若复制的字符里也有以上符号,则会在复制后自动补全,这个是比较?宓模?挥屑觳馐欠褚丫?谷?耍?灰?龅椒?诺目?迹?突峤?胁谷??/p>
相关文章
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- 这篇文章主要介绍了Angular性能优化之第三方组件和懒加载技术,对性能优化感兴趣的同学,可以参考下...2021-05-11
- 20岁老牌网页程序语言PHP,最快将在10月底释出PHP 7新版,这是十年来的首次大改版,最大特色是在性能上的大突破,能比前一版PHP 5快上一倍,PHP之父Rasmus Lerdorf表示,甚至能比HHVM虚拟机下的PHP程序性能更快。HHVM 是脸书为自...2015-11-24
- 这篇文章主要介绍了JavaScript提高网站性能优化的建议(二)的相关资料,需要的朋友可以参考下...2016-07-29
详解SpringBoot之访问静态资源(webapp...)
这篇文章主要介绍了详解SpringBoot之访问静态资源(webapp...),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-14利用 Chrome Dev Tools 进行页面性能分析的步骤说明(前端性能优化)
这篇文章主要介绍了利用 Chrome Dev Tools 进行页面性能分析的步骤说明(前端性能优化),本文给大家介绍的非常想详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-02-24- 这篇文章主要介绍了提升jQuery的性能需要做好的七件事,希望真的帮助大家提升jQuery性能,需要的朋友可以参考下...2016-01-14
解决Springboot整合shiro时静态资源被拦截的问题
这篇文章主要介绍了解决Springboot整合shiro时静态资源被拦截的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-26- php测试性能代码 function microtime_float () { list ($usec, $sec) = explode(" ", microtime()); return ((float) $usec + (float) $sec); } functio...2016-11-25
- 文章介绍了css中空路径对页面性能影响的解决方案,这个可能很多美工朋友不会去注意这一点,下面我们来看看吧。 在写 CSS 的时候,用 background:url(#) 还是会对页面进...2017-07-06
- 这篇文章主要介绍了如何用Node.js编写内存效率高的应用程序,对Node.js感兴趣的同学,可以参考下...2021-05-01
- 这篇文章主要介绍了Redis 执行性能测试的方法,文中讲解非常细致,帮助大家更好的理解和学习redis,感兴趣的朋友可以了解下...2021-01-15
- 一.Join语法概述join 用于多表中字段之间的联系,语法如下:复制代码 代码如下:... FROM table1 INNER|LEFT|RIGHT JOIN table2 ON conditionatable1:左表;table2:右表。JOIN 按照功能大致分为如下三类:INNER JOIN(内连接,或...2014-05-31
- 这篇文章主要介绍了javascript性能优化之DOM交互操作技巧,结合实例形式总结分析了JavaScript针对DOM操作过程中的各种常见优化操作技巧,需要的朋友可以参考下...2015-12-14
- 这篇文章主要介绍了C#导出数据到excel如何提升性能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-13
- 自己的一个网站,由于单表的数据记录高达了一百万条,造成数据访问很慢,Google分析的后台经常报告超时,尤其是页码大的页面更是慢的不行...2016-09-18
- 开发中需要传递变参,考虑使用 dynamic 还是 Dictionary,dynamic 的编码体验显著优于 Dictionary,如果性能差距不大的话,我会选择使用dynamic。下面通过本文给大家详细介绍下C#中Dynamic和Dictionary性能比较,一起看看吧...2020-06-25
- Nginx ("engine x") 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAPPOP3SMTP 代理服务器。...2016-01-27
- 这篇文章主要介绍Golang标准库和外部库的性能比较,下面文章讲围绕这两点展开内容,感兴趣的小伙伴可以参考一下...2021-10-19
- 数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显。这篇文章主要介绍了MySQL数据库21条最佳性能优化经验的相关资料,需要的朋友可以参考下...2016-10-20