网站被黑客攻击从定位到修复(一)

从黑产中学习和成长

之前做了几个网站,管理员反馈网站除了首页,啥页面都点击不进去了。疑似被攻击

网站首页看似毫无破绽,点看源代码

HTML源代码

首页备注里一些base64编码的文案,用搜索引擎搜索网站地址,得到如下的

搜索引擎内容

得到的结论是:被黑产注入了代码来获取该网站的搜索引起流量。(情况非常严重)

开始排除问题:

  1. 先备份代码和数据库
  2. 重装系统(这是防止黑客在服务器上面留了后门,先绝后患)
  3. 用的是阿里云服务器,近期并没有收到异常登陆服务器的通知,基本排除黑客是通过登陆服务器方式来安装木马的
  4. 从上传的目录里面找问题。很快就定位了,public的上传文件里面有一个异常的PHP文件
  5. 该文件是一个加密的PHP文件(运行起来如下)

WEB 越权攻击界面

该界面功能之全,覆盖之广,真是令人震惊。我将会在后面几天文章的重点讲解这个工具的使用和原理。

只有知道那敌人的招数之后,我们才能有效的防御。并且从中学习和成长

造成漏洞的原因:文件上传被黑客利用,并且通过管理后台的编辑器上传了PHP文件,该PHP文件有调用了一些系统函数获取了系统的权限对原本的首页文件进行了修改。

解决办法:

1. 先关闭该文件中提到的所有的系统级函数(PHP安装的时候配置文件默认的不够,具体信息下一篇文章会调到)

2. 关闭PHP通过URL操作文件的权限(这个很重要)

3. 修改管理后台附件上传组件,严格现在上传文件类型。

PHP多线程

PHP 在liunx里面是可以实现多进程的,但需要使用liunx扩展:pcntl 扩展

主要代码是

