大雄 https://199508.com/ 时光百转千回 en-us Thu, 21 Feb 2019 10:08:06 GMT 前端设计模式 2037 1、模块模式

在立即执行函数表达式中定义的变量和方法,在该函数外部是访问不到的,只能通过该函数提供的接口,"有限制的"进行访问;通过函数的作用域,解决了属性和方法的封装问题。 最常见的立即执行函数写法有以下两种:

(function(){ /* code */ }())

或者

(function(){ /* code */ })()

模块模式代码:

let Person = (function () {
    var age = "12";
    var name = "jerry";

    function getAge() {
        return age;
    }

    function getName() {
        return name;
    }
    return {
        getAge: getAge,
        getName: getName
    }
})()
console.log(age, 'age'); // 报错: Uncaught ReferenceError: age is not defined
console.log(name, 'name'); // 空字符串,为啥不报错?看底部备注
console.log(Person.age); // undefined
console.log(Person.name); // undefined
// 只能通过Person提供的接口访问相应的变量
console.log(Person.getName()); // jerry
console.log(Person.getAge()); // 12

2、构造函数模式

function Person(name, age) {
     this.name = name;
     this.age = age;
 }
 Person.prototype.printName = function () {
     console.log(this.name)
 }
 Person.prototype.printAge = function () {
     console.log(this.age)
 }

 function Student(name, age) {
     // 继承 Person 的属性
     Person.call(this, name, age)
 }

 function create(prototype) {
     function F() {}
     F.prototype = prototype
     return new F()
 }
 // 让Student的原型指向一个对象,该对象的原型指向了Person.prototype,通过这种方式继承 Person 的方法
 Student.prototype = create(Person.prototype)
 Student.prototype.printAge = function () {
     console.log(this.age)
 }
 let student = new Student('jerry', 12)
 student.printName() // "jerry"
 student.printAge() // "12"

3、混合模式

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.printName = function () {
    console.log(this.name)
}

function Student(name, age) {
    // 继承 Person 的属性
    Person.call(this, name, age)
}

function create(prototype) {
    function F() {}
    F.prototype = prototype
    return new F()
}
// 让Student的原型指向一个对象,该对象的原型指向了Person.prototype,通过这种方式继承 Person 的方法
Student.prototype = create(Person.prototype)
Student.prototype.printAge = function () {
    console.log(this.age)
}
let student = new Student('jerry', 12)
student.printName() // "jerry"
student.printAge() // 12

4、工厂模式

function Person(name, age) {
    let person = new Object()
    person.name = name
    person.age = age
    person.printName = function () {
        console.log(this.name)
    }
    person.printAge = function () {
        console.log(this.age)
    }
    return person
}
let person = Person('jerry', 12)
person.printName()
person.printAge()

5、单例模式

let Singleton = (function () {
     let instantiated

     function init() {
         /*定义单例代码*/
         return {
             publicMethod: function () {
                 console.log("Hello World");
             },
             publicProperty: "Test"
         }
     }
     return {
         getInstance: function () {
             if (!instantiated) {
                 instantiated = init()
             }
             return instantiated
         }
     }
 }())
 Singleton.getInstance().publicMethod()

单例之间的通讯: 建立两个独立的对象:jim&&lily,两者之间通过door直接通讯,如果没有新建door,有直接通讯。代码如下:

let jim = (function (argument) {
     let door
     let jimHome = function (msg) {
         this.doorbell = msg
     }
     let info = {
         sendMessage: function (msg) {
             if (!door) {
                 door = new jimHome(msg)
             }
             return door
         },
         coming: function () {
             return "來了來了"
         }
     }
     return info
 }())
 let lily = {
     callJim: function (msg) {
         let _xw = jim.sendMessage(msg)
         alert(_xw.doorbell)
         console.log(_xw.doorbell)
         _xw = null // 等待垃圾回收
         let coming = jim.coming()
         console.log(coming)
     }
 }
 lily.callJim("叮咙")

6、发布-订阅模式

订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身主题变化时,会通知所有订阅者对象,使他们能够自动更新自己的状态。

将一个系统分割成一系列相互协作的类有一个很不好的副作用:需要维护相应对象间的一致性,这样会给维护、扩展和重用都带来不便。当一个对象的改变需要同时改变其他对象,而且他不知道具体有多少对象需要改变时,此时建议使用订阅发布模式。

应用场景:

DOM事件。DOM事件是一种典型的发布-订阅模式,对一个DOM节点的DOM事件进行监听;当操作DOM节点时,触发相应的事件并执行函数。

自定义时间。指定发布者,类似于一个对象(key:value);key表示事件的名称,value是一个数组;发布消息后,遍历value的数组,依次执行订阅者的回调函数。

应用Demo如下:

 let Event = (function () {
    var events = {}

    function on(evt, handler) {
        events[evt] = events[evt] || [];
        events[evt].push({
            handler: handler
        })
    }

    function fire(evt, args) {
        if (!events[evt]) {
            return
        }
        for (var i = 0; i < events[evt].length; i++) {
            events[evt][i].handler(args)
        }
    }

    function off(evt) {
        delete events[evt]
    }
    return {
        on: on,
        fire: fire,
        off: off
    }
}())
Event.on('change', function (val) {
    console.log('change事件,value is' + val)
})
Event.on('click', function (val) {
    console.log('click事件,value is ' + val)
})
Event.fire('change', 'jerry1')
Event.fire('click', 'jerry2')
Event.off('change')

