jwj 发布的文章

在自己电脑(Windows)开发测试代码都没问题,但一上生产环境就报错了。经过对比,本机和服务器的PHP版本和OpenSSL版本不一样,猜测可能是这个原因导致的。经过一番查找,找到了从代码上解决问题的办法,规避了调整生产服务器的风险。

报错的代码

/**
 * 字符串加密(加密方法:DES-ECB)
 * @param string $data 待加密字符串
 * @param string $key 对称加密密钥
 * @return string
 */
function encryptData(string $data, string $key)
{
    // 获取密码iv长度
    $length = openssl_cipher_iv_length('DES-ECB');
    // 生成一个伪随机字节串
    $iv = openssl_random_pseudo_bytes($length);
    // 加密数据
    $ciphertext = openssl_encrypt($data, 'DES-ECB', $key, OPENSSL_RAW_DATA, $iv);

    // 把包含数据的二进制字符串转换为十六进制值,然后返回结果
    return bin2hex($ciphertext);
}

报错信息

Fatal error: Uncaught Error: Length must be greater than 0 in frame.php:87
Stack trace: 
    #0 frame.php(87): openssl_random_pseudo_bytes()
    #1 frame.php(60): encryptData()
    #2 {main} thrown in frame.php on line 87

根据报错信息,主要问题是openssl_cipher_iv_length()返回的长度为0,而openssl_random_pseudo_bytes()的参数的长度必须大于0,所以就产生了报错。
但是openssl_cipher_iv_length()为什么返回0呢?难道是不支持DES-ECB加密方法?
使用openssl_get_cipher_methods()方法获取可用的加密算法的列表,发现DES-ECB在列表内,那应该是支持的!
这时候,我就猜想是不是openssl低版本的BUG,因为以前也见过一些因为openssl版本过低导致的问题,于是继续Google一番。
经过查找,发现了两条有用的结果:

在第一条中得知ECB 加密模式是不安全的,因为它没有初始化矢量openssl_cipher_iv_length()返回的长度为0的原因就得知了。

但结论没有经过仔细论证。也不知道为什么java为什么要ECB。
在第二条中找到了调整代码的思路。
最终得到了以下没有报错的代码~
/**
 * 字符串加密(加密方法:DES-ECB)
 * @param string $data 待加密字符串
 * @param string $key 对称加密密钥
 * @return string
 */
function encryptData(string $data, string $key)
{
    // 获取密码iv长度
    $length = openssl_cipher_iv_length('DES-ECB');
    // 生成一个伪随机字节串
    if ($length > 0) {
        $iv = openssl_random_pseudo_bytes($length);
    } else {
        $iv = '';
    }
    // 加密数据
    $ciphertext = openssl_encrypt($data, 'DES-ECB', $key, OPENSSL_RAW_DATA, $iv);

    // 把包含数据的二进制字符串转换为十六进制值,然后返回结果
    return bin2hex($ciphertext);
}

在制作项目中,难免会遇到有跨域问题,需要增加指定响应头来满足跨域的需求。但ThinkPHP5.1版本的手册中,对跨域怎么设置提供的方法比较局限,所以这里经过研究,总结出了几种办法,推荐使用第三种。

一、路由

这方法是手册当中介绍的,这里简单复制下,深入了解可以查看手册

如果某个路由或者分组需要支持跨域请求,可以使用

Route::get('new/:id', 'News/read')->ext('html')->allowCrossDomain();
Route::group('index', function() {
    Route::get('new/:id', 'News/read');
})->prefix('index/')->allowCrossDomain();

这方法,仅适合定义了路由的情况下使用,如果是默认路由,这方法不适用。

二、header()函数

可以在入口文件index.php、公共函数文件common.php等文件里使用header()函数定义跨域响应头。代码如下:

header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE');
header('Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With');
header('Access-Control-Allow-Origin: *');
全局允许跨域的话,在入口文件或全局公共函数文件里增加跨域代码
单模块允许跨域的话,在模块公共函数文件里增加跨域代码
单控制器允许跨域的话,在控制器文件里增加跨域代码(命名空间与类声明之间区域)
单方法允许跨域的话,在方法代码开头增加跨域代码。或者使用路由设置跨域。

这个方法可能会存在响应头被覆盖的问题(框架输出响应内容时,设置了相同的响应头,后设置覆盖前设置)

三、中间件

可以使用中间件,更改响应输出内容。这种方法适合全局或部分模块使用。
创建application/http/middleware/AllowCrossDomain.php文件,文件内容如下:

注意修改允许跨域的域名
<?php
namespace app\http\middleware;

/**
 * 跨域中间件
 * @package app\http\middleware
 */
class AllowCrossDomain
{
    /**
     * @param \think\Request $request
     * @param \Closure $next
     * @return mixed|\think\Response
     */
    public function handle($request, \Closure $next)
    {
        // 允许跨域的域名
        $allowOriginDomain = ['www.kancloud.cn', 'll00.cn'];
        // HTTP请求头中的Origin
        $origin = $request->header('origin', '');
        // 附加响应头
        $header = [];

        if (!empty($origin)) {
            $domain = explode('://', $origin)[1] ?? '';
            if (in_array($domain, $allowOriginDomain)) {
                $header['Access-Control-Allow-Credentials'] = 'true';
                $header['Access-Control-Allow-Methods']     = 'GET, POST, PATCH, PUT, DELETE';
                $header['Access-Control-Allow-Headers']     = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With';
                $header['Access-Control-Allow-Origin']      = '*';
            }
        }

        return $next($request)->header($header);
    }
}

application/middleware.phpapplication/index/middleware.php文件加入

\app\http\middleware\AllowCrossDomain::class

示例:

