《JavaScript高级程序设计》(第3版)读书笔记 第9章 客户端检测

DevOps传教士
• 阅读 1594
  • 检测Web客户端的手段很多,各有利弊,但不到万不得已就不要使用客户端检测。只要能找到更通用的方法,就应该优先采用更通用的方法。一言蔽之,先设计最通用的方案,然后再使用特定于浏览器的技术增强方案

能力检测

  • 能力检测(又称特性检测),是广泛为人接受的客户端检测形式,目标不是识别特定的浏览器,而是识别浏览器的能力。
  • IE5.0之前的版本不支持document.getElementById()这个DOM方法,尽管可以使用非标准的document.all属性实现相同的目的。于是就有类似下面的能力检测代码
function getElement(id) {
  if (document.getElementById) {
    return document.getElementById(id);
  } else if (document.all) {
    return document.all[id];
  } else {
    throw new Error("No way to retrieve element!");
  }
}
  • 一个特性存在,不一定另一个特性也存在
function getWindowWidth() {
  if (document.all) {
    // 假设这里是IE浏览器
    return document.documentElement.clientWidth;     // 错误的用法
  } else {
    return window.innerWidth;
  }
}

更可靠的能力检测

// 这不是能力检测——只是检测了是否存在相应的方法
function isSortable(object) {
  return !!object.sort;
}

// 任何包含sort属性的对象都会返回true
var result = isSortable({sort: true});
// 这样更好:检测sort是不是函数
function isSortable(object) {
  return typeof object.sort == "function";
}
  • 在可能的情况下,尽量使用typeof操作符进行能力检测。特别是,宿主对象没有义务让typeof返回合理的值。最令人发指的事就发生在 IE 中。大多数浏览器在检测到document.createElement()存在时,都会返回true
// 在IE8及之前版本不行
function hasCreateElement() {
  return typeof document.createElement == "function";
}
  • IE8- 中这个函数返回false,因为typeof document.createElement返回的的是“object”,而不是“function”。如前所述,DOM对象是宿主对象,IE及更早版本中的宿主对象是通过COM而非JScript实现的。因此,document.createElement()函数确实是一个COM对象。IE9纠正了这个问题,对所有DOM方法都返回"function"。

能力检测,不是浏览器检测

// 还不够具体
var isFirefox = !!(navigator.vendor && navigator.vendorSub);

// 假设过头了
var isIE = !!(document.all && document.uniqueID);
  • 检测某个或几个属性并不能够确定浏览器。navigator.vendornavigator.vendorSub确实是Firefox的独有属性,但是后来Safari也依样画葫芦实现了相同的属性。document.all && document.uniqueID这两个属性是早期IE的独有属性,目前还存在,但不保证未来IE不会去掉。
  • 根据浏览器不同将能力组合起来是更可取的方式。如果你知道自己的应用程序需要使用某些特定的浏览器特性,那么最好是一次性检测所有相关特性,而不是分别检测。
// 确定浏览器是否支持 Netscape风格的插件
var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);

// 确定浏览器是否具有DOM1级规定的能力
var hasDOM1 = !!(document.getElementById && document.createElement && document.getElementsByTagName);
  • 在实际开发中,应该将能力检测作为确定下一步解决方案的依据,而不是用它来判断用户使用的是什么浏览器。

怪癖检测

  • 怪癖检测 (quirks detection) 的目标是识别浏览器的特殊行为。但与能力检测不同,怪癖检测是想知道浏览器存在什么缺陷(也就是bug)。
  • 例如IE8及以前版本存在一个bug,即如果某个实例属性与[[Enumerbale]]标记为false的某个原型属性同名,那么该实例将不会出现在for-in循环中。
// 检测上述怪癖的代码
var hasDontEnumQuirk = function() {

  var o = { toString: function() {} };
  for (var prop in o) {
    if (prop == "toString") {
      return false;
    }
  }
  return true;
}
  • 另一个经常需要检测的怪癖是Safari 3 以前版本会枚举被隐藏的属性。
var hasEnumShadowsQuirk = function() {

  var o = { toString: function() {} };
  var count = 0;
  for (var prop in o) {
    if (prop == "toString") {
      count++;
    }
  }
  // 如果浏览器存在这个bug
  // 就会返回两个 toString 的实例
  return (count > 1);
}
  • 由于检测怪癖涉及运行代码,因此建议仅检测那些对你有直接影响的怪癖,而且最好在脚本一开始就执行此类检测。

