PHP 如何更优雅地调用 API 接口 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
topthink
V2EX    PHP

PHP 如何更优雅地调用 API 接口

  •  
  •   topthink 2021-01-20 16:53:37 +08:00 4012 次点击
    这是一个创建于 1793 天前的主题,其中的信息可能已经有所发展或是发生改变。

    API 接口在各种场景中已经非常普遍使用,通常在 PHP 后台调用 API 接口,需要通过 Curl 库来自己封装,且不说各种充值门槛,还要被各种 api 接口平台的 appKey 、appSecret 之类的参数困惑,没法实现统一调用,很多小白更是被 Curl 各种配置参数弄得头大。ThinkPHP官方出品的ThinkAPI服务正是为了解决 PHP 接口调用的各种麻烦问题。

    ThinkAPI统一API接口服务是由官方联合合作伙伴封装的一套接口调用服务及 SDK,旨在帮助ThinkPHP开发者更方便和更低成本调用官方及第三方的提供的各类API接口及服务,从而更好的构建开发者生态。

    通过ThinkAPI提供的 SDK 功能可以以更优雅的方式来调用 API 接口,首先需要在你的项目里面安装 think-api 库(适用于任何 PHP5.6+项目,没有任何框架要求)。

    composer require topthink/think-api 

    然后就可以调用你需要的接口进行查询和返回数据,支持ThinkAPI所有的 API 接口,以查询身份证所属地区接口为例:

    use think\api\Client; $client = new Client("appCode"); $result = $client->idcardIndex() ->withCardno('身份证号码') ->request(); 

    idcardIndex方法就是调用了身份证归属地查询接口 withCardno方法则表示传入了cardno参数,如果还需要传入更多的参数则链式调用更多的方法即可,最后通过request方法进行实际调用并返回数据。通过 IDE 配合的话,你不需要自己记住任何接口方法名和参数方法名,都会有自动提示。

    ThinkAPI所有的 API 调用服务必须设置appCode值(只需要注册一个账号即可获取),用于接口调用的身份认证。如需多次调用的话,建议自己在项目里面封装一个助手函数,例如:

    use think\api\Client; /** * API 接口调用助手函数 * @return Client */ function api(): Client { return new Client('yourAppCode'); } // 调用示例 $result = api()->idcardIndex() ->withCardno('身份证号码') ->request(); 

    所有的接口服务和方法都支持 IDE 自动提示和完成(请务必注意方法大小写必须保持一致),所有的返回数据都是 JSON 格式,因此基本上不需要文档即可完成接口开发工作。API 接口调用中的一些常见问题通过系统的方法封装都可以规避掉,你甚至不需要关心接口是要用 GET 还是 POST,都是系统自动处理的。

    SDK 把所有接口和参数都封装为一个个独立的方法,你可以像调用一个类的方法一样简单的调用官方支持的任何 API 接口,也无需再去记住每个接口的参数有哪些。

    如果你的环境不支持 Composer 或者 PHP 版本过低,可能需要你自己封装 Curl 库来调用接口。ThinkAPI 接口文档都提供了两种方式调用:直接调用接口地址和使用 SDK 调用。

    目前 ThinkAPI 已经接入包括实名认证、人工智能、电子商务、新闻资讯和生活服务等类目在内的常用 API 接口共 269 个,包含大量免费接口,付费接口的价格比较实在、门槛也低,并且还在陆续扩充中。更详细的用法可以参考: https://docs.topthink.com/think-api

    25 条回复    2021-01-23 18:45:18 +08:00
    ben1024
        1
    ben1024  
       2021-01-20 18:04:08 +08:00
    推荐的是付费接口,没有免费接口服务列表吗
    topthink
        2
    topthink  
    OP
       2021-01-20 19:27:43 +08:00
    @ben1024 https://docs.topthink.com/think-api/1888715 这里有 标注为免费的就是免费接口
    lepig
        3
    lepig  
       2021-01-20 21:40:14 +08:00
    这个对开发来说确实听友好的。但是奈何老板不同意。

    老板说,用付费的金接口,我要你们干啥。
    topthink
        4
    topthink  
    OP
       2021-01-20 21:53:02 +08:00
    @lepig 现代化开发理念不是什么都要自己独立开发的。每个接口做自己专业的事情 因为专注才能更专业 就和组件化开发一样 接口化也是一种趋势。而且大多数接口自己没有数据没法开发的,比如身份证实名认证 只能调用第三方的。那么 ThinkAPI 的优势就是帮你简化开发 同时也能降低成本。我认为老板关注的不是用不用接口的问题 是项目什么时候能上线的问题吧~
    Actrace
        5
    Actrace  
       2021-01-20 22:06:23 +08:00
    @topthink 我觉得可能他就是那个老板
    imgbed
        6
    imgbed  
       2021-01-20 22:18:02 +08:00
    这种付费接口很多商家在做
    lepig
        7
    lepig  
       2021-01-20 22:38:06 +08:00
    @Actrace 我还真不是。只是我们 cto 为了节约成本新申请了个阿里云账号。然后迁移所有的服务器 /oss/容器等等。然后基础运维部断断续续肝了一个多月才完成。java,php 的所有项目全部要更改配置。
    坑的一笔。
    > 我是 php 开发。
    justseemore
        8
    justseemore  
       2021-01-21 13:17:16 +08:00
    把自己的三方 sdk 放到你那?
    topthink
        9
    topthink  
    OP
       2021-01-21 15:45:37 +08:00
    @imgbed 接口平台确实很多 但我们的优势是提供优雅的 SDK 以及更低的门槛
    @zpfhbyx 没太明白你的意思 是指接口入驻么?
    topthink
        10
    topthink  
    OP
       2021-01-21 15:47:52 +08:00
    @lepig 你们这样迁移风险太大了吧 难不成每年重新申请一个账号么 哈哈
    lepig
        11
    lepig  
       2021-01-21 15:50:58 +08:00
    @topthink 我觉得真的可能干的出来。反正今年 2/3 时间都是 996. 明年准备溜了,不管了。
    unicloud
        12
    unicloud  
       2021-01-21 16:22:08 +08:00
    看标题,以为是技术讨论。
    justseemore
        13
    justseemore  
       2021-01-21 16:38:09 +08:00
    @topthink 额,你不就是一个各种 3 方 api 的代理么。无非就是写了一个更好用的 phpsdk 。。
    ztxcccc
        14
    ztxcccc  
       2021-01-21 17:04:55 +08:00
    标题和内容有什么关系吗?
    dvaknheo
        15
    dvaknheo  
       2021-01-21 18:02:36 +08:00
    @topthink 提供优雅的 SDK

    什么叫优雅:

    老大:想查身份证地区编码,你们这么调就够了:
    ```
    /** @var string */ $area = IdCardService::G()->idcardIndex('身份证号');
    ```
    小弟:收到.

    几天后,
    小弟:报告老大 ,我们身份证地区编码得不到了
    老大:收到。现在改
    老大:打开入口代码
    ```
    // IdCardService::G(AliIdService::G());
    IdCardService::G(TencentIdService::G());
    ```
    老大:好了,已经由接入 A 家改为接入 T 家了。

    出错的时候跟踪方便。切换服务提供商方便。小弟们不需要乱动,这才叫优雅。
    dvaknheo
        16
    dvaknheo  
       2021-01-21 18:10:33 +08:00
    再来个优雅的:

    $client = new Client("YourAppCode");

    $result = $client->gstoreDisease()
    ->withSymptom('发烧')
    ->request();

    老板: 免费接口,每日 100 次免费调用,会员可不限次数调用。 我们接口要烧那么多钱么?减少调用!

    老大:没问题。

    ```
    class MyService{ function gstoreDisease($Symptom ){
    // 如果我们数据库已经有,返回数据库已经存好的
    // 如果数据库没有,则请求第三方并存入数据
    }}
    小弟代码不变
    // ...
    $result = MyService::G()->gstoreDisease('发烧');
    ```
    dvaknheo
        17
    dvaknheo  
       2021-01-21 21:00:33 +08:00
    立一个 flag,我重写一个客户端,这三天内完成
    imgbed
        18
    imgbed  
       2021-01-21 21:50:12 +08:00 via Android
    @topthink tp 我是支持的,api 目前还没用到,可能后续有需求吧
    dvaknheo
        19
    dvaknheo  
       2021-01-22 00:13:20 +08:00
    写好了,一百五十多,放这里可能太长,又没排版 就是实现上述代码的第三方 think-api 接入类。
    有人感兴趣的话我就贴在这
    topthink
        20
    topthink  
    OP
       2021-01-22 10:31:47 +08:00
    @dvaknheo 欢迎探讨 我只是说如何更优雅 我也没说是最优雅的 没必要抠字眼,至少目前我没有看到第三方的接口 SDK 有更好的
    @zpfhbyx 没错 就是通过封装一个更好用的 SDK 让接口可以更优雅的调用
    dvaknheo
        21
    dvaknheo  
       2021-01-22 16:36:07 +08:00
    那我贴出来吧。为什么要有 facade 等东西,目的就是为了实现 “调用方式不变,实现方式可变”啊。

    ```
    <?php
    require_once(__DIR__.'/vendor/autoload.php'); //@DUCKPHP_HEADFILE
    use think\api\Client;
    use GuzzleHttp\Client as GuzzleHttp_Client;
    use GuzzleHttp\HandlerStack ;
    use think\helper\Str;

    trait SingletonExTrait
    {
    protected static $_instances = [];
    public static function G($object = null)
    {
    if (defined('__SINGLETONEX_REPALACER')) {
    $callback = __SINGLETONEX_REPALACER;
    return ($callback)(static::class, $object);
    }
    //fwrite(STDOUT,"SINGLETON ". static::class ."\n");
    if ($object) {
    self::$_instances[static::class] = $object;
    return $object;
    }
    $me = self::$_instances[static::class] ?? null;
    if (null === $me) {
    $me = new static();
    self::$_instances[static::class] = $me;
    }

    return $me;
    }
    }
    class MyService
    {
    use SingletonExTrait;
    public $optiOns=[
    'endpoint' => 'https://api.topthink.com/',
    'app_code' => '???',
    'default_http_method'=>'GET',
    ];
    public function init(array $options, object $cOntext= null)
    {
    $this->optiOns= array_intersect_key(array_replace_recursive($this->options, $options) ?? [], $this->options);
    }
    protected function do_call($method, $args)
    {
    $http_method = $this->get_http_method($method);
    $uri = $this->get_uri($method);
    $parameters = $this->get_parameters($method, $args);

    try {
    return $this->do_request($this->options['endpoint'], $this->options['app_code'], $http_method, $uri, $parameters);
    } catch (RequestException $e) {
    if ($e->hasResponse()) {
    $respOnse= $e->getResponse();
    throw new Exception($response->getStatusCode(), $response->getBody()->getContents());
    }
    throw $e;
    }
    }
    protected function get_http_method($method)
    {
    return $this->options['default_http_method'];
    }
    protected function get_uri($method)
    {
    //TODO 移除 Str 的引用
    return $this->uri_map[$method] ?? Str::snake(class_basename($method), "/");
    }
    protected function get_parameters($method, $args)
    {
    $reflect = new \ReflectionMethod($this, $method);
    $ret=[];
    $params = $reflect->getParameters();
    foreach ($args as $i => $v) {
    if($v === null){
    continue;
    }
    $name = $params[$i]->getName();
    $ret[$name] = $v;
    }
    return $ret;
    }

    protected function do_request($endpoint, $app_code, $method, $uri, $parameters)
    {
    $body =[];
    if ($method == 'GET') {
    $options['query'] = $data;
    } else {
    $options['body'] = $data;
    }

    $handleStack = HandlerStack::create(null);
    $client = new GuzzleHttp_Client([
    'base_uri' => $endpoint,
    'handler' => $handleStack,
    'headers' => [
    'Authorization' => "AppCode ".$app_code,
    'User-Agent' => "ThinkApi/1.0",
    ],
    'verify' => false,
    ]);
    $respOnse= $client->request($method, $uri, $options);
    $result = $response->getBody()->getContents();
    if (false !== strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
    $result = json_decode($result, true);
    }

    return $result;
    }
    }

    // 这个类用脚本生成,省略更多
    class CalendarService extends MyService
    {
    /**
    * 查日历
    *
    * @access public
    * @param mixed $year_month
    */
    public function calendarMonth($yearMOnth= null){
    return $this->do_call(__FUNCTION__, func_get_args());
    }
    }
    class LocalCalendarService extends CalendarService
    {
    public function calendarMonth($yearMOnth= null){
    return ['这是模拟数据'];
    }
    }

    //*
    // 这里是核心工程师老大的干活
    //CalendarService::G(LocalCalendarService::G()); // 线上出问题的时候切这句
    $optiOns=['app_code'=>'???'];
    CalendarService::G()->init($options);

    //// 下面是小弟写
    $result = CalendarService::G()->calendarMonth('2019-1');
    var_dump($result);
    return;
    //*/
    $client = new Client('???');
    $result = $client->calendarMonth()->withYearMonth('2019-1')->request();
    var_dump($result);
    ```
    topthink
        22
    topthink  
    OP
       2021-01-22 17:29:38 +08:00
    @dvaknheo 你这还是基于 think-api 的库 有什么优势 我还以为你要用 100 行代码写一个 think-api 出来呢,其实 think-api 库就是一个 IDE 提示工具而已 真正的调用又不是在这个库 你把问题想的太简单了吧
    dvaknheo
        23
    dvaknheo  
       2021-01-22 18:54:21 +08:00
    @topthink 没基于 think-api 啊, 除了用字符串处理部分还用到 think\helper\Str 而已。 最前面的 use think\api\Client 是后面代码切换演示方便而已。
    topthink
        24
    topthink  
    OP
       2021-01-23 16:40:06 +08:00
    @dvaknheo 问题是我看不明白你如何离开了 think-api 或者切换到其它 sdk 的支持 怎么做到同样的优雅调用? think-api 的优雅的前提是统一封装了 api 接口的 这个才是最大的问题和工作量
    dvaknheo
        25
    dvaknheo  
       2021-01-23 18:45:18 +08:00
    @topthink
    这个客户端,优雅在这里: 小弟不用改业务代码,只要老大在核心代码那里修改到子类就可以切换实现

    CalendarService 是根据 think-api 的库生成的。CalendarService 的所有方法都会在 IDE 里实现。没必要方法和文档分离。 所有的 API 函数方法 内容都一句 return $this->do_call(__FUNCTION__, func_get_args());


    CalendarService::G(LocalCalendarService::G());

    演示的是切到第三方的实现 。 这里只是简单的输出 ['这是模拟数据'] 。 实际应用的时候,可以针对性的各种修改,比如 api 结果如果有缓存则用缓存。 比如当对 think-api 的返回的结果异常 如接口上线, 可以添加其他 服务端的支持。

    这一切,不需要改动业务代码。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3311 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 04:24 PVG 12:24 LAX 20:24 JFK 23:24
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86