PHP商品秒杀功能实现思路分析

https://blog.csdn.net/weixin_41380972/article/details/86242066

用户: 超大量, 正常/坏人

地域: 全国各地 [因为全国各地不同, 因此需要用cdn将服务发送到离用户最近的那个服务器]

业务流程: [前台]商品展示, 登记, [后台]数据输入, 数据处理

以下为架构方案 :

分为两个大层

一 用户比较关心, 用户看的见的层

1 商品展示层/页

2 用户登记层

二 后台层 / 数据持久化层

1 数据接入层

2 后续处理层

分层详解

一 页面状态

1 商品展示: 秒杀倒计时页面

2 秒杀进行中: 点击进入秒杀页面

3 秒杀活动结束: 提示活动已经结束

从用户的角度看 : 

秒杀倒计时和秒杀开始是利用linux定时任务和shell脚本来做

秒杀进行中到秒杀结束是利用php处理对应的服务器程序来做

二 页面状态

1 秒杀进行中: 秒杀登记页面

2 秒杀结束: 秒杀结束页面

流程图

数据持久化层

数据校验: 完成对数据 / 用户验证 (加密解密算法)

存入nosql队列: 去重复 / 排序数据 (redis有序集合)

检测商品最大数量: 提示活动已经结束 (技术标志位)

页面功能

数据持久化: 转存nosql数据到mysql数据库

干货

第一层: 商品展示层

知识点: 页面 / 服务器优化, cdn忘了加速, 隐藏跳转界面, 状态切换

linux 页面切换:

#!/bin/bash

# 看一下我们的shell脚本是否正常执行

date >> /var/www/ms/log.txt

#### 页面切换 ####

# 删除index.html秒杀等待页面

rm -rf '/var/www/ms/first.html'

# 将秒杀开始页面切换到秒杀等待页面

cp '/var/www/ms/first_begin.html' '/var/www/ms/first.html'

linux定时任务:

*/1 * * * * root /bin/51miao.sh #执行秒杀页面的定时任务代码

第二层: 秒杀进行和秒杀结束切换:

<?php

// 第一步:删除文件

$file_path = 'first.html';

$f = file_exists($file_path) && unlink($file_path);

$file_path = 'second.html';

$f = file_exists($file_path) && unlink($file_path);

// 第二步:生成自己需要的文件

$file_path = 'first.html';

$myfile = fopen($file_path, "w");

// 获取文件内容

$file_path = 'first_over.html';

$txt = file_get_contents($file_path);

fwrite($myfile, $txt);

$file_path = 'second.html';

$myfile = fopen($file_path, "w");

fwrite($myfile, $txt);

用户登记层

知识点: token加 / 解密, ajax跨域

$(function(){

//var url = 'http://www.maizi.net/miao%20sha%20prepare/level%203/';

//var url = 'http://www.miaosha_level3.net/';

var url = 'http://level3.5ihy.com/';

//check函数验证了手机号, 会员

function check(myphone,mynumber){

// TODO 添加你的检验规则

// 检测 手机号

var myphone_reg = /^(13[0-9]|15[7-9]|153|156|18[7-9])[0-9]{8}$/;

var myphone_reg_test = myphone_reg.test(myphone);

if(myphone_reg_test){

return true;

}

alert('你是输入信息有误');

return false;

}

/**** 跨域操作 ****/

$("#qianggou").click(function(){

var myphone = $("#myphone").val();

var mynumber = $("#mynumber").val();

var data = {'phone':myphone, 'number':mynumber};

var res = check(myphone,mynumber);

if(res){

$.ajax({

url:url,

data:data,

dataType:'jsonp',

jsonp:'callback',//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)

jsonpCallback:"success_jsonpCallback",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名

success:function(cc){

if(cc.msg=='ok'){

$.cookie('miao','ok');

}

},

error:function(){

$.cookie('miao',null);

},

timeout:300

});

}

});

// 检测是否秒杀了

var miao = $.cookie('miao');

//alert(miao);

if(miao){

alert('你已经成功登记');

//$("#qianggou").remove();

}

});

jquery的cookie封装

/**

* Cookie plugin

* Resources from http://down.liehuo.net

* Copyright (c) 2006 Klaus Hartl (stilbuero.de)

* Dual licensed under the MIT and GPL licenses:

* http://www.opensource.org/licenses/mit-license.php

* http://www.gnu.org/licenses/gpl.html

*

*/

