php后台对接ios,安卓,API接口设计和实践完全攻略,涨薪必备技能
原则条件
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是应用程序状态的引擎,资源表示通过超链接互联。
REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的
但是规则是很早之前的人设计的,但是it这个行业日新月异,业务结构复杂,变化性大,所有,你可以选择遵守,也是适当改变,方便开发,比如现在移动化发展快速,app接口,微信网页接口等
第一点要做的就是HTTPS 自2017年1月1号,开始新的ios都需要是https才能访问,版本好像是10.0系列开始,并且满足 苹果ATS安全
如果您的APP如果仍采用HTTP传输,那么,在Apple Store中您的APP将不再能被用户下载使用 ,所以,呵呵
场景分析和理论
首先说下几种运用场景:
1,后台服务端->app客户端 ios 安卓
一般来说单向信任加密解密就可以,客户端向服务器请求进行数据加密,发送服务器端进行解密,然后返回数据,
但是请求返回的数据是不加密的,信任是单向的,有时候也需要加密就是双向加密,效率来说就比较麻烦和文件返回用
base64进行传输,处理效率也比较差,特别是服务器端还有专门的文件服务器的,代码层级处理起来就比较麻烦,效率也
很差,做过ios和java的同学应该比较了解,直接作为文件传输,php文件上传的时候,会先把文件上传系统的临时文件目录
而不是先去验证权限在上传,全局变量$_FILE php在上传到其他文件服务器,或者移动到文件目录,如果客户端
没有做验证,或者允许大文件上传,就会存在temp文件夹爆炸,服务器宕机的危险,对于中小项目来说,不是被人恶意攻击
问题不大,这种情况下需要使用base64传输就可以先验证在判断是不是在做处理,然后就是在服务器验证时候加上针对每
个接入端标识存储,线上好处理具体问题来自哪些方面的接口,可以比较好的定位
2,后台服务端->html5 微信 浏览器
因为现在狠多为了一种开发多处使用,必须现在流行的html5混合app开发简单方便,如果不是做游戏,性能一般app足够
使用,不要被原生性能更好的屁话左右,为了适配机器app需要做无数处理,麻烦的很,而且对于小公司人力成本是巨大的,
而且不同语言的数据对接本身就是时间消耗,精力消耗,各种未知bug的调试,特别是第一次做个的人,有些东西在不同语言
显示方式根本不一样,比如 一个数组(php)
key=>0,name=>z key在ios解析的时候key不显示,安卓key显示NULL,呵呵,所以一些细节很麻烦,但是也并不是原生的
不好,只是就现在的技术潮流来说是这样,比如如果一些ios新特性,在app的混合开发框架肯定不会那么及时的更新api接口
,比如hbuilder,做的HTML5+,整体性能也肯定没有object-c好,swift个人没什么了解,所以不清楚,所以有利有弊,需要技
术经理,公司,开发人员来衡量。
htnl5页面在微信和浏览器里面需要的快速,如果每次都需要验证数据加密,机密,效率首先需要考虑,而且js处理session
和cookies,在页面不太好处理,很多现在很多都是纯js去渲染数据,如果使用php来混合编写也是可以的,使用php来模拟
数据加密,请求接口,这样写比较容易,但是无法再混合app中使用,如果你只是单独开发手机浏览器和微信里面来使用php
混写是没有问题的,如果是页面需要纯js处理数据的话么就需要注意我说的上面的问题
数据认证的话,可以简单做用户登录,比如微信自动等,根据name和password,salt计算一个唯一值作为token去数据库校验,
接口请求的时候js发送请求的时候校验即可
3,文件服务器 特殊处理
很多项目到了后期都需要考虑单独的文件服务器的问题,比如我前面博客说道的,挂在nfs文件服务,或者文件服务器接口
文件服务器处理起来比较麻烦,但是对于大的项目来说也是必要的,所有服务都需要接口化,在接口认证里面进行权限和
资源分配,这就是SOA的过程,比如使用文件接口,在富文本编辑器上传文件的时候,就需要改造富文本编辑器的文件上传
所以有点麻烦,uedit修改文件上传路劲,支持api文件接口 我这篇博客就有介绍,文件服务器也有好处,就是可以规避一部
分文件安全问题
4,后台服务端->游戏客户端
这个和html有些相似,又有些不同,一个是为了高效数据传输,保持socket稳定,或者数据传输的速度,还有就是保证
数据不被篡改,比如游戏作弊,考虑的东西比较多,还有比如一局游戏某个用户断线了,多久T掉,保证游戏继续进行下去,接口里面
需要处理的东西很多,因为游戏需要更新的状态很多,合理的设计表结构也是无比重要,比如一个buff就增加一个字段的话,一个玩家状态
对应就有几百个字段,传输数据当然就会大,一次更新那么数据,速度和性能就得再次考虑
(后面更新)
API接口理论设计实践
1, 加密解密
使用https的 公钥 私钥 加密解密,但是其实也是发送数据不加密,只是通过签名pkf crt来加密和验证签名的正确性,
很多借口采用的就是这种设计,但是数据安全性,我不是很理解,发送的是明文数据,木马程序可以很轻松的监听,
这种单向信任的明文数据发送验证借口,在浏览器访问的时候是有自带https的公钥,但是php curl的是可以不带证书访问的,
所有依然是接口里面是明文发送出去的,(如果这段有错,请反馈)
2接口理论
现在普遍采用3des加密,只是因为方便,现在都出都有php ios java的通用demo方便,不方便的地方也有很多,发送的就是加密的数据,
虽然是对称加密解密,单向信任,但是也是根据单个接口的账号密码进行二次验证,如果你key和sercet,被别人知道,也是可以解密出加密发送的数据,如
果想使用接口获取数据,还是得有该用的用户名和秘密的,当然现在流行的手机号码+手机短信验证码也是可以,但是服务器对应实现才可以,而且
最好ios和安卓开发框架需要支持session,cookies,https等为好,后面会有说明
推荐框架,但是需要支持这个多个协议
3,数据传输格式
json xml 数据流 二进制 等等,但是数据解析方便来说json还是最方便的。建议json,主要是怕麻烦
4,支持请求方法POST GET 文件上传
麻烦一点就是文件上传,base64上传,或者直接文件上传,但是临时文件会出现上面说的系统临时文件爆了情况
5,兼容旧代码进行模拟登陆session cookies ,权限兼容
其他很多系统都需要去旧的rabc的里面去获取数据,模拟登录的时候,就会涉及到权限问题,当然你也可以单独剥离出来
这些功能,时间花的也比较长,特别是需要在客户端也要实现一定的权限的时候就麻烦了,你单独剥离出来,你又要去模拟
权限,当然,最好办法就是,接口对应的账号里面也去实现一套RBAC,这样后续开发人员就轻松了,看起来有点蛋疼,但是是比较
实际的问题,这个问题如果在设计接口的时候没有就考虑进去的话,就在对接一些旧功能就忒麻烦,特别是没有独立方法化的代码时候,
耦合度大,改起来麻烦的要死
6,目前接口优缺点。另一种接口设计方式
这个接口是所有终端同一个加密的秘钥,也就说一个终端秘钥丢失,就是可以获得其他终端发送的数据进行解密,获得发送数据,
所以中小项目,或者某个公司内部项目使用还不错,但是大项目api化的数据安全性就不是很好
另一种接口设计,就是每个终端都是自己使用的8位key和32位的 value 然后加密还是下面的加密方法,但是在吧user_key直接明文也带过
来先把数据库的这个用户的key 和value ,先解密,后吧对比解密数据里面的value 对比一样就通过,去处理数据,就像一般的
用户名密码登录校验一样,或者你想更安全就是md5('value .key') 去对比,全部不明文发送过来的数据
如果循环数据库的key 和value去解密,效率太低,app接口需要就速度和效率
7,一些细节问题
$_REQUEST['header'] 为什么要这样获取数据,因为在ios和安卓,字典必须是要key和vaule的,所以兼容
urldecode建议不要使用这对函数 使用
rawurldecode($str);
rawurlencode($str);
会出现+ 和%2D 空格,多种语言交互的时候这样的问题很多,所以要注意,特别是对接接口的时候,签名的时候
签名加密算法:sha1 长度40 (因为语言可能导致生产长度不一样)
foreach (unserialize($res['contract']['idcards_file_ids']) as $k => $v) { $rr['user_idcard_data'][$k]['file_paths'] = app_standard_path_new($file_path['file_path']);
$rr['user_idcard_data'][$k]['file_id'] = $v['file_id'];
$rr['user_idcard_data'][$k]['name'] = $v['name']; }
}
如果把 $rr['user_idcard_data'] json传给app端,他就是个字典不是数组,对于ios和安卓来说
"customer_idcard_file": {
"1": {
"file_paths": "http://app.xinyzx.com/Uploads/personal_app_ios/201704/11/58ec42d23c090.jpeg",
"file_id": "717892",
"name": "work_card"
}
},
$rr['user_idcard_data'] = array_values($rr['user_idcard_data']);
需要处理成以下格式
"file_id":[
{
"file_paths":"58ec42d22f310.jpeg",
"file_id":"717891",
"name":"bank_card1"
},
{
"file_paths":"58ec42d23c090.jpeg",
"file_id":"717892",
"name":"work_card"
}
]
demo实例代码,测试代码
本代码基于tp3.1.2 目前不提供完整测试类,后面有时间在更新
目前只支持 key 8位 secket 32位,支持更多位数的后续跟新
Crypt3Des.class.php 加密算法 3DES
<?php class Crypt3Des { private $key = "Symetric";
private $iv = "Symetric"; /**
* 构造,传递二个已经进行base64_encode的KEY与IV
*
* @param string $key
* @param string $iv
*/ function __construct($key, $iv) {
if (empty($key) || empty($iv)) {
echo 'key and iv is not valid';
exit();
}
$this->key = $key;
$this->iv = $iv;
} /**
*加密
* @param $value
* @return
*/
public function encrypt($value) {
$td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_ECB, '');
$iv = base64_decode($this->iv);
$value = $this->PaddingPKCS7($value);
$key = base64_decode($this->key);
mcrypt_generic_init($td, $key, $iv);
$ret = base64_encode(mcrypt_generic($td, $value));
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $ret;
} /**
*解密
* @param $value
* @return
*/
public function decrypt($value) {
$td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_ECB, '');
$iv = base64_decode($this->iv);
$key = base64_decode($this->key);
mcrypt_generic_init($td, $key, $iv);
$ret = trim(mdecrypt_generic($td, base64_decode($value)));
$ret = $this->UnPaddingPKCS7($ret);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $ret;
} private function PaddingPKCS7($data) {
$block_size = mcrypt_get_block_size('tripledes', 'cbc');
$padding_char = $block_size - (strlen($data) % $block_size);
$data .= str_repeat(chr($padding_char), $padding_char);
return $data;
} private function UnPaddingPKCS7($text) {
$pad = ord($text{strlen($text) - 1});
if ($pad > strlen($text)) {
return false;
} if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
return false;
}
return substr($text, 0, - 1 * $pad);
} }
BaseAction.class.php
<?php //API父类
class BaseAction extends Action { protected $user_id;
protected $appkey;
protected $secret;
protected $appIDname; //接入用户来源标示,可能在用户数据录入的时候会用到 public function _initialize() { myLog($_REQUEST, 'api_log');
if (empty($_REQUEST) && empty($_FILES)) {
$this->response(array('code' => 0, 'msg' => '发送数据不能为空'), 'json', 200);
}
$data = $_REQUEST['header']; if (empty($data)) {
$data = urldecode(file_get_contents('php://input')); //兼容php发送数据接收 和 php模拟测试,正式不一定 if (empty($data)) {
$this->response(array('code' => 0, 'msg' => '发送数据不能为空'), 'json', 200);
}
} $data = $this->crypt3des()->decrypt($data);
myLog($data, 'api_log');
$_POST = json_decode($data, true);
//api接口日志记录--打印发送数据
myLog($_POST, 'api_log'); $this->origin = $this->check_sign($_POST);
} function crypt3des() {
import('App.ORG.Crypt3Des');
$crypt3des = new Crypt3Des(base64_encode(C('crypt_key')), base64_encode(C('crypt_iv')));
return $crypt3des;
} function _empty() {
$this->response(array('code' => 0, 'msg' => '_empty,非法操作'), 'json', 200);
} protected function check_sign($data, $expires = 300) {
$params = array();
$params['timestamp'] = $data['timestamp'];
if (time() - strtotime($params['timestamp']) > $expires) {
$this->response(array('code' => 0, 'msg' => '签名已过期'), 'json', 200);
}
//数据库查询校验相关用户数据
$where['app_api_name'] = $data['appkey'];
$res = M('app_api_partner')->where($where)->find(); if (empty($res)) {
$this->response(array('code' => 0, 'msg' => 'appkey错误'), 'json', 200);
}
if ($res['api_status'] !== '0') {
$this->response(array('code' => 0, 'msg' => '目前api账户不可用'), 'json', 200);
}
if ($res['app_api_key'] !== $data['secret']) {
$this->response(array('code' => 0, 'msg' => '通信密钥错误'), 'json', 200);
}
$params['appkey'] = $res['app_api_name'];
$params['secret'] = $res['app_api_key']; $sign = $this->data_auth_sign($params); // $this->response(array('msg' => $params, 'code' => 0), 'json', 200);
if ($sign !== $data['sign']) {
$this->response(array('code' => 0, 'msg' => '签名错误'), 'json', 200);
} else {
myLog('签名解析正确', 'api_log');
$this->appIDname = $res['app_api_mark']; //返回接入的使用的名称
}
} private function data_auth_sign($data) {
if (!is_array($data)) {
$data = (array) $data;
}
ksort($data);
$param = array();
foreach ($data as $key => $val) {
$param[] = $key . "=" . $val;
}
$param = join("&", $param);
$sign = sha1($param);
return $sign;
} }
C 获取系统配置数据
myLog 打印系统日志
function myLog($str, $flag = 'default') {
//if( APP_DEBUG != true )return '';
is_array($str) && $str = print_r($str, true);
$dir = SYSTEM_ROOT . '/Uploads/logs/' . $flag . '/'; !is_dir($dir) && @mkdir($dir, 0755, true);
$file = $dir . date('Ymd') . '.log.txt';
$fp = fopen($file, 'a');
if (flock($fp, LOCK_EX)) {
$content = "[" . date('Y-m-d H:i:s') . "]\r\n";
$content .= $str . "\r\n\r\n";
fwrite($fp, $content);
flock($fp, LOCK_UN);
fclose($fp);
return true;
} else {
fclose($fp);
return false;
}
}
随机生成一个8位随机字符串
function get_rand_str($len) {
$chars = array(
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
);
$charsLen = count($chars) - 1;
shuffle($chars);
$output = "";
for ($i = 0; $i < $len; $i++) {
$output .= $chars[mt_rand(0, $charsLen)];
}
return $output;
}
集成这个父控制就可以写你自己的代码了,下面是一个实力和一个接口测试的demo
<?php //客户信息相关api接口类 class CustomerAction extends BaseAction { public function test() { $this->response(array('code' => 1, 'msg' => '测试成功', 'data' => $data), 'json', 200);
} }
测试接口的demo
import('app.ORG.Crypt3Des');
$crypt3des = new Crypt3Des(base64_encode(C('crypt_key')), base64_encode(C('crypt_iv'))); $data['appkey'] = $_data['appkey'] = '11111111'; // 用于签名参数 对应C的取到的值
$data['secret'] = $_data['secret'] = md5('11111111'); //用于签名参数
$data['timestamp'] = $_data['timestamp'] = date('YmdHis', time()); //用于签名参数 $data['sign'] = $this->data_auth_sign($_data); $data = json_encode($data); $crypt_data = (urlencode($crypt3des->encrypt($data))); $url = "http://127.0.0.1/app.php/customer/test"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $crypt_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $return_data = curl_exec($ch); print_r($return_data);
echo '<hr />';
print_r(json_decode($return_data, true)); if ($error = curl_error($ch)) {
die($error);
}
// $rinfo = curl_getinfo($ch);
// P($rinfo);
curl_close($ch);
2017年6月24日23:17:10
这里补充一点,这个接口有个比较大的问题,就是没有版本控制,比如在接口加个V参数。
最新碰到一个问题就是,有个比较大的改版,因为ios发布不通过,导致安卓可以升级但是ios不能,所以整体升级就只能直接进行
解决发难,就是根据V参数的版本号去访问不同的比如,在访问控制器的ZxAction.class.php 的test方法的时候,实际访问的是Zxv3Action.class.php,或者 V3_zxAction.class.php,
在基础控制器里面处理一下,不然在大版本更新,如果某些接口是不能升级的话,就很需要这个参数进行处理对应的接口
/* 新增版本控制参数v很重要,根据版本控制,比如
*
* 访问 m=test&a=zx
* 里面有$_POST['v'] =v1
* 实际访问的就是 m=test&a=v1_zx
*/
if (!empty($_POST['v'])) {
$module = 'App_admin://' . MODULE_NAME;
$function = $_POST['v'] . '_' . ACTION_NAME;
A("$module")->$function(); 这里实例化的时候,会再次经过_initialize方法,有问题
die;
} 这个如果写在父控制器就会出现无限循环,出现问题 需要在自控制器里面加入这个,因为在初期没有考虑到这个,就只能补充这个,唉,经验不足
public function __construct() {
parent::__construct();
if (!empty($_POST['v'])) {
$function = trim($_POST['v']) . '_' . ACTION_NAME;
$this->$function();
die;
}
}
其实还可以在父控制使用二级域名来控制,进行版本控制
php后台对接ios,安卓,API接口设计和实践完全攻略,涨薪必备技能的更多相关文章
- atitit.基于http json api 接口设计 最佳实践 总结o7
atitit.基于http json api 接口设计 最佳实践 总结o7 1. 需求:::服务器and android 端接口通讯 2 2. 接口开发的要点 2 2.1. 普通参数 meth,p ...
- API接口设计
1.场景描述 比如说我们要做一款APP,需要通过api接口给app提供数据.假设我们是做商城,比如我们卖书的.我们可以想象下这个APP大概有哪些内容: 1)首页:banner区域(可以是一些热门书籍的 ...
- Web API接口设计经验总结
在Web API接口的开发过程中,我们可能会碰到各种各样的问题,我在前面两篇随笔<Web API应用架构在Winform混合框架中的应用(1)>.<Web API应用架构在Winfo ...
- Web API接口设计(学习)
1.在接口定义中确定MVC的GET或者POST方式 由于我们整个Web API平台是基于MVC的基础上进行的API开发,因此整个Web API的接口,在定义的时候,一般需要显示来声明接口是[HttpG ...
- Java生鲜电商平台-API接口设计之token、timestamp、sign 具体架构与实现(APP/小程序,传输安全)
Java生鲜电商平台-API接口设计之token.timestamp.sign 具体设计与实现 说明:在实际的业务中,难免会跟第三方系统进行数据的交互与传递,那么如何保证数据在传输过程中的安全呢(防窃 ...
- API接口设计之token、timestamp、sign 具体架构与实现(APP/小程序,传输安全)
Java生鲜电商平台-API接口设计之token.timestamp.sign 具体设计与实现 说明:在实际的业务中,难免会跟第三方系统进行数据的交互与传递,那么如何保证数据在传输过程中的安全呢(防窃 ...
- Retrofit/OkHttp API接口加固技术实践(下)
作者/Tamic http://blog.csdn.net/sk719887916/article/details/65448628 imageMogr2/auto-orient/strip%7Cim ...
- 免费安卓IOS测试API接口,后续会陆续增加接口
各位博友好!开发的安卓或者ios的朋友们,经常会遇到想测试但是没有公开的api接口进行进行测试.但自己又不会开发服务端或者没有服务器,这里我免费提供了一整套API接口.欢迎大家调用,目标是方便大家. ...
- 微信小程序的Web API接口设计及常见接口实现
微信小程序给我们提供了一个很好的开发平台,可以用于展现各种数据和实现丰富的功能,通过小程序的请求Web API 平台获取JSON数据后,可以在小程序界面上进行数据的动态展示.在数据的关键 一环中,我们 ...
随机推荐
- 【PMP】项目的定义和特点
1.定义 项目是为创建独特的产品.服务和成果而进行的的临时性工作. 2.特点 2.1 独特的产品.服务或成果 实现项目目标可能产生一个或多个可交付成果.例如:即便采用相同的材料或者相同的施工单位来建设 ...
- spring cloud配置中心属性加密处理
在现实的场景里,我们会在配置中心配置很多中间件的账号密码(通常都是读写账号),如果采用明文存储将会有很大的风险导致账号泄露,解决方案: http://blog.didispace.com/spring ...
- 3728 联合权值[NOIP 2014 Day1 T2]
来源:NOIP2014 Day1 T2 OJ链接: http://codevs.cn/problem/3728/ https://www.luogu.org/problemnew/show/P1351 ...
- 简单的redis 的list应用
error_reporting(E_ALL); if(empty($a)){ echo 111; }else{ echo 3333; } die; phpinfo();die; $redis = ne ...
- xcode9 报错 “Swift Language Version” (SWIFT_VERSION) build setting must be set to a supported value for targets which use Swift
用xcode编译后会出现这个错误的情况: 1.使用cocopod导入第三方swift包后,swift的包是比较老的swift开发的. 2.用xcode9 打开老的swift(比如swift2.0)的工 ...
- 【转载】C# Graphics类具体解释
封装一个 GDI+ 画图图面. 此类不能被继承.System.Drawing 命名空间 名称 说明 Clip 获取或设置 Region.该对象限定此 Graphics 的画图区域. ClipBoun ...
- [android开发教程] 一个神奇的Demo 帮你掌握所有android控件
(本文内容来源:http://www.eoeandroid.com/thread-182392-1-1.html 转载请注明出处!) 2.jpg (23.78 KB, 下载次数: 0) 下载附件 ...
- docker的swarm介绍
转载自:https://blog.csdn.net/karamos/article/details/80132082 另外一篇:https://www.jianshu.com/p/9eb9995884 ...
- 基于mindwave脑电波进行疲劳检测算法的设计(3)
这一节我将讲解thinkgear.h 里面的函数和宏定义.这一些都可以在MindSet Development Tools\ThinkGear Communications Driver\docs\h ...
- 【Spark 深入学习 02】- 我是一个凶残的spark
学一门新鲜的技术,其实过程都是相似的,先学基本的原理和概念,再学怎么使用,最后深究这技术是怎么实现的,所以本章节就带你认识认识spark长什么样的,帅不帅,时髦不时髦(这货的基本概念和原理),接着了解 ...