V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
topthink
V2EX  ›  PHP

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

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

    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 开发。
    zpfhbyx
        8
    zpfhbyx  
       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
    看标题,以为是技术讨论。
    zpfhbyx
        13
    zpfhbyx  
       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   ·   我们的愿景   ·   实用小工具   ·   4812 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 52ms · UTC 09:56 · PVG 17:56 · LAX 02:56 · JFK 05:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.