小蒋同学 - 2019年11月
type: channel
link: https://www.eybk.cn/2019/11/
description: 小蒋同学
items: BAT 文件实现批量安装 .msu 补丁
items: MySQL 提示 Field * doesn't have a default value 的解决办法
items: CentOS 中 iptables 和 firewall 防火墙开启、关闭、查看状态、基本设置
items: 关于 PHP 时间戳最大支持到 2038 年问题解决办法
items: PHP 使用 CURL 不依赖 COOKIEJAR 获取 COOKIE 的方法
items: JetBrains License Server PHP版
items: 检查电脑是否感染 Synaptics 蠕虫木马
items: mount 挂载与 umount 卸载
items: PHP 实现QQ 微信 STEAM等第三方登录
items: php 记录代码 / 页面执行时间

BAT 文件实现批量安装 .msu 补丁
type: item
date: 2019-11-24T22:24:00+08:00
description: 从事网管工作的朋友，总会遇到一些局域网补丁更新不统一的问题，但是要每一台机都自行下载这补丁，未免太浪费网络带宽了。虽然可以通过其他计算机共享补丁，但补丁太多，总不能每个都手动点击安装吧？其实可以通过批处理解决。1、将下载的补丁统一放到一个文件夹 hotfix：2、在文件夹中新建一个 txt 文本文档 update.txt，输入以下内容：FOR /R %%F IN (*.msu) DO start /wait wusa %%F /quiet /norestart3、将 update.txt 的后缀名修改为 update.bat：4、然后把 hotfix 文件夹拷贝到需要安装补丁的电脑，双击 update.bat 安装：5、安装完成后，窗口会自动关闭。附网上分享的一个批处理命令：@Echo Off Title Install Windows Update pack Echo 正在安装Windows系统补丁，请稍等...... for %%i in (*.exe) do %%i /passive /norestart /nobackup For %%F In (*.msu) Do Call :Update %%F Shutdown.exe -r -t 19 Exit :Update Echo 安装完成 Start /Wait %1 /quiet /norestart GoTo :EOF Exit