用户代理检测

  • 用户代理检测是争议最大的客户端检测技术。
  • 用户代理检测通过用户代理字符串来确定实际使用的浏览器。在每一次HTTP请求过程中,用户代理字符串是作为响应首部发送的,而且该字符串可以通过JavaScript的navigator.userAgent属性访问。
  • 在服务端,通过检测用户代理字符串来确定用户使用的浏览器是一种常用的而且广为接受的做法。而在客户端,用户代理检测一般被当做一种万不得已采用的做法,其优先级排在能力检测和怪癖检测之后。
  • 有关的争议不得不提电子诈骗(spoofing)。浏览器通过在自己的用户代理字符串加入一些错误或误导信息,来达到欺骗服务器的目的。

用户代理字符串的历史

用户代理字符串检测技术

识别呈现引擎

  • 确切的纸袋浏览器的名字和版本不如确切的纸袋它使用的是什么引擎。
  • 我们要编写脚本将五大呈现引擎:IE, Gecko, WebKit, KHTML, Opera
  • 为了不在全局作用域中添加多余变量,我们将使用模块增强模式来封装检测脚本
var client = function() {
  
  var engine = {

    // 呈现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 具体的版本号
    ver: null
  };

  // 在此检测呈现引擎、平台和设备
  ...

  return {
    engine: engine
  };
}();
  • 匿名函数内定义了一个局部变量engin,包含默认设置的对象字面量,每个呈现引擎都对应着一个属性,默认值为0.如果检测到了哪个呈现引擎,那么就以浮点数值形式,将引擎的版本号写入相应的属性。而呈现引擎的完整版本(一个字符串)则被写入 ver 属性。
if (client.engine.ie) {
  // 如果是IE client.ie 应该大于0
  ...
} else if (client.engine.gecko > 1.5) {
  if (client.engine.ver === "1.8.1") {
    // 针对这个版本的操作
    ...
  }
}
  • 第一个要检测是Opera,我们不相信Opera,是因为其用户代理字符串不会将自己标识为Opera。要识别Opera,必须检测window.opera对象。Opera5+都有这个版本。在Opera7.6+中调用version()方法可以返回一个表示浏览器版本的字符串。
if (window.opera) {
  engine.ver = window.opera.version();
  engine.opera = parseFloat(engine.ver);
}
  • 第二个要检测是Webkit。因为WebKit用户代理字符串中包含"Gecko"和"KHTML"这两个子字符串,所以如果首先检测它们可能会得出错误的结论。不过“AppleWebKit"是独一无二的。
  • iPhone 6s Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
  • Chrome 74 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36
  • 由于实际的版本号可能会包含数字、小数点和字母,所以捕获组中使用了表示非空格的特殊字符 \S 。用户代理字符串中的版本号与下一部分的分隔是一个空格,因此这个模式可以保证捕获所有版本信息。
var ua = navigator.userAgent;

if (/AppleWebKit\/(\S+)/.test(ua)) {
  // \S 表示非空格的特殊字符
  // \S+ 表示不包含空格的子字符串
  // 小括号表示将此子字符串加入捕获组
  // RegExp["$1"] 表示捕获组的第一个元素,即为上面描述的子字符串
  engine.ver = RegExp["$1"];
  engine.webkit = parseFloat(engine.ver);
}
  • 接下来要测试的是KHTML。同样,字符串中也包含“Gecko”,因此在排除KHTML之前,我们无法准确检测基于GECKO的浏览器。格式与Webkit差不多。此外由于Konqueror3.1及更早版本中不包含KHTML的版本,故而就要使用Konqueror代替。
if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.khtml = parseFloat(engine.ver);
}
  • 下面检测Gecko。版本号不在Gecko后面,而是在 “rv:”后面。比如WindowsXP 下的Firefox2.0.0.11:Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11
