微信小程序 尝试 WebSocket

服务端开启 WebSocket,使用 WorkerMan + phpSocket.io

开启的端口为 2120,访问为 ws://wanaioa.unetu.net:2120/

由于微信小程序只能使用 443 端口,需将域名的 443 端口进行转发,同时为了保证原 https 的正常运作,参照网上的教程,将域名的 uri 匹配  xxx.xxx/wss 进行转发,其余不进行转发

location /wss
  {
    proxy_pass http://0.0.0.0:2120;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header X-Real-IP $remote_addr;
  }

参考文献

https://www.kancloud.cn/walkor/workerman/315297

以下为 WorkerMan 的原文

创建wss服务

问:

Workerman如何创建一个wss服务,使得客户端可以用过wss协来连接通讯,比如在微信小程序中连接服务端。

答:

wss协议实际是websocket+SSL,就是在websocket协议上加入SSL层,类似https(http+SSL)。
所以只需要在websocket协议的基础上开启SSL即可支持wss协议。

方法一 ,直接用Workerman开启SSL

准备工作:

1、Workerman版本不小于3.3.7

2、PHP安装了openssl扩展

3、已经申请了证书(pem/crt文件及key文件)放在磁盘任意目录

代码:

<?php
require_once __DIR__ . '/Workerman/Autoloader.php';
use Workerman\Worker;

// 证书最好是申请的证书
$context = array(
    // 更多ssl选项请参考手册 http://php.net/manual/zh/context.ssl.php
    'ssl' => array(
        // 请使用绝对路径
        'local_cert'                 => '磁盘路径/server.pem', // 也可以是crt文件
        'local_pk'                   => '磁盘路径/server.key',
        'verify_peer'                => false,
        // 'allow_self_signed' => true, //如果是自签名证书需要开启此选项
    )
);
// 这里设置的是websocket协议(端口任意,但是需要保证没被其它程序占用)
$worker = new Worker('websocket://0.0.0.0:443', $context);
// 设置transport开启ssl,websocket+ssl即wss
$worker->transport = 'ssl';
$worker->onMessage = function($con, $msg) {
    $con->send('ok');
};

Worker::runAll();

通过以上的代码,Workerman就监听了wss协议,客户端就可以通过wss协议来连接workerman实现安全即时通讯了。

测试

打开chrome浏览器,按F12打开调试控制台,在Console一栏输入(或者把下面代码放入到html页面用js运行)

// 证书是会检查域名的,请使用域名连接
ws = new WebSocket("wss://域名");
ws.onopen = function() {
    alert("连接成功");
    ws.send('tom');
    alert("给服务端发送一个字符串:tom");
};
ws.onmessage = function(e) {
    alert("收到服务端的消息:" + e.data);
};

注意:

1、如果无法启动,则一般是443端口被占用,请改成其它端口,注意改成其它端口后客户端连接时需要带上端口号,客户端连接时地址类似wss://domain.com:xxx ,xxx为端口号。如果必须使用443端口请使用方法二代理的方式实现wss。

2、wss端口只能通过wss协议访问,ws无法访问wss端口。

3、证书一般是与域名绑定的,所以测试的时候客户端请使用域名连接,不要使用ip去连。

4、如果出现无法访问的情况,请检查服务器防火墙。

5、此方法要求PHP版本>=5.6,因为微信小程序要求tls1.2,而PHP5.6以下版本不支持tls1.2。

方法二、利用nginx/apache代理wss

除了用Workerman自身的SSL,也可以利用nginx/apache作为wss代理转发给workerman(注意此方法workerman部分千万不要设置ssl,否则将无法连接)。

通讯原理及流程是:

1、客户端发起wss连接连到nginx/apache

2、nginx/apache将wss协议的数据转换成ws协议数据并转发到Workerman的websocket协议端口

3、Workerman收到数据后做业务逻辑处理

4、Workerman给客户端发送消息时,则是相反的过程,数据经过nginx/apache转换成wss协议然后发给客户端

nginx配置参考

前提条件及准备工作:

1、已经安装nginx,版本不低于1.3

2、假设Workerman监听的是8282端口(websocket协议)

