前段经常看同事的接口bug是报这个错:“unserialize() Error at offset 76 of 176 bytes",
然后清理下缓存就不报错了,虽然觉得诡异,但由于事情比较多,也没时间去深究。
今天在处理关于客服系统的问题的时候,也遇到了这个问题,就索性探一探究竟。
首先可以确定的是,报错的地方是在缓存cache()的底层逻辑,背景是客服系统的框架是tp5,
客户系统的框架是tp6;于是对比了下两个版本的cache,
发现tp6的cache在存入、取出缓存的驱动(Driver.php)上做了很大的改动
tp5的存入取出代码如下:

/**
 * 序列化数据
 * @access protected
 * @param  mixed $data
 * @return string
 */
protected function serialize($data)
{
    if (is_scalar($data) || !$this->options['serialize']) {
        return $data;
    }

    $serialize = self::$serialize[0];

    return self::$serialize[2] . $serialize($data);
}

/**
 * 反序列化数据
 * @access protected
 * @param  string $data
 * @return mixed
 */
protected function unserialize($data)
{
    if ($this->options['serialize'] && 0 === strpos($data, self::$serialize[2])) {
        $unserialize = self::$serialize[1];

        return $unserialize(substr($data, self::$serialize[3]));
    } else {
        return $data;
    }
}

$serialize 参数的值

/**
 * 序列化方法
 * @var array
 */
protected static $serialize = ['serialize', 'unserialize', 'think_serialize:', 16];

tp6的存入取出代码如下:

/**
 * 序列化数据
 * @access protected
 * @param mixed $data 缓存数据
 * @return string
 */
protected function serialize($data): string
{
    if (is_numeric($data)) {
        return (string) $data;
    }

    $serialize = $this->options['serialize'][0] ?? "serialize";

    return $serialize($data);
}

/**
 * 反序列化数据
 * @access protected
 * @param string $data 缓存数据
 * @return mixed
 */
protected function unserialize(string $data)
{
    if (is_numeric($data)) {
        return $data;
    }

    $unserialize = $this->options['serialize'][1] ?? "unserialize";

    return $unserialize($data);
}

看到这里,大概就知道为什么在tp6获取cache值的时候会报这个错了。
tp5的存入值是否加serialize序列化是判断传入$data是否是标量is_scalar,
而tp6是判断是否为数值is_numeric,导致两边数据不一致。
知道了原因,解决方法就个显神通了,当前的方式比较粗暴,直接修改底层框架,
在tp6 unserialize的方法里面修改判断是否反序列化的逻辑,代码如下:

    /**
     * 反序列化数据
     * @access protected
     * @param string $data 缓存数据
     * @return mixed
     */
    protected function unserialize(string $data)
    {
        //判断$data是否为序列化字符串,如果不是直接返回不需要反序列化
        if (!preg_match( '/^[asO]:[0-9]+:/s', $data)) {
            return $data;
        }

        $unserialize = $this->options['serialize'][1] ?? "unserialize";

        return $unserialize($data);
    }

Node.js中文网 http://nodejs.cn/

选择适合自己系统的安装包,复制安装包链接地址

以最新版本为例

cd /usr/local  //进入安装目录
wget https://npm.taobao.org/mirrors/node/v16.11.1/node-v16.11.1-linux-x64.tar.xz  //获取压缩包
tar -xvzf node-v16.11.1-linux-x64.tar.xz //解压缩
mv node-v10.15.3-linux-x64 node  // 修改解压包名称
ln -s /usr/local/node/bin/node /usr/bin/node  --加入node命令软链到环境变量
ln -s /usr/local/node/bin/npm /usr/bin/npm  --包管理工具npm软链到环境变量

如果你觉得npm慢的话,可以安装cnpm

npm install cnpm -g --registry=https://registry.npm.taobao.org

配置node文件安装路径

cd /usr/local/node
mkdir node_global
mkdir node_cache
npm config set prefix 'node_global'
npm config set prefix 'node_cache'

数据库备份

mysqldump这个命令相信大家都知道,但是实际使用过的人可能不多,再加上繁多的参数设置更是让人望而止步,除了项目负责人要对数据负责。
这里就简单记录一下他的常用方式:


//最直接的用法 -h连接地址 -u用户名 -p 密码 -P端口 -B 指定导出数据库 > 数据库备份名称-前面可以加上路径
mysqldump -hlocalhost -uusername -ppassword -P3306 -B database > backup_database.sql
//其他常用参数
   --all-databases ,-A 导出全部数据库。
   --allow-keywords 允许创建是关键词的列名字
   --tables 指定备份表名
   --single-transaction 导出数据之前提交一个BEGIN SQL语句,BEGIN 不会阻塞任何应用程序且能保证导出时数据库的一致性状态。

不出意外的话,mysql5.6以后的版本在执行以上命令的时候系统会有如下提示

mysqldump: [Warning] Using a password on the command line interface can be insecure.

大致意思是,在命令行直接使用明文密码是不安全的。虽然可以用后输入密码的方式解决,但是数据库备份正常是要放在定时脚本去执行,没法手动输入密码。最直接的解决方法是,在my.cnf文件里加入以下配置

[mysqldump]
user=username
password=password