备注:console.log(name, 'name')没有报错,是因为name是浏览器的窗口变量名,已存在于浏览器内部。

]]>
Thu, 21 Feb 2019 00:06:03 GMT 2037
每个人身上的世界 2036

每一个人,身上都拖着一个世界,由他所见过、爱过的一切所组成的世界。 戏子多秋,可怜一处情深旧。满座衣冠皆老朽,黄泉故事无止休。戏无骨难左右,换过一折又重头,只道最是人间不能留。

]]>
Tue, 19 Feb 2019 09:30:05 GMT 2036
运用JS设置cookie、读取cookie、删除cookie 2035 JavaScript是运行在客户端的脚本,因此一般是不能够设置Session的,因为Session是运行在服务器端的。

而cookie是运行在客户端的,所以可以用JS来设置cookie.

假设有这样一种情况,在某个用例流程中,由A页面跳至B页面,若在A页面中采用JS用变量temp保存了某一变量的值,在B页面的时候,同样需要使用JS来引用temp的变量值,对于JS中的全局变量或者静态变量的生命周期是有限的,当发生页面跳转或者页面关闭的时候,这些变量的值会重新载入,即没有达到保存的效果。解决这个问题的最好的方案是采用cookie来保存该变量的值,那么如何来设置和读取cookie呢?

首先需要稍微了解一下cookie的结构,简单地说:cookie是以键值对的形式保存的,即key=value的格式。各个cookie之间一般是以“;”分隔。

JS设置cookie:

假设在A页面中要保存变量username的值("jack")到cookie中,key值为name,则相应的JS代码为:

document.cookie="name="+username;

JS读取cookie:

假设cookie中存储的内容为:name=jack;password=123

则在B页面中获取变量username的值的JS代码如下:

var username=document.cookie.split(";")[0].split("=")[1];

JS操作cookies方法

//写cookies 

function setCookie(name,value) 
{ 
    var Days = 30; 
    var exp = new Date(); 
    exp.setTime(exp.getTime() + Days*24*60*60*1000); 
    document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString(); 
} 

//读取cookies 
function getCookie(name) 
{ 
    var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");

    if(arr=document.cookie.match(reg))

        return unescape(arr[2]); 
    else 
        return null; 
} 

//删除cookies 
function delCookie(name) 
{ 
    var exp = new Date(); 
    exp.setTime(exp.getTime() - 1); 
    var cval=getCookie(name); 
    if(cval!=null) 
        document.cookie= name + "="+cval+";expires="+exp.toGMTString(); 
} 

使用示例

setCookie("name","hayden"); 
alert(getCookie("name")); 

如果需要设定自定义过期时间 那么把上面的setCookie 函数换成下面两个函数就ok;

程序代码

function setCookie(name,value,time)
{ 
    var strsec = getsec(time); 
    var exp = new Date(); 
    exp.setTime(exp.getTime() + strsec*1); 
    document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString(); 
} 
function getsec(str)
{ 
   alert(str); 
   var str1=str.substring(1,str.length)*1; 
   var str2=str.substring(0,1); 
   if (str2=="s")
   { 
        return str1*1000; 
   }
   else if (str2=="h")
   { 
       return str1*60*60*1000; 
   }
   else if (str2=="d")
   { 
       return str1*24*60*60*1000; 
   } 
} 

这是有设定过期时间的使用示例:

s20是代表20秒 h是指小时,如12小时则是:h12 d是天数,30天则:d30

setCookie("name","hayden","s20");

]]>
Sun, 18 Feb 2018 20:03:39 GMT 2035
ThinkPHP5使用jwt进行会话验证 2034 以往,没有做过前后端分离的项目之前,都是服务器渲染的模板,然后用cookie和session进行账号的权限验证或者是登录状态的管理。后来接触了vue和小程序之后,在进行前后端分离的时候,就会遇到权限验证和登录会话保存。因为HTTP协议是开放的,可以任人调用。所以,如果接口不希望被随意调用,就需要做访问权限的控制,认证是好的用户,才允许调用API。

JWT优点

1:服务端不需要保存传统会话信息,没有跨域传输问题,减小服务器开销。

2:jwt构成简单,占用很少的字节,便于传输。

3:json格式通用,不同语言之间都可以使用。

jwt由三部分组成:

头部(header) 载荷(payload) 包含一些定义信息和自定义信息 签证(signature)

所以这里就会用到bearer的令牌访问,就是jwt;定义:为了验证使用者的身份,需要客户端向服务器端提供一个可靠的验证信息,称为Token,这个token通常由Json数据格式组成,通过hash散列算法生成一个字符串,所以称为Json Web Token(Json表示令牌的原始值是一个Json格式的数据,web表示是在互联网传播的,token表示令牌,简称JWT)

首先我们从GitHub处用composer require firebase/php-jwt下载firebase/php-jwt,怎么用composer我就不累述了,我过去的文章里面有安装教程。

安装好了之后,我们可以新建一个user控制来测试代码的完整性,首先我们创建三个控制方法

Base.php

基础控制器base.php主要是用来验证每次接受请求的时候,验证http请求头里面是否携带了token,如何将token放到请求头里面,这个前端会做的了。

