8天学会PHP之day7 实战:开发PV模块

 

这一章主要讲解PHP开发PV模块。前面花了6天时间,将PHP的方方面面都学了个遍,那么现在就来验证一下学习成果。

 

一、创建项目

我们的项目是前后端分离的,前端使用的是react框架,这里默认你会使用react框架。

创建服务端PHP项目

首先按照第四天所学,我们创建一个新laravel项目。

进入vagrant虚拟机的映射目录,创建一个名为blog的laravel项目。

由于网络问题,这个过程需要花费一些时间,我们可以先继续做别的事情。

修改Homestead.yaml配置文件。在sites选项中添加一个映射。

sites:
  - map: homestead.test
    to: /home/vagrant/code/Laravel/public
  - map: blog.test
    to: /home/vagrant/code/blog/public

修改hosts文件,添加一个路由映射。

192.168.10.10 blog.test

等待项目创建完成,重启homestead使配置生效,在Homestead根目录下执行vagrant reload --provision

创建前端React项目

为了简化学习成本,我们使用create-react-app来创建react项目。

扫描二维码关注公众号,回复: 7144652 查看本文章

这里假设你已经安装好了npm、yarn和create-react-app环境。

运行craete-react-app blog-ui命令,创建一个名为blog-ui的react项目。

推荐使用VSCode来编写前端代码。

 

二、业务需求分析

PV是什么?

PV(Page View)访问量, 即页面浏览量或点击量,衡量网站用户访问的网页数量;在一定统计周期内用户每打开或刷新一个页面就记录1次,多次打开或刷新同一页面则浏览量累计。

我们我们根据以上内容总结一下:

  1. 页面打开或刷新,PV+1

  2. 同一IP用户,在一定周期内不会多次统计PV量,防止刷PV。

那么我们需要做什么呢?

由于我的博客并不打算开放注册功能,所以采用ip来记录用户。

打开文章时,发送一个 http get 请求,这个请求只需要附带文章 id,而用户 ip 则通过后端来获取。后端将这一条数据记录到 redis 中,并从 redis 中取出 PV 数,作为返回值给请求方。前端接收到后展示在页面上。再每隔 30 秒从 redis 读取点赞数据写入数据库中做持久化存储。

你可能会有疑问,为什么不在获取文章数据的接口中写PV逻辑呢?这样做的唯一好处是能减少1个请求。坏处是无论在什么场景下,只要访问文章数据,都会记录PV。比如后台管理系统里面,访问获取文章数据的接口,仍然会记录PV,这样会让接口变得高度耦合,无法达到正常的复用。

针对这种情况,业界的解决方案是微服务,通过组合基础API服务来实现各种高级API服务。我们这里没有必要做得这么复杂。

三、数据格式设计

redis支持5种数据结构,分别为String、List、Set、Hash、Zset。

我们当前的业务需求可以使用最简单的String存储。predis有对应的api让我们像操作number一样来递增和递减这个String值。

四、后端代码编写

创建数据库

CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

创建表

CREATE TABLE `blog`.`Untitled`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `count` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

修改.env文件的数据库配置

DB_DATABASE=blog

在PHP项目中安装predis

composer require predis/predis

在RouteServiceProvider.php中添加版本前缀

protected function mapApiRoutes()
    {
        Route::prefix('api/v1')
             ->middleware('api')
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }

创建一个controller

php artisan make:controller PVController  --invokable

在里面写记录PV的逻辑。

在redis中维护2个值,一个是通过每个ip和文章id组合的用于检测是否在30秒内的值,另一个是每篇博客的PV值。

public function __invoke(Request $request, string $id)
{
    $ip = $request->ip();
    $timeout_key = $ip . '@' . $id;
    $blog_pv_id = 'pv@' . $id;
    if (Redis::exists($timeout_key)) {
        if (time() - Redis::get($timeout_key) > 30) {
            if (Redis::exists($blog_pv_id)) {
                Redis::incr($blog_pv_id);
                Redis::set($timeout_key, time());
            } else {
                Redis::set($blog_pv_id, 1);
            }
        }
    } else {
        Redis::set($timeout_key, time());
    }
    return Redis::get($blog_pv_id);
}

添加路由

Route::get('getPV/{id}', 'PVController');

至此,主要的逻辑代码已经完成。可以从浏览器访问,或者通过专业测试工具来测试这个接口。

 

五、前端代码编写

首先编写前端代码,安装一下路由和axios。

yarn add react-router-dom axios

在package.json文件中添加配置进行代理,解决一下跨域问题。

"proxy": "http://blog.test/api/v1"