// 在上面的字符串中实际匹配的是"rv:1.8.1.11) Gecko/20071127"
// gecko的版本号位于 rv: 与一个闭括号之间,因此为了提取出这个版本号
// [^\)]+ 就是将 rv: 之后的字符串排除 ) 闭括号 之后加入捕获组
// 正则表达式要查找所有不是闭括号的字符,还要查找字符串"Gecko/"后跟8个数字
if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.gecko = parseFloat(engine.ver);
}
  • 最后一个检测IE。IE的版本号位于字符串"MSIE"的后面、一个分号的前面
// 五个呈现引擎完整的检测代码如下
var ua = navigator.userAgent;

if (window.opera) {
  engine.ver = window.opera.version();
  engine.opera = parseFloat(engine.ver);
} else if (/AppleWebKit\/(\S+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.webkit = parseFloat(engine.ver);
} else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.khtml = parseFloat(engine.ver);
} else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.gecko = parseFloat(engine.ver);
} else if (/MSIE ([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.ie = parseFloat(engine.ver);
}

识别浏览器

  • 识别呈现引擎大多数情况下足以为我们采取正确的操作提供了依据(pc上,移动端大部分都不行)。
  • 苹果公司的Safari浏览器和谷歌的Chrome浏览器都是用Webkit作为呈现引擎,但它们的JavaScript引擎却不同。
// 我们的检测代码需要添加浏览器的检测
var client = function() {
  
  var engine = {

    // 呈现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 具体的版本号
    ver: null
  };

  var browser = {

    // 浏览器
    ie: 0,
    firefox: 0,
    safari: 0,
    konq: 0,
    opera: 0,
    chrome: 0,

    // 具体的版本号
    ver: null
  }

  // 在此检测呈现引擎、平台和设备
  ...

  return {
    engine: engine,
    browser: browser
  };
}();
  • 由于大多数浏览器与其呈现引擎密切相关,所以下面的检测浏览器代码和检测呈现引擎的代码是混合在一起的。
var ua = navigator.userAgent;

if (window.opera) {
  engine.ver = browser.ver = window.opera.version();
  engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit\/(\S+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.webkit = parseFloat(engine.ver);

  // 确定是Chrome还是Safari
  if (/Chrome\/(\S+)/.test(ua)) {
    browser.ver = RegExp["$1"];
    browser.chrome = parseFloat(browser.ver);
  } else if (/Version\/(\S+)/.test(ua)) {
    browser.ver = RegExp["$1"];
    browser.safari = parseFloat(browser.ver);
  } else {
    // 近似的确定版本号
    var safariVersion = 1;
    if (engine.webkit < 100) {
      safariVersion = 1;
    } else if (engine.webkit < 312) {
      safariVersion = 1.2;
    } else if (engine.webkit < 412) {
      safariVersion = 1.3;
    } else {
      safariVersion = 2;
    }

    browser.safari = browser.ver = safariVersion;
  }
} else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
  engine.ver = browser.ver = RegExp["$1"];
  engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.gecko = parseFloat(engine.ver);

  // 确定是不是Firefox浏览器
  if (/Firefox\/(\S+)/.test(ua)) {
    browser.ver = RegExp["$1"];
    browser.firefox = parseFloat(browser.ver);
  }
} else if (/MSIE ([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.ie = parseFloat(engine.ver);
}
  • 有了上述代码之后,我们就可以编写以下逻辑
if (client.engine.webkit) {
  if (client.browser.chrome) {
    // 执行针对Chrome的代码
  } else if (client.browser.safari) {
    // 执行针对Safari的代码
  }
} else if (client.engine.gecko) {
  if(client。browser.Firefox) {
    // 执行针对Firefox的代码
  } else {
    // 执行针对其他Gecko浏览器代码
  }
}

识别平台

  • 浏览器针对不同平台会有不同的版本,如Safari Firefox Opera , 在不同平台下可能会有不同问题。目前三大主流平台是 Window/Mac/Unix(包括各种Linux)。
// 我们的检测代码需要添加平台的检测
var client = function() {
  
  var engine = {

    // 呈现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 具体的版本号
    ver: null
  };

  var browser = {

    // 浏览器
    ie: 0,
    firefox: 0,
    safari: 0,
    konq: 0,
    opera: 0,
    chrome: 0,

    // 具体的版本号
    ver: null
  }

  var system = {
    win: false,
    mac: false,
    // Unix
    x11: false
  }

  // 在此检测呈现引擎、平台和设备
  ...

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();
  • 检测navigator.platform来确定平台,在不同浏览器中给出的值都是一致的,检测起来非常直观
var p = navigator.platform;
// window 可能有 Win32 和 Win64
system.win = p.indexOf("Win") == 0;
system.win = p.indexOf("Mac") == 0;
system.win = (p.indexOf("U11") == 0) || (p.indexOf("Linux") == 0);

识别Windows系统

  • 在WindowsXP之前,Windows有分别针对家庭和商业用户的两个版本。针对家庭的分别是Windows95和WindowsME。针对商业的一直叫WindowsNT,最后由于市场原因改名为Windows2000。这两个产品线后来又合并成一个由WindowsNT发展而来的公共代码基,代表产品就是WindowsXP。
  • 原著编撰年代较早(2012.3),笔记这里只列举WindowsXP以后的代码,并补充Windows10
版本 IE 4+ Gecko Opera < 7 Opera 7+ WebKit
XP "Windows NT 5.1" "Windows NT 5.1" "WindowsXP" "Windows NT 5.1" "Windows NT 5.1"
Vista "Windows NT 6.0" "Windows NT 6.0" n/a "Windows NT 6.0" "Windows NT 6.0"
7 "Windows NT 6.1" "Windows NT 6.1" n/a "Windows NT 6.1" "Windows NT 6.1"
10 "Windows NT 10.0" "Windows NT 10.0" n/a "Windows NT 10.0" "Windows NT 10.0"
  • 忽略掉Opera 7- 的用户和WindowsXP以前的系统,将原著代码修改如下:
if (system.win) {
  // 比如在Windows10的Chrome里,userAgent返回字符串
  // Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
  // 我们要提取两个子字符串 'NT' 和 '10.0'
  // ([^dows]{2}) 表示排除包含'dows'的字符之后的后2位字符 加入捕获组 $1
  // (\d+\.\d+) 表示 x.x(x) 形式的版本号加入捕获组 $2
  if (/Windows ([^dows]{2})\s?(\d+\.\d+)?/.test(ua)) {
    if (RegExp["$1"] == "NT") {
      switch (RegExp["$2"]) {
        case "5.1":
          system.win = "XP";
          break;
        case "6.0":
          system.win = "Vista";
          break;
        case "6.1":
          system.win = "7";
          break;
        case "10.0":
          system.win = "10";
          break;
        default:
          system.win = "NT";
          break;
      }
    } else {
      system.win = RegExp["$1"];
    }
  }
}

识别移动设备

// 我们在系统变量里添加移动设备的属性
var client = function() {
  ...

  var system = {
    win: false,
    mac: false,
    // Unix
    x11: false,

    // 移动设备
    iphone: false,
    ipod: false,
    ipad: false,
    ios: false,
    android: false,
    nokiaN: false,
    winMobile: false
  }

  // 在此检测呈现引擎、平台和设备
  ...

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();
  • 通常的检测字符串"iphone", "ipod", "ipad", 就可以分别设置相应属性的值了。
system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipad = ua.indexOf("iPad") > -1;
  • 除了知道iOS设备,最好还能知道iOS的版本号。在iOS3之前,用户代理字符串只包含"CPU like Mac OS",后来iPhone中又改成"CPU iPhone OS 3_0 like Mac OS X",iPad中又改成"CPU OS 3_2 like Mac OS X"。
  • 检查系统是不是 Mac OS、字符串中是否存在"Mobile",可以保证不论是什么版本,system.ios中都不会是 0
// 检测iOS版本
if (system.max && ua.indexOf("Mobile") > -1) {
  if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)) {
    system.ios = parseFloat(RegExp.$1.repalce("_", "."));
  } else {
    system.ios = 2;   // 不能准确判断只能靠猜
  }
}
  • 检测Android操作系统也很简单,搜索字符串"Android"并取得紧跟其后的版本号
// 检测Android版本
if (/Android (\d+\.\d+)/.test(ua)) {
  system.android = parseFloat(RegExp.$1);
}
  • 远在天国的诺基亚,略
  • 在天国门口的Windows Phone , 略

识别游戏系统

  • 除了移动设备之外,视频游戏系统中的Web浏览器也开始日益普及。任天堂Wii和PlayStation3+等等。
  • Wii的浏览器实际上是定制版的Opera,专门为Wii Remote设计的。用户代理字符串:Opera/9.10 (Nintendo Wii;U; ; 1621; en)
  • PlayStation的浏览器是自己开发的,没有基于前面提到的任何呈现引擎。用户代理字符串:Mozilla/5.0 (PLAYSTATION 3; 2.00)
// 我们在系统变量里添加游戏设备的属性
var client = function() {
  ...

  var system = {
    ...

    // 游戏系统
    wii: false,
    ps: false
  }

  // 在此检测呈现引擎、平台和设备
  ...

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();
  • 检测前述游戏系统的代码如下
system.wii = ua.indexOf("Wii") > -1;
// ps要忽略大小写
system.ps = /playstation/i.test(ua);

完整的代码

// 个人做了部分修改 修改时间 2019.5.17
var client = function() {
  
  var engine = {

    // 呈现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 具体的版本号
    ver: null
  };

  var browser = {

    // 浏览器
    ie: 0,
    firefox: 0,
    safari: 0,
    konq: 0,
    opera: 0,
    chrome: 0,

    // 具体的版本号
    ver: null
  }

  var system = {
    win: false,
    mac: false,
    // Unix
    x11: false,

    // 移动设备
    iphone: false,
    ipod: false,
    ipad: false,
    ios: false,
    android: false,
    nokiaN: false,
    winMobile: false,

    // 游戏系统
    wii: false,
    ps: false
  }

  // 在此检测呈现引擎、平台和设备
  var ua = navigator.userAgent;

  if (window.opera) {
    engine.ver = browser.ver = window.opera.version();
    engine.opera = browser.opera = parseFloat(engine.ver);
  } else if (/AppleWebKit\/(\S+)/.test(ua)) {
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);

    // 确定是Chrome还是Safari
    if (/Chrome\/(\S+)/.test(ua)) {
      browser.ver = RegExp["$1"];
      browser.chrome = parseFloat(browser.ver);
    } else if (/Version\/(\S+)/.test(ua)) {
      browser.ver = RegExp["$1"];
      browser.safari = parseFloat(browser.ver);
    } else {
      // 近似的确定版本号
      var safariVersion = 1;
      if (engine.webkit < 100) {
        safariVersion = 1;
      } else if (engine.webkit < 312) {
        safariVersion = 1.2;
      } else if (engine.webkit < 412) {
        safariVersion = 1.3;
      } else {
        safariVersion = 2;
      }

      browser.safari = browser.ver = safariVersion;
    }
  } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
    engine.ver = browser.ver = RegExp["$1"];
    engine.khtml = browser.konq = parseFloat(engine.ver);
  } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);

    // 确定是不是Firefox浏览器
    if (/Firefox\/(\S+)/.test(ua)) {
      browser.ver = RegExp["$1"];
      browser.firefox = parseFloat(browser.ver);
    }
  } else if (/MSIE ([^;]+)/.test(ua)) {
    engine.ver = RegExp["$1"];
    engine.ie = parseFloat(engine.ver);
  }

  // 检测浏览器
  browser.ie = engine.ie;
  browser.opera = engine.opera;

  // 检测平台
  var p = navigator.platform;
  system.win = p.indexOf("Win") == 0;
  system.win = p.indexOf("Mac") == 0;
  system.win = (p.indexOf("U11") == 0) || (p.indexOf("Linux") == 0);

  // 检测Windos操作系统
  // 排除WindosXP以前的系统
  if (system.win) {
    if (/Windows ([^dows]{2})\s?(\d+\.\d+)?/.test(ua)) {
      if (RegExp["$1"] == "NT") {
        switch (RegExp["$2"]) {
          case "5.1":
            system.win = "XP";
            break;
          case "6.0":
            system.win = "Vista";
            break;
          case "6.1":
            system.win = "7";
            break;
          case "10.0":
            system.win = "10";
            break;
          default:
            system.win = "NT";
            break;
        }
      } else {
        system.win = RegExp["$1"];
      }
    }
  }

  // 移动设备
  // 剔除了诺基亚和windows phone
  system.iphone = ua.indexOf("iPhone") > -1;
  system.ipod = ua.indexOf("iPod") > -1;
  system.ipad = ua.indexOf("iPad") > -1;

  // 检测iOS版本
  if (system.max && ua.indexOf("Mobile") > -1) {
    if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)) {
      system.ios = parseFloat(RegExp.$1.repalce("_", "."));
    } else {
      system.ios = 2;   // 不能准确判断只能靠猜
    }
  }

  // 检测Android版本
  if (/Android (\d+\.\d+)/.test(ua)) {
    system.android = parseFloat(RegExp.$1);
  }

  // 游戏系统
  system.wii = ua.indexOf("Wii") > -1;
  system.ps = /playstation/i.test(ua);

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();