<?php
/**
 * Created by PhpStorm.
 * User: nobita
 * Date: 2/15
 * Time: 14:55
 */

namespace app\user\controller;

use think\Request;
use Firebase\JWT\JWT;

use think\Controller;

class Base extends Controller
{
    public function _initialize()
    {
        parent::_initialize();
        $this->checkToken();
    }

    public function checkToken()
    {
        $header = Request::instance()->header();
        if ($header['authorization'] == 'null'){
            echo json_encode([
                'status' => 1002,
                'msg' => 'Token不存在,拒绝访问'
            ]);
            exit;
        }else{
            $checkJwtToken = $this->verifyJwt($header['authorization']);
            if ($checkJwtToken['status'] == 1001) {
                return true;
            }
        }
    }

    //校验jwt权限API
    protected function verifyJwt($jwt)
    {
        $key = md5('nobita');
        // JWT::$leeway = 3;
        try {
            $jwtAuth = json_encode(JWT::decode($jwt, $key, array('HS256')));
            $authInfo = json_decode($jwtAuth, true);
            $msg = [];
            if (!empty($authInfo['user_id'])) {
                $msg = [
                    'status' => 1001,
                    'msg' => 'Token验证通过'
                ];
            } else {
                $msg = [
                    'status' => 1002,
                    'msg' => 'Token验证不通过,用户不存在'
                ];
            }
            return $msg;
        } catch (\Firebase\JWT\SignatureInvalidException $e) {
            echo json_encode([
                'status' => 1002,
                'msg' => 'Token无效'
            ]);
            exit;
        } catch (\Firebase\JWT\ExpiredException $e) {
            echo json_encode([
                'status' => 1003,
                'msg' => 'Token过期'
            ]);
            exit;
        } catch (Exception $e) {
            return $e;
        }
    }
}

Login.php

登录控制器,只要是用来验证用户输入的账号密码是否匹配数据库的信息,如果匹配的话,就申请token,并且返回token给前端储存在本地,每次请求的时候把token假如到请求头里面

<?php
/**
 * Created by PhpStorm.
 * User: nobita
 * Date: 2/15
 * Time: 14:55
 */

namespace app\user\controller;

use app\common\model\nobita\Test as TestModel;

use Firebase\JWT\JWT;

class Login
{
    public function index()
    {
        $data = input('post.');
        $username = htmlspecialchars($data['username']);
        $password = htmlspecialchars($data['password']);
        $user = TestModel::where('username', $username)->find();
        if (!empty($user)) {
            if ($username === $user['username'] && $password === $user['password']) {
                $msg = [
                    'status' => 1001,
                    'msg' => '登录成功',
                    'jwt' => self::createJwt($user['id'])
                ];
                return $msg;
            } else {
                return [
                    'status' => 1002,
                    'msg' => '账号密码错误'
                ];
            }
        } else {
            return [
                'status' => 1002,
                'msg' => '请输入账号密码'
            ];
        }
    }

    public function createJwt($userId)
    {
        $key = md5('nobita'); //jwt的签发密钥,验证token的时候需要用到
        $time = time(); //签发时间
        $expire = $time + 14400; //过期时间
        $token = array(
            "user_id" => $userId,
            "iss" => "https://199508.com",//签发组织
            "aud" => "https://199508.com", //签发作者
            "iat" => $time,
            "nbf" => $time,
            "exp" => $expire
        );
        $jwt = JWT::encode($token, $key);
        return $jwt;
    }
}

User.php

用来验证代码的完整性

<?php
/**
 * Created by PhpStorm.
 * User: nobita
 * Date: 2/15
 * Time: 15:24
 */

namespace app\user\controller;

use think\Request;

use app\common\model\nobita\Test as TestModel;

class User extends Base //继承基础控制器
{
    public function index()
    {
        return TestModel::all();
    }
}
]]>
Sat, 16 Feb 2019 11:01:59 GMT 2034
MVC框架自定义隐藏index.php入口文件伪静态 2033 PHP:

public function initPath(){
        $path = $_SERVER['PATH_INFO'];
        $lastFix = strrchr($path,'.');
        $path = str_replace($lastFix,'',$path);
        $path = substr($path,1);
        $array = explode('/',$path);
        $lenght = count($array);
        if ($lenght == 1){
            $_GET['m'] = $array[0];
        }elseif ($lenght == 2){
            $_GET['m'] = $array[0];
            $_GET['c'] = $array[1];
        }elseif ($lenght == 3){
            $_GET['m'] = $array[0];
            $_GET['c'] = $array[1];
            $_GET['a'] = $array[2];
        }else{
            $_GET['m'] = $array[0];
            $_GET['c'] = $array[1];
            $_GET['a'] = $array[2];
            for ($i=3;$i<$lenght;$i+=2){
                $_GET[$array[$i]] = $array[$i+1];
            }
        }
    }

Nginx:

if (!-d $request_filename){
    set $rule_0 1$rule_0;
}
if (!-f $request_filename){
    set $rule_0 2$rule_0;
}
if ($rule_0 = "21"){
    rewrite /(.*) /index.php/$1;
}

Apache:

<IFModule rewrite_module>
    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f

    RewriteRule (.*) index.php/$1

