zoukankan      html  css  js  c++  java
  • 让火狐等浏览器也能使用HTC(HTML component)的方法

    HTML组件(HTML conponent)是DTHML里的东西,相信现在大部分的web开发或者是网站都不使用DHTML了。它只能在IE下有直接的支持,它直接在样式表中用

    behavior: url(htcfilename.htc);

    来指示样式的行为使用的htc文件,IE支持behavior,但火狐不能支持。

    如何让火狐等浏览器也支持htc呢,这里有个已实现的示例:

    此方法来自http://dean.edwards.name/

    它的示例http://dean.edwards.name/my/examples/moz-behaviors/

    示例下载http://deanedwards.googlecode.com/svn/trunk/download/moz-behaviors.zip

    下面以他的示例来解释下实现方法。

    1.         IE下是在样式表中用behavior,那么火狐下有它自己专用的css,那就是用-moz-binding,最终css就是这样的:

    /* 用于IE的 behaviors */
    behavior: url(drag-box.htc);
    /* 用于火狐的 behaviors */
    -moz-binding: url(bindings.xml# drag-box.htc);

    2.         发现css中有个bindings.xml文件全名,这个就是作者写的xml文件,其中主要是有两个地方是重要的,其他的可以不管它(除非你也需要)。

    <!—此处最重要,意思就是用moz-behaviors.xml来提供对behavior的支持(moz-behaviors.xml中有解析htc文件的javascript代码)-->
    <binding id="behavior" extends="moz-behaviors.xml#behavior"/>
    <!—类似这些都是用来标识需要用到的htc文件的 -->
    <binding id="test.htc" extends="#behavior"/>

    3.         对于moz-behaviors.xml文件是用来提供对behavior的支持的,它里面有段代码是进行“优化”后的,看起来是加密的,显示乱码。你下载的示例中有这个文件的源文件moz-behaviors-source.xml,它在运行中是不需要的,但这个文件才是火狐下使用htc最关键的地方,下面把源码贴出来供大家学习。

    moz-behaviors-source.xml
      1 <?xml version="1.0" encoding="ISO-8859-1"?>
      2  <bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
      3  <!--
      4     moz-behaviors.xml - version 1.1.1 (2005-08-19)
      5     Copyright 2004-2005, Dean Edwards
      6     License: http://creativecommons.org/licenses/LGPL/2.1/
      7  -->
      8  <!--
      9 =======================================================================
     10  TO DO
     11 =======================================================================
     12 
     13 hideFocus
     14 fix CSSStyleDeclaration pixel*.__defineGetter__
     15 behaviors FAQ
     16 test Event.returnValue
     17 http://developer-test.mozilla.org/docs/Working_around_the_Firefox_1.0.3_DHTML_regression
     18  -->
     19 
     20  <!-- add a behavior through css -->
     21  <binding id="behavior" extends="#-moz-behaviors">
     22  <implementation><constructor>
     23 <![CDATA[
     24 addBehavior(0);
     25 ]]></constructor></implementation>
     26 </binding>
     27 
     28 <!-- manually attach behaviors to child elements of <table>s -->
     29 <binding id="table" extends="#-moz-behaviors">
     30 <implementation><constructor><![CDATA[
     31     var i, j;
     32     if (tHead) tHead.addBehavior(0);
     33     for (i = 0; i < tBodies.length; i++) tBodies[i].addBehavior(0);
     34     for (i = 0; i < rows.length; i++) {
     35         rows[i].addBehavior(0);
     36         for (j = 0; j < rows[i].cells.length; j++) {
     37             rows[i].cells[j].addBehavior(0);
     38         }
     39     }
     40     if (tFoot) tFoot.addBehavior(0);
     41 ]]></constructor></implementation>
     42 </binding>
     43 
     44 <binding id="-moz-behaviors" extends="#-moz-ie">
     45 <implementation><constructor>
     46 <![CDATA[if(!this.addBehavior){
     47 // ------------------------------------------------------------------
     48 //  htc extensions for mozilla
     49 // ------------------------------------------------------------------
     50 
     51 /* here we define the addBehavior/removeBehavior methods for an element
     52    these methods are used to add and remove all dhtml behaviors
     53 */
     54 
     55 var _cookie = -1; // no support for removeBehavior yet
     56 
     57 // implement the addBehavior method for all elements
     58 Element.prototype.addBehavior = function(_url) {
     59 try {
     60     // calling this method with the first argument as zero
     61     //  initialises the object's behaviors
     62     if (_url === 0) {
     63         // grab the htc's url from the css setting
     64         var $binding = getComputedStyle(this, null).getPropertyValue("-moz-binding");
     65         $binding = $binding.replace(/^url\(([^)]*)\)$/, "$1").split("#");
     66         if ($binding) {
     67             var $path = $binding[0].replace(/[^\/]+$/, "");
     68             // support multiple behaviors
     69             var $htcs = $binding[1].split("|");
     70             var i = $htcs.length;
     71             while (i--) this.addBehavior($path + $htcs[i]);
     72         }
     73     }
     74     if (!_url) return;
     75 
     76     // check the cache
     77     if (!document.behaviorUrns[_url]) {
     78         // constants
     79         var $SEPARATOR = ";";
     80 
     81         function _getTagName($node) {
     82             var $tagName = $node.tagName.toLowerCase();
     83             // this fixes a bug(?) in Mozilla 1.6b that includes the
     84             //  namespace prefix in the tagName
     85             return $tagName.slice($tagName.indexOf(":") + 1);
     86         };
     87 
     88         function _getAttribute($node, $attribute) {
     89             return $node.getAttribute($attribute) || $node.getAttribute($attribute.toUpperCase());
     90         };
     91 
     92         // this function converts elements in a behavior to a program
     93         //  declaration, for example:
     94         //    <public:attach for="window" event="onload" handler="init"/>
     95         //  becomes:
     96         //    window.addEventListener("load", init);
     97         function _asDeclaration($behaviorNode) {
     98             switch (_getTagName($behaviorNode)) {
     99                 case "event":
    100                     var id = _getAttribute($behaviorNode, "id");
    101                     return (id) ? "var " + id + "={fire:function(event){element.fireEvent('" +
    102                         _getAttribute($behaviorNode, "name") + "',event)}}" : "";
    103                 case "property":
    104                     var $name = _getAttribute($behaviorNode, "name");
    105                     var $get = _getAttribute($behaviorNode, "get") || "";
    106                     if ($get) $get = "__defineGetter__('" + $name + "'," + $get + ")";
    107                     var $put = _getAttribute($behaviorNode, "put") || "";
    108                     if ($put) $put = ";__defineSetter__('" +  $name + "'," + $put + ")";
    109                     var id = _getAttribute($behaviorNode, "id") || "";
    110                     if (id) id = "var " + id + "={fireChange:new Function};";
    111                     return id + $get + $put;
    112                 case "method":
    113                     return "element." + _getAttribute($behaviorNode, "name") + "=" +
    114                         _getAttribute($behaviorNode, "name");
    115                 case "attach":
    116                     var $handler = _getAttribute($behaviorNode, "handler") || "";
    117                     $handler += ($handler) ? "()" : _getAttribute($behaviorNode, "onevent");
    118                     $handler = "function(event){window.event=event;return " + $handler + "}";
    119                     var $event = _getAttribute($behaviorNode, "event");
    120                     switch ($event) {
    121                         case "oncontentready": return "window.setTimeout(" + $handler + ",1)";
    122                         case "ondocumentready": return "document.behaviorUrns.__private.push(" + $handler + ")";
    123                     }
    124                     return (_getAttribute($behaviorNode, "for")||"element") +
    125                         ".addEventListener('" + $event.slice(2) + "'," + $handler + ",false)";
    126                 case "defaults":
    127                     // not implemented
    128                 default:
    129                     return "";
    130             }
    131         };
    132 
    133         function _asDefault($node) {
    134             return (_getAttribute($node, "put")) ? ";var __tmp=getAttribute('" + _getAttribute($node, "name") + "')||" +
    135                 (_getAttribute($node, "value") || "null") +
    136                 ";if(__tmp!=null)element['" + _getAttribute($node, "name") + "']=__tmp" : "";
    137         };
    138 
    139         // extract the body of a function
    140         function _getFunctionBody($function) {
    141             with (String($function)) return slice(indexOf("{") + 1, lastIndexOf("}"));
    142         };
    143 
    144         // behaviors are defined as xml documents, so we can use
    145         //  the http request object to load them and the dom parser
    146         //  object to parse them into a dom tree
    147         var _httpRequest = new XMLHttpRequest;
    148         function _loadFile($url) {
    149         try {
    150             // load the behavior
    151             _httpRequest.open("GET", $url, false);
    152             _httpRequest.send(null);
    153             return _httpRequest.responseText;
    154         } catch ($ignore) {
    155             // ignore (but don't crash)
    156         }};
    157 
    158         // analyse the dom tree, build the interface and create the script
    159         var _declarations = [];
    160         var _defaults = "";
    161         var _script = "";
    162         function _load() {
    163             // build a dom representation of the loaded xml document
    164             var $dom = (new DOMParser).parseFromString(_loadFile(_url), "text/xml");
    165             var $childNodes = $dom.documentElement.childNodes, $node;
    166             for (var i = 0; ( $node = $childNodes[i]); i++) {
    167                 if ($node.nodeType == Node.ELEMENT_NODE) {
    168                     if (_getTagName($node) == "script") {
    169                         var $src = _getAttribute($node, "src");
    170                         if ($src) {
    171                             _script += _loadFile($src);
    172                         } else {
    173                             // build the script from the text nodes of the script element
    174                             for (var j = 0; j < $node.childNodes.length; j++)
    175                                 _script += $node.childNodes[j].nodeValue;
    176                         }
    177                     } else {
    178                         // convert the dom node representation of a
    179                         //  <public:declaration/>  to a javascript statement
    180                         //  and store it in our declarations collection
    181                         _declarations.push(_asDeclaration($node));
    182                         if (_getTagName($node) == "property") {
    183                             _defaults += _asDefault($node);
    184                         }
    185                     }
    186                 }
    187             }
    188             _defaults += ";delete __tmp";
    189         };
    190         _load();
    191         // we've finished collecting interface declarations.
    192         //  they are now held as an array of strings.
    193 
    194         // build a function from the script and extract the function body
    195         //  this has the effect of formatting the script (removing comments etc)
    196         _script = _getFunctionBody(new Function(_script));
    197 
    198         // support: new ActiveXObject
    199         var $ACTIVEX = /\bnew\s+ActiveXObject\s*\(\s*(["'])\w\.XMLHTTP\1\s*\)/gi;
    200         _script = _script.replace($ACTIVEX, "new XMLHttpRequest()");
    201 
    202         // begin: annoying parse of script to "shuffle" declarations
    203         //        and inline code.
    204         // microsoft dhtml behaviors add the interface first, then
    205         //  apply inline script.
    206         // to achieve this, we have to strip out all of the inline
    207         //  code, leaving only function declarations. the inline code
    208         //  then gets appended to the script block for later
    209         //  execution.
    210         // in between the function declarations and inline script, we
    211         //  sandwich the property getters and setters.
    212         // this is a real nuisance actually...
    213 
    214         // on the upside regular expressions are really quick...
    215 
    216         // i'm using "#" as a placeholder, so i'll have to escape these out
    217         _script = _script.replace(/#/g, "\\x23");
    218 
    219         // parse out strings, regexps and program
    220         //  blocks - anything between curly braces {..}
    221         var $ = [_declarations.join($SEPARATOR)];
    222         var $BLOCKS_REGEXPS_STRINGS = /(\"[^\"\n]+\")|(\/[^\/\n]+\/)|(\{[^\{\}]*\})/g;
    223         var _ENCODED = /#(\d+)\b/g;
    224         // store a string and return a unique id
    225         function _encode($match) {return "#" + $.push($match)};
    226         function _decode($match, $index) {return $[$index - 1]};
    227         while ($BLOCKS_REGEXPS_STRINGS.test(_script)) {
    228             _script = _script.replace($BLOCKS_REGEXPS_STRINGS, _encode);
    229         }
    230         // we are now left with function declarations and inline statements
    231 
    232         // remove function declarations and save them
    233         var $FUNCTIONS = /\n\s*function[^\n]*\n/g;
    234         var _functions = _script.match($FUNCTIONS) || [];
    235         _script = _script.replace($FUNCTIONS, "");
    236 
    237         // re-assemble the encoded script, in the following
    238         //  order: function declarations, interface definition
    239         //  (getters and setters), inline script
    240         _script = _functions.concat("#1", _script).join($SEPARATOR);
    241 
    242         // decode the script
    243         var i = $.length;
    244         $ = $.join("\x01").replace(/\$\$/g, "$$$$$$$$").split("\x01"); // Scott Shattuck
    245         do _script = _script.replace("#" + i, $[--i]); while (i);
    246         // end: annoying parse of script
    247 
    248         // build the final script
    249         _script += _defaults;
    250 
    251         // create an anonymous function in the global namespace.
    252         // this function will add the interface defined by the dhtml behavior.
    253         // after we've built this function we'll store it so that we don't
    254         //  have to go through this process again.
    255         document.behaviorUrns[_url] = new Function("element", "with(this){" + _script + "}");
    256     }
    257 
    258     // because we loaded synchronously (or got it from the cache)
    259     //  we can apply the behavior immediately...
    260     document.behaviorUrns[_url].call(this, this);
    261 
    262     // this might mean somthing later
    263     return _cookie;
    264 } catch ($error) {
    265     return 0;
    266 }};
    267 
    268 // implement the removeBehavior method for all elements
    269 Element.prototype.removeBehavior = function($cookie) {
    270     // mmm, not in a hurry to write this
    271 };
    272 
    273 // cache for previously loaded behaviors
    274 // -also store some "default" behaviors
    275 document.behaviorUrns = {
    276     __private : []
    277 };
    278 
    279 // support multiple behaviors and ondocumentready
    280 window.addEventListener("load", function() {
    281 try {
    282     var $handlers = document.behaviorUrns.__private;
    283     var i = $handlers.length;
    284     while (i) $handlers[--i]();
    285     delete document.behaviorUrns.__private;
    286 } catch ($ignore) {
    287 }}, false);
    288 
    289 }]]></constructor></implementation>
    290 </binding>
    291 
    292 <binding id="-moz-ie">
    293 <implementation><constructor>
    294 <![CDATA[if(!this.attachEvent){
    295 // ------------------------------------------------------------------
    296 //  explorer emulation for mozilla
    297 // ------------------------------------------------------------------
    298 
    299 // thanks to Erik Arvidsson (http://webfx.eae.net/dhtml/ieemu/)
    300 
    301 /* we're going to mess about with some of mozilla's interfaces to
    302    make them more explorer-like
    303 */
    304 
    305 /* note: in my comments where i say support/mimic a property
    306    support = exactly the same as explorer
    307    mimic = close enough
    308 */
    309 
    310 // CSSStyleDeclaration
    311 // -------------------
    312 // support microsoft's styleFloat
    313 CSSStyleDeclaration.prototype.__defineGetter__("styleFloat", function() {
    314     return this.cssFloat;
    315 });
    316 CSSStyleDeclaration.prototype.__defineSetter__("styleFloat", function($value) {
    317     this.cssFloat = $value;
    318 });
    319 // mimic microsoft's pixel representations of left/top/width/height
    320 // the getters only work for values that are already pixels
    321 CSSStyleDeclaration.prototype.__defineGetter__("pixelLeft", function() {
    322     return parseInt(this.left) || 0;
    323 });
    324 CSSStyleDeclaration.prototype.__defineSetter__("pixelLeft", function($value) {
    325     this.left = $value + "px";
    326 });
    327 CSSStyleDeclaration.prototype.__defineGetter__("pixelHeight", function() {
    328     return parseInt(this.height) || 0;
    329 });
    330 CSSStyleDeclaration.prototype.__defineSetter__("pixelHeight", function($value) {
    331     this.height = $value + "px";
    332 });
    333 CSSStyleDeclaration.prototype.__defineGetter__("pixelTop", function() {
    334     return parseInt(this.top) || 0;
    335 });
    336 CSSStyleDeclaration.prototype.__defineSetter__("pixelTop", function($value) {
    337     this.top = $value + "px";
    338 });
    339 CSSStyleDeclaration.prototype.__defineGetter__("pixelWidth", function() {
    340     return parseInt(this.width) || 0;
    341 });
    342 CSSStyleDeclaration.prototype.__defineSetter__("pixelWidth", function($value) {
    343     this.width = $value + "px";
    344 });
    345 
    346 // for older versions of gecko we need to use getPropertyValue() to
    347 // access css properties returned by getComputedStyle().
    348 // we don't want this so we fix it.
    349 try {
    350 var $computedStyle = getComputedStyle(this, null);
    351 // the next line will throw an error for some versions of mozilla
    352 var $test = $computedStyle.display;
    353 } catch ($ignore) {
    354 // the previous line will throw an error for some versions of mozilla
    355 } finally {
    356 if (!$test) {
    357     // the above code didn't work so we need to fix CSSStyleDeclaration
    358     var $UPPER_CASE = /[A-Z]/g;
    359     function _dashLowerCase($match){return "-" + $match.toLowerCase()};
    360     function _cssName($propertyName) {return $propertyName.replace($UPPER_CASE, _dashLowerCase)};
    361     function _assignStyleGetter($propertyName) {
    362         var $cssName = _cssName($propertyName);
    363         CSSStyleDeclaration.prototype.__defineGetter__($propertyName, function() {
    364             return this.getPropertyValue($cssName);
    365         });
    366     };
    367     for (var $propertyName in this.style) {
    368         if (typeof this.style[$propertyName] == "string") {
    369             _assignStyleGetter($propertyName);
    370         }
    371     }
    372 }}
    373 
    374 // HTMLDocument
    375 // ------------
    376 // support microsoft's "all" property
    377 HTMLDocument.prototype.__defineGetter__("all", function() {
    378     return this.getElementsByTagName("*");
    379 });
    380 // mimic the "createEventObject" method for the document object
    381 HTMLDocument.prototype.createEventObject = function() {
    382     return document.createEvent("Events");
    383 };
    384 
    385 // HTMLElement
    386 // -----------
    387 // mimic microsoft's "all" property
    388 HTMLElement.prototype.__defineGetter__("all", function() {
    389     return this.getElementsByTagName("*");
    390 });
    391 // support "parentElement"
    392 HTMLElement.prototype.__defineGetter__("parentElement", function() {
    393     return (this.parentNode == this.ownerDocument) ? null : this.parentNode;
    394 });
    395 // support "uniqueID"
    396 HTMLElement.prototype.__defineGetter__("uniqueID", function() {
    397     // a global counter is stored privately as a property of this getter function.
    398     // initialise the counter
    399     if (!arguments.callee.count) arguments.callee.count = 0;
    400     // create the id and increment the counter
    401     var $uniqueID = "moz_id" + arguments.callee.count++;
    402     // creating a unique id, creates a global reference
    403     window[$uniqueID] = this;
    404     // we don't want to increment next time, so redefine the getter
    405     this.__defineGetter__("uniqueID", function(){return $uniqueID});
    406     return $uniqueID;
    407 });
    408 // mimic microsoft's "currentStyle"
    409 HTMLElement.prototype.__defineGetter__("currentStyle", function() {
    410     return getComputedStyle(this, null);
    411 });
    412 // mimic microsoft's "runtimeStyle"
    413 HTMLElement.prototype.__defineGetter__("runtimeStyle", function() {
    414 //# this doesn't work yet (https://bugzilla.mozilla.org/show_bug.cgi?id=45424)
    415 //# return this.ownerDocument.defaultView.getOverrideStyle(this, null);
    416     return this.style;
    417 });
    418 // support "innerText"
    419 HTMLElement.prototype.__defineGetter__("innerText", function() {
    420     return this.textContent;
    421 });
    422 HTMLElement.prototype.__defineSetter__("innerText", function($value) {
    423     this.textContent = $value;
    424 });
    425 // mimic the "attachEvent" method
    426 HTMLElement.prototype.attachEvent = function($name, $handler) {
    427     this.addEventListener($name.slice(2), $handler, false);
    428 };
    429 // mimic the "removeEvent" method
    430 HTMLElement.prototype.removeEvent = function($name, $handler) {
    431     this.removeEventListener($name.slice(2), $handler, false);
    432 };
    433 // mimic the "createEventObject" method
    434 HTMLElement.prototype.createEventObject = function() {
    435     return this.ownerDocument.createEventObject();
    436 };
    437 // mimic the "fireEvent" method
    438 HTMLElement.prototype.fireEvent = function($name, $event) {
    439     if (!$event) $event = this.ownerDocument.createEventObject();
    440     $event.initEvent($name.slice(2), false, false);
    441     this.dispatchEvent($event);
    442     // not sure that this should be here??
    443     if (typeof this[$name] == "function") this[$name]();
    444     else if (this.getAttribute($name)) eval(this.getAttribute($name));
    445 };
    446 // support the "contains" method
    447 HTMLElement.prototype.contains = function($element) {
    448     return Boolean($element == this || ($element && this.contains($element.parentElement)));
    449 };
    450 
    451 // Event
    452 // -----
    453 // support microsoft's proprietary event properties
    454 Event.prototype.__defineGetter__("srcElement", function() {
    455     return (this.target.nodeType == Node.ELEMENT_NODE) ? this.target : this.target.parentNode;
    456 });
    457 Event.prototype.__defineGetter__("fromElement",function() {
    458     return (this.type == "mouseover") ? this.relatedTarget : (this.type == "mouseout") ? this.srcElement : null;
    459 });
    460 Event.prototype.__defineGetter__("toElement", function() {
    461     return (this.type == "mouseout") ? this.relatedTarget : (this.type == "mouseover") ? this.srcElement : null;
    462 });
    463 // convert w3c button id's to microsoft's
    464 Event.prototype.__defineGetter__("button", function() {
    465     return (this.which == 1) ? 1 : (this.which == 2) ? 4 : 2;
    466 });
    467 // mimic "returnValue" (default is "true")
    468 Event.prototype.__defineGetter__("returnValue", function() {
    469     return true;
    470 });
    471 Event.prototype.__defineSetter__("returnValue", function($value) {
    472     if (this.cancelable && !$value) {
    473         // this can't be undone!
    474         this.preventDefault();
    475         this.__defineGetter__("returnValue", function() {
    476             return false;
    477         });
    478     }
    479 });
    480 // mozilla already supports the read-only "cancelBubble"
    481 //  so we only need to define the setter
    482 Event.prototype.__defineSetter__("cancelBubble", function($value) {
    483     // this can't be undone!
    484     if ($value) this.stopPropagation();
    485 });
    486 Event.prototype.__defineGetter__("offsetX", function() {
    487     return this.layerX;
    488 });
    489 Event.prototype.__defineGetter__("offsetY", function() {
    490     return this.layerY;
    491 });
    492 // and that's it!
    493 // thanks mozilla for being such a developer's playground :D
    494 }]]></constructor></implementation>
    495 </binding>
    496 
    497 <binding id="block-netscape6">
    498 <content>
    499 <html:script type="text/javascript"><![CDATA[
    500 // netscape6 does not retain the -moz-binding css property value
    501 //  so we disable moz-behaviors
    502 if (/netscape6/i.test(navigator.userAgent)) {
    503     document.styleSheets[0].insertRule("*{-moz-binding:none!important}", 0);
    504 }
    505 ]]></html:script>
    506 <children/>
    507 </content>
    508 </binding>
    509 
    510 </bindings>
    511 

    4.         htc文件中也要注意一个地方,那就是要使用<![CDATA[ ]]>javsscript代码括住,像下面这样。

    <script type="text/javascript">
    //<![CDATA[
    function beginDrag() {
        
    if(true && true)
        {
        }
    };
    //]]>
    </script>


    如果不用它括住会出现很搞笑的事情,true&&true这样的javascript代码会有错,当然浏览器不会提示错误,firebug也无法调试(因为htc中的javascript并不是直接调用该文件执行的,而是由moz-behaviors.xml文件中的javascript翻译后执行的,执行是由与css样式关联的DOM对象的js对象原型prototype进行的),所以像&这样的特殊字符会影响到moz-behaviors.xml文件中的javascript的翻译过程,同时一旦“有错”,htc文件中的任何javascript代码都无法执行,因为整个翻译过程已经被打乱了,我是这样通俗理解的。

  • 相关阅读:
    scp 一个最简单的Linux 数据copy
    ORA-65096: invalid common user or role 解决方法
    SQL Server 查询 数据库 & 表格 大小
    SQL Server 配置 Job 监控 tempdb 变化
    SQL Server 邮箱告警配置
    浅谈 SQL Server 中的等待类型(Wait Type)
    Oracle 常用命令大全(持续更新)
    连接Oracle 12c R2 报错ORA-28040:No matching authentication protocal
    Oracle 数据库启动和关闭
    SQL Server 日志收缩方法
  • 原文地址:https://www.cnblogs.com/pains/p/1617874.html
Copyright © 2011-2022 走看看