3、已经申请了证书(pem/crt文件及key文件)放在了/etc/nginx/conf.d/ssl下

4、打算利用nginx开启443端口对外提供wss代理服务(端口可以根据需要修改)

5、nginx一般作为网站服务器运行着其它服务,为了不影响原来的站点使用,这里使用地址 域名/wss 作为wss的代理入口。也就是客户端连接地址为 wss://域名/wss

nginx配置类似如下

server {
  listen 443;

  ssl on;
  ssl_certificate /etc/ssl/server.pem;
  ssl_certificate_key /etc/ssl/server.key;
  ssl_session_timeout 5m;
  ssl_session_cache shared:SSL:50m;
  ssl_protocols SSLv3 SSLv2 TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;

  location /wss
  {
    proxy_pass http://127.0.0.1:8282;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header X-Real-IP $remote_addr;
  }
  
  # location / {} 站点的其它配置...
}

测试

// 证书是会检查域名的,请使用域名连接
ws = new WebSocket("wss://域名/wss");

ws.onopen = function() {
    alert("连接成功");
    ws.send('tom');
    alert("给服务端发送一个字符串:tom");
};
ws.onmessage = function(e) {
    alert("收到服务端的消息:" + e.data);
};

利用apache代理wss

也可以利用apache作为wss代理转发给workerman(注意如使用apache代理SSL,则workerman部分千万不要设置ssl,否则将无法连接)。

准备工作:
1、GatewayWorker 监听 8282 端口(websocket协议)

2、已经申请了ssl证书, 放在了/server/httpd/cert/ 下

3、利用apache转发443端口至指定端口8282

4、httpd-ssl.conf 已加载

5、openssl 已安装

启用 proxy_wstunnel_module 模块

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

配置SSL及代理

#extra/httpd-ssl.conf
DocumentRoot "/网站/目录"
ServerName 域名

# Proxy Config
SSLProxyEngine on

ProxyRequests Off
ProxyPass /wss ws://127.0.0.1:8282
ProxyPassReverse /wss ws://127.0.0.1:8282

# 添加 SSL 协议支持协议,去掉不安全的协议
SSLProtocol all -SSLv2 -SSLv3
# 修改加密套件如下
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM
SSLHonorCipherOrder on
# 证书公钥配置
SSLCertificateFile /server/httpd/cert/your.pem
# 证书私钥配置
SSLCertificateKeyFile /server/httpd/cert/your.key
# 证书链配置,
SSLCertificateChainFile /server/httpd/cert/chain.pem

测试

// 证书是会检查域名的,请使用域名连接
ws = new WebSocket("wss://域名/wss");

ws.onopen = function() {
    alert("连接成功");
    ws.send('tom');
    alert("给服务端发送一个字符串:tom");
};
ws.onmessage = function(e) {
    alert("收到服务端的消息:" + e.data);
};

ThinkPHP 3.2.3 提示 could not find driver PDO 链接报错

ThinkPHP 3.2.3 提示

could not find driver

抛出了 PDOException 异常,说明 PDO 连接报错

解决方法安装 pdo_mysql 扩展,该扩展在 php 源码包里 下载 php 源码并解压

cd ~
cd php-5.6.30/ext/pdo_mysql
/usr/local/php5/bin/phpize
./configure --with-php-config=/usr/local/php5/bin/php-config
make
make install

在 php.ini 里添加 extension=pdo_mysql.so

手动安装 Let’s Encrypt SSL 证书 HTTPS

CentOS 6、7,先执行:

yum install epel-release
cd /root/
wget https://dl.eff.org/certbot-auto --no-check-certificate
chmod +x ./certbot-auto
./certbot-auto -n

单域名生成证书:

./certbot-auto certonly --email jollyfon@gmail.com --agree-tos --no-eff-email --webroot -w /home/wwwroot/wanai.unetu.net -d wanai.unetu.net

安装成功返回

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/wanai.unetu.net/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/wanai.unetu.net/privkey.pem
   Your cert will expire on 2019-01-30. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again. To non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

多域名单目录生成单证书:(即一个网站多个域名使用同一个证书)

