• PHP 使用 PHPRedis 与 Predis


    # 前言

    点我查看 - Redis 基本知识,快来回顾一下


    点我查看 - Redis 订阅与 Redis Stream 技术


    点我查看 - Redis 持久化 - RDB 与 AOF


    一、 PHP 使用 PHPRedis

    PHPRedis 简单介绍

    • 打开 GitHub - PHPRedis
    • c 写的 php 扩展。
    • 可以保持php-fpmredis 的长连接,性能更好。
    • 任何 PHP 版本可用。
    • 业务复杂,非常依赖 redis 时可用 PHPRedis

    使用之前, 请确保已成功安装 redis 服务及 PHP Redis 扩展, 并且正常运行 PHP


    安装流程请各位大大自行解决哈。

    1.1 实例化连接本地服务

    
    
    # 1. 实例化连接本地 Redis 
    $obj = new Redis();
    $obj->connect('127.0.0.1', 6379);
    
    # $redis_obj->connect('127.0.0.1', 6379); # 短链接
    
    $redis_obj->pconnect('127.0.0.1', 6379); # 长链接
    
    $redis_obj->auth('password'); # 登录验证密码, true|false
    
    $redis_obj->select(0); # 选择 redis 库, 如果不设置, 默认连接 0 数据库, 默认共 16 个库
    
    $redis_obj->close(); # 断开连接, 释放资源	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1.2 字符串(strings) 实例

    
        
    # 设置单个键值
    $redis_obj->set('is_collect', 1);
    
    # 设置单个键值 - 如果数据库中不存在键,则将参数中的字符串值设置为键的值。
    $redis_obj->setNx('is_comment', 1);
    
    # 设置多个键值
    $redis_obj->mset(['domain'=>'bqhub.com', 'no'=>'京-123456']);
    
    # 获取单个键值
    $is_collect = $redis_obj->get('is_collect');
    $is_comment = $redis_obj->get('is_comment');
    
    # 获取多个键值
    $site_config = $redis_obj->mGet(['domain', 'no']);
    
    # 删除键
    $redis_obj->del('is_collect');
    $redis_obj->del(['is_collect', 'is_comment']);
    
    # 重新赋值
    $redis_obj->getSet('is_collect', 'open');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    1.3 哈希(hashes) 实例

    
        
    # 设置单个
    $redis_obj->hSet('bqhub', 'domain', 'bqhub.com');
    $redis_obj->hSet('bqhub', 'author', 'Chon');
    
    # 设置多个
    $redis_obj->hMSet('blog', ['name'=>'BQHUBBlog', 'code'=>'123456']);
    
    # 获取单个
    $bqhub_domain = $redis_obj->hGet('bqhub', 'domain'); # bqhub.com
    
    # 获取多个
    $bqhub_data = $redis_obj->hMGet('bqhub', ['domain', 'author']);
    $blog_data = $redis_obj->hMGet('blog', ['name', 'code']);
    
    # 根据键获取全部
    $data = $redis_obj->hGetAll('bqhub');
    
    # 获取所有字段名
    $keys = $redis_obj->hKeys('bqhub');
    
    # 获取所有值
    $vals = $redis_obj->hVals('bqhub');
    
    # 删除
    $redis_obj->hDel('bqhub', 'domain');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    1.4 列表(lists) 实例

    
        
    # 左侧插入(列表不存在则创建)
    $redis_obj->lPush('left_list_key', 'l1', 'l2', 'l3', 'l4');
    
    # 左侧插入(列表不存在, 则没有操作)
    $redis_obj->lPushx('left_list_key', 'l11');
    
    # 右侧插入(列表不存在则创建)
    $redis_obj->rPush('right_list_key', 'r1', 'r2', 'r3', 'r4');
    
    # 右侧插入(列表不存在, 则没有操作)
    $redis_obj->rPushx('right_list_key', 'r11');
    
    # 查询列表 - 根据索引下标
    $list = $redis_obj->lRange('left_list_key', 0 ,3);
    
    # 查询单个 - 根据索引下标
    $index = $redis_obj->lIndex('left_list_key', 2);
    
    # 左侧第一个元素弹出
    $redis_obj->lPop('left_list_key');
    
    # 左侧第一个元素弹出, 如果列表为空, 则等待超时停止或超时内发现元素后弹出
    $redis_obj->blPop('left_list_key', 3);
    
    # 右侧第一个元素弹出
    $redis_obj->rPop('right_list_key');
    
    # 右侧第一个元素弹出, 如果列表为空, 则等待超时停止或超时内发现元素后弹出
    $redis_obj->brPop('right_list_key', 3);
    
    # 删除某数量的元素, # 大于0-从左往右数; 小于0-从右往左数; 等于0 - 删除所有
    $redis_obj->lRem('left_list_key', 'l2', 3);
    
    # 保留某范围内的元素 - 根据索引
    $redis_obj->lTrim('left_list_key', 0, 2);
    
    # 替换某索引的值
    $redis_obj->lSet('left_list_key', 2, 'v22');
    
    # 获取列表的长度
    $redis_obj->lLen('left_list_key');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    1.5 无序集合(sets) 实例

    
        
    # 新增
    $redis_obj->sAdd('set_key', 'set_value1', 'set_value2', 'set_value3');
    $redis_obj->sAddArray('set_key_array', ['a_1', 'a_2', 'a_3']);
    
    # 查询
    $set_key = $redis_obj->sMembers('set_key_array');
    
    # 删除
    $redis_obj->sRem('set_key', 'set_value1', 'set_value2');
    
    # 判断是否是列表中的值
    $is_member = $redis_obj->sIsMember('set_key', 'set_value3');
    
    # 随机移除一个并返回该值
    $pop_value = $redis_obj->sPop('set_key_array');
    
    # 随机获取一个并返回该值
    $rand_value = $redis_obj->sRandMember('set_key_array');
    
    # 差集 - 只获取第一个集合与后续集合之间的差集
    $s_diff = $redis_obj->sDiff('set_key_array', 'set_key');
    
    # 交集 - 返回所有集合间的交集
    $s_inter = $redis_obj->sInter('set_key_array', 'set_key');
    
    # 并集 - 返回所有集合间的并集
    $union = $redis_obj->sUnion('set_key_array', 'set_key');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    1.6 有序集合(Sorted Set) 实例

    
        
    # 设置
    $redis_obj->zAdd('z_set_1', 90, '语文', 108, '数学', 92, '英语');
    
    # 查询 - 根据值获取分数
    $score = $redis_obj->zScore('z_set_1', '语文');
    
    # 查询 - 从小到大 - 根据 下标排序, true(可选): 是否显示分数(例: 获取第1名到第5名)
    $z_range = $redis_obj->zRange('z_set_1', 0, 1, true);
    
    # 查询 - 从大到小 - 根据 下标排序, true(可选): 是否显示分数
    $z_rev_range = $redis_obj->zRevRange('z_set_1', 0, 1, true);
    
    # 查询 - 从小到大 - 根据 分数 查询, withscores(可选): 显示分数; limit(可选): 分页
    $z_range_score = $redis_obj->zRangeByScore('z_set_1', 60, 100, array('withscores' => TRUE, 'limit'=>array(0,2)));
    
    # 查询 - 从大到小 - 根据 分数 查询, withscores(可选): 显示分数; limit(可选): 分页
    $z_rev_range_score = $redis_obj->zRevRangeByScore('z_set_1', 120, 90, array('withscores' => TRUE, 'limit'=>array(0,2)));
    
    # 查询排名 - 从小到大 - 根据值
    $z_rank = $redis_obj->zRank('z_set_1', '数学');
    
    # 查询排名 - 从大到小 - 根据值
    $z_rev_rank = $redis_obj->zRevRank('z_set_1', '数学');
    
    # 查询元素总数
    $z_card = $redis_obj->zCard('z_set_1');
    
    # 查询区间个数
    $z_count = $redis_obj->zCount('z_set_1', 90, 100);
    
    # 移除 - 移除 count 个最大值的元素
    $redis_obj->zPopMax('z_set_1', 2);
    
    # 移除 - 移除 count 个最小值的元素
    $redis_obj->zPopMin('z_set_1', 3);
    
    # 移除 - 根据元素值
    $z_rem = $redis_obj->zRem('z_set_1', '数学');
    
    # 移除 - 根据索引范围
    $redis_obj->zRemRangeByRank('z_set_1', 0, 2);
    
    # 移除 - 根据分数范围
    $redis_obj->zRemRangeByScore('z_set_1', 0, 60);
    
    # 加法
    $redis_obj->zIncrBy('z_set_1', '10', '数学');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    1.7 服务操作

    
        
    # 查看状态
    $redis_obj->info();
    
    # 获取当前数据库中 键 的个数
    $size = $redis_obj->dbSize();
    
    # 清空所有数据库
    $redis_obj->flushAll();
    
    # 清空当前数据库
    $redis_obj->flushDB();
    
    # rdb - save 保存
    $redis_obj->save();
    
    # rdb - bgsave 保存
    $redis_obj->bgsave();
    
    # 监视一个或多个 key,如果在事务执行之前,相关 key 被其他命令所改动,则事务将被打断不执行
    $redis_obj->watch('collect_num');
    
    # 开启事务
    $redis_obj->multi(Redis::MULTI);
    
    # 执行一些操作
    
    # 执行事务块内的所有命令
    $redis_obj->exec();
    
    # 取消事务
    $redis_obj->discard();
    
    # 开启管道, 一大堆命令,一次性的准备、执行、返回
    $redis_obj->multi(Redis::PIPELINE);
    
    # 同样适合执行 管道命令
    $redis_obj->exec();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    二、 PHP 使用 Predis

    Predis

    • 打开 GitHub - Predis

    • php 写的工具库。

    • composer 即可安装。

    • PHP5.3.2 版本之上可用。

    • 应用场景简单时可用 Predis

    2.1 实例化连接本地服务

    
    
    # composer 引入 predis 后, 直接自动加载
    require('./vendor/autoload.php');
    
    use Predis\Client;
    
    # 1. 连接IP、端口、要使用的数据库序号(默认 16 个数据库)
    $server = array('host'=>'127.0.0.1', 'port'=>'6379', 'database'=>1);
    
    $predis_obj = new Client($server); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.2 字符串(strings) 实例

    
    
    # 设置单个值
    $predis_obj->set('is_collect', 'YES');
    
    # 设置单个键值 - 如果数据库中不存在键,则将参数中的字符串值设置为键的值
    $predis_obj->setnx('is_comment', 1);
    
    # 设置多个键值
    $predis_obj->mset(['domain'=>'bqhub.com', 'no'=>'京-123456']);
    
    # 获取单个键值
    $is_collect = $predis_obj->get('is_collect');
    
    # 获取多个值 - 根据键
    $site_config = $predis_obj->mget(['domain', 'no', 'is_collect']);
    
    # 删除键
    $predis_obj->del('is_collect');
    $predis_obj->del(['is_collect', 'domain']);
    
    # 重新赋值
    $predis_obj->getset('is_collect', 'NO');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.3 哈希(hashes) 实例

    
        
    # 设置单个
    $predis_obj->hset('bqhub', 'domain', 'bqhub.com');
    $predis_obj->hset('bqhub', 'author', 'Chon');
    
    # 设置多个
    $predis_obj->hmset('blog', ['name'=>'BQHUBBlog', 'code'=>'123456']);
    
    # 获取单个
    $domain = $predis_obj->hget('bqhub', 'domain'); # bqhub.com
    
    # 获取多个
    $bqhub_data = $predis_obj->hmget('bqhub', ['domain', 'author']);
    $blog_data = $predis_obj->hmget('blog', ['name', 'code']);
    
    # 根据键获取全部
    $data = $predis_obj->hgetall('bqhub');
    
    # 获取所有字段名
    $keys = $predis_obj->hkeys('bqhub');
    
    # 获取所有值
    $values = $predis_obj->hvals('bqhub');
    
    # 删除
    $predis_obj->hdel('bqhub', 'domain');    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2.4 列表(lists) 实例

    
        
    # 左侧插入(列表不存在则创建)
    $predis_obj->lpush('left_list_key', ['l1', 'l2', 'l3', 'l4']);
    
    # 左侧插入(列表不存在, 则没有操作)
    $predis_obj->lpushx('left_list_key', ['l1', 'l2', 'l3', 'l4']);
    
    # 右侧插入(列表不存在则创建)
    $predis_obj->rpush('right_list_key', ['r1', 'r2', 'r3', 'r4']);
    
    # 右侧插入(列表不存在, 则没有操作)
    $predis_obj->rpushx('left_list_key', ['r1', 'r2', 'r3', 'r4']);
    
    # 查询列表 - 根据索引下标
    $list = $predis_obj->lrange('left_list_key', 0, 2);
    
    # 查询单个 - 根据索引下标
    $index = $predis_obj->lindex('left_list_key', 2);
    
    # 左侧第一个元素弹出
    $predis_obj->lpop('left_list_key');
    
    # 左侧第一个元素弹出, 如果列表为空, 则等待超时停止或超时内发现元素后弹出
    $predis_obj->blpop('left_list_key', 3);
    
    # 右侧第一个元素弹出
    $predis_obj->rpop('right_list_key');
    
    # 右侧第一个元素弹出, 如果列表为空, 则等待超时停止或超时内发现元素后弹出
    $predis_obj->brpop('right_list_key', 3);
    
    # 删除某数量的元素, # 大于0-从左往右数; 小于0-从右往左数; 等于0 - 删除所有
    $predis_obj->lrem('left_list_key', 3, 'l2');
    
    # 保留某范围内的元素 - 根据索引
    $predis_obj->ltrim('left_list_key', 0, 2);
    
    # 替换某索引的值
    $predis_obj->lset('left_list_key', 2, 'l22');
    
    # 获取列表的长度
    $len = $predis_obj->llen('left_list_key');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    2.5 无序集合(sets) 实例

    
    
    # 新增
    $predis_obj->sadd('set_key', ['set_v1', 'set_v2', 'set_v3', 'set_v4']);
    $predis_obj->sadd('set_key_1', ['set_1', 'set_2', 'set_3', 'set_4']);
    
    # 查询
    $set_key = $predis_obj->smembers('set_key');
    
    # 删除
    $predis_obj->srem('set_key', 'set_v3');
    
    # 判断是否是列表中的值
    $is_member = $predis_obj->sismember('set_key', 'set_v2');
    
    # 随机移除一个并返回该值
    $pop_value = $predis_obj->spop('set_key');
    
    # 随机获取一个并返回该值
    $rand_value = $predis_obj->srandmember('set_key');
    
    # 差集 - 只获取第一个集合与后续集合之间的差集
    $s_diff = $predis_obj->sdiff(['set_key', 'set_key_1']);
    $s_store_diff = $predis_obj->sdiffstore('new_set_key', ['set_key', 'set_key_1']); # 差集保存至新集合
    
    # 交集 - 返回所有集合间的交集
    $s_inter = $predis_obj->sinter(['set_key', 'set_key_1']);
    $s_store_inter = $predis_obj->sinterstore('new_set_key', ['set_key', 'set_key_1']); # 交集保存至新集合
    
    # 并集 - 返回所有集合间的并集
    $s_union = $predis_obj->sunion(['set_key', 'set_key_1']);
    $s_store_union = $predis_obj->sunionstore('new_set_key', ['set_key', 'set_key_1']); # 并集保存至新集合
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2.6 有序集合(Sorted Set) 实例

    
    
    # 设置
    $predis_obj->zadd('z_set', ['语文'=>102, '政治'=>90, '数学'=>113, '英文'=>89]);
    
    # 查询 - 根据值获取分数
    $score = $predis_obj->zscore('z_set', '语文');
    
    # 查询 - 从小到大 - 根据 下标排序, withscores(可选): 是否显示分数(例: 获取第1名到第5名)
    $z_range = $predis_obj->zrange('z_set', 0, 2, 'withscores');
    
    # 查询 - 从大到小 - 根据 下标排序, withscores(可选): 是否显示分数
    $z_range = $predis_obj->zrevrange('z_set', 0, 2, 'withscores');
    
    # 查询 - 从小到大 - 根据 分数 查询, withscores(可选): 显示分数; limit(可选): 分页
    $z_range_score = $predis_obj->zrangebyscore('z_set', 60, 100, ['withscores'=>true, 'limit'=>[0, 1]]);
    
    # 查询 - 从小到大 - 根据 分数 查询, withscores(可选): 显示分数; limit(可选): 分页
    $z_rev_range_score = $predis_obj->zrevrangebyscore('z_set', 100, 70, ['withscores'=>true, 'limit'=>[0, 1]]);
    
    # 查询排名 - 从小到大 - 根据值
    $z_rank = $predis_obj->zrank('z_set', '数学');
    
    # 查询排名 - 从大到小 - 根据值
    $z_rev_rank = $predis_obj->zrevrank('z_set', '英文');
    
    # 查询元素总数
    $z_card = $predis_obj->zcard('z_set');
    
    # 查询区间个数
    $z_count = $predis_obj->zcount('z_set', 60, 100);
    
    # 移除 - 移除 count 个最大值的元素
    $predis_obj->zpopmax('z_set', 3);
    
    # 移除 - 移除 count 个最小值的元素
    $predis_obj->zpopmin('z_set', 2);
    
    # 移除 - 根据元素值
    $predis_obj->zrem('z_set', '数学');
    
    # 移除 - 根据索引范围
    $predis_obj->zremrangebyrank('z_set', 0, 2);
    
    # 移除 - 根据分数范围
    $predis_obj->zremrangebyscore('z_set', 60, 100);
    
    # 加法
    $predis_obj->zincrby('z_set', 10, '英文');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    2.7 服务操作

    
    
    # 查看状态
    $info = $predis_obj->info();
    
    # 获取当前数据库中 键 的个数
    $size = $predis_obj->dbsize();
    
    # 清空所有数据库
    $predis_obj->flushall();
    
    # 清空当前数据库
    $predis_obj->flushdb();
    
    # rdb - save 保存
    $predis_obj->save();
    
    # rdb - bgsave 保存
    $predis_obj->bgsave();
    
    # 监视一个或多个 key,如果在事务执行之前,相关 key 被其他命令所改动,则事务将被打断不执行
    $predis_obj->watch('collect_num');
    
    # 开启事务
    $predis_obj->multi();
    
    # 执行一些操作
    
    # 执行事务块内的所有命令
    $predis_obj->exec();
    
    # 取消事务
    $predis_obj->discard();
    
    # 开启管道, 一大堆命令,一次性的准备、执行、返回
    $predis_obj->pipeline();
    
    # 同样适合执行 管道命令
    $predis_obj->exec();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    点我查看 - Redis 基本知识,快来回顾一下


    点我查看 - Redis 订阅与 Redis Stream 技术


    点我查看 - Redis 持久化 - RDB 与 AOF

  • 相关阅读:
    企业刚开始上云选择什么配置的云服务器比较合适?
    盗刷积分怎么防止,怎么风控?
    HIve数仓新零售项目DWB层的构建
    计算机视觉技术的革新:医疗领域的应用
    软考下午第5题——面向对象程序设计——代码填空(老程序员必得15分)
    嵌入式驱动学习第一周——内核的中断机制
    OpenApi(Swagger)快速转换成 TypeScript 代码 - STC
    机器人运动学、动力学与控制及Matlab实现
    设计模式-外观模式
    Bootstrap的宽度和高度的设置(相对于父元素的宽度和高度、相对于视口的宽度和高度)
  • 原文地址:https://blog.csdn.net/qq_35453862/article/details/126107915