• PHP开发API小技巧,还不快上车?【php下的电商API封装接口】


    restful api
    传统api
    • 获取用户信息 get /api/user/read

    • 更新用户信息 post /api/user/updat

    • 新增用户信息 post/api/user/add

    • 删除用户信息 post/api/user/delete

    restful api
    • 获取用户信息 get /api/user/1

    • 更新用户信息put /api/user/1

    • 新增用户信息 post /api/user

    • 删除用户信息deletean/api/user/1

    API数据结构格式
    • HTTP状态码使用TK自带的json实现

    • status 业务状态码

    • message 提示信息

    • data 数据层

    1. function show($status, $message, $data=[], $httpCode=200) {
    2.    $data = [
    3.        'status' => $status,
    4.        'message' => $message,
    5.        'data' => $data,
    6.    ];
    7.    return json($data, $httpCode);
    8. }
    不可预知的内部异常api数据输出解决方案
    • 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;    } }

    API数据安全

    图片

    • 背景

      • 接口请求地址和参数暴露

      • 重要接口返回数据明文暴露

      • APP登录态请求的数据完全性问题

      • 代码层的数据完全问题

    • 解决方式就是各种的加密:MD5 AES(对称加密) RSA(非对称,效率较低)

      • 解决:获取服务端时间,客户端拿到服务端正确时间进行对比。然后如果有差值,然后把差值在计算的时候补上

      • 基本参数放入header

      • 每次http请求都携带sign (有效时间,唯一性)

      • sign唯一性保证

      • 请求参数、返回数据按安全性适当加密

      • access token

      • 客户端和服务器端时间不一致性解决方案

    1. /**
    2.     * 生成每次请求的sign
    3.     * @param array $data
    4.     * @return string
    5.     */
    6.    public static function setSign($data = []) {
    7.        // 1 按字段排序
    8.        ksort($data);
    9.        // 2拼接字符串数据  &
    10.        $string = http_build_query($data);
    11.        // 3通过aes来加密
    12.        $string = (new Aes())->encrypt($string);
    13.        return $string;
    14.    }
    1. /**
    2.     * 检查sign是否正常
    3.     * @param array $data
    4.     * @param $data
    5.     * @return boolen
    6.     */
    7.    public static function checkSignPass($data) {
    8.        $str = (new Aes())->decrypt($data['sign']);
    9.        if(empty($str)) {
    10.            return false;
    11.        }
    12.        // diid=xx&app_type=3
    13.        parse_str($str, $arr);
    14.        if(!is_array($arr) || empty($arr['did'])
    15.            || $arr['did'] != $data['did']
    16.        ) {
    17.            return false;
    18.        }
    19.        // 有效时间:时间间隔不能超过60s (时间使用13位时间戳,这样唯一性会比较强)
    20.        if(!config('app_debug')) {
    21.            if ((time() - ceil($arr['time'] / 1000)) > config('app.app_sign_time')) {
    22.                return false;
    23.            }
    24.            //echo Cache::get($data['sign']);exit;
    25.            // 唯一性判定
    26.            if (Cache::get($data['sign'])) {
    27.                return false;
    28.            }
    29.        }
    30.        return true;
    31.    }
     
    

    /** * 检查每次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接口文档编写(API入参,出参的格式)
    • 有利于客户端工程师熟悉接口

    • 有利于服务端工程师接手项目

    • API接口地址

    • 请求方式

    • 入参格式

    • 出参格式

    • http code

    APP版本升级
    • 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);    }

    APP端异常、性能监控及定位分析
    • 常见的移动端异常

      • Crash在使用APP的过程中突然发生闪退现象

      • 卡顿出现画面的卡顿

      • Exception 程序被catch起来的exception

      • ANR 出现提示无响应弹框(android)

    • 端异常数据收集方案解剖(统计数据)

      • Crash 卡顿Exception ANR次数

      • 影响用户数

    • 第三方成熟服务

      • 听云

      • oneapm

      • 端APP只需要集成第三方平台提供的sdk

    APP推送
    • 原始-轮训法处理

      • 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影响索引性能。

  • 相关阅读:
    qt中弱属性机制
    SpringCloud的那些中间件
    1.74HC138控制LED发光
    Ascii码表
    JavaWeb核心(2)
    CoM-Px30|RK3358开发初步连载-Andorid系统固件的烧写(三)
    leetcode:67. 二进制求和
    uni-app引入地图map组件--APP开发
    就推荐 4 个 yyds 的开源项目
    B+树索引(8)之索引适用场景(下)
  • 原文地址:https://blog.csdn.net/2301_79478575/article/details/132650326