参考链接 https://cloud.tencent.com/developer/article/1722230
搭建内网穿透主要是为了能随时随地访问到本地设备。适用但不限于以下场景：本地有一台常用设备，且常态开机，用于存放资料或者挂项目；其他设备需要经常访问到该设备。一般在局域网下这种使用方式没有任何问题，但离开局域网后（如出差时）则无法进行访问，那么内网穿透的作用就来了。需要两个必要条件被穿透的设备需要能正常联网一台服务器，宽带视需求而定，其他如cpu内存配置可以比较低frp github下载 中文文档服务端 只需要部署好 frps 即可客户端 （穿透机）只需要部署好 frpc 即可{collapse}{collapse-item label="服务端配置" open}服务器需要放行一些端口，以保证frps可以正常运行，具体依配置文件配置文件frps.ini（可参考frps_full.ini）[common] #绑定IP bind_addr = 0.0.0.0 #绑定端口 bind_port = 7000 #自定义token，客户端连接需要 token = 123456 #端口白名单， allow_ports = 4000-50000 # 面板设置 # 面板入口端口 dashboard_port = 7500 # 管理员账户 dashboard_user = admin dashboard_pwd = 123456 # http访问端口 vhost_http_port = 8010 # 日志 log_file = /logpath/frp.log log_level = info log_max_days = 7使用systemd启动：https://gofrp.org/docs/setup/systemd/也可以单独运行命令：./frps -c ./frps.ini启动后，能看到面板就算成功（注意放行端口）如果不想ip:端口访问，且dns不支持除80/443以外的端口，可以挂一个反向代理，这样就能直接访问域名进入了。{/collapse-item}{collapse-item label="客户端配置"} 运行跟服务端没差，看文档。这里主要记录一下配置文件 frpc.ini# 公共配置 [common] # 服务端ip server_addr = 127.0.0.1 # 服务端端口 server_port = 7000 # 服务端定义的token token = 123456 # 日志 log_file = logpath/frpc.log log_level = info log_max_days = 7 [自定义映射1 这里的名字对应管理页显示的name列] # 类型 type = tcp # 映射ip 一般是本地 local_ip = 127.0.0.1 # 映射端口 3389是远程桌面 local_port = 3389 # 挂载端口 在服务器上访问的 remote_port = 4780 [自定义映射2 ssh] type = tcp local_ip = 127.0.0.1 local_port = 22 remote_port = 5000如果需要开机自启动, 可以通过AlwaysUp实现{/collapse-item}{/collapse}
通过WebSocket建立客户端与服务端的双向通信使用websocket的好处：相同的每秒客户端轮询的次数，当次数高达数万每秒的高频率次数的时候，WebSocket消耗流量仅为轮询的几百分之一安装Workermancomposer require topthink/think-worker配置服务 config/worker_server.php（主要配置如图内容）配置好后，在命令行执行php think worke:server（命令行不要关闭）{collapse}{collapse-item label="前端连接测试" open} websocket.onerror = (e)=>onError(e); websocket.onclose = (e)=>onClose(e); websocket.onmessage = (e)=>onMessage(e); document.getElementById('send').onclick = function (){ let msg = contentDom.value; console.log('发送消息: '+ msg); msgDom.innerHTML += ' '+ getNow() +' - 发送消息: '+ msg +' '; websocket.send(msg); } document.getElementById('clear').onclick = function (){ contentDom.value = ''; } document.getElementById('close').onclick = function (){ websocket.close(); } // 打开websocket function onOpen(e){ console.log('打开websocket: ' + webSocketStatus(), e); msgDom.innerHTML += " "+ getNow() +" - 打开websocket "; } // websocket出错 function onError(e){ console.log('websocket出错: ', e); msgDom.innerHTML += " "+ getNow() +" - 出错 "; } // 监听消息 function onMessage(e){ console.log('监听消息: ', e); msgDom.innerHTML += " "+ getNow() +" - 服务器消息: "+ e.data +" "; } // 关闭连接 function onClose(e){ console.log(e); msgDom.innerHTML += " "+ getNow() +" - 关闭连接 "; } // 状态信息 function webSocketStatus(){ let status = ['未连接', '连接成功', '可通讯', '正在关闭', '已关闭或无法打开']; return status[websocket.readyState]; } // 当前时间 function getNow(){ let date = new Date(); return date.getFullYear() + "/" + (date.getMonth()+1) + "/" + date.getDate() + " " + date.getHours() + ":" + String(date.getMinutes()).padStart(2, '0') + ":" + String(date.getSeconds()).padStart(2, '0'); } {/collapse-item}{collapse-item label="后端代码（app/controller/worker）"}<?php declare (strict_types = 1); namespace app\controller; use think\Request; use think\worker\Server; /** * workerman的服务类 app\controller\worker */ class worker extends Server { protected $socket = "websocket://ltp6:2345"; /** * Workerman启动时 * @param $worker */ public function onWorkerStart($worker){ echo "workerman启动了!".PHP_EOL; } /** * Workerman重载 * @param $worker */ public function onWorkerReload($worker){ echo "workerman重载了!".PHP_EOL; } /** * 被客户端连接 * @param $connection */ public function onConnect($connection){ $connection->send('客户端连接了!'); } public function onMessage($connection, $data){ // 收到消息后的处理 $connection->send("收到消息: $data"); } public function onClose($connection){ echo "客户端关闭连接了!".PHP_EOL; } public function onError($connection, $code, $msg){ echo "出错了! 错误代码: $code, 错误信息: $msg".PHP_EOL; } } {/collapse-item}{/collapse}效果
最近拷了一个老项目的数据，导入发现一直报错，查了以下发现是mysql5.7在 严格模式下不允许日期和月份为零值（0000-00-00 00:00:00） 主要是以下两条规则导致：NO_ZERO_IN_DATE：在严格模式下，不允许日期和月份为零NO_ZERO_DATE：不允许插入零日期，如果插入会抛出错误而不是警告解决方法：使用命令 select @@sql_mode 查看sql mode如果返回信息有 NO_ZERO_IN_DATE,NO_ZERO_DATE 则去掉，并全局设置执行命令 SET GLOBAL sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; （内容可以按当时返回的结果需改）
定界符 <<< 一般用于超长文本，有以下特性：定界符中的任何特殊字符都不需要转义定界符中的PHP变量会被正常的用其值来替换<<<Eof 文本内容····· Eof;Eof字符是可以自定义的，但是必须结尾相同；尾行的 Eof; 必须单独开一行
微信【限制新设备登录功能】解决办法
刷机后重新登录微信，刚登上没多久就被强制下线，然后出现图中的问题问题就出现在 我的常用设备就是这台刚刷了机的手机，也是唯一常用的设备。张小🐉你******（优美的中国话然后我通过以下的方法申诉，当天可登录回来。记录一下步骤，以防万一需要一个已经登录的微信号，搜索公众号： 腾讯客服发送消息： 解除微信保护状态一般会回复四个选项， 选择： 系统保护冻结需要解冻按操作填写资料，进行人脸验证。等待工单处理，会有一个短信发过来。按照回复操作改密码，用新密码就可以登录了
Redis keys 命令下表给出了与 Redis 键相关的基本命令：命令描述DEL key该命令用于在 key 存在是删除 keyDUMP key序列化给定 key ，并返回被序列化的值EXISTS key检查给定 key 是否存在EXPIRE key seconds为给定 key 设置过期时间EXPIREAT key timestampEXPIREAT 的作用和 EXPIRE 类似，都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)PEXPIRE key milliseconds设置 key 的过期时间亿以毫秒计PEXPIREAT key milliseconds-timestamp设置 key 过期时间的时间戳(unix timestamp) 以毫秒计KEYS pattern查找所有符合给定模式( pattern)的 keyMOVE key db将当前数据库的 key 移动到给定的数据库 db 当中PERSIST key移除 key 的过期时间，key 将持久保持PTTL key以毫秒为单位返回 key 的剩余的过期时间TTL key以秒为单位，返回给定 key 的剩余生存时间(TTL, time to live)RANDOMKEY从当前数据库中随机返回一个 keyRENAME key newkey修改 key 的名称RENAMENX key newkey仅当 newkey 不存在时，将 key 改名为 newkeyTYPE key返回 key 所储存的值的类型{collapse}{collapse-item label="业务代码（PHP）" open}class RedisKeyDemo{ public $redis; // redis实例 public $type_list = ['Not Found', 'String', 'Set', 'List', 'Zset', 'Hash', 'Stream']; // 数据类型 public function __construct(){ $this->redis = new Redis(); } /** * 链接 Redis * @param string $host * @param int $port * @return bool */ public function connect(string $host = '127.0.0.1', int $port = 6379): bool{ return $this->redis->connect($host, $port); } /** * 打印到html * @param $str */ public function printToHtml(string $str = ''): void{ echo $str." "; } /** * 打印所有keys */ public function printAllKeys(): void{ $this->keys(); } /** * 判断键是否存在 * @param string $key * @return bool|int */ public function isExists(string $key = ''): void{ $this->redis->exists($key) ? $this->printToHtml("$key is exists") : $this->printToHtml("$key is not exists"); } /** * 返回键类型 * @param string $type */ public function keyType(string $key = ''): void{ $this->printToHtml($this->type_list[$this->redis->type($key)]); } /** * 删除指定键 * 原本方法可以 key1,key2···, 接受string或array * @param string $key */ public function delKey(string $key = ''): void{ $this->printToHtml($this->redis->del($key) ? "'$key'删除成功" : "'$key'删除失败"); } /** * 为指定键设置过期时间 * @param string $key * @param int $ttl 过期时间 * @param string $type second 使用秒， timestamp 使用时间戳， milliseconds 使用时间亿以毫秒计 */ public function expiredKey(string $key = '', int $ttl, string $type = 'second'): void{ $error = "设置过期时间失败"; if ($ttl > 0){ switch ($type){ case 'second': $this->printToHtml($this->redis->expire($key, $ttl) ? "$key 将在 $ttl s 后删除" : $error);break; case 'timestamp': $this->printToHtml($this->redis->expireAt($key, $ttl) ? "$key 将在 ".date('Y-m-d H:i:s', $ttl)." 删除": $error);break; case 'milliseconds': $this->printToHtml($this->redis->pExpire($key, $ttl) ? "$key 将在 $ttl 亿以毫秒后删除" : $error);break; default: $this->printToHtml('$type: second 使用秒， timestamp 使用时间戳， milliseconds 使用时间亿以毫秒计'); } } else { $this->printToHtml($error); } } /** * 查找符合给定模式的key * 例如 查找te开头的所有key: te* ; 查找包含a的key: *a* ; 获取所有key: * * @param string $pattern */ public function keys(string $pattern = '*'): void{ $this->printToHtml(json_encode($this->redis->keys($pattern))); } /** * 将当前数据库的 key 移动到给定的数据库 db 当中 * @param string $key * @param int $db * @return bool */ public function move(string $key = '', int $db = 0): bool{ return $this->redis->move($key, $db); } /** * 移除 key 的过期时间，key 将持久保持。 * @param string $key * @return bool */ public function persist(string $key): bool{ return $this->redis->persist($key); } /** * 返回key的剩余过期时间 * @param string $key * @param string $type ttl以秒为单位, pttl以毫秒为单位 */ public function ttl(string $key, string $type = 'ttl'): void{ $error_flag = [ -1 => '没有设置过期时间', -2 => '不存在的key', false => '不存在的key' ]; switch ($type){ case 'ttl': $temp = $this->redis->ttl($key); $this->printToHtml($error_flag[$temp] ?? "剩余 $temp 秒");break; case 'pttl': $temp = $this->redis->pttl($key); $this->printToHtml($error_flag[$temp] ?? "剩余 $temp 毫秒");break; default: $this->printToHtml('$type ttl以秒为单位, pttl以毫秒为单位'); } } /** * 从数据库中随机返回一个key */ public function randomKey(){ $this->printToHtml("Random Key: ".$this->redis->randomKey()); } /** * 修改 key 的名称 * @param string $key * @param string $new_name * @param bool $strict == true时, 仅当 new_name 不存在时，将 key 改名为 new_name */ public function rename(string $key = '', string $new_name = '', bool $strict = false): void{ $flag = !$strict ? $this->redis->rename($key, $new_name) : $this->redis->renameNx($key, $new_name); $this->printToHtml($flag ? "重命名 $key->$new_name" : '重命名失败'); } }{/collapse-item}{collapse-item label="调用示例"}$demo = new RedisKeyDemo(); $demo->connect(); // 判断指定键是否存在 $demo->isExists('cao'); // 判断指定键类型 $demo->keyType('redis_yes'); $demo->keyType('myname'); // 删除键 //$demo->delKey('myname'); $demo->printAllKeys(); // 为key设置过期时间 $demo->expiredKey('test', time()+9999, 'timestamp'); // 使用时间戳 // 查找'te'开头的所有key $demo->keys('te*'); // 查看key的过期时间 $demo->ttl('test'); $demo->ttl('philip', 'pttl'); $demo->ttl('hstab'); // 移除过期时间 $demo->printToHtml('test 移除过期时间'. ($demo->persist('test') ? '成功' : '失败')); // 从数据库中随机获取一个key $demo->randomKey(); // 重命名key $demo->redis->set('test2', 5555); $demo->rename('test2', 'testt'); $demo->rename('testt', 'hstab', true); // 仅当new_name不存在时改名{/collapse-item}{/collapse}
拓展下载 php_igbinary php_redis{alert type="warning"}注意：ts表示线程安全的，nts表示非线程安全的。使用哪种版本以当前运行的环境而定。{/alert}{alert type="error"} phpinfo()里展示的信息，决定拓展文件版本 {/alert}下载好后解压，将.dll和.pdb文件拷贝至php的ext目录下修改php.ini，加入以下参数;php_redis redis拓展 extension=php_igbinary.dll extension=php_redis.dll注意extension=php_igbinary.dll一定要在前面，不然拓展不会生效重启apache，查看是否生效（如图）测试使用<?php $br = " "; // 链接本地的 Redis 服务 $redis = new Redis(); echo $redis->connect('127.0.0.1', 6379) ? "Redis链接成功!$br" : "Redis链接失败!$br"; // 设置字符串数据 echo $redis->set('name', 'philip') ? "set {name} philip success!$br" : 'set {name} philip fail!$br'; echo "get name: {$redis->get('name')}$br"; // 删除字符串数据 echo $redis->del('name') ? "del name success!$br" : "del name fail!$br"; echo "get name: {$redis->get('name')}$br";