./certbot-auto certonly --email jollyfon@gmail.com --agree-tos --no-eff-email --webroot -w /home/wwwroot/wanai.unetu.net -d wanai.unetu.net www.unetu.net

多域名多目录生成一个证书:(即一次生成多个域名的一个证书)

./certbot-auto certonly --email jollyfon@gmail.com --agree-tos --no-eff-email --webroot -w /home/wwwroot/wanai.unetu.net -d wanai.unetu.net www.unetu.net -w /home/wwwroot/wanaioa.unetu.net -d wanaioa.unetu.net -d unetu.net

安装完成后证书文件位置

/etc/letsencrypt/live

有四个文件

/etc/letsencrypt/live/wanai.unetu.net/cert.pem
/etc/letsencrypt/live/wanai.unetu.net/chain.pem
/etc/letsencrypt/live/wanai.unetu.net/fullchain.pem
/etc/letsencrypt/live/wanai.unetu.net/privkey.pem

Nginx 配置

listen 443 ssl;   
server_name wanai.unetu.net;     
index index.html index.htm index.php default.html default.htm default.php;
root /home/wwwroot/wanai.unetu.net;          
ssl_certificate /etc/letsencrypt/live/wanai.unetu.net/fullchain.pem;    #前面生成的证书,改一下里面的域名就行
ssl_certificate_key /etc/letsencrypt/live/wanai.unetu.net/privkey.pem;   #前面生成的密钥,改一下里面的域名就行
SSLCertificateChainFile /etc/letsencrypt/live/wanai.unetu.net/chain.pem; #Apache 2.2版本需要加入该中间证书,否则浏览器可能不信任
ssl_ciphers "EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;

Let’s Encrypt 证书的有效期为 90 天,可自动续期

打开 crontab 

crontab -e

添加规则

0 3 */5 * * /root/certbot-auto renew --disable-hook-validation --renew-hook "/etc/init.d/nginx reload"

我的示例

server
    {
        listen 443 ssl http2;
        #listen [::]:443 ssl http2;
        server_name wanai.unetu.net ;
        index index.html index.htm index.php default.html default.htm default.php;
        root  /home/wwwroot/wanai.unetu.net;
        ssl on;
        ssl_certificate /etc/letsencrypt/live/wanai.unetu.net/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/wanai.unetu.net/privkey.pem;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;

        include rewrite/codeigniter.conf;

        location  ~ [^/]\.php(/|$)
        {
            fastcgi_pass  127.0.0.1:9001; #注意此端口
            fastcgi_index index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;

        }

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }

        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }

        location ~ /.well-known {
            allow all;
        }

        location ~ /\.
        {
            deny all;
        }

        access_log  /home/wwwlogs/wanai.unetu.net.log;
    }

参考文献:

CodeIgnter 报错 The Encrypt library requires the Mcrypt extension.

An Error Was Encountered
The Encrypt library requires the Mcrypt extension.

CI 框架 缺少 mcrypt 扩展

yum 安装 mcrypt

yum install libmcrypt libmcrypt-devel mcrypt mhash

源码编译没试,可参考这篇文章 

https://www.cnblogs.com/huangzhen/archive/2012/09/12/2681861.html

安装 php 的 mcrypt 扩展

扩展在 php 安装包的 ext 目录下

[root@instance-jewlel2q ~]# cd php-5.6.30/
[root@instance-jewlel2q php-5.6.30]# cd ext/mcrypt/
[root@instance-jewlel2q mcrypt]# /usr/local/php5/bin/phpize 

返回

Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226
[root@instance-jewlel2q mcrypt]# ./configure --with-php-config=/usr/local/php5/bin/php-config

返回

creating libtool
appending configuration tag "CXX" to libtool
configure: creating ./config.status
config.status: creating config.h

编译

[root@instance-jewlel2q mcrypt]# make
[root@instance-jewlel2q mcrypt]# make install
Installing shared extensions:     /usr/local/php5/lib/php/extensions/no-debug-non-zts-20131226/

查看扩展目录