MySQL 提示 Field * doesn't have a default value 的解决办法
type: item
date: 2019-11-16T23:52:00+08:00
description: 解决办法在 MySQL 5.7 以上版本中，启用了严格模式。在配置文件中 /etc/mysql/my.cnf 中找到：sql-model=STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION修改为：sql-mode=NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION然后重启 MySQLSTRICT_TRANS_TABLES 存储引擎启用严格模式，非法数据值被拒绝出现此问题的原因在 MySQL 5.0.2 以前，MySQL 对非法值检查并不严厉，而且为了数据输入还会强制将他们变为合法值。在 MySQL 5.0.2 以后的版本中，保留了以前的默认行为，但你可以为不良值选择更传统的处理方法，从而使得服务器能够拒绝并放弃出现不良值的语句。严格模式如果未使用严格模式，下面的情况是合法的：如果将不正确的值插入到列，如将 null 值插入非 null 列，或将过大的数据插入数值列，MySQL 会将这些列设置为最可能的值，而不是抛出错误信息。如果视图将超过范围的值保存到数值列，MySQL 服务器将保存 0 (最小的可能值) 取而代之，或最大的可能值。对于字符串，MySQL 或保存空字符串，或将字符串可能多的部分保存到列中。如果打算将不是以数值开头的字符串保存到数值列，MySQL 将保存 0 。MySQL 允许将特定的不正确日期值保存到 DATE 和 DATETIME 列（如：2000-02-31 或 2000-02-00）。其观点在于，验证日期不是 SQL 服务器的任务。如果 MySQL 能保存日期值并准确检索相同的值，MySQL 就能按给定的值保存它。如果日期完全不正确（超出服务器能保存的范围）将在列中保存特殊的日期值 0000-00-00 取而代之。如果视图将 null 值保存到不接受 null 值的列，对于单行 insert 语句，将出现错误。对于多行 insert 语句或者 insert into ... select 语句，MySQL 服务器会保存针对列数据类型的隐含默认值。一般情况下，对于数值类型，它是 0，对于字符串类型，它是空字符串（''），对于日期和时间类型是 zero 。如果 insert 语句未为列指定值，如果列定义包含明确的 default 子句，MySQL 将插入默认值。如果在定义中没有这类 default 子句，MySQL 会插入列数据类型的隐含默认值。采用前描述规则的原因在于，在语句开始执行前，无法检查这些情况。如果在更新了数行后遇到这类问题，我们不能仅靠回滚解决，这是因为存储引擎可能不支持回滚。中止语句并不是良好的选择，在该情况下，更新完成了 “一半”，这或许是最差的情况。对于本例，较好的方式是 “尽可能做到最好”，然后就像什么都没有发生那样继续执行。在 MySQL 5.0.2 以后的版本中，可以使用 STRICT_TRANS_TABLES 或 STRICT_ALL_TABLES SQL 模式，选择更严格的处理方式。STRICT_TRANS_TABLES的工作方式：对于事务性存储引擎，在语句中任何地方出现的不良数据值均会导致放弃语句并执行回滚。对于非事务性存储引擎，如果错误出现在要插入或更新的第 1 行，将放弃语句。（在这种情况下，可以认为语句未改变表，就像事务表一样）。首行后出现的错误不会导致放弃语句。取而代之的是，将调整不良数据值，并给出告警，而不是错误。换句话讲，使用 STRICT_TRANS_TABLES 后，错误值会导致 MySQL 执行回滚操作，如果可以，所有更新到此为止。要想执行更严格的检查，请启用 STRICT_ALL_TABLES。除了非事务性存储引擎，它与 STRICT_TRANS_TABLES 等同，即使当不良数据出现在首行后的其他行，所产生的错误也会导致放弃语句。这意味着，如果错误出现在非事务性表多行插入或更新过程的中途，仅更新部分结果。前面的行将完成插入或更新，但错误出现点后面的行则不然。对于非事务性表，为了避免这种情况的发生，可使用单行语句，或者在能接受转换警告而不是错误的情况下使用 STRICT_TRANS_TABLES。要想在第 1 场合防止问题的出现，不要使用 MySQL 来检查列的内容。最安全的方式（通常也较快）是，让应用程序负责，仅将有效值传递给数据库。有了严格的模式选项后，可使用 INSERT IGNORE 或 UPDATE IGNORE 而不是不带 IGNORE 的 INSERT 或 UPDATE，将错误当作告警对待。

CentOS 中 iptables 和 firewall 防火墙开启、关闭、查看状态、基本设置
type: item
date: 2019-11-16T08:42:00+08:00
description: CentOS7 默认使用的是 firewall 作为防火墙firewall 防火墙1、查看 firewall 服务状态systemctl status firewalld2、查看 firewall 的状态firewall-cmd --state3、开启、重启、关闭、firewalld.service 服务停止 firewallsystemctl stop firewalld.service开启 firewallsystemctl start firewalld.service禁止 firewall 开机启动systemctl disable firewalld.service4、开启、重启、关闭、firewalld.service 服务开启 firewallservice firewalld start重启 firewallservice firewalld restart关闭 firewallservice firewalld stop5、查看防火墙规则firewall-cmd --list-all6、查询、开放、关闭端口查询端口是否开放firewall-cmd --query-port=8080/tcp开放 80 端口firewall-cmd --permanent --add-port=80/tcp移除端口firewall-cmd --permanent --remove-port=8080/tcp6、重启防火墙 (修改配置后要重启防火墙)firewall-cmd --reload参数解释firwall-cmd：是 Linux 提供操作 firewall 的一个工具；--permanent：表示设置为持久；--add-port：标识添加的端口。iptables 防火墙1、基本操作查看防火墙状态service iptables status停止防火墙service iptables stop启动防火墙service iptables start重启防火墙service iptables restart永久关闭防火墙chkconfig iptables off永久关闭后重启chkconfig iptables on2、查看防火墙状态防火墙处于开启状态并且只开放了 22 端口3、开启 80 端口vim /etc/sysconfig/iptables加入如下代码-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT保存退出后重启防火墙service iptables restart