</IFModule>
]]>
Mon, 12 Feb 2018 13:55:22 GMT 2033
PHP原生操作redis 2032 Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 Redis支持的数据类型有 Stirng(字符串), List(列表), Hash(字典), Set(集合), Sorted Set(有序集合); redis版本是Redis 2.6.12 系统是在Windows+Apache2.4+php5.6 连接:

//实例化redis
$redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//检测是否连接成功
echo "Server is running: " . $redis->ping();
// 输出结果 Server is running: +PONG    
Strng(字符串):
// 设置一个字符串的值
$redis->set('cat', 111);
//获取一个字符串的值
echo $redis->get('cat'); // 111
// 重复set
$redis->set('cat', 222);
echo $redis->get('cat'); // 222
List(列表):
//列表
//存储数据到列表中
$redis->lpush('list', 'html');
$redis->lpush('list', 'css');
$redis->lpush('list', 'php');

//获取列表中所有的值
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>'; 

//从右侧加入一个
$redis->rpush('list', 'mysql');
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>';

//从左侧弹出一个
$redis->lpop('list');
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>';

//从右侧弹出一个
$redis->rpop('list');
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>';

// 结果
// Array ( [0] => php [1] => css [2] => html )
// Array ( [0] => php [1] => css [2] => html [3] => mysql )
// Array ( [0] => css [1] => html [2] => mysql )
// Array ( [0] => css [1] => html ) 
<?php
//实例化redis
$redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//列表
//存储数据到列表中
$redis->lpush('list', 'html');
$redis->lpush('list', 'css');
$redis->lpush('list', 'php');
$redis->lpush('list', 'mysql');
$redis->lpush('list', 'javascript');
$redis->lpush('list', 'ajax');

//获取列表中所有的值
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>'; 

//获取列表的长度
$length = $redis->lsize('list');
echo $length;echo '<br>';

//返回列表key中index位置的值
echo $redis->lget('list', 2);echo '<br>';
echo $redis->lindex('list', 2);echo '<br>';

//设置列表中index位置的值
echo $redis->lset('list', 2, 'linux');echo '<br>';
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>';

//返回key中从start到end位置间的元素
$list = $redis->lrange('list', 0, 2);
print_r($list);echo '<br>';

$list = $redis->lgetrange('list', 0, 2);
print_r($list);echo '<br>';

//截取链表中start到end的元素
//截取列表后列表发生变化,列表保留截取的元素,其余的删除
$list = $redis->ltrim('list', 0, 1);
print_r($list);echo '<br>';

$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>';
// 结果
// Array ( [0] => ajax [1] => javascript [2] => mysql [3] => php [4] => css [5] => html )
// 6
// mysql
// mysql
// 1
// Array ( [0] => ajax [1] => javascript [2] => linux [3] => php [4] => css [5] => html )
// Array ( [0] => ajax [1] => javascript [2] => linux )
// Array ( [0] => ajax [1] => javascript [2] => linux )
// 1
// Array ( [0] => ajax [1] => javascript )  
<?php
    //实例化redis
    $redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//列表
//存储数据到列表中
$redis->lpush('list', 'html');
$redis->lpush('list', 'html');
$redis->lpush('list', 'html');
$redis->lpush('list', 'css');
$redis->lpush('list', 'php');
$redis->lpush('list', 'mysql');
$redis->lpush('list', 'javascript');
$redis->lpush('list', 'html');
$redis->lpush('list', 'html');
$redis->lpush('list', 'html');
$redis->lpush('list', 'ajax');
//获取列表中所有的值
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>'; 

//删除列表中count个值为value的元素
//从左向右删
$redis->lrem('list', 'html', 2);
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>'; 

//从右向左删
$redis->lrem('list', 'html', -2);
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>'; 

//删除所有
$redis->lrem('list', 'html', 0);
$list = $redis->lrange('list', 0, -1);
print_r($list);echo '<br>';

// 结果
// Array ( [0] => ajax [1] => html [2] => html [3] => html [4] => javascript [5] => mysql [6] => php [7] => css [8] => html [9] => html [10] => html )
// Array ( [0] => ajax [1] => html [2] => javascript [3] => mysql [4] => php [5] => css [6] => html [7] => html [8] => html )
// Array ( [0] => ajax [1] => html [2] => javascript [3] => mysql [4] => php [5] => css [6] => html )
// Array ( [0] => ajax [1] => javascript [2] => mysql [3] => php [4] => css )     
Hash(字典):
<?php
    //实例化redis
    $redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//字典
//给hash表中某个key设置value
//如果没有则设置成功,返回1,如果存在会替换原有的值,返回0,失败返回0
echo $redis->hset('hash', 'cat', 'cat');echo '<br>';
echo $redis->hset('hash', 'cat', 'cat');echo '<br>';
echo $redis->hset('hash', 'cat', 'cat1');echo '<br>';
echo $redis->hset('hash', 'dog', 'dog');echo '<br>';
echo $redis->hset('hash', 'bird', 'bird');echo '<br>';
echo $redis->hset('hash', 'monkey', 'monkey');echo '<br>';
//获取hash中某个key的值
echo $redis->hget('hash', 'cat');echo '<br>';

//获取hash中所有的keys
$arr = $redis->hkeys('hash');
print_r($arr);echo '<br>';

//获取hash中所有的值 顺序是随机的
$arr = $redis->hvals('hash');
print_r($arr);echo '<br>';

