news 2026/10/15 15:53:06

CRMEB 项目中的 Guzzle HTTP Client(v3)深度使用指南:客户端、请求选项与并行发送

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CRMEB 项目中的 Guzzle HTTP Client(v3)深度使用指南:客户端、请求选项与并行发送
  • 后端
  • 电商
  • 前端

【免费下载链接】CRMEB

🔥CRMEB开源商城免费开源多语言商城系统,Tp6框架商城,系统可商用;包含小程序商城、H5商城、公众号商城、PC商城、App,支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序直播、页面DIY,前后端分离,方便二开,更有详细使用文档、接口文档、数据字典、二开文档/视频教程,欢迎大家提出宝贵意见和建议

项目地址:https://gitcode.com/gh_mirrors/cr/CRMEB
点击查看免费下载

本指南以 Guzzle v3(guzzle/guzzle 3.9.3)官方文档为骨架,完整讲解 HTTP 客户端的三大核心组成(Client / Request / Response)、Base URL 合并规则、全部请求选项(Request Options)以及并行请求与插件事件机制,并结合 CRMEB 商城源码展示它在对象存储上传、第三方云 SDK 依赖中的真实应用场景。读完本文,你将能熟练使用 Guzzle v3 构造任意 HTTP 请求、配置认证与代理、控制超时与 SSL 校验,并理解它在 CRMEB 项目中的落地位置。

一、Guzzle HTTP Client 是什么:三大核心部件

Guzzle 让 PHP 开发者能够完全掌控 HTTP 请求,同时遵循 HTTP/1.1 的最佳实践。它的 HTTP 功能是构建在 PHP libcurl 绑定之上的健壮框架。其核心由三部分组成(对应文档 crmeb/vendor/guzzle/guzzle/docs/http-client/client.rst):

组成类职责
ClientsGuzzle\Http\Client创建并发送请求,将响应关联到请求上
RequestsGuzzle\Http\Message\Request无请求体的请求
RequestsGuzzle\Http\Message\EntityEnclosingRequest携带请求体的请求
ResponsesGuzzle\Http\Message\Response服务端返回的响应对象

Client 对象负责三件事:创建请求、发送请求、把响应设置到请求对象上。所有请求都由请求工厂(Guzzle\Http\Message\RequestFactoryInterface)创建,默认工厂通常够用,你也可以通过$client->setRequestFactory()注入自定义工厂。

二、创建客户端与 Base URL 合并规则

2.1 基本用法

实例化 Client 时,可以传入一个可选的 base URL(本质是一个 URI 模板)和一个可选配置数组。使用相对 URL 创建请求时,base URL 会自动与请求 URL 合并:

use Guzzle\Http\Client; // 创建客户端并提供 base URL $client = new Client('https://api.github.com'); $request = $client->get('/user'); $request->setAuth('user', 'pass'); echo $request->getUrl(); // >>> https://api.github.com/user // 必须显式发送请求才会真正发起传输 $response = $request->send(); echo $response->getBody(); // >>> {"type":"User", ... echo $response->getHeader('Content-Length'); // >>> 792 $data = $response->json(); echo $data['type']; // >>> User

2.2 Base URL 与相对 URL 的合并规则