$pid = pcntl_fork();    //创建子进程
if ($pid == -1) {
   die('could not fork');   //错误处理:创建子进程失败时返回-1.
}else if ($pid) {
   pcntl_wait($status,WNOHANG);
     //父进程会得到子进程号,所以这里是父进程执行的逻辑
     //如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句,或写成:
    //等待子进程中断,防止子进程成为僵尸进程。
}else {     
     exit(0);//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}

PHP进程

PHP进程

从这样,可以看出来,父进程和子进程不是同步关系。父进程结束,子进程可以继续执行,父进程在等待子进程结束并回收。

PHP进程

PHP进程

 这里的PHP-fpm 都是fork出来的子进程,并且都是并行执行的。 所以原本单线程需要23分钟执行的代码,这里fork出20个子进程,就可以把时间缩短到30秒

微信公众号接口发红包

最近接到一个任务,需要用微信来给用户自动发红包。要完成这个任务需要这么已经一些物料

  1. 微信商户号,已申请微信支付
  2. 微信商户号主体下面的微信公仔号

先看一下效果图

只需要完成后面几步就可以了。

  1. 在微信支付的服务器上面部署红包代码
  2. 在微信公众号服务器上面调用红包代码

/*
**微信红包功能
*/

public function sendredpack(){

  $re_openid = $this->_pg('re_openid');
   $inputObj = new sendredpack_pub();

if(!$re_openid){
    return "微信红包功能,收红包用户不能为空";
}

   $inputObj->setParameter('re_openid',$re_openid); //收红包的用户的openid
   $inputObj->setParameter('send_name',"汽配一号铺"); //红包发送者名称
   $inputObj->setParameter('total_amount',"100"); //收红包的用户的金额,精确到分
   $inputObj->setParameter('total_num',"1"); //收红包的个数
   $inputObj->setParameter('wishing',"恭喜发财,谢谢支持,小小心意"); //收红包的用户的openid
   $inputObj->setParameter('client_ip',"121.40.157.243"); //调用接口的IP
   $inputObj->setParameter('act_name',"小邓感恩红包"); //红包主题
   $inputObj->setParameter('remark',"谢谢大家一路一来的支持"); //备注
   $response = $inputObj->getResult();

   return $response;
}

在微信支付辅助工具层加一个类,来完成红包功能

/**
* 微信发红包接口
**/
class sendredpack_pub extends Wxpay_client_pub
{
     function __construct() {
    //设置接口链接
     $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
    //设置curl超时时间
    $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT;
}

/**
* 生成接口参数xml
*/
function createXml()
{
   try
  {
    $this->parameters["mch_billno"] = WxPayConf_pub::MCHID.createUnique();//商户订单号
    $this->parameters["wxappid"] = WxPayConf_pub::APPID;//公众账号ID
    $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号
    $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
    $this->parameters["sign"] = $this->getSign($this->parameters);//签名
    return $this->arrayToXml($this->parameters);
  }catch (SDKRuntimeException $e) {
      die($e->errorMessage());
  }
}

/**
* 作用:获取结果,使用证书通信
*/
function getResult()
{
    $this->postXmlSSL();
    $this->result = $this->xmlToArray($this->response);
    return $this->result;
}
}

然后部署返微信支付的服务上面,就可以了!!然后在做微信公众号(这个公众号)的服务上面加入“红包”。就能达到上面的效果了

根据文档进行开发
请您仔细阅读接口文档,参照文档进行开发,请注意,为了保证商户资金安全,接口强校验商户号与appid之间的绑定关系,以及appid与openid之间的对应关系(如果商户号与appid之间没有绑定关系,即appid没有申请微信支付或者申请微信支付商户号不匹配,或者openid归属appid错误,接口会返回报错)。

支付三部曲-银联支付

之前看我博客百度关键字的时候,很多人都是因为银联搜到我这里,一直想做一个demo 给大家,可惜没商户申请不到,这次机缘巧合帮朋友做支付三部曲:微信支付,支付宝支付和银联支付,其他两个支付都烂大街的,这里就只把银联支付分享给大家,因为涉及到客户的信息,所以就不提供源代码下载了。但可有偿服务

银联支付,首先要注意二重要的部分:

  1. PHP运行环境是5.4.18以上
  2. 开了扩展openss

开发手册上面的列子只做参考,因为基本都是错的。你可以试着去官网下一个demo。。。注意现在银联开发,没有测试密钥提供,只能在正式环境开发【20151219】

下面是我用ThinkPHP编写的一个支付类

/**
* 银联支付 v0.1
* @auther:Summer<dengwz7788@gmail.com>;
* @date:20151202
* **/

class NetPayAction extends BaseAction{
//在类初始化方法中,引入相关类库
public function _initialize() {
  header("Content-type:text/html;charset=utf-8");
  vendor('Netpay.util.common',"",".php"); //导入加密核心文件夹
  vendor('Netpay.util.SecssUtil',"",".class.php"); //导入加密核心文件夹
  vendor('Netpay.util.Settings_INI',"",".php"); //导入加密核心文件夹
  vendor('Netpay.util.Settings',"",".php"); //导入加密核心文件夹
  $this-&gt;securityPropFile= $_SERVER['DOCUMENT_ROOT'] . "/ThinkPHP/Extend/Vendor/Netpay/config/security.properties"; //谁知道这是啥,反正他们要我加的
  $this->b2cPaySend = __APP__."/Index/NetPay/b2cPaySend";
  $this-&gt;b2cRefundSend = __APP__."/Index/NetPay/b2cRefundSend";
  $this->b2cQuerySend = __APP__."/Index/NetPay/b2cQuerySend";
  $this->;MerBgUrl = __APP__."/Index/NetPay/MerBgUrl";
  $this->MerPageUrl = __APP__."/Index/NetPay/MerPageUrl";
}

public function index()
{
  $paramArray=array (
     'MerId' => '商户号',
     'MerOrderNo' => '0000001944663232',
     'OrderAmt' => '1',
     'TranDate' => '20151219',
    'TranTime' =>'171248',
    'TranType' => '0001',
    'BusiType' =>'0001',
    'Version' => '20140728',
    'CurryNo' => 'CNY',
    'AccessType' =>; '0',
    'CommodityMsg' => '测试商品1号',
    'MerPageUrl' => $this-&gt;MerBgUrl,
    'MerBgUrl' =>$this-&gt;MerPageUrl,
    'MerResv' => 'MerResv',
 );
 
 if (count($paramArray) >0) {
    $dispatchUrl = $this->b2cPaySend;
    $transResvedJson = array();
    $cardInfoJson = array();
    $sendMap = array();
     foreach ($paramArray as $key => $value) {
      if (isEmpty($value)) {
        continue;
     }
     if (startWith($key, "trans_")) {
          $key = substr($key, strlen("trans_"));
                $transResvedJson[$key] = $value;
     } else
         if (startWith($key, "card_")) {
              $key = substr($key, strlen("card_"));
              $cardInfoJson[$key] = $value;
         } else {
              $sendMap[$key] = $value;
         }
   }

   $transResvedStr = null;
   $cardResvedStr = null;
   if (count($transResvedJson) >0) {
      $transResvedStr = json_encode($transResvedJson);
   }
   if (count($cardInfoJson) &gt; 0) {
      $cardResvedStr = json_encode($cardInfoJson);
   }

  $secssUtil = new SecssUtil();
  if (! isEmpty($transResvedStr)) {
    $transResvedStr = $secssUtil->decryptData($transResvedStr);
    $sendMap["TranReserved"] = $transResvedStr;
   }
  if (! isEmpty($cardResvedStr)) {
     $cardResvedStr = $secssUtil->decryptData($cardResvedStr);
     $sendMap["card_"] = $cardResvedStr;
   }
   $securityPropFile = $this>securityPropFile;
   $secssUtil->init($securityPropFile);
   $secssUtil->sign($sendMap);

   $sendMap["Signature"] = $secssUtil->getSign();
   $_SESSION = $sendMap;
   header("Location:" . $dispatchUrl);
 }
}

public function b2cPaySend(){
   layout(false);
   $settings = new Settings_INI();
   $settings->oad($this->securityPropFile);
   $pay_url = "https://payment.chinapay.com/CTITS/service/rest/page/nref/000000000017/0/0/0/0/0";
   $html = "<form name='payment' action='{$pay_url}' method='POST' target='_blank'>;";
   $params = "TranReserved;MerId;MerOrderNo;OrderAmt;CurryNo;TranDate;SplitMethod;BusiType;MerPageUrl;MerBgUrl;SplitType;MerSplitMsg;PayTimeOut;MerResv;Version;BankInstNo;CommodityMsg;Signature;AccessType;AcqCode;OrderExpiryTime;TranType;RemoteAddr;Referred;TranTime;TimeStamp;CardTranData";
   foreach ($_SESSION as $k =>$v) {
      if (strstr($params, $k)) {
         $html .= "<input type='hidden' name = '" . $k . "' value ='" . $v . "'/>";
      }
   }

        $html .= "<nput type='button' type='hidden' value='提交订单' >";
        $html .= "<;/from>";
        $this->html = $html;
        $this->display();
}

public function pgReturn(){
  if ($_POST) {
      if (count($_POST) > 0) {
       $secssUtil = new SecssUtil();
       $securityPropFile = $this>securityPropFile;
       $secssUtil->init($securityPropFile);
       $text = array();
       foreach($_POST as $key=>$value){
           $text[$key] = urldecode($value);
        }

      if ($secssUtil->verify($text)) {
                       //支付成功
          $_SESSION["VERIFY_KEY"] = "success";
     } else {
       //支付失败
         $_SESSION["VERIFY_KEY"] = "fail";
     }
    }
   }
 }
}

银联支付应该是算比较简单的!!

Thinkphp打水印,设置水印位置

最近在用Thinkphp的打水印的功能,发现只能打在左下角。 PHP打水印功还是很容易的,最要是用到


bool imagecopymerge ( resource $dst_im , resource $src_im , int $dst_x , int $dst_y , int $src_x , int $src_y , int $src_w , int $src_h , int $pct )
   将 src_im 图像中坐标从 src_x,src_y 开始,宽度为 src_w,高度为 src_h 的一部分拷贝到 dst_im 图像中坐标为 dst_x 和 dst_y 的位置上。两图像将根据 pct 来决定合并程度,其值范围从 0 到 100。当 pct = 0 时,实际上什么也没做,当为 100 时对于调色板图像本函数和 imagecopy() 完全一样,它对真彩色图像实现了 alpha 透明。

水印demo图

水印demo图

我需要把水印打到图片的真中间,查看Thinkphp代码。发现,作者居然是写死了,,我只能做一个修改

/**
* 为图片添加水印
* @static public
* @param string $source 原文件名
* @param string $water 水印图片
* @param string $$savename 添加水印后的图片名
* @param string $postion 水印的具体位置 leftbottom rightbottom lefttop righttop center <新增>
* @param string $alpha 水印的透明度
* @return void
*/
static public function water($source, $water, $savename=null,$postion="center", $alpha=80) {
//检查文件是否存在
if (!file_exists($source) || !file_exists($water))
return false;

//图片信息
$sInfo = self::getImageInfo($source);
$wInfo = self::getImageInfo($water);

//如果图片小于水印图片,不生成图片
if ($sInfo["width"] < $wInfo["width"] || $sInfo['height'] < $wInfo['height']) return false; //建立图像 $sCreateFun = "imagecreatefrom" . $sInfo['type']; $sImage = $sCreateFun($source); $wCreateFun = "imagecreatefrom" . $wInfo['type']; $wImage = $wCreateFun($water); //设定图像的混色模式 imagealphablending($wImage, true); //图像位置,默认为右下角右对齐 $posArr = $this->WaterPostion($postion,$sInfo,$wInfo); //新增

   //生成混合图像
    imagecopymerge($sImage, $wImage, $posArr[0], $posArr[1], 0, 0, $wInfo['width'], $wInfo['height'], $alpha);

   //输出图像
   $ImageFun = 'Image' . $sInfo['type'];
  //如果没有给出保存文件名,默认为原图像名
  if (!$savename) {
     $savename = $source;
     @unlink($source);
   }
 //保存图像
   $ImageFun($sImage, $savename);
      imagedestroy($sImage);
  }

  private function WaterPostion($postion,$sInfo,$wInfo)
  {
     $posY = $sInfo["height"] - $wInfo["height"];
     $posX = $sInfo["width"] - $wInfo["width"];

    switch($postion)
  {
     case "rightbottom":
        return array($posX,$posY);
     break;

     case "leftbottom":
        return array($wInfo["width"],$posY);
     break;

     case "lefttop":
       return array($wInfo["width"],$wInfo["height"]);
     break;

     case "righttop":
        return array($posX,$wInfo["height"]);
     break;

     case "center":
       return array($posX/2,$posY/2);
    break;
   }
}

好像就可以了~

亿美短信接口HTTP协议实现

最近在做帮一个朋友做一个发短信的接口,也是我一直想做的一个接口,,亿美公司提供了几种方式,我还是一直很偏向HTTP协议的

因为这种方式不限制语言,而且轻量级。然后刚好这几天也在学习代码重构技术。

根据重构的原则编写了这个发短信的类

这里有说几个坑:

  1.  调用发短信接口后,短信发送成功,但手机收不到短信? 发送短信的格式有要求,必须有是公司简称或者产品简称  类似:【支付宝】【京东】这些的 信息,不然短信没办法发出去。
  2. 签名移到最前端  联通移动什么都不会限制的
  3. 短信内容必须是UTF-8编码的,中文还需要经过urlencode()编码。代码中有实现。

/***
 *  北京亿美短信发送通用接口
*   @version 1.0
*   @author Summer<dengwz7788@gmail.com>
*   @保留版权信息,不限制使用范围
* **/

class SendM{

	private $cdkey = "0SDK-EMY-6688-KHZOS"; // "改成您的密钥"
	private $password = "123456"; //"改成您的密码"
	private $SendMessageApi     = "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/sendsms.action";
	private $PlanSendMessageApi = "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/sendtimesms.action";
	private $QueryMessageNumApi = "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/querybalance.action";
	private $getreportApi 	    = "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/getreport.action";
	private $registApi 	    = "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/regist.action";
	private $registdetailinfoApi= "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/registdetailinfo.action";
	private $querybalanceApi    = "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/querybalance.action";
	private $getmoApi 	    = "http://sdk4report.eucp.b2m.cn:8080/sdkproxy/getmo.action";

	public function __construct() {

		$this->param["cdkey"] = $this->cdkey;
		$this->param['password'] = $this->password;

	}

	/**获取剩余短信余量**/
	public function querybalance()
	{
		$Arr= $this->Curl($this->querybalanceApi, $this->param);
		$this->json($Arr);
	}

	/**获取上行短信接口**/
	public function getmo()
	{
		$Arr= $this->Curl($this->getmoApi, $this->param);
		$this->json($Arr);
	}

	/**获取短信数据报表**/
	public function getreport()
	{
		$Arr= $this->Curl($this->getreportApi, $this->param);
		$this->json($Arr);
	}

	/**公司信息注册**/
	public function registdetailinfo()
	{
		 $this->param["ename"] = "小邓PHP-高效PHP开发";  //企业名称(最多60字节),必须输入
		 $this->param["linkman"] = "小邓";	//联系人姓名(最多20字节),必须输入
		 $this->param["phonenum"] = "";	//联系电话(最多20字节),必须输入
		 $this->param["mobile"] = "";	//联系手机(最多15字节),必须输入
		 $this->param["email"] = "";	//电子邮件(最多60字节),必须输入
		 $this->param["fax"] = "";	//联系传真(最多20字节),必须输入
		 $this->param["address"] = "";	//公司地址(最多60字节),必须输入
		 $this->param["postcode"] = "";	//邮政编码(最多6字节),必须输入

		 $Arr= $this->Curl($this->registdetailinfoApi, $this->param);
		 $this->json($Arr);
	}

	/**查询发送短信数**/
	public function querymessagenum()
	{
		$Arr= $this->Curl($this->QueryMessageNumApi, $this->param);
		$this->json($Arr);
	}

	/**公司注册**/
	private function regist()
	{
		$Arr= $this->Curl($this->registApi, $this->param);
		$this->json($Arr);
	}

	/**发送短信接口,单例发送**/
	public function Sendsms($Content,$phone,$addserial="")
	{
		if(is_array($phone)){
			$phone = implode(",", $phone);
		}
		$this->param["phone"] = $phone;
		$this->param["message"] = $Content;
		$this->param['seqid'] = $this->seqid;

		$this->param["addserial"] = $addserial;
		$Arr= $this->Curl($this->SendMessageApi, $this->param);
		$this->json($Arr);
	}

	private function Curl($url,$param)
	{
		$ch2 = curl_init();
		//获取个人信息
		$url2 = $url."?".$this->_param($param);

		curl_setopt($ch2, CURLOPT_URL, $url2);
		curl_setopt($ch2, CURLOPT_HEADER, 0); //不返回header部分
		curl_setopt($ch2, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");
		curl_setopt($ch2, CURLOPT_RETURNTRANSFER,1);
		curl_setopt($ch2, CURLOPT_FOLLOWLOCATION,1);
		$information = curl_exec($ch2);
		curl_close($ch2);

		return $this->XML_toArr($information);
	}

	private function XML_toArr($XML)
	{
		$p = xml_parser_create();
		xml_parse_into_struct($p, trim($XML), $vals, $index);
		xml_parser_free($p);

		foreach($vals as $key=>$value)
		{
		   $result[strtolower($value['tag'])] = $value['value'];
		}

		return $result;
	}

	private function _param($param)
	{
		 foreach($param as $key=>$value)
		 {
		 	 $tmp[] = $key."=".urlencode($this->encodeToUTF8($value));
		 }

		 return implode("&", $tmp);
	}

	private function json($data)
	{
		echo json_encode($data);
		exit;
	}

	private static function encodeToUTF8($value)
	{

		$encode = mb_detect_encoding($value, array("ASCII","UTF-8","GB2312","GBK","BIG5"));

		if($encode == "UTF-8"){
			return $value;
		}

		return mb_convert_encoding($value, "UTF-8", $encode);

	}

}

Thinkphp导入类的使用模版


 import("@.ORG.SendM");

 $SendM = NEW SendM();

 $SendM->Sendsms("【PHP开发工程师-小邓高新能PHP开发】给你发了短信了", "1397335xxxx");

关于PHP的静态变量的注意细节

之前的公司的小兄弟要去面试PHP,他遇到了一道PHP基础面试题,大致内容是如下:

function test(){
   static $a = 0;
   $a++;
   echo "before unset ".$a."<br/>";
   unset($a);
   $a = 23;
   echo "after unset ".$a."<br/>";
}
test();
test();
test();

看到这题的时候,就注意到了stats这个标记了$a。就知道这题不会是第一反应的结果。(结果如下)

PHP运行结果

PHP运行结果

这个$a 值没有因为unset() 函数改变,而是一直在累计!!我知道unset()对应static状态的变量是无效的。我马上查看了手册

unset() 销毁指定的变量。

unset() 在函数中的行为会依赖于想要销毁的变量的类型而有所不同。

如果在函数中 unset() 一个全局变量,则只是局部变量被销毁,而在调用环境中的变量将保持调用 unset() 之前一样的值。

如果在函数中 unset() 一个静态变量,那么在函数内部此静态变量将被销毁。但是,当再次调用此函数时,此静态变量将被复原为上次被销毁之前的值。

其实这样这么理解:

PHP变量关系图

PHP变量关系图

PHP的内存地址跟变量直接就是通过这种方式进行关联的。一般的变量是是通过关联的方式指向对应的地址,而不是真是的值。所以unset()的过程,其实是断了之间的联系,而不是抹掉了内存地址的值。
而static在初始化变量的,仅在局部函数域中存在,但当程序执行离开此作用域时,其值并不丢失。

所以才看到之前累加的情况出现。而且静态变量在生命周期内只能赋值一次。

PS:现在这样理解着,等读了源码再回来看看逻辑是否正确

PHP面向对象的自动加载机制

在学习PHP的面向对象的时候,会知道很多“语法糖”,也就是魔术方法。有一个加自动加载的魔术方法,叫:__autoload();

先看一段代码


<?php
function __autoload($classname) {
    $filename = "./". $classname .".php";
    include_once($filename);
}

new a();

这里实例化了一个A类,但在代码块中没有A类的相关代码,按常理是应该会报错,因为没有找到对应的A类,但如果你使用了autoload()自动加载函数的话,结果就可以能不一样

QQ截图20150527203846

 

从上面的流程图:在页面实例化一个新类,就会先在当前目录找对应的类代码,如果没有就去autoload堆栈找对应的自动加载函数,如果有的话就自动加载该类,没有话就抛出错误。

这是PHP自动加载的一个机制。然后重点在后面。如果我有多个自动加载的函数,怎么办! 

PHP提供了一个SPL函数


spl_autoload_register(); // 注册autoload函数

官方:spl_autoload_register() 提供了一种更加灵活的方式来实现类的自动加载。因此,不再建议使用 __autoload() 函数,在以后的版本中它可能被弃用。

然而在PHPexecl 和 PHPWord 里面都使用到了 这个函数来做自动加载,但两者有区别!!

PHPexecl 自动加载的方法(这里作者估计是Python工程师,不然花括号都没有,用缩进来表示)


 public static function Register() {
        $functions = spl_autoload_functions();
        foreach ( $functions as  $function)
            spl_autoload_unregister($function);
        $functions = array_merge(array(array('PHPExcel_Autoloader','Load')),$functions);
        foreach ( $functions as $function)
            $x = spl_autoload_register($function);
        return $x;
    }	

PHPWord 自动加载的方法

 public static function Register() {
	return spl_autoload_register(array('PHPWord_Autoloader', 'Load'));
 }

这两种方法,都可以完成重定义自动加载,但有区别? 如果是独立运行代码,两种情况都可以运行,但要是整合到框架中,比如YII框架。那么PHPWord的自动加载就无效了。

因为YII框架自动带有自动加载函数,而且再代码运行的时候就已经注册了,而spl_autoload_register()会将新的自动加载函数,加载autoload队列的后面。所有PHPWord在运行的时候

就调用的是YII框架定义的自动加载机制,而且不是PHPWord这种加载方式。

所以反过来看PHPexecl的加载函数,你就明白了。

如何采集微信公共账号里面的图片

做页面内容抓起,遇到图片获取是必不可免的,这在之前的项目的有提过,但那个做法是很简单的,传统的图片存储。所有按正常的方式可以得到想要的结果。

但技术一直在进步嘛!!现在的WEB的流量中图片占70%的流量,在移动互联网时代,这种情况是不能忍的,所有就出现了微信的这种情况


$url = http://mmbiz.qpic.cn/mmbiz/jZa4drp6SdD6u2bGeiakB9BSfJCoFJLyKLdt2Kmve55TvibSFjgtFCe2A1G0fLNsX84SbMb96EPVb0Je6afnQaSg/640?wx_fmt=jpeg&tp=webp&wxfrom=5

上面这个链接,是可以通过游览器打开的,但你从链接上面根本看出来是什么格式? 其实不是看不出来,是我们不认识这种格式,webp. 这是Google推出的一种移动图片压缩格式

这种格式怎么用?有什么用!,暂时不在这里讨论。

我发现他的原因是


$image = get_headers($url); //只获取访问URL的返回的表头

PHP图片返回的头

PHP图片返回的头

这样就知道,这个URL的指定的图片的后缀名是什么了吧。。然后用普通的方式下载图片,后缀名用webp就OK了。。现在只能用游览器打开。。

你要是有时间,可以把他转换成JEPG格式的,,一个WEBTOOLS:https://cloudconvert.com/

这种格式在移动互联网时代应该会成为主流,毕竟能省很多流量。

Xunsearch 整合到Yii2框架中

公司一直用的YII2框架,然后要做一个中文搜索引擎,所有想的Xunsearch这个项目,之前有文章提到了,怎么安装Xunsearch服务端。Xunsearch项目安装

xunsearch暂时只有Liunx的服务端和PHP-SDK。但作者确提供了一个YII2的扩展。突然感觉好幸福的说。

本来是通过使用PHP-SDK写一个简单的添加索引和更新索引还有删除索引的功能,但有考虑到多项目的情况。就是一个Xunsearch可以被多个项目公用,所有配置文件放置在每一个项目的配置文件里面,根据这个项目的配置文件来区分每一个搜索数据库。这是我一开始的想法,但重点是不是怎么想,而是怎么去做。实践才是硬道理,然后就遇到下面的这些问题。

http://www.yiiframework.com/extension/yii-xunsearch/#hh4 这里目标链接,我接下来要说下安装心得

// application components
    'components => [
        // ... other components ...
        'xunsearch' => [
            'class' => 'hightman\xunsearch\Connection', // 此行必须,这里不一定是这写的,可能是hightman.xunsearch.Connection 根据实际项目出发
            'iniDirectory' => '@app/config',    // 搜索 ini 文件目录,默认:@vendor/hightman/xunsearch/app
            'charset' => 'utf-8',   // 指定项目使用的默认编码,默认即时 utf-8,可不指定
        ],
    ],

然后,如果你的PHP环境是集成的安装包,可能会遇到这么一个问题

  Unexpected character in input: '\' (ASCII=92) state=1

网上有人说是 php.ini 里面的配置问题,说把,short_open_tag 改成 On,我第一看感觉很有道理,然后找到发现,本来就是On。

然后到http://stackoverflow.com(全世界的技术都在用的一个技术论坛,官方语言是英语)上面去找,果然找到了。知道真相的我,真的是不想多了。

因为那个YII2的扩展用到了PHP5.3才有的命名空间特效。而我的本地PHP版本是5.2.6。我表示淡定不了。折磨一天的事情,居然是因为PHP版本太低。

就总结到这里,我现在只想静静。