关于 PHP 时间戳最大支持到 2038 年问题解决办法
type: item
date: 2019-11-12T20:31:00+08:00
description: 原因分析这个问题在 64 位的 PHP 版本中并不存在，因为是在 32 位版本中，以秒计算 PHP 只支持到 2147483648，即：2^31，到 2038-01-19 03:14:08。有效的时间戳通常从 Fri, 13 Dec 1901 20:45:54 GMT 到 Tue, 19 Jan 2038 03:14:07 GMT（对应于 32 位有符号整数的最小值和最大值）。不是所有的平台都支持负的时间戳，那么日记范围就被限制为不能早于 Unix 纪元。这意味着在 1970 年 1 月 1 日之前的日期将不能用在 Windows，一些 Linux 版本，以及几个其它的操作系统中。不过 PHP 5.1.0 及更新的版本克服了此限制。 —— [ PHP手册 ]解决办法如果在32系统PHP 5.1.0之后的版本，可以使用new DateTime解决。代码如下：将时间戳转为年月日：$d = new DateTime("@21474836490"); $d->setTimezone(new DateTimeZone("PRC")); echo $d->format("Y-m-d H:i:s");将年月日转为时间戳：$d = new DateTime('2650-07-06 16:21:30'); echo $d->format('U');如果运行时，遇到如下报错：WARNING: It is not safe to rely on the system’s timezone settings建议在 php.ini 里加上找到 date.timezone 项，设置 date.timezone = "Asia/Shanghai"，再重启环境。利用 DateTime 的替代函数传统 date 替代函数：function newDate($curformat, $utc_value){ while(1){ if($utc_value > 2147483647){ if(date('Y', $utc_value) < 2038){ $mydate2 = new DateTime('@'.$utcValue); $string = $mydate2->format($curformat); break; } } $string = date($curformat, $utc_value); break; } return $string; } // 调用方法 $utc_value = 2247484647 $curformat = 'Y-m-d'; newDate($curformat, $utc_value);传统 strtotime 替代函数：function newStrToTime($str_time){ $result = strtotime($str_time); if(empty($result)){ $date = new DateTime($str_time); $result = $date->format('U'); } return $result; } // 调用方法 $str_time = '2100-10-02'; newStrToTime($str_time);具体 DateTime 类使用方法请详查 - [ PHP手册-DateTime类 ]利用 DateTimeZone 的替代函数1、Unix时间戳转日期function unixtime_to_date($unixtime, $timezone = 'PRC') { $datetime = new DateTime("@$unixtime"); //DateTime类的bug，加入@可以将Unix时间戳作为参数传入 $datetime->setTimezone(new DateTimeZone($timezone)); return $datetime->format("Y-m-d H:i:s"); }2、日期转Unix时间戳function date_to_unixtime($date, $timezone = 'PRC') { $datetime= new DateTime($date, new DateTimeZone($timezone)); return $datetime->format('U'); }

PHP 使用 CURL 不依赖 COOKIEJAR 获取 COOKIE 的方法
type: item
date: 2019-11-12T19:25:00+08:00
description: 使用 COOKIEJAR 方法PHP 中 CURL 类在做请求时非常好用，对于COOKIE，CURL类也有很不错的支持。获得 COOKIE 并存为文件：// 把 COOKIE 保存至 cookie.txt curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookie.txt'); // COOKIE 文件存放在至 temp 文件夹下的随机文件 $cookie_file = tempnam('./temp','cookie'); curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);读取文件并携带 COOKIE：curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookie.txt');先把 COOKIE 保存文件，调用的时候再读取文件。使用正则表达式使用 COOKIEJAR 方法，会有两次 IO 操作，对效率有一定的损失。且运行在 BAE/SAE/GAE 等云计算平台时，不支持本地文件写入。使用正则表达式可以解决这些问题。获取 COOKIE 解析为文本：$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // 获取头部信息 curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $content = curl_exec($ch); curl_close($ch); list($header, $body) = explode("\r\n\r\n", $content); // 解析 COOKIE preg_match("/Set-Cookie: (.*);/iU", $header, $matches); $cookie = trim($matches[1]);携带 COOKIE 文本提交：curl_setopt($ch, CURLOPT_COOKIE, $cookie);返回多条 COOKIE 时的正则表达式：很多时候，网站不止返回了一条 COOKIE，此时使用上面的代码匹配时无法匹配到完整的 COOKIE。对此，需要使用 preg_match_all 来进行匹配，代码如下：preg_match("/Set-Cookie: (.*);/iU", $header, $matches); $cookies = implode('; ', $matches[1]);