//获取一个hash中所有的key和value 顺序是随机的
$arr = $redis->hgetall('hash');
print_r($arr);echo '<br>';

//获取hash中key的数量
echo $redis->hlen('hash');echo '<br>';

//删除hash中一个key 如果表不存在或key不存在则返回false
echo $redis->hdel('hash', 'dog');echo '<br>';
var_dump($redis->hdel('hash', 'rabbit'));echo '<br>';

// 结果
// 1
// 0
// 0
// 1
// 1
// 1
// cat1
// Array ( [0] => cat [1] => dog [2] => bird [3] => monkey )
// Array ( [0] => cat1 [1] => dog [2] => bird [3] => monkey )
// Array ( [cat] => cat1 [dog] => dog [bird] => bird [monkey] => monkey )
// 4
// 1
// int(0) 
<?php
    //实例化redis
    $redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//字典
//批量设置多个key的值
$arr = [1=>1, 2=>2, 3=>3, 4=>4, 5=>5];
$redis->hmset('hash', $arr);
print_r($redis->hgetall('hash'));echo '<br>';

// 批量获得额多个key的值
$arr = [1, 2, 3, 5];
$hash = $redis->hmget('hash', $arr);
print_r($hash);echo '<br>';

//检测hash中某个key知否存在
echo $redis->hexists('hash', '1');echo '<br>';
var_dump($redis->hexists('hash', 'cat'));echo '<br>';

print_r($redis->hgetall('hash'));echo '<br>';

//给hash表中key增加一个整数值
$redis->hincrby('hash', '1', 1);
print_r($redis->hgetall('hash'));echo '<br>';

//给hash中的某个key增加一个浮点值
$redis->hincrbyfloat('hash', 2, 1.3);
print_r($redis->hgetall('hash'));echo '<br>';

//结果
// Array ( [1] => 1 [2] => 2 [3] => 3 [4] => 4 [5] => 5 )
// Array ( [1] => 1 [2] => 2 [3] => 3 [5] => 5 )
// 1
// bool(false)
// Array ( [1] => 1 [2] => 2 [3] => 3 [4] => 4 [5] => 5 )
// Array ( [1] => 2 [2] => 2 [3] => 3 [4] => 4 [5] => 5 )
// Array ( [1] => 2 [2] => 3.3 [3] => 3 [4] => 4 [5] => 5 ) 
Set(集合):
<?php
    //实例化redis
    $redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//集合
// 添加一个元素
echo $redis->sadd('set', 'cat');echo '<br>';
echo $redis->sadd('set', 'cat');echo '<br>';
echo $redis->sadd('set', 'dog');echo '<br>';
echo $redis->sadd('set', 'rabbit');echo '<br>';
echo $redis->sadd('set', 'bear');echo '<br>';
echo $redis->sadd('set', 'horse');echo '<br>';

// 查看集合中所有的元素
$set = $redis->smembers('set');
print_r($set);echo '<br>';

//删除集合中的value
echo $redis->srem('set', 'cat');echo '<br>';
var_dump($redis->srem('set', 'bird'));echo '<br>';

$set = $redis->smembers('set');
print_r($set);echo '<br>';

//判断元素是否是set的成员
var_dump($redis->sismember('set', 'dog'));echo '<br>';
var_dump($redis->sismember('set', 'bird'));echo '<br>';

//查看集合中成员的数量
echo $redis->scard('set');echo '<br>';

//移除并返回集合中的一个随机元素(返回被移除的元素)
echo $redis->spop('set');echo '<br>';

print_r($redis->smembers('set'));echo '<br>';

// 结果
// 1
// 0
// 1
// 1
// 1
// 1
// Array ( [0] => rabbit [1] => cat [2] => bear [3] => dog [4] => horse )
// 1
// int(0)
// Array ( [0] => dog [1] => rabbit [2] => horse [3] => bear )
// bool(true)
// bool(false)
// 4
// bear
// Array ( [0] => dog [1] => rabbit [2] => horse ) 
<?php
    //实例化redis
    $redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//集合
$redis->sadd('set', 'horse');
$redis->sadd('set', 'cat');
$redis->sadd('set', 'dog');
$redis->sadd('set', 'bird');
$redis->sadd('set2', 'fish');
$redis->sadd('set2', 'dog');
$redis->sadd('set2', 'bird');

print_r($redis->smembers('set'));echo '<br>';
print_r($redis->smembers('set2'));echo '<br>';

//返回集合的交集
print_r($redis->sinter('set', 'set2'));echo '<br>';

//执行交集操作 并结果放到一个集合中
$redis->sinterstore('output', 'set', 'set2');
print_r($redis->smembers('output'));echo '<br>';

//返回集合的并集
print_r($redis->sunion('set', 'set2'));echo '<br>';

//执行并集操作 并结果放到一个集合中
$redis->sunionstore('output', 'set', 'set2');
print_r($redis->smembers('output'));echo '<br>';

//返回集合的差集
print_r($redis->sdiff('set', 'set2'));echo '<br>';

//执行差集操作 并结果放到一个集合中
$redis->sdiffstore('output', 'set', 'set2');
print_r($redis->smembers('output'));echo '<br>';

