找到
171
篇与
技术教程
相关的结果
- 第 18 页
-
Typecho开发主题常用函数及调用方法 1、站点名称 <?php $this->options->title() ?>2、站点网址 <?php $this->options ->siteUrl(); ?>3、完整路径标题如分享几个 Typecho 中常用的调用函数 <?php $this->archiveTitle(' » ', < span class="string">'', ' | '); ?><?php $this ->options->title(); ?>4、站点说明 <?php $this->options->description() ?>5、模板文件夹地址 <?php $this->options->themeUrl(); ?>6、导入模板文件夹内的 php 文件 <?php $this->need('.php'); ?>7、文章或者页面的作者 <?php $this->author(); ?>8、作者头像 < ?php $this->author->gravatar('40') ?> //此处输出的完整的img标签,40是头像的宽和高9、该文作者全部文章列表链接 <?php $this->author->permalink (); ?>10、该文作者个人主页链接 <?php $this->author->url(); ?>11 、该文作者的邮箱地址 <?php $this->author->mail(); ?>12、上一篇与下一篇调用代码 <?php $this->thePrev(); ?> <?php $this->theNext(); ?>13、判断是否为首页,输出相关内容 <?php if ($this->is('index')): ?> //首页输出内容 <?php else: ?> //不是首页输出内容 <?php endif; ?>14、文章或页面,评论数目 <?php $this->commentsNum('No Comments', '1 Comment' , '%d Comments'); ?>15、截取部份文章(首页每篇文章显示摘要),350 是字数 <?php $this->excerpt(350, '...'); ?>16、调用自定义字段 <?php $this->fields->fieldName ?>17、RSS 地址 <?php $this->options->feedUrl(); ?>18、获取最新 post <?php $this->widget('Widget_Contents_Post_Recent', 'pageSize=8&type=category')->parse('<li><a href="{permalink}">{title}</a></li>'); ?>19、纯文字分类名称,不带链接 <?php $this->category(',', false); ?>20、获取当前文章所属分类(包含链接) <?php if ($this->is('post')): ?> <span><?php $this->category(' '); ?></span> <?php endif; ?>21、获取文章分类列表 <ul> <?php $this->widget('Widget_Metas_Category_List') ->parse('<li><a href="{permalink}">{name}</a> ({count})</li>'); ?> </ul>22、获取某分类 post <ul> <?php $this->widget('Widget_Archive@indexyc', 'pageSize=8&type=category', 'mid=1') ->parse('<li><a href="{permalink}" title="{title}">{title}</a></li>'); ?> </ul>23、获取最新评论列表 <ul> <?php $this->widget('Widget_Comments_Recent')->to($comments); ?> <?php while($comments->next()): ?> <li><a href="<?php $comments->permalink(); ?>"><?php $comments->author(false); ?></a>: <?php $comments->excerpt(50, '...'); ?></li> <?php endwhile; ?> </ul>24、首页获取 最新文章 代码限制条数 <?php while ($this->next()): ?> <?php if ($this->sequence <= 3): ?> html <?php endif; ?> <?php endwhile; ?>25、获取最新评论列表第二个版本,只显示访客评论不显示博主也就是作者或者说自己发的评论 <?php $this->widget('Widget_Comments_Recent','ignoreAuthor=true')->to($comments); ?> <?php while($comments->next()): ?> <li><a href="<?php $comments->permalink(); ?>"><?php $comments->author(false); ?></a>: <?php $comments->excerpt(50, '...'); ?></li> <?php endwhile; ?>26、获取文章时间归档 <ul> <?php $this->widget('Widget_Contents_Post_Date', 'type=month&format=F Y') ->parse('<li><a href="{permalink}">{date}</a></li>'); ?> </ul>27、获取标签集合,也就是标签云 <?php $this->widget('Widget_Metas_Tag_Cloud', 'ignoreZeroCount=1&limit=28')->to($tags); ?> <?php while($tags->next()): ?> <a href="<?php $tags->permalink(); ?>" class="size-<?php $tags->split(5, 10, 20, 30); ?>"><?php $tags->name(); ?></a> <?php endwhile; ?>28、调用该文相关文章列表 <?php $this->related(5)->to($relatedPosts); ?> <?php if ($relatedPosts->have()): ?> //这句也可以写成 if (count($relatedPosts->stack)) <ul><?php while ($relatedPosts->next()): ?> <li><a href="<?php $relatedPosts->permalink(); ?>" title="<?php $relatedPosts->title(); ?>"><?php $relatedPosts->title(); ?></a></li> <?php endwhile; ?></ul> <?php else : ?> <li>无相关文章</li> <?php endif; ?>29、隐藏 head 区域的程序版本和模版名称 <?php $this->header("generator=&template="); ?>30、获取读者墙 <?php $period = time() - 999592000; // 時段: 30 天, 單位: 秒 $counts = Typecho_Db::get()->fetchAll(Typecho_Db::get() ->select('COUNT(author) AS cnt','author', 'url', 'mail') ->from('table.comments') ->where('created > ?', $period ) ->where('status = ?', 'approved') ->where('type = ?', 'comment') ->where('authorId = ?', '0') ->group('author') ->order('cnt', Typecho_Db::SORT_DESC) ->limit(25) ); $mostactive = ''; $avatar_path = 'http://www.gravatar.com/avatar/'; foreach ($counts as $count) { $avatar = $avatar_path . md5(strtolower($count['mail'])) . '.jpg'; $c_url = $count['url']; if ( !$c_url ) $c_url = Helper::options()->siteUrl; $mostactive .= "<a href='" . $c_url . "' title='" . $count['author'] . " (参与" . $count['cnt'] . "次互动)' target='_blank'><img src='" . $avatar . "' alt='" . $count['author'] . "的头像' class='avatar' width='32' height='32' /></a>\n"; } echo $mostactive; ?>31、登陆与未登录用户展示不同内容 <?php if($this->user->hasLogin()): ?> // 登陆可见 <?php else: ?> // 未登录和登陆均可见 <?php endif; ?>32、导航页面列表调用隐藏特定的页面 这个演示隐藏了 album 和 search 两个页面 <ul> <li<?php if($this->is('index')): ?> class="current"<?php endif; ?>><a href="<?php $this->options->siteUrl(); ?>">主页</a></li> <?php $this->widget('Widget_Contents_Page_List')->to($pages); ?> <?php while($pages->next()): ?> <?php if (($pages->slug != 'album') && ($pages->slug != 'search')): ?> <li<?php if($this->is('page', $pages->slug)): ?> class="current"<?php endif; ?>><a href="<?php $pages->permalink(); ?>" title="<?php $pages->title(); ?>"><?php $pages->title(); ?></a></li> <?php endif; ?> <?php endwhile; ?> </ul> //参数说明:9.0 版 typecho 支出在后台管理页面编辑时选择隐藏页面。33、Typecho 归档页面 (牧风提供) <?php $this->widget('Widget_Contents_Post_Recent', 'pageSize=10000')->to($archives); $year=0; $mon=0; $i=0; $j=0; $output = '<div id="archives">'; while($archives->next()): $year_tmp = date('Y',$archives->created); $mon_tmp = date('m',$archives->created); $y=$year; $m=$mon; if ($mon != $mon_tmp && $mon > 0) $output .= '</ul></li>'; if ($year != $year_tmp && $year > 0) $output .= '</ul>'; if ($year != $year_tmp) { $year = $year_tmp; $output .= '<h3 class="al_year">'. $year .' 年</h3><ul class="al_mon_list">'; //输出年份 } if ($mon != $mon_tmp) { $mon = $mon_tmp; $output .= '<li><span class="al_mon">'. $mon .' 月</span><ul class="al_post_list">'; //输出月份 } $output .= '<li>'.date('d日: ',$archives->created).'<a href="'.$archives->permalink .'">'. $archives->title .'</a> <em>('. $archives->commentsNum.')</em></li>'; //输出文章日期和标题 endwhile; $output .= '</ul></li></ul></div>'; echo $output; ?>34、获取当前文章页缩略图 <?php $this->attachments(1)->attachment->url(); ?> //进行熊掌号改造的朋友请务必注意,这里所谓缩略图指的是当前文章页第一个附件地址,请确保第一个附件类型为图片。35、根据页面类型显示内容 //判断是文章页则显示内容 <?php if ($this->is('post')): ?> 想要显示的内容1 <?php endif; ?> //判断是页面则显示内容 <?php if ($this->is('page', 'about')): ?> 想要显示的内容2 <?php endif; ?>图片
-
PHP操作文件详解 本文主要讲解php文件操作的方法和实例。希望对大家有帮助,操作方法仅供参考。 1、判断是否是一个文件 is_file('./test.txt'); //如果文件是常规的文件,该函数返回 true。 //该函数的结果会被缓存。请使用 clearstatcache() 来清除缓存。2、判断文件是否存在 file_exists('./test.txt');3、打开文件 $fp = fopen('./test.txt', 'r'); /** 只读, r 读写,文件覆盖:r+ 清空写入:w 可创建清空写入:w+ 追加写入:a 创建追加写入:a+ **/4、写入内容到文件 fwrite($fp, 'str字符串');5、指定读取多少字节的文件 fread($fp, 1024);6、一次性读取文件大小 filesize($path); fread($fp, filesize($path));7、关闭文件 fclose($fp);8、给文件重命名 rename(oldname, newname, context);9、删除文件 unlink($path);
-
PHP对API接口请求进行限流的几种算法 接口限流的意义 那么什么是限流呢?顾名思义,限流就是限制流量,包括一定时间内的并发流量和总流量。 就像你有一个 GB 流量的宽带包,用完就没了,所以控制好自己的使用频率和单次使用的总消费。 通过限流,我们可以很好地控制系统的 qps,从而达到保护系统或者接口服务器稳定的目的。 图片 接口限流的常用算法 1、计数器法 计数器法是限流算法里最简单也是最容易实现的一种算法。 比如我们规定,对于 A 接口来说,我们 1 分钟的访问次数不能超过 100 个。那么我们可以这么做:在一开始的时候,我们可以设置一个计数器 counter,每当一个请求过来的时候,counter 就加 1,如果 counter 的值大于 100 并且该请求与第一个请求的间隔时间还在 1 分钟之内,那么说明请求数过多; 如果该请求与第一个请求的间隔时间大于 1 分钟,且 counter 的值还在限流范围内,那么就重置 counter。 代码如下: class CounterDemo { private $first_request_time; private $request_count = 0; //已请求的次数 public $limit = 100; //时间窗口内的最大请求数 public $interval = 60; //时间窗口 s public function __construct() { $this->first_request_time = time(); } public function grant() { $now = time(); if ($now < $this->first_request_time + $this->interval) { //时间窗口内 if ($this->request_count < $this->limit) { $this->request_count++; return true; } else { return false; } } else { //超出前一个时间窗口后, 重置第一次请求时间和请求总次数 $this->first_request_time = $now; $this->request_count = 1; return true; } } } $m = new CounterDemo(); $n_success = 0; for ($i = 0; $i < 200; $i++) { $rt = $m->grant(); if ($rt) { $n_success++; } } echo '成功请求 ' . $n_success . ' 次';计数器算法很简单,但是有个严重的 bug: 一个恶意用户在 0:59 时瞬间发送了 100 个请求,然后再 1:00 时又瞬间发送了 100 个请求,那么这个用户在 2 秒内发送了 200 个请求。 上面我们规定 1 分钟最多处理 100 个请求, 也就是每秒 1.7 个请求。用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过系统的承载能力,导致系统挂起或宕机。 上面的问题,其实是因为我们统计的精度太低造成的。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法。 图片 上图中,我们把一个时间窗口(一分钟)分成 6 份,每份(小格)代表 10 秒。每过 10 秒钟我们就把时间窗口往右滑动一格, 每一个格子都有自己独立的计数器。 比如一个请求在 0:35 秒到达的时候,就会落在 0:30-0:39 这个区间,并将此区间的计数器加 1。 从上图可以看出, 0:59 到达的 100 个请求会落在 0:50-0:59 这个灰色的格子中, 而 1:00 到达的 100 个请求会落在黄色的格子中。 而在 1:00 时间统计时, 窗口会往右移动一格,那么此时的时间窗口内的请求数量一共是 200 个,超出了限制的 100 个,触发了限流,后面的 100 个请求被抛弃或者等待。 如果我们把窗口时间划分越多, 比如 60 格,每格 1s, 那么限流统计会更精确。 2、漏桶算法 (Leaky Bucket) 漏桶算法(Leaky Bucket): 平滑网络上的突发流量。使其整流为一个稳定的流量。 图片 有一个固定容量的桶,有水流进来,也有水流出 去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。当桶满了之后,多余的水将会溢出(多余的请求会被丢弃)。 简单的算法实现代码: class LeakyBucketDemo { private $last_req_time; //上一次请求的时间 public $capacity; //桶的容量 public $rate; //水漏出的速度(个/秒) public $water; //当前水量(当前累积请求数) public function __construct() { $this->last_req_time = time(); $this->capacity = 100; $this->rate = 20; $this->water = 0; } public function grant() { $now = time(); $water = max(0, $this->water - ($now - $this->last_req_time) * $this->rate); // 先执行漏水,计算剩余水量 $this->water = $water; $this->last_req_time = $now; if ($water < $this->capacity) { // 尝试加水,并且水还未满 $this->water += 1; return true; } else { // 水满,拒绝加水 return false; } } } $m = new LeakyBucketDemo(); $n_success = 0; for ($i = 0; $i < 500; $i++) { $rt = $m->grant(); if ($rt) { $n_success++; } if ($i > 0 && $i % 100 == 0) { //每发起100次后暂停1s echo '已发送', $i, ', 成功 ', $n_success, ', sleep' . PHP_EOL; sleep(1); } } echo '成功请求 ' . $n_success . ' 次';3、令牌桶算法 (Token Bucket) 令牌桶算法比漏桶算法稍显复杂。首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的(可用 token 数为 0),token 以一个固定的速率 r 往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。 实现代码如下: class TokenBucketDemo { private $last_req_time; //上次请求时间 public $capacity; //桶的容量 public $rate; //令牌放入的速度(个/秒) public $tokens; //当前可用令牌的数量 public function __construct() { $this->last_req_time = time(); $this->capacity = 100; $this->rate = 20; $this->tokens = 100; //开始给100个令牌 } public function grant() { $now = time(); $tokens = min($this->capacity, $this->tokens + ($now - $this->last_req_time) * $this->rate); // 计算桶里可用的令牌数 $this->tokens = $tokens; $this->last_req_time = $now; if ($this->tokens < 1) { // 若剩余不到1个令牌,则拒绝 return false; } else { // 还有令牌,领取1个令牌 $this->tokens -= 1; return true; } } } $m = new TokenBucketDemo(); $n_success = 0; for ($i = 0; $i < 500; $i++) { $rt = $m->grant(); if ($rt) { $n_success++; } if ($i > 0 && $i % 100 == 0) { //每发起100次后暂停1s echo '已发送', $i, ', 成功 ', $n_success, ', sleep' . PHP_EOL; sleep(1); } } echo '成功请求 ' . $n_success . ' 次';我们可以使用 redis 的队列作为令牌桶容器使用,使用 lPush(入队),rPop(出队),实现令牌加入与消耗的操作。 TokenBucket.php <?php /** * PHP基于Redis使用令牌桶算法实现接口限流,使用redis的队列作为令牌桶容器,入队(lPush)出队(rPop)作为令牌的加入与消耗操作。 * public add 加入令牌 * public get 获取令牌 * public reset 重设令牌桶 * private connect 创建redis连接 */ class TokenBucket { // class start private $_config; // redis设定 private $_redis; // redis对象 private $_queue; // 令牌桶 private $_max; // 最大令牌数 /** * 初始化 * @param Array $config redis连接设定 */ public function __construct($config, $queue, $max) { $this->_config = $config; $this->_queue = $queue; $this->_max = $max; $this->_redis = $this->connect(); } /** * 加入令牌 * @param Int $num 加入的令牌数量 * @return Int 加入的数量 */ public function add($num = 0) { // 当前剩余令牌数 $curnum = intval($this->_redis->lSize($this->_queue)); // 最大令牌数 $maxnum = intval($this->_max); // 计算最大可加入的令牌数量,不能超过最大令牌数 $num = $maxnum >= $curnum + $num ? $num : $maxnum - $curnum; // 加入令牌 if ($num > 0) { $token = array_fill(0, $num, 1); $this->_redis->lPush($this->_queue, ...$token); return $num; } return 0; } /** * 获取令牌 * @return Boolean */ public function get() { return $this->_redis->rPop($this->_queue) ? true : false; } /** * 重设令牌桶,填满令牌 */ public function reset() { $this->_redis->delete($this->_queue); $this->add($this->_max); } /** * 创建redis连接 * @return Link */ private function connect() { try { $redis = new Redis(); $redis->connect($this->_config['host'], $this->_config['port'], $this->_config['timeout'], $this->_config['reserved'], $this->_config['retry_interval']); if (empty($this->_config['auth'])) { $redis->auth($this->_config['auth']); } $redis->select($this->_config['index']); } catch (RedisException $e) { throw new Exception($e->getMessage()); return false; } return $redis; } } ?>令牌的假如与消耗: <?php /** * 演示令牌加入与消耗 */ require 'TokenBucket.php'; // redis连接设定 $config = array( 'host' => 'localhost', 'port' => 6379, 'index' => 0, 'auth' => '', 'timeout' => 1, 'reserved' => NULL, 'retry_interval' => 100, ); // 令牌桶容器 $queue = 'mycontainer'; // 最大令牌数 $max = 5; // 创建TrafficShaper对象 $tokenBucket = new TokenBucket($config, $queue, $max); // 重设令牌桶,填满令牌 $tokenBucket->reset(); // 循环获取令牌,令牌桶内只有5个令牌,因此最后3次获取失败 for ($i = 0; $i < 8; $i++) { var_dump($tokenBucket->get()); } // 加入10个令牌,最大令牌为5,因此只能加入5个 $add_num = $tokenBucket->add(10); var_dump($add_num); // 循环获取令牌,令牌桶内只有5个令牌,因此最后1次获取失败 for ($i = 0; $i < 6; $i++) { var_dump($tokenBucket->get()); } ?>
-
PHP8.1的10个新特性和性能改进 PHP 8.1现已推出,它带来了新的特性和性能改进——最令人向往的是新的JIT编译器。于2021年11月25日推出。 我们将详细演示PHP 8.1提供的10大特性,以便您可以开始在项目中使用它们,并改善您的PHP体验。初学者和有经验的开发人员可以从这篇文章中受益。 PHP图片 8.1 提供的 10 大功能 枚举 Fiber(纤维) never 返回类型 readonly 属性 final 类常量 新的 array_is_list() 函数 新的 fsync() 和 fdatasync() 函数 对字符串键数组解包的支持 $_FILES 新的用于目录上传的 full_path 键 新的 IntlDatePatternGenerator 类 1. 枚举 PHP 8.1 添加了对枚举的支持,简写为 enum 。它是一种逐项类型,包含固定数量的可能值。请参阅以下代码片段以了解如何使用枚举。 <?php /** * Declare an enumeration. * It can also contain an optional 'string' or 'int' value. This is called backed Enum. * Backed enums (if used) should match the following criteria: * - Declare the scalar type, whether string or int, in the Enum declaration. * - All cases have values. * - All cases contain the same scalar type, whether string or int. * - Each case has a unique value. */ enum UserRole: string { case ADMIN = '1'; case GUEST = '2'; case WRITER = '3'; case EDITOR = '4'; } /** * You can access a case by using * the '::' scope resolution operator. * And, to get the name of the enum case, you * can use the '->' followed by the attribute 'name'. */ echo UserRole::WRITER->name; /** * To get the value of the enum case, you can * use the '->' followed by the attribute 'value'. */ echo UserRole::WRITER->value; ?>2. Fiber(纤维) PHP 8.1 添加了对 Fiber 的支持,这是一个低级组件,允许在 PHP 中执行并发代码。Fiber 是一个代码块,它包含自己的变量和状态堆栈。这些 Fiber 可以被视为应用程序线程,可以从主程序启动。 一旦启动,主程序将无法挂起或终止 Fiber。它只能从 Fiber 代码块内部暂停<或终止。在 Fiber 挂起后,控制权再次返回到主程序,它可以从挂起的点继续执行 Fiber。 Fiber 本身不允许同时执行多个 Fiber 或主线程和一个 Fiber。但是,对于 PHP 框架来说,高效管理执行堆栈并允许异步执行是一个巨大的优势。 请参阅以下代码片段以了解如何使用 Fiber <?php /** * Initialize the Fiber. */ $fiber = new Fiber(function (): void { /** * Print some message from inside the Fiber. * Before the Fiber gets suspended. */ echo "Welcome to Fiber!\n"; /** * Suspend the Fiber. */ Fiber::suspend(); /** * Print some message from inside the Fiber. * After the Fiber gets resumed. */ echo "Welcome back to Fiber!\n"; }); /** * Print a message before starting a Fiber. */ echo "Starting a Fiber\n"; /** * Start the Fiber. */ $fiber->start(); /** * Fiber has been suspened from the inside. * Print some message, and then resume the Fiber. */ echo "Fiber has been suspended\n"; echo "Resuming the Fiber\n"; /** * Resume the Fiber. */ $fiber->resume(); /** * End of the example. */ echo "Fiber completed execution\n"; ?>3. never 返回类型 PHP 8.1 添加了名为 never 的返回类型。该 never 类型可用于指示函数将在执行一组指定的任务后终止程序执行。这可以通过抛出异常、调用 exit() 或 die() 函数来完成。 never 返回类型类似于 void 返回类型。但是,void 返回类型在函数完成一组指定的任务后继续执行。 请参阅以下代码片段以了解如何使用 never 返回类型 <?php /** * Route Class */ class Route { /** * Constructor of the class * @return void */ public function __construct() { } /** * Redirect To a Page * This function redirects to an URL specified by the user. * @method redirect() * @param string $url * @param integer $httpCode * @author Tara Prasad Routray <someemailaddress@example.com> * @access public * @return never */ public static function redirect($url, $httpCode = 301): never { /** * Redirect to the URL specified. */ header("Location: {$url}", true, $httpCode); die; } } Route::redirect('https://www.google.com'); ?>4. readonly 属性 PHP 8.1 添加了名为 readonly 的类属性。已声明为只读的类属性只能初始化一次。里面设置的值不能改变。如果尝试强行更新该值,应用程序将抛出错误。请参阅以下代码片段以了解如何使用只读属性。 <?php /** * User Class */ class User { /** * Declare a variable with readonly property. * @var $authUserID * @access public */ public readonly int $authUserID; /** * Constructor of the class. * @param integer $userID * @return void */ public function __construct($userID) { /** * Change the value of the property as specified. * Updating the value of readonly properties are * allowed only through the constructor. */ $this->authUserID = $userID; } /** * Update Auth User ID * This function tries to update the readonly property (which is not allowed). * @method updateAuthUserID() * @param integer $userID * @author Tara Prasad Routray <someemailaddress@example.com> * @access public * @return void */ public function updateAuthUserID($userID) { /** * Change the value of the property as specified. * Executing this function will throw the following error; * PHP Fatal error: Uncaught Error: Cannot modify readonly property User::$authUserID */ $this->authUserID = $userID; } } /** * Initialize the class and update the value of the readonly property. */ $user = new User(30); /** * Print the readonly property value. * This will print 30. */ echo $user->authUserID; /** * Call another function inside the class and try to update the class property. */ $user->updateAuthUserID(50); /** * Print the readonly property value. */ echo $user->authUserID; ?>5. final 类常量 PHP 8.1 添加了对名为 final 的类常量的支持。最终类常量不能被修改,即使是通过继承,这意味着它们不能被子类扩展或覆盖。 这个标志不能用于私有常量,因为它不能在类之外被访问。声明 final 和 private 常量将导致致命错误。 请参阅以下代码片段以了解如何使用最终标志 <?php /** * UserRole Class */ class UserRole { /** * Declare a final class constant with a value. */ final public const ADMIN = '1'; } /** * User Class extending the UserRole Class */ class User extends UserRole { /** * Declare another constant with the same name * as of the parent class to override the value. * * Note: Overriding the value will throw the following error: * PHP Fatal error: User::ADMIN cannot override final constant UserRole::ADMIN */ public const ADMIN = '2'; } ?>6. 新的 array_is_list() 函数 PHP 8.1 添加了名为 array_is_list() 的数组函数。它标识指定的数组是否具有从 0 开始的所有连续整数。如果数组是值的语义列表(一个数组,其键从 0 开始,都是整数,并且之间没有间隙),则此函数返回 true。对于空数组,它也返回 true。请参阅以下代码片段以了解如何使用 array_is_list () 函数。 <?php /** * Returns true for empty array. */ array_is_list([]); /** * Returns true for sequential set of keys. */ array_is_list([1, 2, 3]); /** * Returns true as the first key is zero, and keys are in sequential order. * It is same as [0 => 'apple', 1 => 2, 2 => 3] */ array_is_list(['apple', 2, 3]); /** * Returns true as the first key is zero, and keys are in sequential order. * It is same as [0 => 'apple', 1 => 'scissor'] */ array_is_list(['apple', 'orange']); /** * Returns true as the first key is zero, and keys are in sequential order. * It is same as [0 => 'apple', 1 => 'scissor'] */ array_is_list([0 => 'apple', 'orange']); /** * Returns true as the first key is zero, and keys are in sequential order. */ array_is_list([0 => 'rock', 1 => 'scissor']); ?>键不是从 0 开始的数组,或者键不是整数,或者键是整数但不按顺序出现的数组将评估为 false。 <?php /** * Returns false as the first key does not start from zero. */ array_is_list([1 => 'apple', 'orange']); /** * Returns false as the first key does not start from zero. */ array_is_list([1 => 'apple', 0 => 'orange']); /** * Returns false as all keys are not integer. */ array_is_list([0 => 'apple', 'fruit' => 'orange']); /** * Returns false as the keys are not in sequential order. */ array_is_list([0 => 'apple', 2 => 'orange']); ?>7. 新的 fsync() 和 fdatasync() 函数 PHP 8.1 添加了对 fsync() 和 fdatasync() 函数的支持。两者都与现有 fflush() 函数有相似之处,该函数当前用于将缓冲区刷新到操作系统中。 然而,fsync() 和 fdatasync() 刷新该缓冲区到物理存储。它们之间的唯一区别是该 fsync() 函数在同步文件更改时包含元数据,而该 fdatasync() 函数不包含元数据。 fsync() 函数将采用文件指针并尝试将更改提交到磁盘。成功时返回 true,失败时返回 false,如果资源不是文件,则会发出警告。fdatasync() 函数的工作方式相同,但速度稍快一些,因为 fsync () 将尝试完全同步文件的数据更改和有关文件的元数据(上次修改时间等),这在技术上是两次磁盘写入。 请参阅以下代码片段以了解如何使用 fsync () 和 fdatasync () 函数。 <?php /** * Declare a variable and assign a filename. */ $fileName = 'notes.txt'; /** * Create the file with read and write permission. */ $file = fopen($fileName, 'w+'); /** * Add some text into the file. */ fwrite($file, 'Paragraph 1'); /** * Add a line break into the file. */ fwrite($file, "\r\n"); /** * Add some more text into the file. */ fwrite($file, 'Paragraph 2'); /** * You can use both the fsync() or fdatasync() functions * to commit changs to disk. */ fsync($file); // or fdatasync($file). /** * Close the open file pointer. */ fclose($file); ?>8. 对字符串键数组解包的支持 PHP 8.1 添加了对字符串键数组解包的支持。为了解压数组,PHP 使用展开 (…) 运算符。PHP 7.4 中引入了这个运算符来合并两个或多个数组,但语法更简洁。但在 PHP 8.1 之前,展开运算符仅支持带数字键的数组。请参阅以下代码片以了解如何将展开运算符用于字符串键控数组。 <?php /** * Declare an array */ $fruits1 = ['Jonathan Apples', 'Sapote']; /** * Declare another array */ $fruits2 = ['Pomelo', 'Jackfruit']; /** * Merge above two arrays using array unpacking. */ $unpackedFruits = [...$fruits1, ...$fruits2, ...['Red Delicious']]; /** * Print the above unpacked array. * This will print: * array(5) { * [0]=> * string(15) "Jonathan Apples" * [1]=> * string(6) "Sapote" * [2]=> * string(6) "Pomelo" * [3]=> * string(9) "Jackfruit" * [4]=> * string(13) "Red Delicious" * } */ var_dump($unpackedFruits); ?>9. $_FILES 新的用于目录上传的 full_path 键 PHP 8.1 添加了对 $_FILES 全局变量中 full_path 新键的支持。在 PHP 8.1 之前,$_FILES 没有存储到服务器的相对路径或确切目录。因此,您无法使用 HTML 文件上传表单上传整个目录。 新 full_path 键解决了这个问题。它存储相对路径并在服务器上重建确切的目录结构,使目录上传成为可能。请参阅以下代码片段以了解如何将 full_path 键与 $_FILES 全局变量一起使用。 <?php /** * Check if the user has submitted the form. */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { /** * Print the $_FILES global variable. This will display the following: * array(1) { * ["myfiles"]=> array(6) { * ["name"]=> array(2) { * [0]=> string(9) "image.png" * [1]=> string(9) "image.png" * } * ["full_path"]=> array(2) { * [0]=> string(25) "folder1/folder2/image.png" * [1]=> string(25) "folder3/folder4/image.png" * } * ["tmp_name"]=> array(2) { * [0]=> string(14) "/tmp/phpV1J3EM" * [1]=> string(14) "/tmp/phpzBmAkT" * } * // ... + error, type, size * } * } */ var_dump($_FILES); } ?> <form action="" method="POST" enctype="multipart/form-data"> <input name="myfiles[]" type="file" webkitdirectory multiple /> <button type="submit">Submit</button> </form>10. 新的 IntlDatePatternGenerator 类 PHP 8.1 添加了对新 IntlDatePatternGenerator 类的支持。在 PHP 8.1 之前,只能使用 IntlDateFormatter 。虽然它支持昨天、今天和明天使用的八种预定义格式,但是这些格式和 IntlDatePatternGenerator 不太一样。 这个类允许指定日期、月份和时间的格式,并且顺序将由类自动处理。请参阅以下代码片段以了解如何使用 IntlDatePatternGenerator 类。 <?php /** * Define a default date format. */ $skeleton = "YYYY-MM-dd"; /** * Parse a time string (for today) according to a specified format. */ $today = \DateTimeImmutable::createFromFormat('Y-m-d', date('Y-m-d')); /** * =========================== * PRINTING DATE IN USA FORMAT * =========================== * Initiate an instance for the IntlDatePatternGenerator class * and provide the locale information. * In the below example, I've used locale: en_US. */ $intlDatePatternGenerator = new \IntlDatePatternGenerator("en_US"); /** * Get the correct date format for the locale: en_US. * Following function "getBestPattern" will return: * MM/dd/YYYY */ $enUSDatePattern = $intlDatePatternGenerator->getBestPattern($skeleton); /** * Use the "formatObject" function of IntlDateFormatter to print as per specified pattern. * This will print the following: * Date in en-US: 12/03/2021 */ echo "Date in en-US: " . \IntlDateFormatter::formatObject($today, $enUSDatePattern, "en_US") . "\n"; /** * ============================= * PRINTING DATE IN INDIA FORMAT * ============================= * Initiate an instance for the IntlDatePatternGenerator class * and provide the locale information. * In the below example, I've used locale: en_IN. */ $intlDatePatternGenerator = new \IntlDatePatternGenerator("en_IN"); /** * Get the correct date format for the locale: en_IN. * Following function "getBestPattern" will return: * dd/MM/YYYY */ $enINDatePattern = $intlDatePatternGenerator->getBestPattern($skeleton); /** * Use the "formatObject" function of IntlDateFormatter to print as per specified pattern. * This will print the following: * Date in en-IN: 03/12/2021 */ echo "Date in en-IN: " . \IntlDateFormatter::formatObject($today, $enINDatePattern, "en_IN") . "\n"; ?>
-
正则表达式详解 一、介绍 正则表达式是一种文本模式,它包括普通字符(例如,A和Z之间的字母)和特殊字符(称为“元字符”)。 正则表达式使用单个字符串来描述和匹配一系列符合特定语法规则的字符串。 正则表达式虽然繁琐,但是功能强大,学会之后的应用不仅会提高你的效率,还会给你带来绝对的成就感。 许多编程语言都支持使用正则表达式进行字符串操作,它的匹配模式在各种编程语言内都是大同小异。 PCRE正则表达式图片 PHP语言支持两种风格的正则表达式语法:POSIX 和 Perl。 这两个表达式是编译PHP时指定的默认样式,但是在PHP5.3之后,POSIX风格被抛弃了。 二、本人常用的正则匹配工具 菜鸟工具提供的正则表达式工具,链接:https://c.runoob.com/front-end/854 三、基本知识介绍 正则表达式是描述文本中包含的模式的一种方式。 在PHP中,匹配正则表达式更像是strstr()函数匹配而不是相等比较,因为它在一个字符串的某个位置,如果不指定,它可能在字符串的任何地方匹配另一个字符串。 例:字符串“shop”匹配正则表达式“shop”,也可以匹配h、ho等正则表达式。 除了精确匹配字符之外,特殊字符也可以用于指定表达式的元意义。 说了这么多基本理论本人也很烦 ::(喷) ,言归正传!我们直接进入主题 ↓ 四、基础字符介绍 {card-default label="分隔符:/"} 作用:每个表达式必须包含在一対分隔符中,字符串的开始和结束都必须要有匹配的分隔符 例:编写一个匹配 shop 的正则表达式 : /shop/ {/card-default} {card-default label="模式选择:|(选择分支的开始(读为或))"} 例: /com|edu|net/ 匹配 com 、edu 或 net 字符串 {/card-default} {card-default label="模式修饰符:i"} 作用:使用模式修饰符 将以不区分大小写的方式匹配字符串 例:不区分大小写的方式匹配 shop 的正则表达式 /shop/i {/card-default} {card-default label="方括号字符:[]"} 例如:/[a-z]at/ 表示限定第一个字符必须是a-z之间的字符。 {/card-default} {card-default label="脱字符号:^"} 作用:脱字符号 ^ 用于正则表达式的开始,表示子字符串必须出现在被搜索字符串的开始处;当脱字符号 ^ 在方括号里面时,表示否; 在下面两张表中会具体介绍例子 {/card-default} 五、在PCRE正则表达式中,用于方括号外面的特殊字符 字符意义例子\转义字符在正则表达式匹配字符 / 则使用 \ 来转义:/http:\/\//^在字符开始处匹配/^[a-z]$/ 匹配只包含 a-z 之间一个字符的字符串$在字符末尾处匹配/com$/ 匹配将以 com 为结束的字符串.匹配除换行符 \n 之外的字符/.at/ 可以匹配cat 、sat 、mat 等字符串,适用于操作系统的文件名匹配()子模式/(very)*large/ 可以匹配“large”、“very large”、“very very large”*重复出现0次或多次[[:alnum:]]* 表示没有或多个字母字符+重复出现1次或多次[[:alnum:]]+ 表示至少有一个字母字符?重复出现1次或0次[[:alnum:]]? 表示有零个或一个字母字符{}最小/最大 量记号{3} 表示重复三次;{2,4} 重复2-4次;{2, } 表示至少要重复两次六.在PCRE正则表达式中,用于方括号里面的特殊字符 字符意义例子\转义字符在正则表达式匹配字符 / 则使用 \ 来转义:/http:\/\//^非,进开始处使用/[^a-z]/ 匹配任何不在 a-z 之间的字符-用于指定符号的范围/[a-zA-Z]/ 这个范围集代表大写的任何字母七.用于PCRE风格正则表达式的字符类 类匹配[[:alnum:]]文字数字字符[[:alpha:]]字母字符[[:asci:]]ASCLL字符[[:space:]]空白字符[[:cntrl:]]控制符[[:print:]]所有可打印的字符[[:graph:]]除空格外所有可打印的字符[[:punct:]]标点字符[[:blank:]]制表符和空格[[:lower:]]小写字母[[:upper:]]大写字母[[:digit:]]小数[[:xdigit:]]十六进制小数[[:word:]]“word”字符(字母数字或下划线)八、PCRE正则表达式的特殊字符类型 字符类型含义\d十进制数字\D任意非十进制数字\h水平空白字符\H任意非水平空白字符\s空白字符\S任意非空白字符\v垂直空白字符\V任意非垂直空白字符\w单词字符(字母、数字、下划线)\W任意非单词字符九、回溯引用 回溯引用( backreference ,也叫反向引用),非PHP程序员一般会放弃学习和使用,我也快放弃了 :@(哭泣) {% note success no-icon %} 模式的回溯引用是通过一个反斜杠加一个数字(根据上下文不同,也可能多个数字) 它用来匹配多次出现在一个字符串中的相同子表达式,而不用指定要具体匹配的内容{% endnote %} 例: /1是表示第一个子模式回调引用; 图片 /2则是第二个子模式回调引用 图片 十、在邮箱验证中应用 验证邮箱正则表达式:/^[a-zA-Z0-9_\-.]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]*[\.a-zA-Z0-9]{2,6}+$/ 子表达式 /^[a-zA-Z0-9_\-.]+ 表示“至少由一个或多个字母、数字、下划线、连字符、点号组成并且作为整个字符串开始的字符串”。请注意,当“.”用在一个字符类的开始或结束处时,它将失去其特殊通配符的意义,只能成为一个点号字符。 符号 @ 匹配字符“@”。 子表达式 [a-zA-Z0-9\-]+ 与由字母、数字字符和连字符组成的主机名相匹配。请注意,在这里,我们去除了连字符,因为它是方括号内的特殊字符。 字符组合 \. 匹配“.”字符。我们在字符类外部使用点号,因此必须对其转义,使其能够匹配一个点号字符。 子表达式 [a-zA-Z0-9\-] 匹配域名剩余部分,它包含字母、数字和连字符。 子表达式 *[\.a-zA-Z0-9]{2,6}+$/ 匹配域名剩余部分,它包含字母、数字和连字符。最后的子表达式最多只能存在2-6个字符,或者不存在 如图 图片
-
为什么不推荐在MySQL中使用UTF-8编码? 记得去年我在往 MySQL 存入emoji表情😲😳时,一直出错,无法导入。后来找到办法 -- 通过把 utf8 改成 utf8mb4 就可以了,并没有深究。 一年后,我看到一篇文章讲到emoji文字占4个字节,通常要用utf-8去接收才行,其他编码可能会出错。我突然想到去年操作MySQL把utf8改成 utf8mb4 的事儿。 MySQL图片 嗯?他本身不就是utf8编码么!那我当时还改个锤子? 难道,MySQL的utf8不是真正的 UTF-8 编码吗??! 这。。MySQL有bug! 带着疑问查询了很多相关材料,才发现这竟然是MySQL的一个历史遗留问题~~ 我笑了,没想到这么牛的MySQL也会有这段往事。 一、报错回顾 将emoji文字直接写入SQL中,执行 insert 语句报错; INSERT INTO `csjdemo`.`student` (`ID`, `NAME`, `SEX`, `AGE`, `CLASS`, `GRADE`, `HOBBY`); VALUES ('20', '陈哈哈😓', '男', '20', '181班', '9年级', '看片儿'); [Err] 1366 - Incorrect string value: '\xF0\x9F\x98\x93' for column 'NAME' at row 1 改了数据库编码、系统编码以及表字段的编码格式 → utf8mb4 之后,就可以了: INSERT INTO `student` (`ID`, `NAME`, `SEX`, `AGE`, `CLASS`, `GRADE`, `HOBBY`); VALUES (null, '陈哈哈😓😓', '男', '20', '181班', '9年级', );图片 二、MySQL中utf8的趣事 MySQL 的“utf8”实际上不是真正的 UTF-8。 在MySQL中,“utf8”编码只支持每个字符最多三个字节,而真正的 UTF-8 是每个字符最多四个字节。 在utf8编码中,中文是占3个字节,其他数字、英文、符号占一个字节。 但emoji符号占4个字节,一些较复杂的文字、繁体字也是4个字节。所以导致写入失败,应该改成 utf8mb4 MySQL 一直没有修复这个 bug,他们在 2010 年发布了一个叫作“utf8mb4”的字符集,巧妙的绕过了这个问题。 当然,他们并没有对新的字符集广而告之(可能是因为这个 bug 让他们觉得很尴尬),以致于现在网络上仍然在建议开发者使用“utf8”,但这些建议都是错误的。 1. utf8mb4 才是真正的UTF-8 是的,MySQL 的“utf8mb4”才是真正的“UTF-8”。 MySQL 的“utf8”是一种“专属的编码”,它能够编码的 Unicode 字符并不多。 在这里Mark一下:所有在使用“utf8”的 MySQL 和 MariaDB 用户都应该改用“utf8mb4”,永远都不要再使用“utf8” 那么什么是编码?什么是 UTF-8? 我们都知道,计算机使用 0 和 1 来存储文本。比如字符“C”被存成“01000011”,那么计算机在显示这个字符时需要经过两个步骤: 计算机读取“01000011”,得到数字 67,因为 67 被编码成“01000011”。 计算机在 Unicode 字符集中查找 67,找到了“C”。 同样的: 我的电脑将“C”映射成 Unicode 字符集中的 67。 我的电脑将 67 编码成“01000011”,并发送给 Web 服务器。 几乎所有的网络应用都使用了 Unicode 字符集,因为没有理由使用其他字符集。 Unicode 字符集包含了上百万个字符。最简单的编码是 UTF-32,每个字符使用 32 位。这样做最简单,因为一直以来,计算机将 32 位视为数字,而计算机最在行的就是处理数字。但问题是,这样太浪费空间了。 UTF-8 可以节省空间,在 UTF-8 中,字符“C”只需要 8 位,一些不常用的字符,比如“😓”需要 32 位。其他的字符可能使用 16 位或 24 位。一篇类似本文这样的文章,如果使用 UTF-8 编码,占用的空间只有 UTF-32 的四分之一左右。 2. utf8 的简史 为什么 MySQL 开发者会让“utf8”失效? 我们或许可以从MySQL版本提交日志中寻找答案。 MySQL 从 4.1 版本开始支持 UTF-8,也就是 2003 年,而今天使用的 UTF-8 标准(RFC 3629)是随后才出现的。 旧版的 UTF-8 标准(RFC 2279)最多支持每个字符 6 个字节。2002 年 3 月 28 日,MySQL 开发者在第一个 MySQL 4.1 预览版中使用了 RFC 2279。 同年 9 月,他们对 MySQL 源代码进行了一次调整:“UTF8 现在最多只支持 3 个字节的序列”。 是谁提交了这些代码?他为什么要这样做?这个问题不得而知。在迁移到 Git 后(MySQL 最开始使用的是 BitKeeper),MySQL 代码库中的很多提交者的名字都丢失了。2003 年 9 月的邮件列表中也找不到可以解释这一变更的线索。 不过我们可以试着猜测一下: 2002年,MySQL做出了一个决定:如果用户可以保证数据表的每一行都使用相同的字节数,那么 MySQL 就可以在性能方面来一个大提升。为此,用户需要将文本列定义为“CHAR”,每个“CHAR”列总是拥有相同数量的字符。如果插入的字符少于定义的数量,MySQL 就会在后面填充空格,如果插入的字符超过了定义的数量,后面超出部分会被截断。 MySQL 开发者在最开始尝试 UTF-8 时使用了每个字符6个字节,CHAR(1) 使用6个字节,CHAR(2)使用12个字节,并以此类推。 应该说,他们最初的行为才是正确的,可惜这一版本一直没有发布。但是文档上却这么写了,而且广为流传,所有了解 UTF-8 的人都认同文档里写的东西。 不过很显然,MySQL 开发者或厂商担心会有用户做这两件事: 使用 CHAR 定义列(在现在看来,CHAR 已经是老古董了,但在那时,在 MySQL 中使用 CHAR 会更快,不过从 2005 年以后就不是这样子了)。 将 CHAR 列的编码设置为“utf8”。 我的猜测是 MySQL 开发者本来想帮助那些希望在空间和速度上双赢的用户,但他们搞砸了“utf8”编码。 所以结果就是没有赢家。那些希望在空间和速度上双赢的用户,当他们在使用“utf8”的 CHAR 列时,实际上使用的空间比预期的更大,速度也比预期的慢。而想要正确性的用户,当他们使用“utf8”编码时,却无法保存像“😓”这样的字符,因为“😓”是4个字节的。 在这个不合法的字符集发布了之后,MySQL 就无法修复它,因为这样需要要求所有用户重新构建他们的数据库。最终, MySQL 在 2010 年重新发布了“utf8mb4”来支持真正的 UTF-8。 三、总结 主要是目前网络上几乎所有的文章都把 “utf8” 当成是真正的 UTF-8,包括之前我写的文章以及做的项目(捂脸);因此希望更多的朋友能够看到这篇文章。 相信还有很多跟我在同一条船上的人,这是必然的。 所以,大家以后再搭建MySQL、MariaDB数据库时,记得将数据库相应编码都改为utf8mb4。
-
三个令人期待的CSS特性即将推出! 大家好,我是易航,今天给大家总结分享一下很 🐂🍺 的3个即将推出的 CSS 属性,等你看完,每一个都要喊:绝了! {callout color="var(--theme)"} 声明一下:这些特性现在基本都不好使,就算有的高级浏览器支持了,也只是个例。可以期待一下以后 ~ {/callout} 前端CSS图片 一、@container @container 是一个容器查询方法,正如它的名字一样,它是用来支持根据当前元素所在容器的大小来进行动态修改添加样式的,这跟 @media 基于视口大小是不一样的。 来举个🌰 先创建一个侧边栏和一个主内容 <body> <aside class="sidebar"> <div class="card"> <h4>侧边栏</h4> <p> To the world you may be one person, but to one person you may be the world. </p> </div> </aside> <main class="content"> <div class="card"> <h4>主内容</h4> <p> To the world you may be one person, but to one person you may be the world. </p> </div> </main> </body>让这两个元素横向布局,且侧边栏宽度占 30% ,主内容宽度占 70% body { display: flex; color: white; } h4 { color: black; } .sidebar { width: 30%; } .content { width: 70%; background: #f0f5f9; /* 给个底色,与侧边栏区分 */ } .card { background: lightpink; box-shadow: 3px 10px 20px rgba(0, 0, 0, 0.2); border-radius: 8px; }目前为止是这样的效果: 图片 现在我们发现主内容这块儿空间很富余,便想改变一下标题和内容文字的布局,此时就可以用上 @container 了,直接让主内容在当前容器宽度大于 400px 时变成横向布局 @container (min-width: 400px) { .content .card { display: flex; } }此时效果如下: 图片 是不是很酷 😎 基于这点,还想到了一个之前我做过的需求中很头疼的需求,就是字体大小随着容器宽高的改变而动态改变,如果支持了这个特性,那这个需求也就很简单了 二、object-view-box object-view-box 属性就类似于 SVG 中的 viewBox 属性。它允许您使用一行 CSS 来平移、缩放、裁剪 图像。 我们就对这张图来动动刀子 图片 加一行代码 .crop { object-view-box: inset(10% 50% 35% 5%); }实现的效果就是这样: 图片 跟原图对比一下就是这样: 图片 除了简单的裁剪,我们还能基于它实现一些好玩的效果,例如: 图片 三、animation-timeline animation-timeline 相比前两个就更好玩了!它允许我们基于容器滚动的进度来对动画进行处理,简而言之就是页面滚动了百分之多少,动画就执行百分之多少。而且动画也能根据页面倒着滚动而倒着播放 .shoes { animation-name: Rotate; animation-duration: 1s; animation-timeline: scrollTimeline; } @scroll-timeline scrollTimeline { source: selector('#container'); orientation: "vertical"; } @keyframes Rotate { from { transform: translate(-200px, -200px) rotate(0deg); } to { transform: translate(100vw, 100vh) rotate(720deg); } }使用起来很简单,就是在本身的基础动画上,新增一个 animation-timeline 属性即可,我们也可以对这个 timeline 定义是基于哪个容器,滚动方向是水平还是竖直 大致效果就是: 图片 最后 这三个 CSS 新特性,你们最喜欢哪个?有没有想到一些比较实用的场景,欢迎在评论区分享~
-
PHP实现客户端HTTPS协议强制退回到HTTP状态 前言 网上有很多HTTP升级为HTTPS的方法,但是让客户端所有用户从HTTPS退回HTTP的有效方法却很少。为了自己站点能够退回HTTP,我也是折腾了很长时间才想出来这个方法。 废话不多说,直接上本人自己研究出来的方法 HTTPS退回HTTP图片 实现方法 首先要在站点部署一个错误的SSL证书,如果无证书或证书正确会陷入301跳转循环! 然后在网站的入口文件最上方放入一段代码 <?php if ($_SERVER['HTTPS'] == 'on') { if ($_COOKIE['HTTPS']) { ?> <script type="text/javascript"> var targetProtocol = "http:"; if (window.location.protocol != targetProtocol) { window.location.href = targetProtocol + window.location.href.substring(window.location.protocol.length) } </script> <?php exit('请使用http协议访问本站'); } if (!$_COOKIE['HTTPS']) { setcookie("HTTPS", true, time() + 3600); } sleep(1); $url = "http://" . $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"]; header('HTTP/1.1 301 Moved Permanently'); header('Location:' . $url); } ?>原理总结 浏览器检测到错误的SSL证书就不会让站点强制HTTPS,没有了强制HTTPS我们就可以进行跳转到HTTP协议状态,但是只用301重定向这种方式会无限循环,导致浏览器检测到301重定向过多返回错误码。那么我们就只把301重定向给到搜索引擎来看,这种代码的写法不用专门检测是不是搜索引擎,避免误判之类的情况,给到用户这边用JS同样进行无感跳转网页。
-
PHP如何有效缩短接口响应时间 前言 中医讲对症下药,我们也需要定位到哪个位置消耗了多长时间,具体到每个位置去进行优化,这样我们就需要知道请求某个接口,每个流程所需要的时间,这样一般都是由日志来查看的。 本文从两点来说一下 PHP图片 一、缩短数据库操作耗时 这里以TP5框架为例,TP5开启日志的情况,会很详细的记录程序运行所使用的时间,来看下一个很常见的请求接口日志: [运行时间:1.207150s] [吞吐率:0.83req/s] [内存消耗:2,881.48kb] [文件加载:68] [ info ] [ LANG ] D:\webroot\www\thinkphp\lang\zh-cn.php [ info ] [ ROUTE ] array ( 'type' => 'module', 'module' => array ( 0 => 'admin', 1 => 'Order', 2 => 'getConfirmOrder', ), ) [ info ] [ HEADER ] array ( 'content-type' => '', 'content-length' => '0', 'x-original-url' => '/admin/Order/getConfirmOrder', 'origin' => 'http://www.***.com', 'x-requested-with' => 'XMLHttpRequest', 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36', 'referer' => 'http://www.***.com/admin/baby/index.html', 'host' => 'www.***.com', 'cookie' => 'security_session_verify=d66a8d5d0b34e8c6431d6bc554fbf24c; PHPSESSID=c2nph8nll013k9cu95l3a6d8h1', 'accept-language' => 'zh-CN,zh;q=0.9', 'accept-encoding' => 'gzip, deflate', 'accept' => 'application/json, text/javascript, */*; q=0.01', 'connection' => 'keep-alive', ) [ info ] [ PARAM ] array ( ) [ info ] [ SESSION ] INIT array ( 'prefix' => 'admin', 'type' => '', 'auto_start' => true, ) [ info ] [ DB ] INIT mysql [ info ] [ RUN ] app\admin\controller\Order->getConfirmOrder[ D:\webroot\www.***.com\application\admin\controller\Order.php ] [ info ] [ LOG ] INIT File [ sql ] [ DB ] CONNECT:[ UseTime:1.056759s ] mysql:host=localhost;port=3306;dbname=***;charset=utf8 [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_kanban` [ RunTime:0.011518s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` ORDER BY `kan_id` DESC [ RunTime:0.000676s ] [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_auth` [ RunTime:0.009073s ] [ sql ] [ SQL ] SELECT * FROM `gmy_auth` [ RunTime:0.002334s ] [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_admin` [ RunTime:0.010282s ] [ sql ] [ SQL ] SELECT * FROM `gmy_admin` WHERE `user_name` = 'admin99sj' LIMIT 1 [ RunTime:0.001304s ] [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_role` [ RunTime:0.010645s ] [ sql ] [ SQL ] SELECT * FROM `gmy_role` WHERE `role_id` IN (91) LIMIT 1 [ RunTime:0.000684s ] [ sql ] [ SQL ] SELECT * FROM `gmy_role` WHERE `role_id` IN (196) LIMIT 1 [ RunTime:0.000588s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/yingxiao' LIMIT 1 [ RunTime:0.000677s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/xingzheng' LIMIT 1 [ RunTime:0.000469s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/kefu' LIMIT 1 [ RunTime:0.000447s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/yanguangshi' LIMIT 1 [ RunTime:0.000519s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/shichang' LIMIT 1 [ RunTime:0.000443s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/wangluo' LIMIT 1 [ RunTime:0.000463s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/table' LIMIT 1 [ RunTime:0.000506s ] [ sql ] [ SQL ] SELECT * FROM `gmy_auth` WHERE `auth_des` LIKE '%order/getconfirmorder%' AND ( fen_id<>9 ) LIMIT 1 [ RunTime:0.001173s ]从运行时间来看1.2秒左右,1秒左右的时间对于人的正常感应来说已经能够感知到慢了,往下看,我们会看到具体的每个流程所使用的时间,首先是数据库连接时间:1.056759s,接下来是一些数据表查询所用的时间都很少,都在1毫秒左右,可以忽略不计。那么这里最主要的症结就是连接时间,为什么数据库连接要这么久呢?来看一下连接的参数: mysql:host=localhost;port=3306;dbname=***;charset=utf8;优化:数据库连接不要使用localhost,改成127.0.0.1 我们试下把数据库主机host改成127.0.0.1,再来测试下,看日志 [运行时间:0.115078s] [吞吐率:8.69req/s] [内存消耗:481.07kb] [文件加载:66] [ info ] [ LANG ] D:\webroot\www.***.com\thinkphp\lang\zh-cn.php [ info ] [ ROUTE ] array ( 'type' => 'module', 'module' => array ( 0 => 'admin', 1 => 'order', 2 => 'getConfirmOrder', ), ) [ info ] [ HEADER ] array ( 'content-type' => '', 'content-length' => '0', 'x-original-url' => '/admin/order/getConfirmOrder', 'sec-fetch-dest' => 'document', 'sec-fetch-user' => '?1', 'sec-fetch-mode' => 'navigate', 'sec-fetch-site' => 'none', 'upgrade-insecure-requests' => '1', 'sec-ch-ua-platform' => '"Android"', 'sec-ch-ua-mobile' => '?1', 'sec-ch-ua' => '" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"', 'user-agent' => 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36', 'host' => 'www.***.com', 'cookie' => 'security_session_verify=435345dacd7ada35e3d0ef60f48a169f; PHPSESSID=687tptp9lbd3aip2gph1uth450', 'accept-language' => 'zh-CN,zh;q=0.9', 'accept-encoding' => 'gzip, deflate, br', 'accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'connection' => 'keep-alive', 'cache-control' => 'max-age=0', ) [ info ] [ PARAM ] array ( ) [ info ] [ SESSION ] INIT array ( 'prefix' => 'admin', 'type' => '', 'auto_start' => true, ) [ info ] [ DB ] INIT mysql [ info ] [ RUN ] app\admin\controller\Order->getConfirmOrder[ D:\webroot\www.***.com\application\admin\controller\Order.php ] [ info ] [ LOG ] INIT File [ sql ] [ DB ] CONNECT:[ UseTime:0.001759s ] mysql:host=127.0.0.1;port=3306;dbname=***;charset=utf8 [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_kanban` [ RunTime:0.014265s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` ORDER BY `kan_id` DESC [ RunTime:0.000765s ] [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_auth` [ RunTime:0.009900s ] [ sql ] [ SQL ] SELECT * FROM `gmy_auth` [ RunTime:0.001970s ] [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_admin` [ RunTime:0.012493s ] [ sql ] [ SQL ] SELECT * FROM `gmy_admin` WHERE `user_name` = 'admin99sj' LIMIT 1 [ RunTime:0.000990s ] [ sql ] [ SQL ] SHOW COLUMNS FROM `gmy_role` [ RunTime:0.008893s ] [ sql ] [ SQL ] SELECT * FROM `gmy_role` WHERE `role_id` IN (91) LIMIT 1 [ RunTime:0.000611s ] [ sql ] [ SQL ] SELECT * FROM `gmy_role` WHERE `role_id` IN (196) LIMIT 1 [ RunTime:0.000652s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/yingxiao' LIMIT 1 [ RunTime:0.000524s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/xingzheng' LIMIT 1 [ RunTime:0.000387s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/kefu' LIMIT 1 [ RunTime:0.000376s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/yanguangshi' LIMIT 1 [ RunTime:0.000338s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/shichang' LIMIT 1 [ RunTime:0.000346s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/wangluo' LIMIT 1 [ RunTime:0.000362s ] [ sql ] [ SQL ] SELECT * FROM `gmy_kanban` WHERE `auth_des` = 'index/table' LIMIT 1 [ RunTime:0.000337s ] [ sql ] [ SQL ] SELECT * FROM `gmy_auth` WHERE `auth_des` LIKE '%order/getconfirmorder%' AND ( fen_id<>9 ) LIMIT 1 [ RunTime:0.000816s ]再看运行时间只有0.11秒,大约快了10倍,我们看下具体哪里快了,看连接数据库的时间0.001759s,这比改之前快了几百倍! 二、php性能优化 接下来我们来看下php性能的优化,Opcache和JIT,JIT在我的实际项目中体验是,开启JIT可以大略缩短100ms的时间,也就是0.1秒,现在php8都说性能很好,但是如果你不开启JIT,那么性能是体会不到的。 我们来看下,php5.6下如何开启Opcache,php5.6版本默认是安装过Opcache扩展的,在ext下找到php_opcache.dll,php8是需要自己手动下载安装这个扩展的,具体方法需要你自己去百度,接下来就是php.ini配置了,公众号里前面文章有讲过,感兴趣的可以去看看,这里不在多说,只是把opcache跑起来,配置如下: zend_extension = "D:\webroot\zzidcconf\php\php5.6\ext\php_opcache.dll" ; Determines if Zend OPCache is enabled opcache.enable=1 ; Determines if Zend OPCache is enabled for the CLI version of PHP opcache.enable_cli=1 ; The OPcache shared memory storage size. opcache.memory_consumption=128 ; The amount of memory for interned strings in Mbytes. opcache.interned_strings_buffer=8 ; The maximum number of keys (scripts) in the OPcache hash table. ; Only numbers between 200 and 100000 are allowed. opcache.max_accelerated_files=2000把上边的配置项该开启的开启,该添加的添加,有的需要修改的修改下,然后重启服务器web环境,测试phpinfo,如果看到下图,则代表opcache启动成功。 PHP配置图片 总结 本篇文章从两个方面谈了下如何提升接口响应速度,一个是数据库时间,一个是php自身的运行时间,不是特别复杂的点,如果你碰到了同样的情况,可以立马使用改善。最后,告诫自己,写代码的时候,一定要尽可能的一遍成,不要想着后期再来完善。一起加油!