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
1
ben1024 2021-01-20 18:04:08 +08:00
推荐的是付费接口,没有免费接口服务列表吗
|
2
topthink OP @ben1024 https://docs.topthink.com/think-api/1888715 这里有 标注为免费的就是免费接口
|
3
lepig 2021-01-20 21:40:14 +08:00
这个对开发来说确实听友好的。但是奈何老板不同意。
老板说,用付费的金接口,我要你们干啥。 |
4
topthink OP @lepig 现代化开发理念不是什么都要自己独立开发的。每个接口做自己专业的事情 因为专注才能更专业 就和组件化开发一样 接口化也是一种趋势。而且大多数接口自己没有数据没法开发的,比如身份证实名认证 只能调用第三方的。那么 ThinkAPI 的优势就是帮你简化开发 同时也能降低成本。我认为老板关注的不是用不用接口的问题 是项目什么时候能上线的问题吧~
|
6
imgbed 2021-01-20 22:18:02 +08:00
这种付费接口很多商家在做
|
7
lepig 2021-01-20 22:38:06 +08:00
@Actrace 我还真不是。只是我们 cto 为了节约成本新申请了个阿里云账号。然后迁移所有的服务器 /oss/容器等等。然后基础运维部断断续续肝了一个多月才完成。java,php 的所有项目全部要更改配置。
坑的一笔。 > 我是 php 开发。 |
8
zpfhbyx 2021-01-21 13:17:16 +08:00
|
9
topthink OP |
12
unicloud 2021-01-21 16:22:08 +08:00
看标题,以为是技术讨论。
|
14
ztxcccc 2021-01-21 17:04:55 +08:00
标题和内容有什么关系吗?
|
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 家了。 出错的时候跟踪方便。切换服务提供商方便。小弟们不需要乱动,这才叫优雅。 |
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('发烧'); ``` |
17
dvaknheo 2021-01-21 21:00:33 +08:00
立一个 flag,我重写一个客户端,这三天内完成
|
19
dvaknheo 2021-01-22 00:13:20 +08:00
写好了,一百五十多,放这里可能太长,又没排版 就是实现上述代码的第三方 think-api 接入类。
有人感兴趣的话我就贴在这 |
20
topthink OP |
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); ``` |
22
topthink OP @dvaknheo 你这还是基于 think-api 的库 有什么优势 我还以为你要用 100 行代码写一个 think-api 出来呢,其实 think-api 库就是一个 IDE 提示工具而已 真正的调用又不是在这个库 你把问题想的太简单了吧
|
23
dvaknheo 2021-01-22 18:54:21 +08:00
@topthink 没基于 think-api 啊, 除了用字符串处理部分还用到 think\helper\Str 而已。 最前面的 use think\api\Client 是后面代码切换演示方便而已。
|
24
topthink OP @dvaknheo 问题是我看不明白你如何离开了 think-api 或者切换到其它 sdk 的支持 怎么做到同样的优雅调用? think-api 的优雅的前提是统一封装了 api 接口的 这个才是最大的问题和工作量
|
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 的返回的结果异常 如接口上线, 可以添加其他 服务端的支持。 这一切,不需要改动业务代码。 |