// 结果
// Array ( [0] => cat [1] => dog [2] => bird [3] => horse )
// Array ( [0] => bird [1] => dog [2] => fish )
// Array ( [0] => bird [1] => dog )
// Array ( [0] => dog [1] => bird )
// Array ( [0] => cat [1] => dog [2] => bird [3] => horse [4] => fish )
// Array ( [0] => cat [1] => dog [2] => bird [3] => horse [4] => fish )
// Array ( [0] => horse [1] => cat )
// Array ( [0] => horse [1] => cat ) 
Sorted Set(有序集合):
<?php
    //实例化redis
    $redis = new Redis();
//连接
$redis->connect('127.0.0.1', 6379);
//有序集合
//添加元素
echo $redis->zadd('set', 1, 'cat');echo '<br>';
echo $redis->zadd('set', 2, 'dog');echo '<br>';
echo $redis->zadd('set', 3, 'fish');echo '<br>';
echo $redis->zadd('set', 4, 'dog');echo '<br>';
echo $redis->zadd('set', 4, 'bird');echo '<br>';

//返回集合中的所有元素
print_r($redis->zrange('set', 0, -1));echo '<br>';
print_r($redis->zrange('set', 0, -1, true));echo '<br>';

//返回元素的score值
echo $redis->zscore('set', 'dog');echo '<br>';

//返回存储的个数
echo $redis->zcard('set');echo '<br>';

//删除指定成员
$redis->zrem('set', 'cat');
print_r($redis->zrange('set', 0, -1));echo '<br>';

//返回集合中介于min和max之间的值的个数
print_r($redis->zcount('set', 3, 5));echo '<br>';

//返回有序集合中score介于min和max之间的值
print_r($redis->zrangebyscore('set', 3, 5));echo '<br>';
print_r($redis->zrangebyscore('set', 3, 5, ['withscores'=>true]));echo '<br>';

//返回集合中指定区间内所有的值
print_r($redis->zrevrange('set', 1, 2));echo '<br>';
print_r($redis->zrevrange('set', 1, 2, true));echo '<br>';

//有序集合中指定值的socre增加
echo $redis->zscore('set', 'dog');echo '<br>';
$redis->zincrby('set', 2, 'dog');
echo $redis->zscore('set', 'dog');echo '<br>';

//移除score值介于min和max之间的元素
print_r($redis->zrange('set', 0, -1, true));echo '<br>';
print_r($redis->zremrangebyscore('set', 3, 4));echo '<br>';
print_r($redis->zrange('set', 0, -1, true));echo '<br>';

//结果
// 1
// 0
// 0
// 0
// 0
// Array ( [0] => cat [1] => fish [2] => bird [3] => dog )
// Array ( [cat] => 1 [fish] => 3 [bird] => 4 [dog] => 4 )
// 4
// 4
// Array ( [0] => fish [1] => bird [2] => dog )
// 3
// Array ( [0] => fish [1] => bird [2] => dog )
// Array ( [fish] => 3 [bird] => 4 [dog] => 4 )
// Array ( [0] => bird [1] => fish )
// Array ( [bird] => 4 [fish] => 3 )
// 4
// 6
// Array ( [fish] => 3 [bird] => 4 [dog] => 6 )
// 2
// Array ( [dog] => 6 )
]]>
Mon, 12 Feb 2018 13:54:26 GMT 2032
使用MySQL处理百万级以上数据时,不得不知道的几个常识 2031

经测试对一个包含400多万条记录的表执行一条件查询,其查询时间竟然高达40几秒,相信这么高的查询延时,任何用户都会抓狂。因此如何提高sql语句查询效率,显得十分重要。以下是结合网上流传比较广泛的几个查询语句优化方法:

首先,数据量大的时候,应尽量避免全表扫描,应考虑在 where及 order by 涉及的列上建立索引,建索引可以大大加快数据的检索速度。但是,有些情况索引是不会起效的:

1、应尽量避免在 where子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

2、应尽量避免在 where子句中对字段进行 null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0

3、尽量避免在 where子句中使用 or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20

4、下面的查询也将导致全表扫描:

select id from t where name like '%abc%'
若要提高效率,可以考虑全文检索。

5、in和 not in也要慎用,否则会导致全表扫描,如:

select id from t where num in(1,2,3)
对于连续的数值,能用 between就不要用 in了:
select id from t where num between 1 and 3

6、如果在 where子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num

7、应尽量避免在 where子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2=100
应改为:
select id from t where num=100*2

8、应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3)='abc'–name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30′)=0–'2005-11-30'生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'

9、不要在 where子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

10、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

11、不要写一些没有意义的查询,如需要生成一个空表结构:

select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)

12、很多时候用 exists代替 in是一个好的选择:

select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)

建索引需要注意的地方:

1、并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段 sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

2、索引并不是越多越好,索引固然可以提高相应的 select的效率,但同时也降低了 insert及 update的效率,因为 insert或 update时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

3、应尽可能的避免更新 clustered索引数据列,因为 clustered索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered索引数据列,那么需要考虑是否应将该索引建为 clustered索引。

其他需要注意的地方:

1、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

2、任何地方都不要使用 select * from t,用具体的字段列表代替"*",不要返回用不到的任何字段。

3、尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

4、避免频繁创建和删除临时表,以减少系统表资源的消耗。

5、临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

6、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into代替 create table,避免造成大量 log,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