[root@instance-jewlel2q mcrypt]# cd  /usr/local/php5/lib/php/extensions/no-debug-non-zts-20131226/
[root@instance-jewlel2q no-debug-non-zts-20131226]# ll
total 2092
-rwxr-xr-x 1 root root  170080 Nov  1 17:02 mcrypt.so
-rwxr-xr-x 1 root root 1340288 Nov  1 14:59 opcache.a
-rwxr-xr-x 1 root root  623648 Nov  1 14:59 opcache.so

安装成功

向 php.ini 写入扩展目录

vim /usr/local/php5/lib/php.ini

重启,查看 phpinfo

采坑记录

php 的配置文件应放在 lib 目录下,自己在 ext 目录下一直不生效,换到 lib 目录下重载后生效了

重装 LNMP 环境 MySQL 5.7 的初始配置

安装 lnmp.org

wget http://soft.vpser.net/lnmp/lnmp1.5.tar.gz -cO lnmp1.5.tar.gz && tar zxf lnmp1.5.tar.gz && cd lnmp1.5 && ./install.sh lnmp

其中 MySQL 版本 5.7 安装位置 /usr/local/mysql

添加用户用于外部访问,设置 3306 防火墙白名单,安全组白名单 范围% 重启主机

vim /etc/my.cnf

在 [mysqld] 下,添加

sql_mode=NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

否则可能出现 [Err] 1067 – Invalid default value for ‘xxxxx’,因为 MySQL 5.7 datetime 不允许默认值为 ‘0000-00-00 00:00:00’ 

https://blog.csdn.net/achuo/article/details/54618990

导入备份的 SQL

[Err] 1153 – Got a packet bigger than ‘max_allowed_packet’ bytes

原因是 MySQL 默认读取执行的 SQL 文件最大为 1M,有些为 16M,我这个 SQL 文件大于这个值

vim /etc/my.cnf

在 [mysqld] 下,添加或修改

max_allowed_packet=400M

微信小程序 invalid page hint

微信小程序消息推送在测试可以发送成功,正式发送失败

错误信息如下

{
"errcode": 41030,
"errmsg": "invalid page hint: [YJmNKa07043954]"
}
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/notice.html?search-key=41030

page 不正确

错误 /pages/index/index

正确 pages/index/index

PHPExcel 超过 26 列 解决方法

ThinkPHP 3.2 使用 PHPExcel

使用 chr(ord(‘A’)++) 的方法进行 Excel 列的自增,当列数超过26列时,会报如下错误

Invalid cell coordinate [1
错误位置
FILE: E:\wwwroot\wanaioa.a.cc\ThinkPHP\Library\Vendor\Excel\PHPExcel\Cell.php  LINE: 539

原因是 ord(‘Z’) 为 90,当 chr(91) 时为 string(1) “[“

但在 Excel 中应为 AA

解决方法是使用 PHPExcel 自带函数 stringFromColumnIndex

调用方法

\PHPExcel_Cell::stringFromColumnIndex(27);

源码为

/**
 *	String from columnindex
 *
 *	@param	int $pColumnIndex Column index (base 0 !!!)
 *	@return	string
 */
public static function stringFromColumnIndex($pColumnIndex = 0)
{
	//	Using a lookup cache adds a slight memory overhead, but boosts speed
	//	caching using a static within the method is faster than a class static,
	//		though it's additional memory overhead
	static $_indexCache = array();

	if (!isset($_indexCache[$pColumnIndex])) {
		// Determine column string
		if ($pColumnIndex < 26) {
			$_indexCache[$pColumnIndex] = chr(65 + $pColumnIndex);
		} elseif ($pColumnIndex < 702) {
			$_indexCache[$pColumnIndex] = chr(64 + ($pColumnIndex / 26)) .
										  chr(65 + $pColumnIndex % 26);
		} else {
			$_indexCache[$pColumnIndex] = chr(64 + (($pColumnIndex - 26) / 676)) .
										  chr(65 + ((($pColumnIndex - 26) % 676) / 26)) .
										  chr(65 + $pColumnIndex % 26);
		}
	}
	return $_indexCache[$pColumnIndex];
}

路径 

ThinkPHP/Library/Vendor/Excel/PHPExcel/Cell.php