Asp.net MVC Bundle 的使用与扩展
一、Asp.net 自带Bundle的使用:
1. 在Globale中注册与配置
BundleConfig.RegisterBundles(BundleTable.Bundles);
public class BundleConfig
{
// For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
public static void RegisterBundles(BundleCollection bundles)
{
// Use the development version of Modernizr to develop with and learn from. Then, when you're
// ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*")); bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js")); #if !DEBUG
BundleTable.EnableOptimizations = true;
#endif
}
}
2. 页面上使用
@Scripts.Render("~/bundles/jquery")
二、扩展使用-动态Bundle
要求达到以下目标:1. 支持动态页面上的Bundle,而不必每次在Global中添加Bundle。2.支持Javascript混淆
1. 扩展方法
public static class Extension
{
public static IHtmlString Script(this HtmlHelper helper, params string[] urls)
{
var bundleDirectory = "~/bundles/" + MakeBundleName("js", urls);
var bundle = BundleTable.Bundles.GetBundleFor(bundleDirectory);
if (bundle == null)
{
var transform = new JavascriptObfuscator();
bundle = new ScriptBundle(bundleDirectory).Include(urls);
bundle.Transforms.Add(transform);
BundleTable.Bundles.Add(bundle);
}
return Scripts.Render(bundleDirectory);
} public static IHtmlString Style(this HtmlHelper helper, params string[] urls)
{
var bundleDirectory = "~/bundles/" + MakeBundleName("css", urls);
var bundle=BundleTable.Bundles.GetBundleFor(bundleDirectory);
if (bundle == null)
{
bundle = new StyleBundle(bundleDirectory).Include(urls);
BundleTable.Bundles.Add(bundle);
}
return Styles.Render(bundleDirectory);
} private static string MakeBundleName(string type, params string[] urls)
{
var array =
urls.SelectMany(url => url.Split('/'))
.SelectMany(url => url.Split('.'))
.Distinct()
.Except(new[] {"~", type}); return string.Join("-", array);
}
}
JavascriptObfuscator 类的实现
public class JavascriptObfuscator : IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
var p = new ECMAScriptPacker(ECMAScriptPacker.PackerEncoding.Normal, true, false); response.Content = p.Pack(response.Content);
}
}
2. 页面上使用
@Html.Style("~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.theme.css", "~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.menu.css")
@Html.Script("~/Scripts/JQueryUI2/ui/jquery.ui.core.js", "~/Scripts/JQueryUI2/ui/jquery.ui.position.js", "~/Scripts/JQueryUI2/ui/jquery.ui.widget.js", "~/Scripts/JQueryUI2/ui/jquery.ui.menu.js")
附录:javascript混淆器代码
/// <summary>
/// Packs a javascript file into a smaller area, removing unnecessary characters from the output.
/// </summary>
public class ECMAScriptPacker : IHttpHandler
{
/// <summary>
/// The encoding level to use. See http://dean.edwards.name/packer/usage/ for more info.
/// </summary>
public enum PackerEncoding { None = 0, Numeric = 10, Mid = 36, Normal = 62, HighAscii = 95 }; private PackerEncoding encoding = PackerEncoding.Normal;
private bool fastDecode = true;
private bool specialChars = false;
private bool enabled = true; string IGNORE = "$1"; /// <summary>
/// The encoding level for this instance
/// </summary>
public PackerEncoding Encoding
{
get { return encoding; }
set { encoding = value; }
} /// <summary>
/// Adds a subroutine to the output to speed up decoding
/// </summary>
public bool FastDecode
{
get { return fastDecode; }
set { fastDecode = value; }
} /// <summary>
/// Replaces special characters
/// </summary>
public bool SpecialChars
{
get { return specialChars; }
set { specialChars = value; }
} /// <summary>
/// Packer enabled
/// </summary>
public bool Enabled
{
get { return enabled; }
set { enabled = value; }
} public ECMAScriptPacker()
{
Encoding = PackerEncoding.Normal;
FastDecode = true;
SpecialChars = false;
} /// <summary>
/// Constructor
/// </summary>
/// <param name="encoding">The encoding level for this instance</param>
/// <param name="fastDecode">Adds a subroutine to the output to speed up decoding</param>
/// <param name="specialChars">Replaces special characters</param>
public ECMAScriptPacker(PackerEncoding encoding, bool fastDecode, bool specialChars)
{
Encoding = encoding;
FastDecode = fastDecode;
SpecialChars = specialChars;
} /// <summary>
/// Packs the script
/// </summary>
/// <param name="script">the script to pack</param>
/// <returns>the packed script</returns>
public string Pack(string script)
{
if (enabled)
{
script += "\n";
script = basicCompression(script);
if (SpecialChars)
script = encodeSpecialChars(script);
if (Encoding != PackerEncoding.None)
script = encodeKeywords(script);
}
return script;
} //zero encoding - just removal of whitespace and comments
private string basicCompression(string script)
{
ParseMaster parser = new ParseMaster();
// make safe
parser.EscapeChar = '\\';
// protect strings
parser.Add("'[^'\\n\\r]*'", IGNORE);
parser.Add("\"[^\"\\n\\r]*\"", IGNORE);
// remove comments
parser.Add("\\/\\/[^\\n\\r]*[\\n\\r]");
parser.Add("\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/");
// protect regular expressions
parser.Add("\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)", "$2");
parser.Add("[^\\w\\$\\/'\"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?", IGNORE);
// remove: ;;; doSomething();
if (specialChars)
parser.Add(";;[^\\n\\r]+[\\n\\r]");
// remove redundant semi-colons
parser.Add(";+\\s*([};])", "$2");
// remove white-space
parser.Add("(\\b|\\$)\\s+(\\b|\\$)", "$2 $3");
parser.Add("([+\\-])\\s+([+\\-])", "$2 $3");
parser.Add("\\s+");
// done
return parser.Exec(script);
} WordList encodingLookup;
private string encodeSpecialChars(string script)
{
ParseMaster parser = new ParseMaster();
// replace: $name -> n, $$name -> na
parser.Add("((\\$+)([a-zA-Z\\$_]+))(\\d*)",
new ParseMaster.MatchGroupEvaluator(encodeLocalVars)); // replace: _name -> _0, double-underscore (__name) is ignored
Regex regex = new Regex("\\b_[A-Za-z\\d]\\w*"); // build the word list
encodingLookup = analyze(script, regex, new EncodeMethod(encodePrivate)); parser.Add("\\b_[A-Za-z\\d]\\w*", new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); script = parser.Exec(script);
return script;
} private string encodeKeywords(string script)
{
// escape high-ascii values already in the script (i.e. in strings)
if (Encoding == PackerEncoding.HighAscii) script = escape95(script);
// create the parser
ParseMaster parser = new ParseMaster();
EncodeMethod encode = getEncoder(Encoding); // for high-ascii, don't encode single character low-ascii
Regex regex = new Regex(
(Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+"
);
// build the word list
encodingLookup = analyze(script, regex, encode); // encode
parser.Add((Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+",
new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); // if encoded, wrap the script in a decoding function
return (script == string.Empty) ? "" : bootStrap(parser.Exec(script), encodingLookup);
} private string bootStrap(string packed, WordList keywords)
{
// packed: the packed script
packed = "'" + escape(packed) + "'"; // ascii: base for encoding
int ascii = Math.Min(keywords.Sorted.Count, (int)Encoding);
if (ascii == 0)
ascii = 1; // count: number of words contained in the script
int count = keywords.Sorted.Count; // keywords: list of words contained in the script
foreach (object key in keywords.Protected.Keys)
{
keywords.Sorted[(int)key] = "";
}
// convert from a string to an array
StringBuilder sbKeywords = new StringBuilder("'");
foreach (string word in keywords.Sorted)
sbKeywords.Append(word + "|");
sbKeywords.Remove(sbKeywords.Length - 1, 1);
string keywordsout = sbKeywords.ToString() + "'.split('|')"; string encode;
string inline = "c"; switch (Encoding)
{
case PackerEncoding.Mid:
encode = "function(c){return c.toString(36)}";
inline += ".toString(a)";
break;
case PackerEncoding.Normal:
encode = "function(c){return(c<a?\"\":e(parseInt(c/a)))+" +
"((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}";
inline += ".toString(a)";
break;
case PackerEncoding.HighAscii:
encode = "function(c){return(c<a?\"\":e(c/a))+" +
"String.fromCharCode(c%a+161)}";
inline += ".toString(a)";
break;
default:
encode = "function(c){return c}";
break;
} // decode: code snippet to speed up decoding
string decode = "";
if (fastDecode)
{
decode = "if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\\\w+'};c=1;}";
if (Encoding == PackerEncoding.HighAscii)
decode = decode.Replace("\\\\w", "[\\xa1-\\xff]");
else if (Encoding == PackerEncoding.Numeric)
decode = decode.Replace("e(c)", inline);
if (count == 0)
decode = decode.Replace("c=1", "c=0");
} // boot function
string unpack = "function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p;}";
Regex r;
if (fastDecode)
{
//insert the decoder
r = new Regex("\\{");
unpack = r.Replace(unpack, "{" + decode + ";", 1);
} if (Encoding == PackerEncoding.HighAscii)
{
// get rid of the word-boundries for regexp matches
r = new Regex("'\\\\\\\\b'\\s*\\+|\\+\\s*'\\\\\\\\b'");
unpack = r.Replace(unpack, "");
}
if (Encoding == PackerEncoding.HighAscii || ascii > (int)PackerEncoding.Normal || fastDecode)
{
// insert the encode function
r = new Regex("\\{");
unpack = r.Replace(unpack, "{e=" + encode + ";", 1);
}
else
{
r = new Regex("e\\(c\\)");
unpack = r.Replace(unpack, inline);
}
// no need to pack the boot function since i've already done it
string _params = "" + packed + "," + ascii + "," + count + "," + keywordsout;
if (fastDecode)
{
//insert placeholders for the decoder
_params += ",0,{}";
}
// the whole thing
return "eval(" + unpack + "(" + _params + "))\n";
} private string escape(string input)
{
Regex r = new Regex("([\\\\'])");
return r.Replace(input, "\\$1");
} private EncodeMethod getEncoder(PackerEncoding encoding)
{
switch (encoding)
{
case PackerEncoding.Mid:
return new EncodeMethod(encode36);
case PackerEncoding.Normal:
return new EncodeMethod(encode62);
case PackerEncoding.HighAscii:
return new EncodeMethod(encode95);
default:
return new EncodeMethod(encode10);
}
} private string encode10(int code)
{
return code.ToString();
} //lookups seemed like the easiest way to do this since
// I don't know of an equivalent to .toString(36)
private static string lookup36 = "0123456789abcdefghijklmnopqrstuvwxyz"; private string encode36(int code)
{
string encoded = "";
int i = 0;
do
{
int digit = (code / (int)Math.Pow(36, i)) % 36;
encoded = lookup36[digit] + encoded;
code -= digit * (int)Math.Pow(36, i++);
} while (code > 0);
return encoded;
} private static string lookup62 = lookup36 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private string encode62(int code)
{
string encoded = "";
int i = 0;
do
{
int digit = (code / (int)Math.Pow(62, i)) % 62;
encoded = lookup62[digit] + encoded;
code -= digit * (int)Math.Pow(62, i++);
} while (code > 0);
return encoded;
} private static string lookup95 = "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; private string encode95(int code)
{
string encoded = "";
int i = 0;
do
{
int digit = (code / (int)Math.Pow(95, i)) % 95;
encoded = lookup95[digit] + encoded;
code -= digit * (int)Math.Pow(95, i++);
} while (code > 0);
return encoded;
} private string escape95(string input)
{
Regex r = new Regex("[\xa1-\xff]");
return r.Replace(input, new MatchEvaluator(escape95Eval));
} private string escape95Eval(Match match)
{
return "\\x" + ((int)match.Value[0]).ToString("x"); //return hexadecimal value
} private string encodeLocalVars(Match match, int offset)
{
int length = match.Groups[offset + 2].Length;
int start = length - Math.Max(length - match.Groups[offset + 3].Length, 0);
return match.Groups[offset + 1].Value.Substring(start, length) +
match.Groups[offset + 4].Value;
} private string encodeWithLookup(Match match, int offset)
{
return (string)encodingLookup.Encoded[match.Groups[offset].Value];
} private delegate string EncodeMethod(int code); private string encodePrivate(int code)
{
return "_" + code;
} private WordList analyze(string input, Regex regex, EncodeMethod encodeMethod)
{
// analyse
// retreive all words in the script
MatchCollection all = regex.Matches(input);
WordList rtrn;
rtrn.Sorted = new StringCollection(); // list of words sorted by frequency
rtrn.Protected = new HybridDictionary(); // dictionary of word->encoding
rtrn.Encoded = new HybridDictionary(); // instances of "protected" words
if (all.Count > 0)
{
StringCollection unsorted = new StringCollection(); // same list, not sorted
HybridDictionary Protected = new HybridDictionary(); // "protected" words (dictionary of word->"word")
HybridDictionary values = new HybridDictionary(); // dictionary of charCode->encoding (eg. 256->ff)
HybridDictionary count = new HybridDictionary(); // word->count
int i = all.Count, j = 0;
string word;
// count the occurrences - used for sorting later
do
{
word = "$" + all[--i].Value;
if (count[word] == null)
{
count[word] = 0;
unsorted.Add(word);
// make a dictionary of all of the protected words in this script
// these are words that might be mistaken for encoding
Protected["$" + (values[j] = encodeMethod(j))] = j++;
}
// increment the word counter
count[word] = (int)count[word] + 1;
} while (i > 0);
/* prepare to sort the word list, first we must protect
words that are also used as codes. we assign them a code
equivalent to the word itself.
e.g. if "do" falls within our encoding range
then we store keywords["do"] = "do";
this avoids problems when decoding */
i = unsorted.Count;
string[] sortedarr = new string[unsorted.Count];
do
{
word = unsorted[--i];
if (Protected[word] != null)
{
sortedarr[(int)Protected[word]] = word.Substring(1);
rtrn.Protected[(int)Protected[word]] = true;
count[word] = 0;
}
} while (i > 0);
string[] unsortedarr = new string[unsorted.Count];
unsorted.CopyTo(unsortedarr, 0);
// sort the words by frequency
Array.Sort(unsortedarr, (IComparer)new CountComparer(count));
j = 0;
/*because there are "protected" words in the list
we must add the sorted words around them */
do
{
if (sortedarr[i] == null)
sortedarr[i] = unsortedarr[j++].Substring(1);
rtrn.Encoded[sortedarr[i]] = values[i];
} while (++i < unsortedarr.Length);
rtrn.Sorted.AddRange(sortedarr);
}
return rtrn;
} private struct WordList
{
public StringCollection Sorted;
public HybridDictionary Encoded;
public HybridDictionary Protected;
} private class CountComparer : IComparer
{
HybridDictionary count; public CountComparer(HybridDictionary count)
{
this.count = count;
} #region IComparer Members public int Compare(object x, object y)
{
return (int)count[y] - (int)count[x];
} #endregion
}
#region IHttpHandler Members public void ProcessRequest(HttpContext context)
{
// try and read settings from config file
if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null)
{
NameValueCollection cfg =
(NameValueCollection)
System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker");
if (cfg["Encoding"] != null)
{
switch (cfg["Encoding"].ToLower())
{
case "none":
Encoding = PackerEncoding.None;
break;
case "numeric":
Encoding = PackerEncoding.Numeric;
break;
case "mid":
Encoding = PackerEncoding.Mid;
break;
case "normal":
Encoding = PackerEncoding.Normal;
break;
case "highascii":
case "high":
Encoding = PackerEncoding.HighAscii;
break;
}
}
if (cfg["FastDecode"] != null)
{
if (cfg["FastDecode"].ToLower() == "true")
FastDecode = true;
else
FastDecode = false;
}
if (cfg["SpecialChars"] != null)
{
if (cfg["SpecialChars"].ToLower() == "true")
SpecialChars = true;
else
SpecialChars = false;
}
if (cfg["Enabled"] != null)
{
if (cfg["Enabled"].ToLower() == "true")
Enabled = true;
else
Enabled = false;
}
}
// try and read settings from URL
if (context.Request.QueryString["Encoding"] != null)
{
switch (context.Request.QueryString["Encoding"].ToLower())
{
case "none":
Encoding = PackerEncoding.None;
break;
case "numeric":
Encoding = PackerEncoding.Numeric;
break;
case "mid":
Encoding = PackerEncoding.Mid;
break;
case "normal":
Encoding = PackerEncoding.Normal;
break;
case "highascii":
case "high":
Encoding = PackerEncoding.HighAscii;
break;
}
}
if (context.Request.QueryString["FastDecode"] != null)
{
if (context.Request.QueryString["FastDecode"].ToLower() == "true")
FastDecode = true;
else
FastDecode = false;
}
if (context.Request.QueryString["SpecialChars"] != null)
{
if (context.Request.QueryString["SpecialChars"].ToLower() == "true")
SpecialChars = true;
else
SpecialChars = false;
}
if (context.Request.QueryString["Enabled"] != null)
{
if (context.Request.QueryString["Enabled"].ToLower() == "true")
Enabled = true;
else
Enabled = false;
}
//handle the request
TextReader r = new StreamReader(context.Request.PhysicalPath);
string jscontent = r.ReadToEnd();
r.Close();
context.Response.ContentType = "text/javascript";
context.Response.Output.Write(Pack(jscontent));
} public bool IsReusable
{
get
{
if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null)
{
NameValueCollection cfg =
(NameValueCollection)
System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker");
if (cfg["IsReusable"] != null)
if (cfg["IsReusable"].ToLower() == "true")
return true;
}
return false;
}
} #endregion
}
总结:
1. 通过继承IBundleTransform接口能够对Asp.net MVC中自带Bundle类进行扩展。
2. 可以动态添加Bundle,而不必在Globale中一次性全都添加。
3. 除了Asp.net MVC自带的Bundle外,还有其他的Bundle插件,例如Bundle Transformer: YUI 1.8.0
本文摘自:http://blog.csdn.net/leewhoee/article/details/19107013#t3
Asp.net MVC Bundle 的使用与扩展的更多相关文章
- [asp.net mvc 奇淫巧技] 05 - 扩展ScriptBundle,支持混淆加密javascript
一.需求: 在web开发中,经常会处理javascript的一些问题,其中就包括js的压缩,合并,发布版本以及混淆加密等等问题.在asp.net 开发中我们使用ScriptBundle已经可以解决ja ...
- Asp.net Mvc模块化开发之分区扩展框架
对于一个企业级项目开发,模块化是非常重要的. 默认Mvc框架的AreaRegistration对模块化开发真的支持很好吗?真的有很多复杂系统在使用默认的分区开发的吗?我相信大部分asp.net的技术团 ...
- Asp.net mvc返回Xml结果,扩展Controller实现XmlResult以返回XML格式数据
我们都知道Asp.net MVC自带的Action可以有多种类型,比如ActionResult,ContentResult,JsonResult……,但是很遗憾没有支持直接返回XML的XmlResul ...
- ASP.NET MVC显示UserControl控件(扩展篇)
昨晚Insus.NET有怀旧一下<念念不忘,ASP.NET MVC显示WebForm网页或UserControl控件>http://www.cnblogs.com/insus/p/3641 ...
- [Asp.net MVC]Bundle合并,压缩js、css文件
摘要 在web优化中有一种手段,压缩js,css文件,减少文件大小,合并js,css文件减少请求次数.asp.net mvc中为我们提供一种使用c#代码压缩合并js和css这类静态文件的方法. 一个例 ...
- ASP.NET MVC Bundle使用 合并压缩
2017-01-06 更新 在 BundleCollection 的构造函数中添加了 3种默认规则 public BundleCollection() { BundleCollection.AddDe ...
- ASP.NET MVC之持久化TempData及扩展方法(十三)
前言 之前在开始该系列之前我们就讲述了在MVC中从控制器到视图传递数据的四种方式,但是还是存在一点问题,本节就这个问题进行讲述同时进行一些练习来看看MVC中的扩展方法. 话题 废话不必多说,我们直接进 ...
- asp.net mvc bundle中数组超出索引
在使用bundle 来加载css的时候报错了, @Styles.Render("~/bundles/appStyles") 第一反应 以为是的css 太多了,可是当我这个style ...
- asp.net mvc Bundle
在使用ASP.NET MVC4中使用BundleConfig 将 js css文件 合并压缩使用,但是文件名含有min及特殊字符的将不引用 ,也不提示其他信息.
随机推荐
- MySQL:创建、修改和删除表
其实对很多人来说对于SQL语句已经忘了很多,或者说是不懂很多,因为有数据库图形操作软件,方便了大家,但是我们不能忘记最根本的东西,特别是一些细节上的东西,可能你用惯了Hibernate,不用写SQL语 ...
- 谈谈防止Ajax重复点击提交
首先说说防止重复点击提交是什么意思. 我们在访问有的网站,输入表单完成以后,单击提交按钮进行提交以后,提交按钮就会变为灰色,用户不能再单击第二次,直到重新加载页面或者跳转.这样,可以一定程度上防止用户 ...
- HttpClient 4.x 执行网站登录并抓取网页的代码
HttpClient 4.x 的 API 变化还是很大,这段代码可用来执行登录过程,并抓取网页. HttpClient API 文档(4.0.x), HttpCore API 文档(4.1) pack ...
- BZOJ 3192 删除物品(树状数组)
题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=3192 题意:(1)一共有N个物品,堆成M堆. (2)所有物品都是一样的,但是它们有不同的 ...
- HDU 2602 (简单的01背包) Bone Collector
很标准的01背包问题 //#define LOCAL #include <algorithm> #include <cstdio> #include <cstring&g ...
- HDU 2512 一卡通大冒险
我感觉这更像个数学问题. dp[i][j]表示将i件物品分成j组的方案数. 状态转移方程: dp[i][j] = dp[i-1][j-1] + j * dp[i-1][j]; 将i张卡分成j组可以有之 ...
- OE中admin的内置帐号
在OE中admin的内置帐号为SUPERUSER_ID,可以用来直接做判断登录用户是否admin from openerp import SUPERUSER_ID if uid == SUPERUSE ...
- HDU 3555 Bomb (数位DP-记忆化搜索模板)
题意 求区间[1,n]内含有相邻49的数. 思路 比较简单的按位DP思路.这是第一次学习记忆化搜索式的数位DP,确实比递推形式的更好理解呐,而且也更通用~可以一般化: [数位DP模板总结] int d ...
- 【英语】Bingo口语笔记(21) - 表达“请客吃饭”
- Heritrix源码分析(十一) Heritrix中的URL--CandidateURI和CrawlURI以及如何增加自己的属性(转)
本博客属原创文章,欢迎转载!转载请务必注明出处:http://guoyunsky.iteye.com/blog/649889 本博客已迁移到本人独立博客: http://www.yun5u.com/ ...