7、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table,然后 drop table,这样可以避免系统表的较长时间锁定。

8、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

9、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

10、与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

11、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON,在结束时设置 SET NOCOUNT OFF。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC消息。

12、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

13、尽量避免大事务操作,提高系统并发能力。

]]>
Tue, 12 Feb 2019 13:54:00 GMT 2031
Thinkphp结合composer实现smtp发送邮件 2030 发送邮件也算是网站的常用功能之一,相信很多人已经在网上找到相应的源码(我以前也是用的那套源码,代码很老了),为了避免重复造轮子和节约时间,使用composer上的nette/mail包。

版本要求:

php:5.3.1+ nette/mail:2.3(更高的版本要php5.6+) thinkphp:5.0+

关于composer的安装百度上有很多,这里不讲述

安装nette/mail

composer require nette/mail

使用

所有参数编码使用UTF-8

邮件参数

use Nette\Mail\Message;

$mail = new Message;
$mail->setFrom('John <john@example.com>')//发件人
    ->addTo('peter@example.com') // 收件人
    ->addTo('jack@example.com') // 添加收件人
    ->setSubject('Order Confirmation') // 邮件主题
    ->setBody("Hello, Your order has been accepted."); // 邮件内容

html格式的邮件:

$mail->setHtmlBody('<h1>内容</h1>');

发送邮件

use Nette\Mail\SendmailMailer;
$mailer = new SendmailMailer;
$mailer->send($mail);

先使用Message类填写邮件信息,使用SendmailMailer类发送邮件

邮件配置

$mailer = new Nette\Mail\SmtpMailer([
        'host' => 'smtp.gmail.com', // smtp主机地址
        'username' => 'john@gmail.com', // smtp账户
        'password' => '*****', // smtp密码
        // ssl配置信息,如果没有使用ssl形式发送只要上面三个参数
        'secure' => 'ssl',
        'context' =>  [
            'ssl' => [
                'capath' => '/path/to/my/trusted/ca/folder',
             ],
        ],
]);
$mailer->send($mail);

下面是在我项目中结合使用的,可以结合自己的项目修改

namespace library\service;
use app\admin\logic\ConfigLogic;
use Nette\Mail\Message;
use Nette\Mail\SmtpMailer;
use Nette\Utils\AssertionException;
use think\Exception;

class EmailService {

    // SMTP服务器
    private $server;

    // SMTP服务器的端口
    private $port = 25;

    // SMTP服务器的用户邮箱
    private $email;

    //SMTP服务器的用户帐号
    private $username;

    //SMTP服务器的用户密码
    private $password;

    // 发件人姓名
    private $name = '';

    public function __construct($config = []) {
        // ConfigLogic是数据库操作,也就是从数据库中读取配置
        if (empty($config)) {
            // 加载默认配置
            $model  = new ConfigLogic();
            $config = $model->getSmtpConfig();
        }
        if (isset($config['server'])) $this->server = $config['server'];
        if (isset($config['port'])) $this->port = $config['port'];
        if (isset($config['email'])) $this->email = $config['email'];
        if (isset($config['username'])) $this->username = $config['username'];
        if (isset($config['password'])) $this->password = $config['password'];
        if (isset($config['name'])) $this->name = $config['name'];
    }

    /**
     * send: 发送邮件
     * @param string $accept 接收人
     * @param string $title 邮件标题
     * @param string $content 邮件内容
     * @return bool 发送成功返回true
     */
    public function send($accept, $title, $content) {
        $mail = new Message();
        try {
            $mail->setFrom($this->name . ' <' . $this->email . '>')
                ->addTo($accept)
                ->setSubject($title)
                ->setBody($content);

            $mailer = new SmtpMailer([
                'host'     => $this->server,
                'username' => $this->username,
                'password' => $this->password,
            ]);
            $mailer->send($mail);
            return true;
        } catch (AssertionException $e) {
            return false;
        }
    }
}
]]>
Mon, 12 Feb 2018 13:52:03 GMT 2030
无限级分类之查找子孙树,面包屑导航 2029 效果演示: 安徽ID=1 parent=0 淮北ID=5 parent=1 淮溪县ID=3 parent=5 北京ID=7 parent=0 海淀ID=2 parent=7 上地ID=8 parent=2 昌平ID=4 parent=7 朝阳ID=6 parent=7

<?php
$area = array(
    array('id' => 1,'name' => '安徽','parent' => 0),
    array('id' => 2,'name' => '海淀','parent' => 7),
    array('id' => 3,'name' => '淮溪县','parent' => 5),
    array('id' => 4,'name' => '昌平','parent' => 7),
    array('id' => 5,'name' => '淮北','parent' => 1),
    array('id' => 6,'name' => '朝阳','parent' => 7),
    array('id' => 7,'name' => '北京','parent' => 0),
    array('id' => 8,'name' => '上地','parent' => 2)
);

function subtree($arr,$id=0,$lev=1){
    //static $subs = array();//子孙数组用静态属性保存
    $subs = array();

    foreach ($arr as $v) {
        if ($v['parent'] == $id) {
            $v['lev'] = $lev;
            $subs[] = $v;
            $subs = array_merge($subs,subtree($arr,$v['id'],$lev+1));
        }
    }

    return $subs;
}

