为了账号安全,请及时绑定邮箱和手机立即绑定

PHP API接口开发(Yaf)

标签:
PHP API
  • Web2.0->API(微服务架构,移动APP时代C/S技术)
    • 如何对API项目中的公共技术抽离,建立有层级的PHP API项目 ?
    • 如何做好异常处理及监控,保障API高效稳定的提供服务?

模板模式开发与API模式开发的区别

  • API模式:去除掉模板的处理,专注于数据
  • API无状态性
    图片描述

图片描述

REST与SOAP、RPC的区别

  • RPC所谓的远程过程调用(面向方法)
  • SOA所谓的面向服务的架构(面向消息)
  • REST 所谓的Representational state transfer(面向资源)
  • REST与SOAP、RPC的区别
    图片描述

环境配置

$ docker run -d -p 8001:8001 -p 8000:8000 -p 80:80 pangee/lnmp:v1 /sbin/init
docker stats
进入环境
docker exec -it aef269e596cc /bin/bash
查看环境
uname -a
docker run -d -p 8888:8888 daocloud.io/library/centos /sbin/init
docker exec -it a257a5c7679c /bin/bash
docker run -it a257a5c7679c /bin/bash
安装lnmp稳定版
yum install wget
wget -c http://soft.vpser.net/lnmp/lnmp1.4.tar.gz && tar zxf lnmp1.4.tar.gz && cd lnmp1.4 && ./install.sh lnmp
cd ~
下载yaf
解压缩
phpize 编译
wget http://pecl.php.net/get/yaf-3.0.5.tgz
tar -zxvf  yaf-3.0.5.tgz
cd 
phpize 初始化configure文件
 ./configure --with-php-config=/usr/local/php/bin/php-config 准备编译时期的前期准备
 make 
 make install
 (实质上是把 find ./ -name 'yaf.so' 这个文件放在/usr/local/php/lib/php/extensions/no-debug-non-zts-20160303/)
 vim /usr/local/php/etc/php.ini
如下
1889 [Yaf]
1890 extension=yaf.so
1891 yaf.environ="product"
 添加一个虚拟域名
 netstat -tpnlu
 lnmp vhost add
 lnmp vhost list
 vim /usr/local/nginx/conf/vhost/hanxiao.com.conf
server
    {
        listen 80;
        server_name hanxiao.com hanxiao.org;
        index  index.php ;
        root  /home/work/hanxiao;


        include enable-php.conf;

        if(!-e $request_filename){
                reqrite ^/(.*) /index.php?$1 last;
        }

        access_log  /home/work/logs/hanxiao.com.log;
    }

图片描述
图片描述

用户登录注册接口

  • 对MySQL的增删改查
  • 实现注册,登录验证函数
  • 简单封装返回JSON数据

文章类别接口(CURD,文章列表页)

一些接口实现方法

邮件接口

  • 第三方整合接口
  • 短信
  • 支付
  • Push细小
  • IP地址转换
  • 其他

接口信息收集

  • 关注接口的整体时间开销
  • xhprof收集API接口开销
    • libpng,graphviz安装
log_format main
'$remote_addr - $remote_user [$time_local] "$request" ' 
'$status $body_bytes_sent $request_time $upstream_response_time "$http_referer" '  
'"$http_user_agent" "$http_x_forwarded_for"';
<7php
xhprof-enable()header("XhprofID:".$run_id);
try{
	define('APPLICATION_PATH',dirnameC_-FILE)."/../");
	$application =new Yaf-Application( APPLICATION_PATH."/conf/application.ini");
	$application->bootstrap)->run)]catch (Exception $e){
	echo jsonencode( array('errno'=>-999999,'errmsg'=>'error.'.Se->getMessage()))}
$xhprofData =xhprof-disable();

$XHPROF_ROOT="/home/work/imooc/application/library/ThirdParty";
include_once $XHPROF_ROOT."/xhprof_lib/utils/xhprof_lib.php";
include_once $XHPROF_ROOT."/xhprof_lib/utils/xhprof_runs.php";