/**

* Create a cookie with the given name and value and other optional parameters.

*

* @example $.cookie('the_cookie', 'the_value');

* @desc Set the value of a cookie.

* @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });

* @desc Create a cookie with all available options.

* @example $.cookie('the_cookie', 'the_value');

* @desc Create a session cookie.

* @example $.cookie('the_cookie', null);

* @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain

* used when the cookie was set.

*

* @param String name The name of the cookie.

* @param String value The value of the cookie.

* @param Object options An object literal containing key/value pairs to provide optional cookie attributes.

* @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.

* If a negative value is specified (e.g. a date in the past), the cookie will be deleted.

* If set to null or omitted, the cookie will be a session cookie and will not be retained

* when the the browser exits.

* @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).

* @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).

* @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will

* require a secure protocol (like HTTPS).

* @type undefined

*

* @name $.cookie

* @cat Plugins/Cookie

* @author Klaus Hartl/klaus.hartl@stilbuero.de

*/

/**

* Get the value of a cookie with the given name.

*

* @example $.cookie('the_cookie');

* @desc Get the value of a cookie.

*

* @param String name The name of the cookie.

* @return The value of the cookie.

* @type String

*

* @name $.cookie

* @cat Plugins/Cookie

* @author Klaus Hartl/klaus.hartl@stilbuero.de

*/

jQuery.cookie = function(name, value, options) {

if (typeof value != 'undefined') { // name and value given, set cookie

options = options || {};

if (value === null) {

value = '';

options.expires = -1;

}

var expires = '';

if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {

var date;

if (typeof options.expires == 'number') {

date = new Date();

date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));

} else {

date = options.expires;

}

expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE

}

// CAUTION: Needed to parenthesize options.path and options.domain

// in the following expressions, otherwise they evaluate to undefined

// in the packed version for some reason...

var path = options.path ? '; path=' + (options.path) : '';

var domain = options.domain ? '; domain=' + (options.domain) : '';

var secure = options.secure ? '; secure' : '';

document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');

} else { // only name given, get cookie

var cookieValue = null;

if (document.cookie && document.cookie != '') {

var cookies = document.cookie.split(';');

for (var i = 0; i < cookies.length; i++) {

var cookie = jQuery.trim(cookies[i]);

// Does this cookie string begin with the name we want?

if (cookie.substring(0, name.length + 1) == (name + '=')) {

cookieValue = decodeURIComponent(cookie.substring(name.length + 1));

break;

}

}

}

return cookieValue;

}

};

数据接入层

知识点: 数据校验, 存入队列, 商品数量检测

数据校验:

<?php

//1 基础数据准备

header("Access-Control-Allow-Origin: http://www.xxxx.net"); //防止 跨域

$data = array('msg'=>'false');// 初始数据准备

//2 库文件引入

include_once 'lib/Mrgister.class.php';

include_once 'lib/Mredis.class.php';

include_once 'lib/function.php';

//3 整理数据 获取前台数据输入

$I = new Mrgister();

$I->I();

//获取前台数据

$value = serialize($I->get_value());

//4 数据队列存储流程

$Mredis = new Mredis();

if($Mredis->set_vaule($value)=='overflow'){

sendOver();// 发送处理

}else{

$data = array('msg'=>'ok');

}

//5 返回结果到前台

$callback = $_GET['callback'];

echo $callback.'('.json_encode($data).')';

function.php

<?php

// 创建token 字符串

function create(){

$str = 1111;

$end = 9999;

$salt = array("L","J","S","H");

$str = rand($str,$end);// 5555 // L 23

$a = $str.$str%ord($salt[0]);// 5555. 21== 555521-555525-565656-

$str = rand($str,$end);

$b = $str.$str%ord($salt[1]);

$str = rand($str,$end);

$c = $str.$str%ord($salt[2]);

$str = rand($str,$end);

$d = $str.$str%ord($salt[3]);

return $a.'-'.$b.'-'.$c.'-'.$d;

}

// 验证字符串

function check($res){

$flag = true;

$salt = array("L","J","S","H");

$res = explode('-',$res);

foreach($res as $k => $v){

$v_start = substr($v,0,4);

$v_end = substr($v,4);

$v_new = $v_start-$v_end;

if($v_new%ord($salt[$k])){

$flag = false;

}

}

return $flag;

}

// 发送通知信息

function sendOver(){

$url_level1 = 'http://www.miaosha_level1.net/set_file.php';// 修改称为自己的触发位置

$url_level2 = 'http://www.miaosha_level2.net/set_file.php';// 修改称为自己的触发位置

$url_level4 = 'http://www.miaosha_level4.net/set_file.php';// 修改称为自己的触发位置

// 收录完成

// 触发第一层:

$ch = curl_init();//初始化 GET 方式

curl_setopt($ch, CURLOPT_URL, $url_level1);//设置选项,包括URL

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_HEADER, 0);

curl_exec($ch);//执行并获取HTML文档内容

curl_close($ch);//释放curl句柄

// 触发第二层:

$ch = curl_init();//初始化 GET 方式