在src下创建pages目录,在pages目录中创建Home页面和Blog页面

Home页面内容:

import React from "react";
import { Link } from "react-router-dom";
​
export default function() {
  return (
    <div>
      欢迎来到我的网站
      <BlogList></BlogList>
    </div>
  );
}
// 这里模拟一下数据
function BlogList() {
  return (
    <ul>
      <li>
        <Link to="/blog/1">博客1</Link>
      </li>
      <li>
        <Link to="/blog/2">博客2</Link>
      </li>
    </ul>
  );
}

Blog页面内容:

import React from "react";
import PV from "../components/PV";
​
export default function(props) {
  const id = props.match.params.id;
  return (
    <div>
      i'm blog {id}
      <PV id={id}></PV>
    </div>
  );
}

在src下创建components目录,在components目录下创建PV组件

PV组件内容:

import React, { Component } from "react";
import Axios from "axios";
​
async function getPV(id) {
  return await Axios.get(`/getPV/${id}`);
}
​
export default class PV extends Component {
  state = {
    count: 0
  };
​
  componentDidMount() {
    getPV(this.props.id).then(res => {
      setState({ count: res.data });
    });
  }
​
  render() {
    return <div>阅读: {this.state.count}</div>;
  }
}

在根目录创建router.js文件

import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import Home from "./pages/Home";
import Blog from "./pages/Blog";
​
export default function() {
  return (
    <BrowserRouter>
      <Route path="/" exact component={Home}></Route>
      <Route path="/blog/:id" component={Blog}></Route>
    </BrowserRouter>
  );
}

修改App.js文件。

import React from "react";
import Router from "./router";
​
function App() {
  return <Router></Router>;
}
​
export default App;

运行yarn start启动

访问http://localhost:3000/,点击进入一个博客.就能看到浏览量了。

 

六、redis定时同步到Mysql

创建定时任务命令。在app/Console下创建Commands文件夹。从中创建PVSynchronization.php文件

内容如下:

<?php
​
namespace App\Console\Commands;
​
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
​
class PVSynchronization extends Command
{
    // 自定义命令
    protected $signature = 'pv-synchronization';
    // 命令说明
    protected $description = 'Timing from PV volume of redis synchronization blog to MySQL';
​
    // 该命令对应的执行代码
    public function handle()
    {
        $keys = Redis::keys('pv@*');
        foreach ($keys as $key) {
            // 这里截取掉前17个字符是因为 predis存储的键,自带 laravel_database_ 前缀,需要截掉
            $p_redis_key = substr($key, 17, mb_strlen($key));
            $count = Redis::get($p_redis_key);
            // 这里截取前3个字符是 pv@
            DB::update('update pv set id = ?, count = ?',
                [substr($p_redis_key, 3, mb_strlen($p_redis_key)), $count]);
        }
    }
}

注册定时任务

在app/Console/Commands/Kernel.php文件中,找到schedule方法,把我们定义的任务加入进去

protected function schedule(Schedule $schedule)
{
    // everyMinute 表示每 1 分钟执行一次。
    $schedule->command('pv-synchronization')->everyMinute();
}

运行 php artisan list命令来检测是否注册成功,看到我们自己定义的命令就意味着注册成功了。

最后,运行php artisan schedule:run,就可以让这个定时任务运行起来。

至此,一个RESTful服务程序已经运行起来了。但是由于时间原因,一些细节设计的并不是很完美。比如redis的设计,由于每个文章id都产生一个键值,这样存取是方便了,但是在用正则索引键时,会耗费非常大的性能。还有如果文章足够多,在定时任务中就会执行非常多的update语句,这会造成性能问题,解决方法是使用批量update。你可以按照我说的方式,或者按照自己的思考,对代码进行优化。这对提升技术是非常有帮助的。最后,我们要明白一个道理:好的代码的就像粥,需要花时间来熬。如果只给我们半天时间来做这个功能,估计就会用这个实现业务的版本发布到生产环境上。刚开始可能没有问题,但随着时间推移,数据逐渐增多,这个接口就会变成一个慢接口。如果是在业务非常复杂的系统中,其他接口依赖这个数据,就可能会导致数据不同步等问题。

 

七、总结

经过 6 天的辛苦学习,终于从零开始,学会了使用PHP开发RESTful服务端程序。如果你从第0天开始和我一起学习,并且花费大量时间来练习的话,我可以明确告诉你,你只要再看一些面试题,就可以去面试PHP工程师了。

猜你喜欢

转载自www.cnblogs.com/luzhenqian/p/11441218.html