JetBrains License Server PHP版
type: item
date: 2019-11-11T21:14:00+08:00
description: JetBrainsLicenseServer 授权服务类代码如下：<?php /** * JetBrains许可服务器 */ class JetBrainsLicenseServer { // 授权给谁 public $licensee = 'jxx'; // license 有效时间（单位：毫秒），默认约为7天多（607875500），原厂 server 传递的数值。 public $prolongationPeriod = 607875500; // RSA 私钥 public $privateKey = <<<Eof -----BEGIN RSA PRIVATE KEY----- [binary] -----END RSA PRIVATE KEY----- Eof; public function ping($salt, $isAnswer = false) { $str = ' OK %s '; $out = sprintf($str, $salt); return $isAnswer ? $this->writeAnswer($out) : $out; } public function obtainTicket($salt, $isAnswer = false) { $ticketId = 1; $str = " %u OK %s %d licensee=%s\tlicenseType=0\t "; $out = sprintf($str, $this->prolongationPeriod, $salt, $ticketId, $this->licensee); return $isAnswer ? $this->writeAnswer($out) : $out; } public function prolongTicket($salt, $isAnswer = false) { $ticketId = 1; $str = ' OK %s %d '; $out = sprintf($str, $salt, $ticketId); return $isAnswer ? $this->writeAnswer($out) : $out; } public function releaseTicket($salt, $isAnswer = false) { $str = ' OK %s '; $out = sprintf($str, $salt); return $isAnswer ? $this->writeAnswer($out) : $out; } public function writeAnswer($str) { $signature = $this->sign($str); $signatureHex = bin2hex($signature); $out = sprintf(" \n%s", $signatureHex, $str); return $out; } private function sign($data) { if (empty ($data)) { return null; } $pkeyid = openssl_get_privatekey($this->privateKey); if (empty ($pkeyid)) { return null; } $signature = ''; $verify = openssl_sign($data, $signature, $pkeyid, OPENSSL_ALGO_MD5); openssl_free_key($pkeyid); return $verify ? $signature : null; } }index.php 文件代码如下：<?php include 'JetBrainsLicenseServer.php'; $uri = htmlspecialchars($_SERVER['REQUEST_URI']); if (stripos($uri, '/rpc/ping.action') !== false) { $method = 'ping'; } elseif (stripos($uri, '/rpc/obtainTicket.action') !== false) { $method = 'obtainTicket'; } elseif (stripos($uri, '/rpc/prolongTicket.action') !== false) { $method = 'prolongTicket'; } elseif (stripos($uri, '/rpc/releaseTicket.action') !== f…

检查电脑是否感染 Synaptics 蠕虫木马
type: item
date: 2019-11-10T17:56:00+08:00
description: 检查木马1、打开显示 “系统隐藏文件”2、是否存在：C:\ProgramData\Synaptics\Synaptics.exe3、C 盘所有 exe 文件将会被植入木马，特征如下清除木马1、下载火绒杀毒：https://www.huorong.cn/ ，对全盘进行病毒查杀。2、删除开机启动项：运行任务管理器禁止开机启动项，也可以使用火绒安全清除开机启动项。3、对接收文件、下载文件进行判别，是否感染此类木马，谨慎运行来源不明的 exe 程序。Synaptics蠕虫木马分析https://www.cnblogs.com/Gj-Dreamer/p/11353230.html

mount 挂载与 umount 卸载
type: item
date: 2019-11-10T17:23:00+08:00
description: mount：挂载挂载本地光盘：mount /dev/cdrom /mntNFS 挂载：mount -t nfs 192.168.1.2:/a /bCentOS 挂载 U 盘：mkdir /mnt/u mount /dev/sda1 /mnt/u查看挂载的情况：df -h查看某台主机提供的 NFS 服务：showmount -e 192.168.1.2 nc -v -w 2 192.168.1.2 -z 2049umount：卸载umount /mnt umount /dev/cdrom umount /dev/cdrom /mnt注意：当你的当前路径为 /mnt 下，即在挂载文件的里面时，卸载会失败，提示：Dev is besy。解决办法：退出挂载的目录。当有别的程序正在访问挂载的文件时，也会提示卸载失败。通过 lsof /mnt 查看是哪个进程占用了 /mnt；通过 kill -9 $pid 杀死进程，然后再卸载。使用：umount -l /mnt-l：卸载前检查占用该挂载文件的程序并迅速 kill 进程，以达到快速卸载的目的。

PHP 实现QQ 微信 STEAM等第三方登录
type: item
date: 2019-11-10T14:47:00+08:00
description: 下载代码：composer require medivh/oauth dev-masterGitHub 项目地址：https://github.com/medivh-jay/oauthQQ 登录<?php require 'vendor/autoload.php'; // 配置信息 $config = [ 'appid' => '申请的appid', 'secret' => '申请的appKey', 'redirect_uri' => '跳转地址', 'response_type' => 'code', 'display' => 'default', // 分 default 和 mobile 'scope' => 'get_user_info,add_share,list_album,add_album,upload_pic,add_topic,add_one_blog,add_weibo,check_page_fans,add_t,add_pic_t,del_t,get_repost_list,get_info,get_other_info,get_fanslist,get_idolist,add_idol,del_idol,get_tenpay_addr' // 这里可以固定成这个 ];得到认证对象Driver命名空间下提供了部分认证驱动类，亦可以自己实现，只要继承了OAuthInterface接口， 都可以使用OAuth来调用$oAuth = \medivh\OAuth\OAuth::register(new \medivh\OAuth\Driver\QQ, $config);生成登录地址$oAuth->getAuthorizeURL();获取access_token$oAuth->getAccessToken();获取用户信息这个方法可以传入两个参数，openid 和 access_token 当服务器保存了用户的 openid 和 access_token 时，可以在用户登录时直接调用这个方法获取用户信息$oAuth->getUser();也可以调用 getOAuth() 获取其他操作，但是完全不必这样$oAuth->getOAuth();登录的生成地址会有一个 state 字段，QQ 会原样返回，当做 csrf_token 验证字段，需要事先记录存入用户本地 cookie 需要自己存，这里不做 cookie 设置操作，提供获取方法，生成完登录地址的时候，可以同时调用该方法得到$oAuth->getCSRFToken();微信登录微信登录跟QQ使用方法都是一样的，唯一不同的是配置信息<?php require 'vendor/autoload.php'; $config = [ 'appid' => '', 'secret' => '', 'redirect_uri' => '回调地址', 'response_type' => 'code', 'scope' => 'snsapi_login' ]; $oAuth = \medivh\OAuth\OAuth::register(new \medivh\OAuth\Driver\WeChatQrCode, $config); // 这里微信还提供刷新access_token 的方法 $refreshToken = $oAuth->getOAuth()->getAccessTokenInfo('refresh_token'); // 这个值建议在获取access_token的时候直接获取并保存 $oAuth->refreshToken($refreshToken);百度登录百度登录跟微信QQ差不多，不过不需要调用太多东西<?php require 'vendor/autoload.php'; $config = [ 'appid' => '', 'secret' => '', 'redirect_uri' => '跳转地址', 'display' => 'popop' ]; $oAuth = \medivh\OAuth\OAuth::register(new \medivh\OAuth\Driver\BaiDu, $config); // 这个方法在回调地址使用，用户登录过后，直接回调之后使用这个方法获取用户信息就好了 var_dump($oAuth->getUser()); //登录地址 $uri = $oAuth->getAuthorizeURL();STEAM 登录Steam 官方提供的第三方登录使用的LightOpenID代码，所以Steam登录略有不同<?php require 'vendor/autoload.php'; $config = [ 'appid…

php 记录代码 / 页面执行时间
type: item
date: 2019-11-10T14:43:00+08:00
description: 代码如下：$t1 = microtime(true); // ... 执行代码 ... $t2 = microtime(true); echo '耗时 '.round($t2-$t1, 3).' 秒';microtime() 如果带有 true 参数，将返回一个浮点类型。这样 t1 和 t2 得到的就是两个浮点数，相减之后得到之间的差。由于浮点的位数很长，或者说不确定，所以再用 round() 取出小数点后 3 位。