<?php
return [
    // 允许跨域
    \app\http\middleware\AllowCrossDomain::class,
    // 登录认证
    //\app\wxamp\middleware\CheckLogin::class,
    // 权限验证
    //\app\wxamp\middleware\CheckPermission::class,
];

四、输出响应对象实例设置

可以在json()jsonp()xml()等函数里设置跨域响应头,如:

return json(
    [
        'code' => 0,
        'msg'  => '操作成功',
    ],
    200,
    [
        'Access-Control-Allow-Credentials' => 'true',
        'Access-Control-Allow-Methods'     => 'GET, POST, PATCH, PUT, DELETE',
        'Access-Control-Allow-Headers'     => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With',
        'Access-Control-Allow-Origin'      => '*',
    ]
);

线上的环境,客户突然反馈不能使用,经过测试,发现了下文中的报错信息。从报错信息中,大概可以看出,Redis快照保存失败,导致无法正常使用。

以下是报错返回的报错信息

MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.

想到有两个可能,权限问题,或磁盘满了。但服务器一直都在正常运行的,只是突然就这样,近期也没有对服务器进行变更。所以先看看磁盘吧。

查看服务器磁盘剩余空间,发现磁盘已满!

# df -hl
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        900M     0  900M   0% /dev
tmpfs           915M     0  915M   0% /dev/shm
tmpfs           915M  8.5M  907M   1% /run
tmpfs           915M     0  915M   0% /sys/fs/cgroup
/dev/vda1        40G   40G   20K 100% /
tmpfs           183M     0  183M   0% /run/user/0

删除部分无用文件,清理出31G空间,短时间内应该是不会再爆满了。

再测试业务功能,已恢复正常,也不用重启redis~

办公时间,总得放点音乐,边听歌边工作。外放音乐又不大好,但电脑又没有蓝牙,不能使用蓝牙耳机,使用有线耳机又碍事。于是想到了用手机来当电脑音箱,手机再连接蓝牙,这样就能间接实现电脑连接蓝牙耳机了。机智如我~

首先,有问题,当然是Google一下,于是找到了SoundWire这款软件。这软件有两个端,分别是服务端(电脑装)和接收端(手机装)。下面,开搞~

服务端

首先,从GeorgieLabs上下载服务端,然后开始安装。

image8e5cb39bbe5ac9e5.png

服务协议

image32f166d997777f2d.png

选择安装路径

imagea89da7c412fefe1d.png

选择开始菜单目录

image5db5b3d5e89b5e20.png

是否创建桌面图标

imagec42f26c53c9d21ac.png

确认安装信息

imagefdfddfeceb32a145.png

安装中

image9f7855e96863e5ba.png

安装成功,直接点Finish

image38fefa9df09f5002.png

一个警告,但似乎没太大影响,如果对音质要求不高的话。记下Server Address的IP地址,这是你电脑的IP地址,一会手机需要用到。

imagec851f363727f9af7.png

接下来就是手机的操作了,如果成功与手机连接上,Status会显示Connected

image15615ecfb833f494.png

接收端

接收端要从Google Play Store上下载,由于国内打不开,这里我保存了一份在蓝奏云(https://wampserver.lanzous.com/b00tv3m1c 密码:8ucv)

安装好之后,在Server处输入服务端的IP地址,然后点中间的弹簧形状图标进行连接。

image71ade25c6796d974.png

结语

到这里就结束了,更多的可以自己探索下。总的来说,感觉效果还不差,就是音频会延迟一秒左右,听歌的话,是能接受的。音质方面,没感觉。

使用了一会,发现会有广告语,每隔一段时间就会有一个机器女声说SoundWire Free

如果想降低延迟,可以在接收端的Settings里调整Audio buffer size,值越小延迟越低。但网络环境不好的话,值越小音频会出现断续问题。

最近良心云10周年庆典放出了满1000-1000的优惠券,然后花8毛钱买了台3年的1H1G服务器。这配置,装个数据库够呛,于是花36元又买了个1年的1H1G数据库。但数据库没有提供外网,所以只能这8毛的服务器做中转,也足够了。

下载 MySQL Router软件包

wget https://dev.mysql.com/get/Downloads/MySQL-Router/mysql-router-community-8.0.21-1.el8.x86_64.rpm

安装 MySQL Router软件包

rpm -ivh mysql-router-community-8.0.21-1.el8.x86_64.rpm

返回提示

warning: mysql-router-community-8.0.21-1.el8.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
[/usr/lib/tmpfiles.d/mysqlrouter.conf:23] Line references path below legacy directory /var/run/, updating /var/run/mysqlrouter → /run/mysqlrouter; please update the tmpfiles.d/ drop-in file accordingly.

编辑配置文件,加上要代理的内网数据库

vim /etc/mysqlrouter/mysqlrouter.conf
# Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have included with MySQL.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

#
# MySQL Router configuration file
#
# Documentation is available at
#    http://dev.mysql.com/doc/mysql-router/en/

[DEFAULT]
logging_folder = /var/log/mysqlrouter
runtime_folder = /var/run/mysqlrouter
config_folder = /etc/mysqlrouter

[logger]
level = INFO

# If no plugin is configured which starts a service, keepalive
# will make sure MySQL Router will not immediately exit. It is
# safe to remove once Router is configured.
[keepalive]
interval = 60

[routing:read_write]
bind_address = 0.0.0.0
bind_port = 3306
destinations = 172.27.16.12:3306
mode = read-write

启动 MySQL Router,并设置开机自启

systemctl start mysqlrouter.service
systemctl enable mysqlrouter.service

如果开机自启不了,可能是权限问题,可以更改下用户和用户组

chown mysqlrouter:mysqlrouter /usr/lib64/mysqlrouter