//print_r(subtree($area,0,1));
$tree = subtree($area,0,1);
foreach($tree as $v){
    echo str_repeat('  ',$v['lev']),$v['name'],'ID=',$v['id'],' parent=',$v['parent'],'<br>';
}
<?php
$area = array(
    array('id' => 1,'name' => '安徽','parent' => 0),
    array('id' => 2,'name' => '海淀','parent' => 7),
    array('id' => 3,'name' => '淮溪县','parent' => 5),
    array('id' => 4,'name' => '昌平','parent' => 7),
    array('id' => 5,'name' => '淮北','parent' => 1),
    array('id' => 6,'name' => '朝阳','parent' => 7),
    array('id' => 7,'name' => '北京','parent' => 0),
    array('id' => 8,'name' => '上地','parent' => 2)
);

function familyTree($arr,$id){
    static $tree = array();

    foreach($arr as $v){
        if($v['id'] == $id){
            $tree[] = $v;
            if($v['parent'] > 0){
                familyTree($arr,$v['parent']);
            }
        }
    }
    return $tree;
}

print_r(familyTree($area,8));
]]>
Mon, 12 Feb 2018 13:51:04 GMT 2029
Thinkphp之快捷查询 OR查询语句 2028 快捷查询方式是一种多字段相同查询条件的简化写法,可以进一步简化查询条件的写法,在多个字段之间用|分割表示OR查询,用&分割表示AND查询,可以实现下面的查询,例如:

Db::table('think_user')
    ->where('name|title','like','thinkphp%')
    ->where('create_time&update_time','>',0)
    ->find();

生成的查询SQL是:

SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' OR `title` LIKE 'thinkphp%' ) AND ( `create_time` > 0 AND `update_time` > 0 ) LIMIT 1

快捷查询支持所有的查询表达式。

区间查询

区间查询是一种同一字段多个查询条件的简化写法,例如:

Db::table('think_user')
    ->where('name',['like','thinkphp%'],['like','%thinkphp'])
    ->where('id',['>',0],['<>',10],'or')
    ->find();

生成的SQL语句为:

SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' AND `name` LIKE '%thinkphp' ) AND ( `id` > 0 OR `id` <> 10 ) LIMIT 1

区间查询的查询条件必须使用数组定义方式,支持所有的查询表达式。

下面的查询方式是错误的:

Db::table('think_user')
    ->where('name',['like','thinkphp%'],['like','%thinkphp'])
    ->where('id',5,['<>',10],'or')
    ->find();

批量查询

可以进行多个条件的批量条件查询定义,例如:

Db::table('think_user')
    ->where([
        'name'  =>  ['like','thinkphp%'],
        'title' =>  ['like','%thinkphp'],
        'id'    =>  ['>',0],
        'status'=>  1
    ])
    ->select();

生成的SQL语句为:

SELECT * FROM `think_user` WHERE `name` LIKE 'thinkphp%' AND `title` LIKE '%thinkphp' AND `id` > 0 AND `status` = '1'

闭包查询

Db::table('think_user')->select(function($query){
    $query->where('name','thinkphp')
        ->whereOr('id','>',10);
});

生成的SQL语句为:

SELECT * FROM `think_user` WHERE `name` = 'thinkphp' OR `id` > 10

使用Query对象查询

也可以事先封装Query对象,并传入select方法,例如:

$query = new \think\db\Query;
$query->name('user')
    ->where('name','like','%think%')
    ->where('id','>',10)
    ->limit(10);
Db::select($query);   

如果使用Query对象的话,select方法之前调用的任何的链式操作都是无效。

混合查询

可以结合前面提到的所有方式进行混合查询,例如:

Db::table('think_user')
    ->where('name',['like','thinkphp%'],['like','%thinkphp'])
    ->where(function($query){
        $query->where('id',['<',10],['>',100],'or');
    })
    ->select();

生成的SQL语句是:

SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' AND `name` LIKE '%thinkphp' ) AND ( `id` < 10 or `id` > 100 )

字符串条件查询

对于一些实在复杂的查询,也可以直接使用原生SQL语句进行查询,例如:

Db::table('think_user')
    ->where('id > 0 AND name LIKE "thinkphp%"')
    ->select();

为了安全起见,我们可以对字符串查询条件使用参数绑定,例如:

Db::table('think_user')
    ->where('id > :id AND name LIKE :name ',['id'=>0, 'name'=>'thinkphp%'])
    ->select();

V5.0.4+开始,ThinkPHP支持对同一个字段多次调用查询条件,例如:

Db::table('think_user')
    ->where('name','like','%think%')
    ->where('name','like','%php%')
    ->where('id','in',[1,5,80,50])
    ->where('id','>',10)
    ->find();

快捷方法(V5.0.5+)

V5.0.5+版本开始新增了一系列快捷方法,用于简化查询,包括:

方法 作用 whereNull 查询字段是否为Null whereNotNull 查询字段是否不为Null whereIn 字段IN查询 whereNotIn 字段NOT IN查询 whereBetween 字段BETWEEN查询 whereNotBetween 字段NOT BETWEEN查询 whereLike 字段LIKE查询 whereNotLike 字段NOT LIKE查询 whereExists EXISTS条件查询 whereNotExists NOT EXISTS条件查询 whereExp 表达式查询

]]>
Mon, 12 Feb 2018 13:40:06 GMT 2028