Guzzle 遵循 RFC 3986 的 5.2 节来合并 base URL 与相对 URL。注意上文例子中传给get()的是/user(带前导斜杠),它是"绝对路径"形式的相对 URL,因此合并后实际得到https://api.github.com/user。具体规则如下:

  1. 绝对路径(带前导斜杠,如/path/to/something):会覆盖 base URL 中的既有路径,同时相对 URL 携带的查询字符串会替换 base URL 的查询字符串。
  2. 相对路径(不带前导斜杠):会追加到 base URL 路径之后。例如 base URL 为https://api.twitter.com/v1.1,用statuses/user_timeline.json创建 GET 请求,得到https://api.twitter.com/v1.1/statuses/user_timeline.json。如果同时提供了查询字符串,则相对路径追加到 base URL 路径,查询参数合并进 base URL 的查询字符串。
  3. 绝对 URL(如http://httpbin.org/ip):请求完全按该 URL 原样发送,base URL 的任何组成部分都不会参与合并。

从源码看,Client 内部将 baseUrl 解析为Guzzle\Http\Url对象,在 Client.php 的createRequest()中完成合并逻辑,你可以直接在源码中验证上述三种合并分支。

三、客户端配置选项(Configuration Options)

构造函数第二个参数是配置数组,可以包含 URI 模板变量或改变客户端行为的特殊选项:

配置项说明
request.options关联数组,作为每个请求的默认 Request options,作用于客户端创建的所有请求
redirect.disable禁用客户端创建的每个请求的 HTTP 重定向跟随
curl.options关联数组,作为每个请求的 cURL 选项;若某项的键或值是字符串,Guzzle 会自动匹配对应的 cURL 常量(如字符串"CURLOPT_PROXY"会被转换为常量CURLOPT_PROXY)
ssl.certificate_authority值为true时使用 Guzzle 内置的 SSL 证书包(默认行为);'system'使用系统证书包;字符串指向某个证书文件;字符串指向某目录则使用其中多个证书;false则关闭 SSL 校验(不推荐)。当 Guzzle 运行在 phar 中时,内置证书会被解压到系统临时目录,每次创建客户端都会做 MD5 完整性校验
command.params使用Guzzle\Service\Client时,作为每个命令默认选项的关联数组

3.1 综合配置示例

下面的示例设置了默认请求头、默认查询参数、默认认证方案和默认代理。配置数组中的变量还可以注入到 base URL 中(即 URI 模板):

use Guzzle\Http\Client; $client = new Client('https://api.twitter.com/{version}', array( 'version' => 'v1.1', 'request.options' => array( 'headers' => array('Foo' => 'Bar'), 'query' => array('testing' => '123'), 'auth' => array('username', 'password', 'Basic|Digest|NTLM|Any'), 'proxy' => 'tcp://localhost:80' ) ));

3.2 自定义 User-Agent

默认 User-Agent 格式为Guzzle/<Guzzle_Version> curl/<curl_version> PHP/<PHP_VERSION>,可通过setUserAgent()定制:

// 完全覆盖默认 User-Agent $client->setUserAgent('Test/123'); // 在默认 User-Agent 前追加字符串 $client->setUserAgent('Test/123', true);

该方法的签名在 Client.php 中为setUserAgent($userAgent, $includeDefault = false),第二个参数控制是否保留默认串。

四、使用客户端创建各类请求

Client 暴露了多种创建 Request 对象的方法:

  • $client->createRequest($method, $uri, array $headers, $body, $options)— 创建任意自定义 HTTP 请求
  • $client->get($uri, array $headers, $options)— 创建 GET 请求
  • $client->head($uri, array $headers, $options)— 创建 HEAD 请求
  • $client->delete($uri, array $headers, $body, $options)— 创建 DELETE 请求
  • $client->post($uri, array $headers, $postBody, $options)— 创建 POST 请求
  • $client->put($uri, array $headers, $body, $options)— 创建 PUT 请求
  • $client->patch($uri, array $headers, $body, $options)— 创建 PATCH 请求
use Guzzle\Http\Client; $client = new Client('http://baseurl.com/api/v1'); // 相对 URL(相对路径追加到 base URL 路径) // 最终 URL: http://baseurl.com/api/v1/path?query=123&value=abc $request = $client->get('path?query=123&value=abc'); $response = $request->send(); // 带绝对路径的相对 URL(覆盖 base URL 的路径部分) // 最终 URL: http://baseurl.com/path?query=123&value=abc $request = $client->head('/path?query=123&value=abc'); $response = $request->send(); // 绝对 URL,完全按原样发送 $request = $client->delete('http://www.example.com/path?query=123&value=abc'); $response = $request->send(); // PUT:以 PHP 流的内容作为请求体,并指定自定义请求头 $request = $client->put('http://www.example.com/upload', array( 'X-Header' => 'My Header' ), fopen('http://www.test.com/', 'r')); $response = $request->send(); // POST:手动追加文件 $request = $client->post('http://localhost:8983/solr/update') ->addPostFiles(array('file' => '/path/to/documents.xml')); $response = $request->send(); // 探测资源是否允许 DELETE 方法 $supportsDelete = $client->options('/path')->send()->isMethodAllowed('DELETE');

这些便捷方法在 Client.php 中均有对应实现,内部统一调用createRequest()。

五、静态客户端(StaticClient)

Guzzle 还提供了静态门面,可以更方便地发送简单 HTTP 请求,无需实例化客户端:

// 挂载静态客户端,之后即可通过 \Guzzle 访问 Guzzle\Http\StaticClient::mount(); $response = Guzzle::get('http://guzzlephp.org');

静态客户端的每个请求方法(get()、post()、put()等)都接受一个请求选项关联数组:

$response = Guzzle::post('http://test.com', array( 'headers' => array('X-Foo' => 'Bar'), 'body' => array('Test' => '123'), 'timeout' => 10 ));

从 StaticClient.php 源码看,mount($className = 'Guzzle', ClientInterface $client = null)通过class_alias注册别名,request()内部惰性创建new Client()并调用createRequest($method, $url, null, null, $options)。

六、请求选项(Request Options)全解析

请求选项可以在创建请求时指定,也可以在客户端的request.options中配置为默认值。它可以控制请求头、查询字符串、响应下载位置、代理、认证等各个方面。

6.1 headers

应用到请求的请求头关联数组。当在创建方法的$options参数中指定时,$options中的 headers 会覆盖$headers参数中同名的头部:

$request = $client->get($url, array(), array( 'headers' => array('X-Foo' => 'Bar') ));

也可以在客户端上配置默认请求头:

$client = new Guzzle\Http\Client(); // 用路径语法设置单个请求头 $client->setDefaultOption('headers/X-Foo', 'Bar'); // 设置全部请求头 $client->setDefaultOption('headers', array('X-Foo' => 'Bar'));

此外,任何客户端默认请求选项都可以通过配置对象设置:

$client->getConfig()->setPath('request.options/headers/X-Foo', 'Bar');

6.2 query

请求的查询字符串参数关联数组。在$options中指定时,会覆盖$url中的同名查询参数:

$request = $client->get($url, array(), array( 'query' => array('abc' => '123') ));

也可在客户端上配置默认查询参数:

$client = new Guzzle\Http\Client(); // 路径语法设置单个查询参数 $client->setDefaultOption('query/abc', '123'); // 设置一组默认查询参数 $client->setDefaultOption('query', array('abc' => '123'));

6.3 body

设置请求体。body选项的值可以是Guzzle\Http\EntityBodyInterface、字符串、fopen资源,或发送 POST 时的数组。一旦指定body选项,它会覆盖创建方法中的$body参数。

6.4 auth

指定 HTTP 认证参数的数组。数组第 0 位是用户名,第 1 位是密码,第 2 位可选认证类型。可选类型为:"Basic"(默认)、"Digest"、"NTLM"、"Any":

$request = $client->get($url, array(), array( 'auth' => array('username', 'password', 'Digest') )); // 让客户端每个请求都携带认证头 $client->setDefaultOption('auth', array('username', 'password', 'Digest'));

6.5 cookies

指定要添加到请求中的 cookie 关联数组。

6.6 allow_redirects

指定请求是否跟随重定向。默认跟随;设为false则禁用(等价于配置项redirect.disable,内部对应Guzzle\Http\RedirectPlugin::DISABLE常量)。

6.7 save_to

指定响应体下载到何处,可以传文件路径、fopen资源或Guzzle\Http\EntityBodyInterface对象。

6.8 events

events选项让挂载事件监听器变得简单,它必须是"事件名 → Closure(或包含 Closure 与优先级的数组)"的关联数组:

$request = $client->get($url, array(), array( 'events' => array( 'request.before_send' => function (\Guzzle\Common\Event $e) { echo 'About to send ' . $e['request']; } ) )); // 静态客户端用法 Guzzle::get($url, array( 'events' => array( 'request.before_send' => function (\Guzzle\Common\Event $e) { echo 'About to send ' . $e['request']; } ) ));

6.9 plugins

plugins选项让你为请求挂载一组插件:

Guzzle::get($url, array( 'plugins' => array( new Guzzle\Plugin\Cache\CachePlugin(), new Guzzle\Plugin\Cookie\CookiePlugin() ) ));

6.10 exceptions

exceptions选项用于在非成功的 HTTP 响应码(如 404、500 等)下禁用异常抛出。设为false则不抛异常。

6.11 params

params选项用于向请求添加数据参数关联数组。注意:这不是查询字符串参数。

6.12 timeout / connect_timeout

  • timeout:整个传输允许的最长秒数,超时即中断。
  • connect_timeout:连接等待的最长秒数。
  • 两者设为0表示无限期等待。
$request = $client->get('http://www.example.com', array(), array( 'timeout' => 20, 'connect_timeout' => 1.5 ));

6.13 verify

true启用 SSL 证书校验(默认);false禁用;或者传入 CA bundle 路径,使用自定义证书进行校验。

6.14 cert

cert指定 PEM 格式的 SSL 客户端证书(用于要求双向认证的服务器)。如果证书需要密码,用数组且密码作为第二个元素。通常与ssl_key配合使用:

$request = $client->get('https://www.example.com', array(), array( 'cert' => '/etc/pki/client_certificate.pem' )); $request = $client->get('https://www.example.com', array(), array( 'cert' => array('/etc/pki/client_certificate.pem', 's3cr3tp455w0rd') ));

6.15 ssl_key

ssl_key指定 PEM 格式私钥文件,可带密码保护。密码属于敏感信息,请妥善保管包含它的 PHP 脚本。通常与cert配合使用:

$request = $client->get('https://www.example.com', array(), array( 'ssl_key' => '/etc/pki/private_key.pem' )); $request = $client->get('https://www.example.com', array(), array( 'ssl_key' => array('/etc/pki/private_key.pem', 's3cr3tp455w0rd') ));

6.16 proxy

proxy用于指定 HTTP 代理,格式如http://username:password@192.168.16.1:10。

6.17 debug

debug用于显示一次传输的详细 cURL 输出。

6.18 stream

使用静态客户端时,stream设为true会返回一个Guzzle\Stream\Stream对象,可以按需从流中拉取数据(而不是让 cURL 一次性把整个响应下载到内存):

$stream = Guzzle::get('http://guzzlephp.org', array('stream' => true)); while (!$stream->feof()) { echo $stream->readLine(); }

七、发送请求:串行与并行

7.1 send() 发送单个请求

请求可以通过 Request 对象的send()发送,也可以通过 Client 的send()发送:

$request = $client->get('http://www.amazon.com'); $response = $client->send($request);

7.2 并行发送(Parallel Requests)

Client 的send()既可以接受单个Guzzle\Http\Message\RequestInterface对象,也可以接受一个 RequestInterface 对象数组——传入数组时,请求将被并行发送。

串行发送大量 HTTP 请求会因"前一个请求必须完成后才能发起下一个"而造成脚本无谓的延迟;并行发送则让整个请求池以其中最慢请求的速度完成,大幅缩短多个 HTTP 请求的总耗时。Guzzle 对 PHP 的 curl_multi 函数提供了封装。

并行发送三个请求的示例:

use Guzzle\Common\Exception\MultiTransferException; try { $responses = $client->send(array( $client->get('http://www.google.com/'), $client->head('http://www.google.com/'), $client->get('https://www.github.com/') )); } catch (MultiTransferException $e) { echo "The following exceptions were encountered:\n"; foreach ($e as $exception) { echo $exception->getMessage() . "\n"; } echo "The following requests failed:\n"; foreach ($e->getFailedRequests() as $request) { echo $request . "\n\n"; } echo "The following requests succeeded:\n"; foreach ($e->getSuccessfulRequests() as $request) { echo $request . "\n\n"; } }

成功时返回一组Guzzle\Http\Message\Response对象。单个请求失败不会导致整个请求池失败——传输过程中抛出的所有异常会被聚合成Guzzle\Common\Exception\MultiTransferException。对应实现为 Client.php 的sendMultiple(),其内部使用CurlMultiProxy(默认 MAX_HANDLES = 3,即最多同时打开 3 个 cURL 句柄)。

八、插件与事件系统

Guzzle 提供了易用的请求插件,通过基于 Symfony2 Event Dispatcher 的"信号槽"事件通知为请求添加行为。任何挂载到 Client 对象上的事件监听器或订阅者,都会自动挂载到该客户端创建的每一个请求上。

8.1 让每个请求共享同一 cookie 会话

将Guzzle\Plugin\Cookie\CookiePlugin挂到客户端,即可让该客户端创建的每个请求都支持 cookie,且共用同一个 cookie 会话:

use Guzzle\Plugin\Cookie\CookiePlugin; use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar; // 创建 cookie 插件 $cookiePlugin = new CookiePlugin(new ArrayCookieJar()); // 将插件作为订阅者挂到客户端 $client->addSubscriber($cookiePlugin);

8.2 客户端发出的事件

Guzzle\Http\Client对象发出如下事件:

事件名说明事件数据
client.create_request客户端创建请求时触发client:客户端对象;request:已创建的请求
use Guzzle\Common\Event; use Guzzle\Http\Client; $client = new Client(); // 监听请求创建,打印出来 $client->getEventDispatcher()->addListener('client.create_request', function (Event $e) { echo 'Client object: ' . spl_object_hash($e['client']) . "\n"; echo "Request object: {$e['request']}\n"; });

九、CRMEB 项目中的真实落地

9.1 Guzzle v3 的引入来源

CRMEB 的 crmeb/composer.lock 中锁定了guzzle/guzzlev3.9.3,它并非由 CRMEB 直接声明,而是由腾讯云对象存储 SDKqcloud/cos-sdk-v5间接依赖(require"guzzle/guzzle": "~3.7")带入的。此外 crmeb/composer.json 还直接声明了guzzlehttp/guzzle: 6.5.5与guzzlehttp/psr7: ^1.7,因此 CRMEB 中同时存在 v3 与 v6 两代 Guzzle 代码路径,属于依赖生态的典型叠加。

9.2 EntityBody 在文件流上传中的使用

文档提到的Guzzle\Http\EntityBodyInterface在 CRMEB 上传组件中有直接应用。以阿里云 OSS 存储实现 crmeb/crmeb/services/upload/storage/Oss.php 为例,stream()方法在上传前把文件内容包装为 Guzzle 的 EntityBody:

use Guzzle\Http\EntityBody; // ... $fileContent = (string)EntityBody::factory($fileContent); $uploadInfo = $this->app()->putObject($this->storageName, $key, $fileContent);

同样地,京东云存储 crmeb/crmeb/services/upload/storage/Jdoss.php 也通过EntityBody::factory()将上传内容转换为统一实体(对应文档中body选项支持Guzzle\Http\EntityBodyInterface值的说明)。

9.3 与 Guzzle v6 / PSR-7 的共存

CRMEB 的云存储模块在另一些实现中则使用新一代 PSR-7 工具类:腾讯云 COS(Cos.php)、华为云 OBS(Obs.php)、火山引擎 TOS(Tyoss.php)都use GuzzleHttp\Psr7\Utils;,微信 OAuth 服务(WechatOauth.php)也使用GuzzleHttp\Psr7\Uri处理 URI。这两套命名空间可以共存,正是 Guzzle 三代架构设计(v3 基于Guzzle\Http\*自定义消息体系、v6 起全面拥抱 PSR-7)在真实项目中的体现。

9.4 对照:CRMEB 自研的 curl 封装

值得注意的是,CRMEB 核心业务代码并未直接依赖 Guzzle 发送常规 HTTP 请求,而是封装了自己的 crmeb/crmeb/services/HttpService.php(基于原生 curl),提供getRequest()、postRequest()、request()等静态方法,并统一处理超时、HTTPS 证书校验、header 输出与http_code判定。这也解释了 Guzzle 在 CRMEB 中主要作为"第三方云 SDK 的传输层依赖"而非"项目自有 HTTP 客户端"的定位——你可以对照本文的timeout/verify/proxy等选项,理解这些概念在 curl 参数(CURLOPT_TIMEOUT、CURLOPT_SSL_VERIFYPEER等)中的等价映射。

十、小结与注意事项

  • Guzzle v3 的核心工作流是"创建 Client → 用便捷方法创建 Request →send()发送 → 解析 Response",Base URL 合并遵循 RFC 3986,三类 URL 形态(绝对路径 / 相对路径 / 绝对 URL)的合并行为截然不同,务必区分。
  • 客户端配置(request.options、curl.options、ssl.certificate_authority等)与单次请求选项(headers、query、auth、timeout、verify、proxy等)是两套机制,前者作用于所有请求,后者单次覆盖。
  • 需要同时请求多个资源时,优先使用send(array(...))并行模式;失败请求通过MultiTransferException::getFailedRequests()与getSuccessfulRequests()分别处理。
  • 在 CRMEB 仓库中,Guzzle v3 由qcloud/cos-sdk-v5间接引入(guzzle/guzzle ~3.7),锁版本为 v3.9.3,其EntityBody被用于 OSS / 京东云文件流上传;若需在项目内直接使用 Guzzle 发送请求,请确认引入路径与版本命名空间(Guzzle\Http\*为 v3,GuzzleHttp\*为 v6+),避免混用导致类加载错误。
  • 后端
  • 电商
  • 前端

【免费下载链接】CRMEB

🔥CRMEB开源商城免费开源多语言商城系统,Tp6框架商城,系统可商用;包含小程序商城、H5商城、公众号商城、PC商城、App,支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序直播、页面DIY,前后端分离,方便二开,更有详细使用文档、接口文档、数据字典、二开文档/视频教程,欢迎大家提出宝贵意见和建议

项目地址:https://gitcode.com/gh_mirrors/cr/CRMEB
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 15:52:54

内蒙古SHP数据包使用指南:坐标系、拓扑与道路里程统计实战

简介&#xff1a;面向GIS研究与规划人员的数据集&#xff0c;提供2022年7月发布的内蒙古自治区三级行政区划&#xff08;省级、地市、区县&#xff09;及道路网、铁路网矢量数据&#xff0c;覆盖高速公路、国道、省道与铁路线路、站点等要素&#xff0c;可支撑行政区划演变、交…

作者头像 李华
网站建设 2026/10/15 15:51:06

Python+OpenCV车牌识别实战:传统图像处理全链路解析

简介&#xff1a;这是一套基于 Python 与 OpenCV 实现的车牌识别系统源码资源&#xff0c;面向计算机视觉入门者、课程设计或毕业设计需求者&#xff0c;以及希望把传统图像处理与摄像头实时识别结合的开发者。项目在图片车牌号识别、GUI 界面设计、识别数据导出 Excel 的基础上…

作者头像 李华
网站建设 2026/10/15 15:51:03

Delphi嵌入ECharts实现工业软件数据可视化

简介&#xff1a;本资源是一个面向Delphi开发者的数据可视化实践项目&#xff0c;聚焦于在原生Windows桌面应用中集成百度ECharts图表库&#xff0c;解决传统Delphi程序缺乏现代交互式图表能力的痛点&#xff0c;适用于数据分析、监控系统、报表工具等需动态展示数据的中高级开…

作者头像 李华
网站建设 2026/10/15 15:50:30

Abp vNext多租户API登录改造实战指南

1. 为什么Abp vNext的租户API登录不是“开箱即用”的功能在某跨平台系统重构项目中&#xff0c;我们团队接手了一个基于Abp vNext构建的多租户SaaS后台。初期需求很明确&#xff1a;前端Web端走常规MVC登录流程&#xff0c;而移动端App、IoT设备管理后台、第三方系统集成模块&a…

作者头像 李华
网站建设 2026/10/15 15:50:19

eve 部署指南:从 Vercel 托管到自建 Node 服务的完整生产化方案

人工智能AI AgentAgent 框架后端工具调用 【免费下载链接】eve The Open Framework for Building Agents 项目地址&#xff1a; https://gitcode.com/gh_mirrors/eve28/eve 点击查看 免费下载 本指南围绕 eve 官方部署概览展开&#xff0c;系统讲解在 Vercel 与自建基础设施两…

作者头像 李华
网站建设 2026/10/15 15:48:54

基于HMS ML Kit的Android运动App姿势抓拍实现

前阵子帮某健身团队做一款跟练App的Demo&#xff0c;需求听起来不复杂&#xff1a;用户对着手机摄像头做动作&#xff0c;App要能识别出“现在摆的是什么姿势”&#xff0c;并且在动作到位的那一刻自动抓拍一张照片。说白了就是很多运动类产品里的“动作瞬间捕获”功能。可真要…

作者头像 李华