使用方法

  • 用户代理检测是客户端检测的最后一个选项。只要可能,都应该优先采用能力检测和怪癖检测。用户代理检测一般适用于下列情形:

    • 不能直接准确的使用能力检测或怪癖检测。例如某些浏览器实现了为将来功能预留的存根函数(stub)。在这种情况下,仅测试相应的函数是否存在还得不到足够信息。
    • 同一款浏览器在不同平台下具备不同的能力。
    • 为了跟踪分析等目的需要知道确切的浏览器。
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
保卫大萝卜 保卫大萝卜
3年前
低代码,虽然有点毒瘤,但管用就好
最近看到不少低门槛开发软件应用的新闻:“30分钟搭一款核酸检测登记应用”、“2小时紧急上线抗疫求助应用”、“00后低代码开发者毕业月薪过万”等等。近期,广西防城港市出现疫情,全市展开一轮大规模核酸检测。柳钢工人彭期文在钉钉上仅用30分钟就通过低代码搭起一款“核酸检测登记”应用,原本需要大规模的排队登记,如今手机一扫,3小时就能完成7000余人
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
java用户角色权限设计
实现业务系统中的用户权限管理B/S系统中的权限比C/S中的更显的重要,C/S系统因为具有特殊的客户端,所以访问用户的权限检测可以通过客户端实现或通过客户端服务器检测实现,而B/S中,浏览器是每一台计算机都已具备的,如果不建立一个完整的权限检测,那么一个“非法用户”很可能就能通过浏览器轻易访问到B/S系统中的所有功能。因此B/S业务系统都需要有
Stella981 Stella981
3年前
HAAR与DLib的实时人脸检测之对比
人脸检测方法有许多,比如opencv自带的人脸Haar特征分类器和dlib人脸检测方法等。对于opencv的人脸检测方法,优点是简单,快速;存在的问题是人脸检测效果不好。正面/垂直/光线较好的人脸,该方法可以检测出来,而侧面/歪斜/光线不好的人脸,无法检测。因此,该方法不适合现场应用。而对于dlib人脸检测方法采用64个特征点检测,效果会好于opencv
Stella981 Stella981
3年前
OpenCV检测轮廓极点(Python C++)
    今天分享一个OpenCV检测轮廓极点实例,原图如下,我们需要检测出地图中最大轮廓的上下左右四个极点,并进行标注显示。!(https://oscimg.oschina.net/oscnet/ae374a72c5404b00b0e976e499eedf36.png)    第一步:阈值处理分割出地图轮廓!(ht
Wesley13 Wesley13
3年前
.net 2.0 4.0 表单中危险字符
asp.net中“从客户端中检测到有潜在危险的Request.Form值”错误的解决办法在提交表单时候,asp.net提示:"从客户端(......)中检测到有潜在危险的Request.Form值"。asp.net中的请求验证特性提供了某一等级的保护措施防止XSS攻击,asp.net的请求验证是默认启动的。这里给出不同版本.net的解决方法。
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
银装素裹 银装素裹
2年前
ModelScope 垂类检测系列模型介绍
本文对ModelScope上的垂类检测系列模型进行介绍,ModelScope是阿里达摩院推出的中文版模型即服务(MaaS,ModelasaService)共享平台。用户可以在上面轻松且免费地使用先进的领域模型,应用于自己的领域。基于垂类检测模型(比如口罩检测模型、安全帽检测模型、香烟检测模型等),可以构建不同的解决方案(如佩戴口罩检测、安全生产、抽烟行为检测等)。
DevOps传教士
DevOps传教士
Lv1
见到你的一瞬间,就像走了很远的路,终于到家了
文章
10
粉丝
0
获赞
0