curl_setopt($ch, CURLOPT_URL, $url_level2);//设置选项,包括URL

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_HEADER, 0);

curl_exec($ch);//执行并获取HTML文档内容

curl_close($ch);//释放curl句柄

// 触发第四层:

$ch = curl_init();//初始化 GET 方式

curl_setopt($ch, CURLOPT_URL, $url_level4);//设置选项,包括URL

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_HEADER, 0);

curl_exec($ch);//执行并获取HTML文档内容

curl_close($ch);//释放curl句柄

}

Mredis.class.php

<?php

class Mredis{

private $redis;

/* 这里替换为实例id和实例password */

// 基础参数准备

//protected $host = "125945f062c14ec1.m.cnsha.kvstore.aliyuncs.com";

protected $host = "127.0.0.1";

protected $port = 6379;

//protected $user = "125945f062c14ec1";

protected $pwd = "pwwd";

protected $max = 5;

protected $key = 'shop';

// 设置数据库

// 构造函数 连接数据库

public function __construct(){

$this->redis = new Redis();

$this->redis->connect($this->host, $this->port);

$this->redis->auth($this->key);

}

//插入数值

public function set_vaule($value){

// 设置基数标志位

if(!$this->redis->get('flag')){

$this->redis->set('flag',1);

}

// 检测溢出

if($this->redis->get('flag')>$this->max){

return 'overflow';

}

// 插入非重复数据

if($this->redis->zAdd($this->key,$this->redis->get('flag'),$value)){

$this->redis->incr("flag");

}

}

// 返回全部存储数据

public function get_value(){

return $this->redis->zRange($this->key,0,-1);

}

public function flushAll(){

return $this->redis->flushAll();

}

// 类结束了

}

Mrgister.class.php

<?php

class Mrgister{

// 数据

protected $phone=0;

protected $number=0;

// 数安全过滤

public function I(){

$this->phone = floatval(isset($_GET['phone'])?$_GET['phone']:0);

$this->number = (isset($_GET['number'])?$_GET['number']:0);

if(!($this->phone&&$this->number&&$this->check_number())){

die('input data wrong');

}

return $this;

}

// 验证输入码安全

public function check_number(){// 其实用一个生成规则就好了

//$this->number = '177022-879765-97143-979171';

$flag = true;

$salt = array("L","J","S","H");

$res = explode('-',$this->number);

foreach($res as $k => $v){

$v_start = substr($v,0,4);

$v_end = substr($v,4);

$v_new = $v_start-$v_end;

if($v_new%ord($salt[$k])){

$flag = false;

}

}

return $flag;

}

// 获取正确 数值

public function get_value(){

return array($this->phone,$this->number);

}

// 类结束了

}

数据处理层

知识点: 数据持久化

set_mysql.php

<?php

// 转存信息 进入到 mysql 进行数据持久化

// 基础准备

namespace jingshan;

header("Content-type:text/html;charset=utf8");

include_once 'lib/Mredis.class.php';

include_once 'lib/PdoMiao.class.php';

// 获取数据

$Mredis = new Mredis();

$v = $Mredis->get_value();

$v = array_map(function($a){

return unserialize($a);

},$v);

// 插入数据

$i = new PdoMiao();

$i->insert($v);

PdoMiao.class.php

<?php

class PdoMiao {

protected $config = array(

//'hostname' => 'rm-bp15c7k47tx39267r.mysql.rds.aliyuncs.com', // 服务器地址

'hostname' => '127.0.0.1',

'database' => 'jingshan', // 数据库名

'hostport' => '3306', // 端口

'charset' => 'utf8', // 数据库编码默认采用utf8

);

protected $dbn;

protected $user = 'root';

protected $pwd = 'H7oq5Io6';

// 远程连接

public function __construct(){

$dsn = $this->parseDsn($this->config);

try{

$this->dbn = new \PDO($dsn,$this->user,$this->pwd);

}catch (\PDOException $e){

echo 'Connection failed: '.$e->getMessage();

}

}

// dsn 组装

protected function parseDsn($config){

$dsn = 'mysql:dbname='.$config['database'].';host='.$config['hostname'];

if(!empty($config['hostport'])) {

$dsn .= ';port='.$config['hostport'];

}

if(!empty($config['charset'])){

$dsn .= ';charset='.$config['charset'];

}

return $dsn;

}

// 插入数据到数据库

Public function insert($datas){

$sql = "INSERT INTO `miaosha` ( `id` , `phone` , `number` ) VALUES ( NULL , ?, ? );";

$sth = $this->dbn->prepare($sql);

foreach($datas as $k => $v){

$sth->execute($v);

}

}

————————————————

版权声明:本文为CSDN博主「KWTIT」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_41380972/article/details/86242066