// save raw data for this profiler run using default
//implementation of iXHProfRuns.
$xhprof_runs =new XHProfRuns_Default();
// save the run under a namespace "xhprof_foo"
$run_id =$xhprof_runs->save.run5xhprofData:"xhprof_foo");

Api自测脚本

<?php
require __DIR__ . '/../vendor/autoload.php';
use \Curl\Curl;

$cookieFile = "/tmp/_tmp_test_api_cookie_file_".rand();

$host = "http://127.0.0.1/?c=wxpay";
$curl = new Curl();
$curl->setCookieJar( $cookieFile );
$itemId = 1;
$uname = 'pangee';
$pwd = '12312312';

/**
 * 生成订单
 */
$curl->post( $host."&a=createbill&itemid=".$itemId, array());
if ($curl->error) {
    die( 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n" );
} else {
	$rep = json_decode( $curl->response, true );
	if( $rep['errno']!==0 ) {
		echo '未登录创建账单,失败为正常。返回信息:'.$rep['errmsg']."\n";
		echo '尝试登陆账号...'."\n";
		$curl->post( str_replace("wxpay","user",$host)."&a=login&submit=1", array(
						'uname' => $uname,
						'pwd'	=> $pwd,
					));
		if ($curl->error) {
			die( 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n" );
		} else {
			$rep = json_decode( $curl->response, true );
			if( $rep['errno']!==0 ) {
				die( '用户登录失败,错误信息:'.$rep['errmsg']."\n" );
			}
			echo "登陆成功!\n";
		}

		/**
		 * 重新生成订单
		 */
		$curl->post( $host."&a=createbill&itemid=".$itemId, array());
		if ($curl->error) {
			die( 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n" );
		} else {
			$rep = json_decode( $curl->response, true );
			if( $rep['errno']!==0 ) {
				echo '已登陆状态下,创建账单,失败。返回信息:'.$rep['errmsg']."\n";
			} else {
				echo "生成订单成功(登陆情况下)\n";
			}
		}
	
	} else {
		echo "生成订单成功(未登陆情况下)\n";
	}
}

echo "微信支付接口测试完毕。\n";
$curl->close();
unlink( $cookieFile );

公共抽离

  • Lib库的抽离
  • SDK的统一管理
  • Composer管理第三方类库
class Common_Request{
    public static function request($key, $default=null,$type=null){
        if( $type == 'get'){
            $result = isset($_GET[$key])?trim($_GET[$key]):null;
        } elseif ($type == 'post'){
            $result = isset($_POST[$key])?trim($_POST[$key]):null;
        } else {
            $result = isset($_REQUEST[$key])?trim($_REQUEST[$key]):null;
        }

        if($default != null && $result==null){
            $result = $default;
        }
        return $result;
    }

    public static function getRequest($key, $default=null){
        return self::request($key,$default,'get');
    }

    public static function postRequest($key, $default=null){
        return self::request($key, $default, 'post');
    }

    public static function response($errno=0,$data=null){
        $resp = Err_Map::get($errno);

        if($data != null){
            $resp['data'] = $data;
        }
        return json_encode($resp);
    }
}

DAO层

class Db_Base{
    public static $errno  = 0;
    public static $errmsg = null;
    public static $db     = null;

    public static function getDb(){
        if(self::$db == null){
            self::$db = new PDO("mysql:host=localhost;dbname=imooc_yaf;","root","root");
            //防止pdo在拼接sql的时候将int转string
            self::$db->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
        }
        return self::$db;
    }

    public function errno(){
        return self::$errno;
    }

    public function errmsg(){
        return self::$errmsg;
    }
}
class Db_User extends Db_Base {
    public function find($uname){
        $query = self::getDb()->prepare("select `pwd`, `id` from `user` where `name` = ? ");
        $query->execute(array($uname));
        $ret   = $query->fetchAll();
        if( !$ret || count($ret)!=1 ) {
            list(self::$errno,self::$errmsg) = Err_Map::get(1003);
            return false;
        }
        return $ret[0];
    }

    public function checkExists($uname){
        $query = self::getDb()->prepare("select count(*) as c from `user` where `name` = ?");
        $query->execute(array($uname));
        $count   = $query->fetchAll();
        if( $count[0]['c'] !=0 ) {
            list(self::$errno, self::$errmsg) = Err_Map::get(1005);
            return false;
        }
        return true;
    }


    public function addUser($uname,$password,$datetime){
        $query = self::getDb()->prepare("insert into `user` (`id`,`name`,`pwd`,`reg_time`) VALUES (null,?,?,?)");
        $ret = $query->execute(array($uname, $password,$datetime));
        if( !$ret ) {
            list(self::$errno,self::$errmsg) = Err_Map::get(1007);
            return false;
        }
        return true;
    }
}

接口异常的规范处理

  • 统一的API接口返回
    • 错误信息字典
  • 统一的API异常处理
    • 容错与降级
class Err_Map{
    const ERRMAP = array(
        0     => '',

        /**
         * 用户类错误提示码
         */
        1001  => '请通过正常渠道提交',
        1002  => '用户名或密码不能为空',
        1003  => '用户查找失败',
        1004  => '密码错误',
        1005  => '用户名已存在',
        1006  => '密码太短,请输入最低8位的密码',
        1007  => '注册失败,写入数据库失败',

        /**
         * 文章类错误提示码
         *
         */
        2000  => '需要管理员权限',
        2001  => '请通过正常渠道提交',
        2002  => '没填写完整',
        2003  => '缺少必要的参数',
        2004  => '找不到文章,请确认是否有该文章',
        2005  => '找不到分类信息-',
        2006  => '操作文章数据表失败,errinfo:',
        2007  => '缺少必要的ID参数',
        2008  => '更新文章状态失败',
        2009  => '查询失败',
        2010  => '获取分类信息失败',
        2011  => '获取文章列表失败, errinfo',
        2012  => '删除数据失败',

        /**
         * 邮箱发送错误提示码
         */
        3001  => '请通过正常渠道提交',
        3002  => '用户id,邮件title,邮件内容不能为空',
        3003  => '邮箱信息查找失败',
        3004  => '邮箱不合法',

        /**
         * 短信发送错误提示码
         */
        4001  => '请通过正常渠道提交',
        4002  => '用户Id,短信内容不能为空',
        4003  => '用户手机号信息查找失败',
        4004  => '手机号不符合规定',
        4005  => '发送失败',
        4006  => '消息发送成功,数据插入失败',

        /**
         * IP地址转详细地址错误提示码
         */
        5001  => 'IP地址不正确',

        /**
         * 微信支付错误提示码
         */
        6001  => '请传递正确的商品ID',
        6002  => '请先登录后操作',
        6003  => '',
        6004  => '找不到这件商品',
        6005  => '商品过期',
        6006  => '商品没有库存',
        6007  => '创建订单失败',
        6008  => '更新库存失败',
        6009  => '请传递正确的订单ID',
        6010  => '',
        6011  => '找不到订单信息',
        6012  => '找不到商品信息',
    );

    public static function get($code){
        if(isset(self::ERRMAP[$code])){
            return array('errno'=>(0-$code), 'errmsg'=>self::ERRMAP[$code]);
        }
        return array('errno'=>(0-$code), 'errmsg'=>"没有定义该类错误码");

    }
}

API文档自动生成

  • PHPDocument
    • -d -p
  • swigger

优化

  • API时间开销定位与分析:
    • 时间开销情况分析
    • 剥洋葱定位方法
  • API上下游性能优化:
    • MySQL时间开销优化
    • 后端服务调优
    • API返回调优
  • 稳定性
    • 服务监控
      • supervisord
    • API负载均衡
    • 服务报警
    • API测试用例
点击查看更多内容
3人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消