然后mysqldump 命令后面就不需要再加上 -u -p参数了。

mysqldump -hlocalhost  -P3306 -B database > backup_database.sql

定时脚本备份数据库,每日凌晨执行

vim backup.sh

#!/bin/sh    

# 数据库连接信息
DB_USER="user"
DB_PASS="pwd"
DB_HOST="host"
# 需备份数据库列表 多个按照空格分割
DB_NAME=("backup_test" "back_test2")
# mysqldump 路径、备份文件存储路径、日期
BIN_DIR="/usr/local/mysql/bin"  
BCK_DIR="/home/backup/mysql"
DATE=`date +%F`

for var in ${DB_NAME[@]};
do
  $BIN_DIR/mysqldump --opt --single-transaction --master-data=2 -u$DB_USER -p$DB_PASS -h$DB_HOST $DB_NAME > $BCK_DIR/db_$var$DATE.sql
done

#删除超过7天的备份文件
BACKUPDIR="/home/backup/mysql/data" #定义备份文件路径
KEEPTIME=7 #定义需要删除的文件距离当前的天数
DELFILE=`find $BACKUPDIR -type f -mtime +$KEEPTIME -exec ls {} \;` #找到天数大于7天的文件
for delfile in ${DELFILE} #循环删除满足天数大于七天的文件
do
 rm -rf $delfile
done

恢复数据库

mysql恢复数据库大致有两种方法
1、mysql命令行

mysql -uroot -ppass;登录数据库
 mysql> use backup_db;
 mysql> source backup_db.sql;  //可以加上路径

2、不需要登录mysql直接恢复数据库

mysql -uroot -ppass backup_db < backup_db.sql; //需要注意的是,这里的命令是mysql,而不是mysqldump

mysql用户权限管理相关表

mysql中存在4个用户及权限相关的表,分别为user、db、tables_priv、columns_priv。
user表存放用户账号以及全局权限信息。
db表存放用户数据库级别的权限,例如哪些主机哪些用户可以访问哪个数据库。
tables_priv表存放表级别的权限,例如哪些主机哪些用户可以访问数据库的哪个表。
columns_priv表存放列级别的权限,例如哪些主机哪些用户可以访问数据库表的哪些字段。

添加用户

//创建用户名为xuxd,密码为xuxd123的用户
create user xuxd identified by 'xuxd123';

授权用户

//将db1库的所有操作权限授权给用户xuxd
//若是要授权所有库的权限 db1.* 改为 *.*
//若是要限制某个ip才能访问 xuxd@'%'改为 xuxd@'ip地址',%代表允许所有的ip远程登录
grant all privileges on db1.* to xuxd@'%' identified by 'xuxd123';

查看所有用户情况

select * from mysql.user

修改密码

update mysql.user set authentication_string = password('xuxd123?') where user = 'xuxd' and host = '%';

删除用户

//删除用户以及对应的权限,执行命令后user表和db表的相应记录都会消失
drop user xuxd@''

更新权限

flush privileges;

事实上,直接用grant授权给某个用户的时候,如果该用户不存在,mysql会自动生成用户信息。


以上命令皆运行于5.7.14版本,其他版本未测试

sql注入攻击

说到安全性问题,首当其冲必定是sql注入问题.所谓的sql注入,就是攻击者在提交数据的时候,会在里面加入一些特殊字符,达到获取数据库访问权限或者破坏数据的目的。
例如,登录功能

sql = "select * from user_table where username=
' "+userName+" ' and password=' "+password+" '";

当用户输入的username = '’or 1 = 1 -- and password='’

sql语句变成
SELECT * FROM user_table WHERE username=
'’or 1 = 1 -- and password='’
条件后面username='' or 1=1 是必定成立的,--是注释后面的sql语句,所以攻击者就轻松绕过了密码验证

那么如何防御sql注入呢,简单的说,就是不要相信任何用户提交过来的数据,具体如下

  1. 严格按照字段所需的格式进行格式化,比如数字就格式化为数字接受,字符串几格式化为字符串接收。
  2. 使用addslashes函数来将单引号、双引号、反斜杠、null等字符前面加上斜杆,使其不具有特殊字符意义。
  3. 使用htmlspecialchars方法来对用户提交的数据进行解析。
  4. 绑定变量,使用预编译语句防止注入。

xss跨站攻击

跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的特殊目的。
防范xss攻击也很简单,一样不要相信用户提交的任何数据,把用户所有的数据都加个htmlspecialchars处理。

csrf跨站伪造请求

跨站伪造请求是攻击者通过一些技术手段欺骗用户去访问曾经认证过的网站,并运行一些操作。可以理解为攻击者盗用了你的身份,以你的名义发送恶意请求。
主要步骤是

  • 用户登录网站A,并在本地生成cookie。
  • 在不登出网站A的情况下,访问危险网站B。

那么如何防御csrf攻击呢

  • 最简单的方法应该是加入页面token了,即每个页面都生成一个随机的token,服务端接收操作指令的时候验证一下。
  • 使用cookie hashing的方式,即每个表单都包含同一个随机数,然后在服务端进行hash验证。
  • 增加隐藏字段验证,或者表单结构验证。