获取用户信息 get /api/user/read
更新用户信息 post /api/user/updat
新增用户信息 post/api/user/add
删除用户信息 post/api/user/delete
获取用户信息 get /api/user/1
更新用户信息put /api/user/1
新增用户信息 post /api/user
删除用户信息deletean/api/user/1
HTTP状态码使用TK自带的json实现
status 业务状态码
message 提示信息
data 数据层
function show($status, $message, $data=[], $httpCode=200) {
$data = [
'status' => $status,
'message' => $message,
'data' => $data,
];
return json($data, $httpCode);
}
TP5是通过thinkphp-library-think-exception-Handle.php-render来呈现异常数据(但是客户端识别不到这种异常)
config配置exception_handle填写异常类路径
'exception_handle' => '\app\common\lib\exception\ApiHandleException',
class ApiHandleException extends Handle { /** * http 状态码 * @var int */ public $httpCode = 500; public function render(\Exception $e) { // 还原正常报错,上线后为flase(服务端开发) if(config('app_debug') == true) { return parent::render($e); } if ($e instanceof ApiException) { $this->httpCode = $e->httpCode; } return show(0, $e->getMessage(), [], $this->httpCode); } }
支持状态码修改
class ApiException extends Exception { public $message = ''; public $httpCode = 500; public $code = 0; /** * @param string $message * @param int $httpCode * @param int $code */ public function __construct($message = '', $httpCode = 0, $code = 0) { $this->httpCode = $httpCode; $this->message = $message; $this->code = $code; } }
背景
接口请求地址和参数暴露
重要接口返回数据明文暴露
APP登录态请求的数据完全性问题
代码层的数据完全问题
解决方式就是各种的加密:MD5 AES(对称加密) RSA(非对称,效率较低)
解决:获取服务端时间,客户端拿到服务端正确时间进行对比。然后如果有差值,然后把差值在计算的时候补上
基本参数放入header
每次http请求都携带sign (有效时间,唯一性)
sign唯一性保证
请求参数、返回数据按安全性适当加密
access token
客户端和服务器端时间不一致性解决方案
/**
* 生成每次请求的sign
* @param array $data
* @return string
*/
public static function setSign($data = []) {
// 1 按字段排序
ksort($data);
// 2拼接字符串数据 &
$string = http_build_query($data);
// 3通过aes来加密
$string = (new Aes())->encrypt($string);
return $string;
}
/**
* 检查sign是否正常
* @param array $data
* @param $data
* @return boolen
*/
public static function checkSignPass($data) {
$str = (new Aes())->decrypt($data['sign']);
if(empty($str)) {
return false;
}
// diid=xx&app_type=3
parse_str($str, $arr);
if(!is_array($arr) || empty($arr['did'])
|| $arr['did'] != $data['did']
) {
return false;
}
// 有效时间:时间间隔不能超过60s (时间使用13位时间戳,这样唯一性会比较强)
if(!config('app_debug')) {
if ((time() - ceil($arr['time'] / 1000)) > config('app.app_sign_time')) {
return false;
}
//echo Cache::get($data['sign']);exit;
// 唯一性判定
if (Cache::get($data['sign'])) {
return false;
}
}
return true;
}
/** * 检查每次app请求的数据是否合法 */ public function checkRequestAuth() { // 首先需要获取headers $headers = request()->header(); // todo // sign 加密需要 客户端工程师 , 解密:服务端工程师 // 1 headers body 仿照sign 做参数的加解密 // 2 // 3 // 基础参数校验 if(empty($headers['sign'])) { throw new ApiException('sign不存在', 400); } if(!in_array($headers['app_type'], config('app.apptypes'))) { throw new ApiException('app_type不合法', 400); } // 需要sign if(!IAuth::checkSignPass($headers)) { throw new ApiException('授权码sign失败', 401); } Cache::set($headers['sign'], 1, config('app.app_sign_cache_time')); // 存储方式 :1、文件 2、mysql 3、redis $this->headers = $headers; }
有利于客户端工程师熟悉接口
有利于服务端工程师接手项目
API接口地址
请求方式
入参格式
出参格式
http code
APP更新迭代快
APP是安装在用户手机设备上
APP版本升级的类型
用户自主选择更新
强制更新
开设API接口
APP识别接口做相关判定3
/** * 客户端初始化接口 * 1、检测APP是否需要升级 */ public function init() { // app_type 去ent_version 查询 $version = model('Version')->getLastNormalVersionByAppType($this->headers['app_type']); if(empty($version)) { return new ApiException('error', 404); } if($version->version > $this->headers['version']) { $version->is_update = $version->is_force == 1 ? 2 : 1; }else { $version->is_update = 0; // 0 不更新 , 1需要更新, 2强制更新 } // 记录用户的基本信息 用于统计 $actives = [ 'version' => $this->headers['version'], 'app_type' => $this->headers['app_type'], 'did' => $this->headers['did'], ]; try { model('AppActive')->add($actives); }catch (\Exception $e) { // todo //Log::write(); } return show(config('code.success'), 'OK', $version, 200); }
常见的移动端异常
Crash在使用APP的过程中突然发生闪退现象
卡顿出现画面的卡顿
Exception 程序被catch起来的exception
ANR 出现提示无响应弹框(android)
端异常数据收集方案解剖(统计数据)
Crash 卡顿Exception ANR次数
影响用户数
第三方成熟服务
听云
oneapm
端APP只需要集成第三方平台提供的sdk
原始-轮训法处理
APP每隔一段时间向后端发送请求
第三方:极光推送
PHP->第三方推送消息凭条jpush->APP
/** * 新闻点赞功能开发 * @return array */ public function save() { $id = input('post.id', 0 , 'intval'); if(empty($id)) { return show(config('code.error'), 'id不存在', [], 404); } // 判定这个 id的新闻文章 -> ent_news 小伙伴自行加入 $data = [ 'user_id' => $this->user->id, 'news_id' => $id, ]; // 查询库里面是否存在 点赞 $userNews = model('UserNews')->get($data); if($userNews) { return show(config('code.error'), '已经点赞过,不能再次点赞', [], 401); } try { $userNewsId = model('UserNews')->add($data); if($userNewsId) { model('News')->where(['id' => $id])->setInc('upvote_count'); return show(config('code.success'), 'OK', [], 202); }else { return show(config('code.error'), '内部错误 点赞失败', [], 500); } }catch (\Exception $e) { return show(config('code.error'), '内部错误 点赞失败', [], 500); } } /** * 取消点赞 */ public function delete() { $id = input('delete.id', 0, 'intval'); if(empty($id)) { return show(config('code.error'), 'id不存在', [], 404); } // 判定这个 id的新闻文章 -> ent_news 小伙伴自行加入 $data = [ 'user_id' => $this->user->id, 'news_id' => $id, ]; // 查询库里面是否存在 点赞 $userNews = model('UserNews')->get($data); if(empty($userNews)) { return show(config('code.error'), '没有被点赞过,无法取消', [], 401); } try { $userNewsId = model('UserNews') ->where($data) ->delete(); if($userNewsId) { model('News')->where(['id' => $id])->setDec('upvote_count'); return show(config('code.success'), 'OK', [], 202); }else { return show(config('code.error'), '取消失败', [], 500); } }catch (\Exception $e) { return show(config('code.error'), '内部错误 点赞失败', [], 500); } }
后台权限控制使用封装Base类库来达到统一管理的目的,_initialize
调试小技巧 : $this->getLastSql(); halt(); echo ** exit;
mysql default null影响索引性能。