新特性解读-mysql-80-memcached-api-新特性

作者:杨涛涛资深数据库专家,专研 MySQL 十余年。擅长 MySQL、PostgreSQL、MongoDB 等开源数据库相关的备份恢复、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生,为各大运营商及银行金融企业提供 MySQL 相关技术支持、MySQL 相关课程培训等工作。本文来源:原创投稿*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。本文关键字:memcached 一款优秀的缓存系统memcache 本身是一款分布式的高速缓存系统,以 key-value 的形式常驻内存,一般用来做网站或者数据库的缓存使用。特别是对以下场景非常适合用 memcache 来做缓存: 频繁访问的数据安全性要求比较低的数据更新比较频繁的小表(用户状态表、物品库存等)MySQL memcached apiMySQL 5.6 —— 开始支持MySQL 5.6 把 memcache 功能以插件形式集成到 MySQL 数据库中,称为 memcached api。这样一来,memcache 的数据以 InnoDB 关系表的形式同步于磁盘,解决了 memcache 的几个问题: 解决了 memcache 的数据持久化的问题;可以很方便的以 SQL 语句的形式直接访问 memcache 的数据;不需要单独安装 memcache,安装 MySQL 即可使用。MySQL 5.7 —— 深入优化MySQL 5.7 对 memcached api 做了深入优化,官方数据显示使用 memcached api,在只读的场景中,QPS 提升到 100W。 MySQL 8.0 —— 新增特性MySQL 8.0 发布后,又在功能上给 memcached api 增加了两个新特性。 批量获取 KEY相比原来一次只能读取一个 Key 来讲,减少了客户端与 MySQL 服务端的数据交互次数。 ...

July 6, 2020 · 4 min · jiezi

一个技术预案让老板当场喊出了奥利给

我还是那个前浪,很多朋友说看了我的故事后觉得有点励志,在大家都处于“中年危机”、“大龄程序员找不到出路”等各种焦虑的时候,我的故事像一碗鸡汤(这个说法莫名感觉很油腻,能不能换一个???) 其实我认为与其消极焦虑,不如发挥自身优势积极面对。自从公司新项目使用了云服务器之后,我就又开始了继续学习的道路。也正因为不断地学习,从最开始被实习生轻视,到后来的让老板赞赏,无疑是打了一个漂亮的翻身仗。(了解前情请戳:甩锅大会上,我是如何绝地求生的) 上次因为业务量的快速上涨,导致服务器请求量远远超出预估,好在华为云弹性云服务器想要变更规格还是非常方便的,4行代码完美解决问题。 但之后,我也在反思,作为一个资深技术人,我们就只能被动地响应需求,处理问题么?不,不应该这样。 相比起后浪们,我们的优势一方面是对技术原理的理解和实际经验的加持,另一方面,还应该能够通过对公司业务的理解甚至于对行业和商业环境的理解,提前准备一些技术方案。 顺着这个思路往下想,公司的新业务是一个内容社区,现在的日活已经突破了10万用户量级,但从后台数据看,用户增长速度明显有所下降,按照运营部门的套路,这个时候一般要提比较大的需求了。 这个需求会是什么呢?一方面,从业务角度来看,我们新业务的目标用户偏年轻化群体,并且以女性居多;另一方面,从行业角度来看,现在新媒体内容的承载形式已经从图文转变为视频和短视频。 那么,运营部门接下来会不会提出要做短视频了呢?如果做短视频,我应该先做点什么呢? 果不其然,在这周的例会上,运营部门提出,我们要通过发展短视频来进一步提升内容的多样性,从而达到提高用户停留时长与留存率的目的。 老板也点头表示赞同,我们研发老大显然事先没有准备,扭头看向了我,目光中的信任让我感动不已,加上之前我就已经预估到了这个需求,于是我代表研发团队发言: “我们研发部门已经预判到接下来项目的发展方向会是短视频,所以提前做了预案,例如从服务器的角度来说,由于短视频相比图文内容,对服务器的磁盘空间要求更大。 但我们的服务器部署在华为云弹性云服务器上,我们可以非常方便地将已有磁盘挂载给弹性云服务器,或调用创建云硬盘的接口创建新的磁盘,然后再挂载到弹性云服务器。 为了快速响应业务调整的需求,我们已经将服务器挂载磁盘的操作文档写好了。” 说着,我打开了提前准备好的文档《弹性云服务器挂载磁盘》: 步骤1:创建云硬盘。1、 创建云硬盘。 (1)接口相关信息 URI格式:POST /v2/{tenant_id}/volumes (2)请求示例 POST: https://_{endpoint}_/v2/74610f3a5ad941998e91f076297ecf27/volumes Body:{ "volume": { "name": "openapi_vol02", "availability_zone":"az_test_01", "description": "create for api test", "volume_type": "SATA", "size": 40 }} (3)响应示例 { "volume": { "status": "creating", "user_id": "f79791beca3c48159ac2553fff22e166", "attachments": [], "links": [ { "href": "https://xxx/v2/74610f3a5ad941998e91f076297ecf27/volumes/51f45e08-1d4f-44c6-a4a9-84a488e0e8d3", "rel": "self" }, { "href": "https://xxx/74610f3a5ad941998e91f076297ecf27/volumes/51f45e08-1d4f-44c6-a4a9-84a488e0e8d3", "rel": "bookmark" } ], "availability_zone": "az_test_01", "bootable": "false", "encrypted": false, "created_at": "2018-05-16T11:19:33.992984", "description": "create for api test", "updated_at": null, "volume_type": "SATA", "name": "openapi_vol02", "replication_status": "disabled", "consistencygroup_id": null, "source_volid": null, "snapshot_id": null, "shareable": false, "multiattach": false, "metadata": { "__system__volume_name": "openapi_vol02" }, "id": "51f45e08-1d4f-44c6-a4a9-84a488e0e8d3", "size": 40 }} 2、 记录响应中“volume”的ID。 ...

July 2, 2020 · 1 min · jiezi

API测试之Postman使用全指南

PostmanPostman是一个可扩展的API开发和测试协同平台工具,可以快速集成到CI/CD管道中。旨在简化测试和开发中的API工作流。 Postman 工具有 Chrome 扩展和独立客户端,推荐安装独立客户端。 Postman 有个 workspace 的概念,workspace 分 personal 和 team 类型。Personal workspace 只能自己查看的 API,Team workspace 可添加成员和设置成员权限,成员之间可共同管理 API。 当然我个人使用一般是不登录的,因为登录之后会自动将你的测试历史数据保存到账户里,你可以登陆网页端进行查看。因为API的很多数据是很敏感的,有的含有Token,或者就是一些私密信息,虽然Postman自己也强调说这样很安全,不会私下窥探用户的信息之类的,但是呢还是至少做一点有效的防范吧,自己不上传,因为网络并没有绝对的安全。所以我每次测试之后会将数据(Case)保存在本地,下次使用或者换设备的情况下将数据拷贝过来又可以继续使用了。 下面正式开始介绍如何使用Postman吧。 为什么选择Postman?如今,Postman的开发者已超过1000万(来自官网),选择使用Postman的原因如下:简单易用 - 要使用Postman,你只需登录自己的账户,只要在电脑上安装了Postman应用程序,就可以方便地随时随地访问文件。使用集合 - Postman允许用户为他们的API调用创建集合。每个集合可以创建子文件夹和多个请求。这有助于组织测试结构。多人协作 - 可以导入或导出集合和环境,从而方便共享文件。直接使用链接还可以用于共享集合。创建环境 - 创建多个环境有助于减少测试重复(DEV/QA/STG/UAT/PROD),因为可以为不同的环境使用相同的集合。这是参数化发生的地方,将在后续介绍。创建测试 - 测试检查点(如验证HTTP响应状态是否成功)可以添加到每个API调用中,这有助于确保测试覆盖率。自动化测试 - 通过使用集合Runner或Newman,可以在多个迭代中运行测试,节省了重复测试的时间。调试 - Postman控制台有助于检查已检索到的数据,从而易于调试测试。持续集成——通过其支持持续集成的能力,可以维护开发实践。 如何下载安装Postman?Step 1) 官网主页:https://www.postman.com/downl..., 下载所需版本进行安装即可。Step2)安装完成之后会要求你必须登录才能使用,没有账号可以进行注册,注册是免费的。(也可使用Google账号,不过基本不能登录,你懂的) Step3)在Workspace选择你要使用的工具并点击“Save My Preferences”保存。Step4)你将看到启动后的页面如下 如何使用Postman?下图是Postman的工作区间,各个模块功能的介绍如下: 1、New,在这里创建新的请求、集合或环境;还可以创建更高级的文档、Mock Server 和 Monitor以及API。2、Import,这用于导入集合或环境。有一些选项,例如从文件,文件夹导入,链接或粘贴原始文本。3、Runner,可以通过Collection Runner执行自动化测试。后续介绍。4、Open New,打开一个新的标签,Postman窗口或Runner窗口。5、My Workspace - 可以单独或以团队的形式创建新的工作区。6、Invite - 通过邀请团队成员在工作空间上进行协同工作。7、History - 所有秦秋的历史记录,这样可以很容易地跟踪你所做的操作。8、Collections - 通过创建集合来组织你的测试套件。每个集合可能有子文件夹和多个请求。请求或文件夹也可以被复制。9、Request tab - 这将显示您正在处理的请求的标题。默认对于没有标题的请求会显示“Untitled Request”。10、HTTP Request - 单击它将显示不同请求的下拉列表,例如 GET, POST, COPY, DELETE, etc. 在测试中,最常用的请求是GET和POST。11、Request URL - 也称为端点,显示API的URL。.12、Save - 如果对请求进行了更改,必须单击save,这样新更改才不会丢失或覆盖。13、Params - 在这里将编写请求所需的参数,比如Key - Value。14、Authorization - 为了访问api,需要适当的授权。它可以是Username、Password、Token等形式。15、Headers - 请求头信息16、Body - 请求体信息,一般在POST中才会使用到17、Pre-request Script - 请求之前 先执行脚本,使用设置环境的预请求脚本来确保在正确的环境中运行测试。 18、Tests - 这些脚本是在请求期间执行的。进行测试非常重要,因为它设置检查点来验证响应状态是否正常、检索的数据是否符合预期以及其他测试。19、Settings - 最新版本的有设置,一般用不到。 ...

June 29, 2020 · 2 min · jiezi

声网-Agora-赴美提价-IPO-申请已服务全球-20-万-开发者

从硅谷一个车库的几行代码开始,一群程序员创立了声网 Agora(上海兆言网络科技有限公司),期待用实时音视频互动 API 改变全世界人们的沟通方式。 美东时间 6 月 5 日,声网 Agora 向美国证券交易委员会(SEC)提交了首次公开募股(IPO)申请,计划在纳斯达克上市,代码为 API,募集至多 1 亿美元资金。 作为一家主营为云通讯的企业,声网受益于疫情下在线教育、音视频娱乐、远程办公的普及。局公开信息表示,2019 年全年声网的营收为 6400 万美元(约 4.5 亿元人民币)。而仅仅在 2020 年 Q1,营收就有 3600 万美元(约 2.5 亿元人民币)。 逾 20 亿 SDK 安装量,服务全球 20 万+开发者声网的实时音视频通讯能力是基于其软件定义实时网络(简称 SD-RTN)传输架构连接运营商的公网资源,并通过算法不断地降低延时、提升抗丢包率,为客户提供更好的音视频体验。 在实时音视频通讯能力之上,声网也提供上百种 API 接口,用多种产品和第三方插件,帮助客户在不同场景下提升体验丰富度,如游戏场景下的变声功能、直播和教育场景下的美颜、伴奏、连麦 PK、白板等多种道具功能等。 在声网的招股书中,还多次提到了身目标和定位是 RTE-PaaS(Real-Time Engagement Platform-as-a-Service),即实时互动 PaaS。在此之前,市场通常把声网和 Twilio 这类公司归为 CPaaS(Communication PaaS,通讯平台即服务)。 声网对自身定位更像是音视频通讯的平台,是 CPaaS 的一个垂直于领域。在招股书中,实时性音视频互动的巨大需求被反复提及,尤其是音视频在不同的场景中的应用。比起来,Twilio 对于通讯的定义似乎更为宽泛,包括电话、短信、视频和邮件等形式。 声网成立至今无全网事故,日均分钟数达到 8 亿。公司积累了为国内外许多巨头企业、流量平台服务的经验,在突增流量、高并发上有各种应急措施和紧急备案。在疫情期间,声网 Agora.io 公布了其日均实时音视频通话分钟数达到 15.6 亿。公司至今在全球200多个国家和地区拥有超过20亿的SDK安装量,服务了全球超20万开发者。 声网创始人赵斌:API 信徒 声网成立于 2013 年,由通讯行业的资深人士赵斌创立。赵斌先生此前是 WebEx 创始工程师之一、前 YY 语音 CTO。 ...

June 7, 2020 · 1 min · jiezi

分享-ApiPost如何使用测试校验

什么是测试校验?协作开发,版本升级,服务器升级,接口返回有可能因为一些bug,和我们预期结果不一致。为了便于开发&测试人员能够更快的发现bug,有利于整个产品质量以及进度的保证。我们推出测试校验功能。 如何使用测试校验?1. 定义测试用例2. 验证测试用例例: 接口返回: { "errcode": 0, "errstr": "success", "post": [], "get": [], "request": [], "put": "", "header": { "Host": "echo.apipost.cn", "Connection": "keep-alive", "Content-Length": "0", "Accept": "application/json, text/javascript, */*; q=0.01", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN", "Content-Type": "application/json", "Cookie": "PHPSESSID=n3k73k06o6ghnie4e9re4rbf0t", "Origin": "https://echo.apipost.cn", "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" }}定义测试用例: apt.assert('response.raw.status==200');apt.assert('response.raw.type=="json"');apt.assert('response.json.errcode==0');apt.assert('response.raw.responseTime<100');apt.assert('response.json.header.Host=="echo.apipost.cn"');点击发送按钮后: 绿色表示测试通过,红色表示测试不通过。 ...

June 2, 2020 · 1 min · jiezi

小技巧使用ApiPost-下载响应数据到本地

ApiPost V3版本开始,已支持下载响应数据到本地,格式不限。如图中所示。

May 29, 2020 · 1 min · jiezi

搜集整理的一些免费API

参考作者开发的QQ机器人 - 这里的大部分API都用在这个机器人里了QQ群 - Javascript高级爬虫 - 作者自建群,欢迎加入!awesome-java-crawler - 作者收集的爬虫相关工具和资料一个帮你自动创建阿里云抢占式实例并开启网络加速的脚本 - 自动创建阿里云海外抢占式节点本机IP查询接口地址:http://cip.cc说明:需添加请求头User-Agent: curl 任意IP查询接口地址:http://cip.cc/<IP地址>说明:需添加请求头User-Agent: curl 手机号(段)归属地查询:接口地址:http://mobsec-dianhua.baidu.com/dianhua_api/open/location?tel=<手机号或7位号段> HTTP请求解析接口地址:https://httpbin.org/get?show_env=1说明:此接口可以回显当前请求的请求头详细信息,可以用来测试代理的匿名性 随机毒鸡汤接口地址:https://nmsl.shadiao.app/api.php?level=min&lang=zh_cn说明:沙雕APP网站的AJAX请求,可以直接外部调用 随机彩虹屁接口地址:https://chp.shadiao.app/api.php说明:沙雕APP网站的AJAX请求,可以直接外部调用 随机语录项目地址:https://hitokoto.cn/说明:开源项目一言 Pixiv排行榜接口地址:https://api.imjad.cn/pixiv/v1/?type=rank&content=illust&mode=<排行榜类型>&per_page=20&page=1&date=<日期>说明: 用这个API可以获取P站插画排行榜,API说明在这里排行榜类型允许值:daily, weekly, monthly, rookie日期格式为YYYY-MM-DD,建议传入前天日期,否则可能发生403错误Pixiv主站在墙外,但是其CDN服务器i.pximg.net在墙内可以访问,另外可以用墙内的反向代理服务,比如pixiv.cat,pixivdl.net等Pixiv主站CDN服务器上的图片开启了反盗链,添加referer请求头即可正常下载随机涩图项目地址:https://api.lolicon.app/#/setu说明:作者君在P站上精心挑选的精品插画 语音合成接口地址:https://dds.dui.ai/runtime/v1/synthesize?voiceId=<发音人ID>&speed=<速度>&volume=100&audioType=mp3&text=<文本>说明: API文档和发音人列表见这里中文文本需要URL编码速度值为0.7~2,越大越慢谷歌翻译接口地址: https://translate.google.cn说明:墙内可访问 搜歌(网易)接口地址:https://music.sounm.com/?name=<关键字>&type=netease说明: 搜你妹引擎的AJAX接口,调用时需要传入以下请求头: Content-Type: application/x-www-form-urlencodedX-Requested-With XMLHttpRequestUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36Referer: https://music.sounm.com/?name=<关键字>&type=netease本接口能够返回可下载的mp3链接

May 27, 2020 · 1 min · jiezi

分享ApiPost自动化测试基础之接口参数依赖的情景处理

ApiPost V3版本的参数依赖处理方式同旧版雷同。什么是接口参数依赖:接口参数依赖又称作接口依赖,简单点说就是后面的接口要用到前面的接口产生的数据。 比如:我们一个接口B需要A接口返回的参数token作为自己的请求参数。常见的场景如:访问一个需要登陆才能浏览的接口。 ApiPost如何处理参数依赖?为了便于演示,首先,我们准备2个接口: get_token接口:请求地址:{{url}}get_token.php 它将返回一个token参数。如图: need_token接口:请求地址:{{url}}need_token.php 它需要get_token接口返回的token参数作为自己的请求参数。 如上图所示,我们定义了一个环境变量token_var,由于此时尚未将其与get_token接口返回的token绑定,所以此时它原样输出了。 绑定get_token的响应结果token到环境变量token_var此时点击get_token接口的【后执行脚本】,通过一个变量赋值,就可以将get_token的响应结果token到环境变量token_var。 此时,点击【发送】按钮,就可以把get_token接口返回的token绑定到环境变量token_var了。 我们进入need_token的接口,并点击发送,就会看到token已经是我们刚才获取到的token了。 注意:我们绑定完成后,我们需要先再次发送请求下get_token接口(执行绑定数据),再请求need_token接口。 附:响应类型对应关系:response.raw:原始响应数据 调用示例: response.raw.status //响应状态码(200、301、404等) response.raw.responseTime //响应时间(毫秒) response.raw.type //响应类型(json等) response.raw.responseText //响应文本 response.json :json格式的响应数据 调用示例如上面示例: response.json.data.token //也可以 response.json.data["token"] response.headers :响应头 调用示例: response.headers.server //也可以 response.headers["server"] response.cookies :响应cookie 调用示例: response.cookies.PHPSESSION //也可以 response.cookies["PHPSESSION"]

May 26, 2020 · 1 min · jiezi

阿里巴巴-Kubernetes-应用管理实践中的经验与教训

导读:云原生时代,Kubernetes 的重要性日益凸显。然而,大多数互联网公司在 Kubernetes 上的探索并非想象中顺利,Kubernetes 自带的复杂性足以让一批开发者望而却步。本文中,阿里巴巴技术专家孙健波在接受采访时基于阿里巴巴 Kubernetes 应用管理实践过程提供了一些经验与建议,以期对开发者有所帮助。在互联网时代,开发者更多是通过顶层架构设计,比如多集群部署和分布式架构的方式来实现出现资源相关问题时的快速切换,做了很多事情来让弹性变得更加简单,并通过混部计算任务来提高资源利用率,云计算的出现则解决了从 CAPEX 到 OPEX 的转变问题。 云计算时代让开发可以聚焦在应用价值本身,相较于以前开发者除了业务模块还要投入大量精力在存储、网络等基础设施,如今这些基础设施都已经像水电煤一样便捷易用。云计算的基础设施具有稳定、高可用、弹性伸缩等一系列能力,除此之外还配套解决了一系列应用开发“最佳实践”的问题,比如监控、审计、日志分析、灰度发布等。原来,一个工程师需要非常全面才能做好一个高可靠的应用,现在只要了解足够多的基础设施产品,这些最佳实践就可以信手拈来了。但是,在面对天然复杂的 Kubernetes 时,很多开发者都无能为力。 作为 Jira 和代码库 Bitbucket 背后的公司,Atlassian 的 Kubernetes 团队首席工程师 Nick Young 在采访中表示: 虽然当初选择 Kubernetes 的战略是正确的(至少到现在也没有发现其他可能的选择),解决了现阶段遇到的许多问题,但部署过程异常艰辛。那么,有好的解决办法吗? 太过复杂的 Kubernetes“如果让我说 Kubernetes 存在的问题,当然是‘太复杂了’”,孙健波在采访中说道,“不过,这其实是由于 Kubernetes 本身的定位导致的。” 孙健波补充道,Kubernetes 的定位是“platform for platform”。它的直接用户,既不是应用开发者,也不是应用运维,而是“platform builder”,也就是基础设施或者平台级工程师。但是,长期以来,我们对 Kubernetes 项目很多时候都在错位使用,大量的应用运维人员、甚至应用研发都在直接围绕 Kubernetes 很底层的 API 进行协作,这是导致很多人抱怨 “Kubernetes 实在是太复杂了”的根本原因之一。 这就好比一名 Java Web 工程师必须直接使用 Linux Kernel 系统调用来部署和管理业务代码,自然会觉得 Linux “太反人类了”。所以,目前 Kubernetes 项目实际上欠缺一层更高层次的封装,来使得这个项目能够对上层的软件研发和运维人员更加友好。 如果可以理解上述的定位,那么 Kubernetes 将 API 对象设计成 all-in-one 是合理的,这就好比 Linux Kernel 的 API,也不需要区分使用者是谁。但是,当开发者真正要基于 K8s 管理应用、并对接研发、运维工程师时,就必然要考虑这个问题,也必然要考虑如何做到像另一层 Linux Kernel API 那样以标准、统一的方式解决这个问题,这也是阿里云和微软联合开放云原生应用模型 Open Application Model (OAM)的原因。 ...

November 5, 2019 · 2 min · jiezi

前端面试每日-31-第184天

今天的知识点 (2019.10.17) —— 第184天[html] 如何给一个下拉选项进行分组?[css] 请描述下你对translate()方法的理解[js] 说下你对面向对象的理解[软技能] 你上家公司的接口是怎么管理的?《论语》,曾子曰:“吾日三省吾身”(我每天多次反省自己)。 前端面试每日3+1题,以面试题来驱动学习,每天进步一点! 让努力成为一种习惯,让奋斗成为一种享受!相信 坚持 的力量!!!欢迎在 Issues 和朋友们一同讨论学习! 项目地址:前端面试每日3+1 【推荐】欢迎跟 jsliang 一起折腾前端,系统整理前端知识,目前正在折腾 LeetCode,打算打通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个Star, 同时欢迎微信扫码关注 前端剑解 公众号,并加入 “前端学习每日3+1” 微信群相互交流(点击公众号的菜单:进群交流)。 学习不打烊,充电加油只为遇到更好的自己,365天无节假日,每天早上5点纯手工发布面试题(死磕自己,愉悦大家)。希望大家在这浮夸的前端圈里,保持冷静,坚持每天花20分钟来学习与思考。在这千变万化,类库层出不穷的前端,建议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢迎大家到Issues交流,鼓励PR,感谢Star,大家有啥好的建议可以加我微信一起交流讨论!希望大家每日去学习与思考,这才达到来这里的目的!!!(不要为了谁而来,要为自己而来!)交流讨论欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个[Star] https://github.com/haizlin/fe...

October 17, 2019 · 1 min · jiezi

php语言检索各类图书信息

书籍是人类的终生朋友,有时候要检索个书籍啥的,一般我们都会去图书馆,但是如果自己开发一款应用,比如网站,app之类的就显然不能这么搞了,一般情况下很多网站都不允许数据外流的,这种情况最好就是调用专门的提供数据的接口,接着笔者就拿php语言为例来实现图书信息检索这个功能,直接上代码: <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><?php/** * @author * @copyright 2019 */ header("content-type:text/html;charset=utf-8"); //设置编码 //配置您申请的appKey和openId$app_key = "***";$open_id = "***";/**$url 请求地址$params 请求参数$ispost 请求方法*/function http_curl($url,$params=false,$ispost=false){ $httpInfo = array(); $ch = curl_init(); curl_setopt( $ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 ); curl_setopt( $ch, CURLOPT_USERAGENT , "xiaocongjisuan"); curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT , 60 ); curl_setopt( $ch, CURLOPT_TIMEOUT , 60); curl_setopt( $ch, CURLOPT_RETURNTRANSFER , true ); if( $ispost ) { curl_setopt( $ch , CURLOPT_POST , true ); curl_setopt( $ch , CURLOPT_POSTFIELDS , $params ); curl_setopt( $ch , CURLOPT_URL , $url ); } else { if($params){ curl_setopt( $ch , CURLOPT_URL , $url.'?'.$params ); }else{ curl_setopt( $ch , CURLOPT_URL , $url); } } $response = curl_exec( $ch ); if ($response === FALSE) { //echo "cURL Error: " . curl_error($ch); return false; } $httpCode = curl_getinfo( $ch , CURLINFO_HTTP_CODE ); $httpInfo = array_merge( $httpInfo , curl_getinfo( $ch ) ); curl_close( $ch ); return $response;}function main(){ global $app_key; global $open_id; $domain="http://api.xiaocongjisuan.com/"; $servlet="data/bookresource/get"; $method="get"; $url=$domain."".$servlet; $params['appKey']=$app_key; $params['openId']=$open_id; //变动部分 $params["q"]="历史"; $params["field"]="name"; $params["currentPage"]=1; $params["pageSize"]=10; $params["order"]="up"; $params["sortField"]="pageNum"; //编码转换 foreach ($params as $key=>$value) { $params[$key]=mb_convert_encoding($value, "UTF-8", "GBK"); } $paramstring = http_build_query($params); $content = http_curl($url,$paramstring,true); return $content;}echo main();?>当然java语言也可以实现,例子如下 ...

October 15, 2019 · 3 min · jiezi

基于-React-和-Redux-的-API-集成解决方案

在前端开发的过程中,我们可能会花不少的时间去集成 API、与 API 联调、或者解决 API 变动带来的问题。如果你也希望减轻这部分负担,提高团队的开发效率,那么这篇文章一定会对你有所帮助。 文章中使用到的技术栈主要有: React 全家桶TypeScriptRxJS文章中会讲述集成 API 时遇到的一些复杂场景,并给出对应解决方案。通过自己写的小工具,自动生成 API 集成的代码,极大提升团队开发效率。 本文的所有代码都在这个仓库:request。 自动生成代码的工具在这里:ts-codegen。 1. 统一处理 HTTP 请求1.1 为什么要这样做?我们可以直接通过 fetch 或者 XMLHttpRequest 发起 HTTP 请求。但是,如果在每个调用 API 的地方都采用这种方式,可能会产生大量模板代码,而且很难应对一些业务场景: 如何为所有的请求添加 loading 动画?如何统一显示请求失败之后的错误信息?如何实现 API 去重?如何通过 Google Analytics 追踪请求?因此,为了减少模板代码并应对各种复杂业务场景,我们需要对 HTTP 请求进行统一处理。 1.2 如何设计和实现?通过 redux,我们可以将 API 请求 「action 化」。换句话说,就是将 API 请求转化成 redux 中的 action。通常来说,一个 API 请求会转化为三个不同的 action: request action、request start action、request success/fail action。分别用于发起 API 请求,记录请求开始、请求成功响应和请求失败的状态。然后,针对不同的业务场景,我们可以实现不同的 middleware 去处理这些 action。 1.2.1 Request Actionredux 的 dispatch 是一个同步方法,默认只用于分发 action (普通对象)。但通过 middleware,我们可以 dispatch 任何东西,比如 function (redux-thunk) 和 observable,只要确保它们被拦截即可。 ...

October 9, 2019 · 5 min · jiezi

如何用代码挖局长尾关键词

长尾关键词(Long Tail Keyword)是指网站上的非目标关键词但与目标关键词相关的也可以带来搜索流量的组合型关键词。对于做SEO的人来说,这个名字再也熟悉不过了,但是如何来挖掘长尾关键词,对于没有数据或者一定计算机知识的人来说,还是很有难度的。所以这里附上非常简单的办法,只要几行代码就能轻松搞定。 # -*- coding: utf-8 -*-# flake8: noqa__author__ = 'wukong'import urllibfrom urllib import urlencode#配置您申请的appKey和openIdapp_key="***"open_id="***""""request_url 请求地址params 请求参数method 请求方法"""def request_content(request_url,params,method): params = urlencode(params) if method and method.lower() =="get": f = urllib.urlopen("%s?%s" % (request_url, params)) else: f = urllib.urlopen(request_url, params) content = f.read() print content def main(): domain="http://api.xiaocongjisuan.com/" servlet="data/longtailword/mining" method="get" request_url=domain+servlet #字典 params ={} params["appKey"]=app_key params["openId"]=open_id #变动部分 params["keyword"]="学前教育" params["upLimit"]=50 params["minLen"]=30 params["lSort"]="up" request_content(request_url,params,method) if __name__ == '__main__': main()当然也可以用c#来实现 using System;using System.Collections.Generic;using System.Text;using System.Net;using System.IO;namespace ConsoleApplication1{ class Program { private static string appKey="yours"; private static string openId = "yours"; static string getResponseAsString(HttpWebResponse rsp, Encoding encoding) { System.IO.Stream stream = null; StreamReader reader = null; try { // 以字符流的方式读取HTTP响应 stream = rsp.GetResponseStream(); reader = new StreamReader(stream, encoding); return reader.ReadToEnd(); } finally { // 释放资源 if (reader != null) reader.Close(); if (stream != null) stream.Close(); if (rsp != null) rsp.Close(); } } /* * parameters 参数 * encode 编码 */ static string buildQuery(IDictionary<string,object> parameters, string encode) { StringBuilder postData = new StringBuilder(); bool hasParam = false; IEnumerator<KeyValuePair<string, object>> dem = parameters.GetEnumerator(); while (dem.MoveNext()) { string name = dem.Current.Key; string value = dem.Current.Value.ToString(); ; // 忽略参数名或参数值为空的参数 if (!string.IsNullOrEmpty(name))//&& !string.IsNullOrEmpty(value) { if (hasParam) { postData.Append("&"); } postData.Append(name); postData.Append("="); if (encode == "gb2312") { postData.Append(System.Web.HttpUtility.UrlEncode(value, Encoding.GetEncoding("gb2312"))); } else if (encode == "utf8") { postData.Append(System.Web.HttpUtility.UrlEncode(value, Encoding.UTF8)); } else { postData.Append(value); } hasParam = true; } } return postData.ToString(); } /** * * @param url 请求地址 * @param params 请求参数 * @param method 请求方法 * @return 请求结果 * @throws Exception */ static string requestContent(string url, IDictionary<string,object> parameters, string method) { if (method.ToLower() == "post") { HttpWebRequest req = null; HttpWebResponse rsp = null; System.IO.Stream reqStream = null; try { req = (HttpWebRequest)WebRequest.Create(url); req.Method = method; req.KeepAlive = false; req.ProtocolVersion = HttpVersion.Version10; req.Timeout = 5000; req.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; byte[] postData = Encoding.UTF8.GetBytes(buildQuery(parameters, "utf8")); reqStream = req.GetRequestStream(); reqStream.Write(postData, 0, postData.Length); rsp = (HttpWebResponse)req.GetResponse(); Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet); return getResponseAsString(rsp, encoding); } catch (Exception ex) { return ex.Message; } finally { if (reqStream != null) reqStream.Close(); if (rsp != null) rsp.Close(); } } else { //创建请求 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url + "?" + buildQuery(parameters, "utf8")); //GET请求 request.Method = "GET"; request.ReadWriteTimeout = 5000; request.ContentType = "text/html;charset=UTF-8"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); //返回内容 string retString = myStreamReader.ReadToEnd(); return retString; } } static void Main(string[] args) { String domain = "http://api.xiaocongjisuan.com/"; String servlet = "data/longtailword/mining"; String method = "get"; String url = domain + servlet; var parameters = new Dictionary<string,object>(); parameters.Add("appKey", appKey); parameters.Add("openId", openId); //变动部分 parameters.Add("keyword", "学前教育"); parameters.Add("upLimit", 50); parameters.Add("minLen", 30); parameters.Add("lSort", "up"); string result = requestContent(url, parameters, method); Console.WriteLine(result); Console.Read(); } }}其他的语言实现方式可以跳转到长尾关键词接口去查看,代码上还是非常简洁的,就也不再做过多解释。 ...

October 9, 2019 · 2 min · jiezi

如何高效的完成中文分词

在说分词之前,笔者先来介绍下何为分词:分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。英文中,单词之间是以空格作为自然分界符的,但是中文的分词就复杂多了,要涉及一些算法,对于初学者来说,还是有很多难度的。这里笔者只介绍一种最简单的方式,有兴趣的朋友可以看下,直接上代码: python实现方式 # -*- coding: utf-8 -*-# flake8: noqa__author__ = 'wukong'import urllibfrom urllib import urlencode#配置您申请的appKey和openIdapp_key="***"open_id="***""""request_url 请求地址params 请求参数method 请求方法"""def request_content(request_url,params,method): params = urlencode(params) if method and method.lower() =="get": f = urllib.urlopen("%s?%s" % (request_url, params)) else: f = urllib.urlopen(request_url, params) content = f.read() print content def main(): domain="http://api.xiaocongjisuan.com/" servlet="data/chinesekeyword/analysis" method="get" request_url=domain+servlet #字典 params ={} params["appKey"]=app_key params["openId"]=open_id #变动部分 params["content"]="我是一个中国人,你知道嘛" request_content(request_url,params,method) if __name__ == '__main__': main()php实现方式 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><?php/** * @author * @copyright 2019 */ header("content-type:text/html;charset=utf-8"); //设置编码 //配置您申请的appKey和openId$app_key = "***";$open_id = "***";/**$url 请求地址$params 请求参数$ispost 请求方法*/function http_curl($url,$params=false,$ispost=false){ $httpInfo = array(); $ch = curl_init(); curl_setopt( $ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 ); curl_setopt( $ch, CURLOPT_USERAGENT , "xiaocongjisuan"); curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT , 60 ); curl_setopt( $ch, CURLOPT_TIMEOUT , 60); curl_setopt( $ch, CURLOPT_RETURNTRANSFER , true ); if( $ispost ) { curl_setopt( $ch , CURLOPT_POST , true ); curl_setopt( $ch , CURLOPT_POSTFIELDS , $params ); curl_setopt( $ch , CURLOPT_URL , $url ); } else { if($params){ curl_setopt( $ch , CURLOPT_URL , $url.'?'.$params ); }else{ curl_setopt( $ch , CURLOPT_URL , $url); } } $response = curl_exec( $ch ); if ($response === FALSE) { //echo "cURL Error: " . curl_error($ch); return false; } $httpCode = curl_getinfo( $ch , CURLINFO_HTTP_CODE ); $httpInfo = array_merge( $httpInfo , curl_getinfo( $ch ) ); curl_close( $ch ); return $response;}function main(){ global $app_key; global $open_id; $domain="http://api.xiaocongjisuan.com/"; $servlet="data/chinesekeyword/analysis"; $method="get"; $url=$domain."".$servlet; $params['appKey']=$app_key; $params['openId']=$open_id; //变动部分 $params["content"]="我是一个中国人,你知道嘛"; //编码转换 foreach ($params as $key=>$value) { $params[$key]=mb_convert_encoding($value, "UTF-8", "GBK"); } $paramstring = http_build_query($params); $content = http_curl($url,$paramstring,true); return $content;}echo main();?>原理主要是调用接口,直接输入一串字符串,然后接口会自动把结果以json或者xml的形式返回,具体文档可以点我查看。这种实现方式很简单,省去了大量的开发时间,屏蔽了语言之间的差异性,值得推荐。 ...

October 8, 2019 · 2 min · jiezi

开放API网关实践三-限流

如何设计实现一个轻量的开放API网关之限流文章地址: https://blog.piaoruiqing.com/blog/2019/08/26/开放api网关实践三-限流/ 前言开发高并发系统时有多重系统保护手段, 如缓存、限流、降级等. 在网关层, 限流的应用比较广泛. 很多情况下我们可以认为网关上的限流与业务没有很强的关联(与系统的承载能力有关), 且各个子系统都有限流这种需求, 将部分限流功能放到网关会比较合适. 什么是限流众所周知, 服务器、网站应用的处理能力是有上限的, 不论配置有多高总会有一个极限, 超过极限如果放任继续接收请求, 可能会发生不可控的后果. 举个栗子????, 节假日网上购票, 常常会遇到排队中、系统繁忙请稍后再试等提示, 这便是服务端对单位时间处理请求的数量进行了限制, 超出限制就会排队、降级甚至拒绝服务, 否则如果把系统搞崩了, 大家都买不到票了╮( ̄▽ ̄)╭. 我们先给出限流的定义: 限流是高并发系统保护保护手段之一, 在网关层的应用很广泛. 其目的是对并发请求进行限速或限制一个时间窗口内请求的数量, 一旦达到阈值就排队等待或降级甚至拒绝服务. 其最终目的是: 在扛不住过高并发的情况下做到有损服务而不是不服务. 常用限流玩法令牌桶令牌桶算法, 是一个存放固定数量令牌的桶按照固定速率添加令牌. 如图: 按照固定速率向桶中添加令牌.桶满时拒绝增加新令牌.每次请求消耗一个令牌(也可根据数据包大小来消耗对应的令牌数).当令牌不足时, 拒绝请求(或等待).特点: 可以应对一定程度的突发.举个现实生活中比较常见的例子来理解, 电影院售票, 每场电影所售出的票数是一定的, 如果来晚了(后面的请求)就没票了, 要么等待下一场(等待新的令牌发放), 要么不看了(被拒绝). 漏桶漏桶是一个底部破洞的桶, 水可以匀速流出(这时候不考虑压强, 不要杠( ̄. ̄)), 所以与令牌桶不一样的是, 漏桶算法是匀速消费, 可以用来进行流量整形和流量控制. 如图: 固定容量的漏桶, 按照固定速率流出水(不要杠水深和压强的问题).流入水的速率固定, 溢出则被丢弃.特点: 平滑处理速率.[版权声明]本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接:blog.piaoruiqing.com. 如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com. 应用级限流一个单体的应用程序有其承受极限, 在高并发情况下, 有必要进行过载保护, 以防过多的请求将系统弄崩. 最简单粗暴的方式就是使用计数器进行控制, 处理请求时+1, 处理完毕后-1, 除此之外我们还可以利用前文提到的令牌桶和漏桶来进行更精细的限流.如果网关是单体应用, 我们完全可以不借助其他介质, 直接在应用级别进行限流. ...

October 7, 2019 · 1 min · jiezi

如何在-Apache-Flink-中使用-Python-API

本文根据 Apache Flink 系列直播课程整理而成,由 Apache Flink PMC,阿里巴巴高级技术专家 孙金城 分享。重点为大家介绍 Flink Python API 的现状及未来规划,主要内容包括:Apache Flink Python API 的前世今生和未来发展;Apache Flink Python API 架构及开发环境搭建;Apache Flink Python API 核心算子介绍及应用。 一.Apache Flink Python API 的前世今生和未来发展1.Flink 为什么选择支持 PythonApache Flink 是流批统一的开源大数据计算引擎,在 Flink 1.9.0 版本开启了新的 ML 接口和全新的Python API架构。那么为什么 Flink 要增加对 Python 的支持,下文将进行详细分析。 最流行的开发语言 Python 本身是非常优秀的开发语言,据 RedMonk 数据统计,除 Java 和 JavaScript 之外,受欢迎度排名第三。 RedMonk 是著名的以开发人员为中心的行业分析公司,其更详细的分析信息,大家在拿到我的PPT之后,可以点击链接进行详细查阅。好了,那么Python的火热,与我们今天向大家分享的流批统一的大数据计算引擎,Apache Flink有什么关系呢?带着这个问题,我们大家想想目前与大数据相关的著名的开源组件有哪些呢?比如说最早期的批处理框架Hadoop?流计算平台Storm,最近异常火热的Spark?异或其他领域数仓的Hive,KV存储的HBase?这些都是非常著名的开源项目,那么这些项目都无一例外的进行了Python API的支持。 众多开源项目支持 Python 的生态已相对完善,基于此,Apache Flink 在 1.9 版本中也投入了大量的精力,去推出了一个全新的 Pyflink。除大数据外,人工智能与Python也有十分密切的关系。 ML青睐的语言 ...

September 10, 2019 · 4 min · jiezi

医疗行业如何使用API市场

医疗健康行业无论在国内外都是采用先进技术的先驱者之一,原因在于业内的利益相关者会更加接近数据、重视数据的重要性,从而加快在决策上面的动作,以期更好的患者的预期寿命和增进社会人口的健康。更重要的是,数据的质量和可用性足够的透明,使得以患者数据为中心的模型在医疗行业中发挥起重要的作用。 根据斯坦福医学2017年健康趋势报告在过去的十年中,医疗健康行业捕获的数据量大幅增长。这是由于例如政府采取了电子病历(EMR)平台、设备(X射线,MRI,CT等)对医疗记录进行数字化,以及无处不在的个人健康/健身监视器(智能穿戴设备如Apple Watch等)。 接下来我们将讨论如何使用API市场(包括其后面的一系列解决方案)作为解决此问题的方法。通过API市场,管理者可以安全地开放或者部分开放这些数据(用户的隐私数据和受到法律保护的信息不在描述范围当中),使医疗行业的患者或医疗机构和公民都可以访问这些数据,从而提高医疗健康行业的效率并实现人口健康技术的创新。 API市场作为解决方案在当今世界,数据平台已经成为了众多不同商业体系的运转中心,而API作为平台的数据连接器起到了关键的作用。各行各业都开始积极采用API优先的系统开发方法,我们在医疗健康领域也能很明显的看到这一点。一个健全的API市场可以解决医疗健康数据的商用问题,它将与数据使用者进行API社交,让他们从数据当中获取更多的信息,以便进行产品和技术的创新。API市场鼓励医疗机构或者医药企业思考如何更好的使用和保管他们合法的研究数据,并通过高可用性的API计划提供医疗数据,并进而产生效益。 从实际出发,医疗健康行业的API市场主要担负的,是将医疗数据使用者和产生者建立起联系,而相应解决方案的最终愿景,则是建立一个以API市场为基础,以医疗健康数据合法交易为导向的经济体。在这个经济体当中,应该有一个全面的API管理平台,它为API市场概念提供了动力,而该平台使数据供应商能够设计,发布和管理API,同时实施安全管理,数据应用治理和收集分析工作。 图1:解决方案架构 通过该解决方案,我们可以了解医疗健康IT领域的整体需求(对于其他行业也有很大一部分数据或者API是有共通的用处)。当所有产品的开发者和管理者都在使用基于API管理解决方案时,数据供应商会将所获得的合法的数据发布在API市场上,有需要的企业则同时会在市场上联系他们感兴趣的API供应商;API市场为供应方和消费方搭建了一个开放而安全的价值交换渠道,使得数据消费方可以根据所获得的数据产生更有价值或者有潜在价值的产品。 API市场各模块角色 图2:API市场角色及数据流向分析 API 生产者:指的是API的设计师、架构师、开发人员、测试人员、发布人员。负责定义每一个API的应用范围(内部使用,外部使用,使用限制和使用权利)。在大型医疗项目的API生产中,生产者可以来自不同的医疗组织机构或者是企业。 平台拥有者:指的是API市场的维护及搭建人员。他们负责API市场模块的维护,并促进API使用者之间的协作,并需要及时向API提供商报告消费者反馈。他们负责了整个API市场的平台宣传、支持和社区建设等任务。 API使用者/消费者:指的主要是应用程序开发,是愿意利用医疗数据并从中获取价值的创新者;同时,这指代的可能是健康发展项目、医疗健康创业公司、跨国卫生组织、医院团体或者政府,他们热衷于提高医疗健康水平,为行业提供更好的医疗解决方案。 医疗健康API市场的组成 图3:Healthcare API市场的组件 API的管理是平台的基础,它包含了API的网关、安全、管理、设计发布以及分析部分。 API网关是整个平台的入口,最终与实际数据后端EHR系统相连接。它与API安全模块是密不可分的,另外它还将基于安全策略和令牌的信息为整个API管理平台提供支持服务。 鉴于API数据的敏感性,在开发、发布、维护等每一个阶段都需要进行强有力的管理——通过API管理系统和平台管理者的维护,能够很好的强化和控制API的使用,并为管理者提供了良好的管理策略和使用模式。 开发人员在发布API后,需要能够持续监控、测试和管理已发布的API,而API管理解决方案除了提供这些功能以外,还应该能够针对API调用失败,响应时间突然增加或API资源访问模式等异常变化,提供建议解决方案并发出报警提醒。 技术选择在提出的解决方案中,我们将系统集成和API管理视为两个不同的必备组件,需要深思的是集成组件应该可以通过不同协议与各种医疗健康系统做到集成。在此之后,它还需要对数据进行转换和规范化,进而提供一个统一的接口以供使用。在底层之上的集成层,应该根据管理者在API管理系统上设置的测策略,对所有数据进行脱敏和匿名处理。当然,我们可以找到非常多不同的技术栈和框架达成此目的,比方说Apache Camel,Apache Synapse,Apache ServiceMix,Spring集成和Ballerina等等。 API管理模块的组成主要有高性能的网关用于身份验证的密钥服务器,用于限制和分析的事件处理器,和用于API列表的开发人员门户以及用于职称管理的工作流引擎。一些流行的开源产品封装了这些功能,包括eolinker API Manager,GoKu API Gateway、Kong API Gateway和Tyk API Gateway。 参考实施下图是对我们所描述的框架进行了整合,并标出了可以使用这些技术选项的地方。这个图偏向于开源平台和其他框架,这为后期平台的发展和功能的定制提供了极大的灵活性。 图4:具有技术选择的实现架构 在此实现中,API管理系统提供了API全生命周期管理的功能,包括API使用、管理、商业化,数据安全和路由API管理,而API请求则将通过API网关发送到管理平台。 参考图的方案同时兼考虑了API的生命周期 - 一旦新API发布,那么就必须弃用先前版本,并且将通知到所有API订阅者。API解决方案应当可以通过内部设置处理此问题,因此在API生命周期的每个阶段都应可以与该阶段对应的操作相关联,如图5所示。 图5:示例API生命周期视图 API的安全性是整个API管理体系首要考虑的因素。虽然身份验证和授权是通过密钥或令牌完成的,但是更高级的权利应当是通过OAuth令牌的对应信息进行授权应用的。该信息可以是高级用户组的确认验证,也可以是关于数据使用者的属性判断,距个例子比如是例如使用者的设备或者地理位置等。 图6:为API操作应用授权OAuth范围的示例 API管理平台应当可以控制通过何种方式向第三方公开数据,例如允许无限制访问或受限制策略限制。而于此同时,API管理平台也应该能对请求数量和速率进行相应的规则限制。 图7:如何将限制策略应用于API订阅的示例视图 API管理系统的应当为使用者提供一个窗口,让他们可以在当中寻找、试用和订阅所有或者部分的API。这个门户网站将为产品和开发人员通过获取的数据进行发展创新。这一个平台记录了每一个入驻平台的API的功能和使用方法,而同时也允许管理者们对这些API进行分类,并推送给所有在此平台上感兴趣的用户,让他们将感兴趣的API有偿(或无偿)地集成到他们的应用当中。 图8:具有Patient / Provider FHIR API的开发人员门户的示例视图 API管理系统的分析功能提供了最常见的API使用数据视图,为不同的使用者提供了有足够客观性的数据。而对于平台维护者来说,这可以为相关技术设备升级规划的决策提供更多的资料和信息。这样的好处是,API消费者可以进一步了解健康数据的可用性和重要性,而API提供商则可以提供和数据采集有关的更多数据。 图9:医疗保健 API分析视图示例 在国内的API接口管理工具中,能完整实现API管理全流程并且体验较好的平台和工具就是 EOLINKER了,而在国外诸如POSTMAN、Swagger功能也很强大,EOLINKER 包括接口文档编辑、API测试、自动化测试和API监控和API网关等功能,能体验到完整的API研发方案。如果要说起其中区别的话,就是POSTMAN注重测试,Swagger注重接口管理,不过国外全英的语言对国人也不是很友好,而 EOLINKER 相对会更加全面,因此有需求或者感兴趣可以各自了解下EOLINKER、POSTMAN、Swagger。 未来的改进我们在上面的讨论中模拟了一个简单的场景,以展示医疗健康API市场的实际用途。不单单是医疗健康类API,所有API都可以从基于API管理解决方案的基础上,提供更多的价值。数据从来都不应该是封闭的,只有充分的利用数据本身的价值,让其在更多的机构、创业者手中得到灵活运用,才能创造出更大的价值。一方面可以让数据提供方获得更多的服务,另一方面则可以促进服务创新发展。 ...

September 9, 2019 · 1 min · jiezi

深入理解-Java-中-SPI-机制

本文首发于 vivo互联网技术 微信公众号  链接:https://mp.weixin.qq.com/s/vpy5DJ-hhn0iOyp747oL5A 作者:姜柱SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,本文由浅入深地介绍了Java SPI机制。 一、简介SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。 SPI与API区别: API是调用并用于实现目标的类、接口、方法等的描述;SPI是扩展和实现以实现目标的类、接口、方法等的描述;换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。 参考:https://stackoverflow.com/questions/2954372/difference-between-spi-and-api?answertab=votes#tab-topSPI整体机制图如下: 当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。 二、应用场景SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。 SPI流程: 有关组织和公式定义接口标准第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件开发者使用比如JDBC场景下: 首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。三、使用demo1.定义一个接口HelloSPI。 package com.vivo.study.spidemo.spi;public interface HelloSPI { void sayHello();}2.完成接口的多个实现。 package com.vivo.study.spidemo.spi.impl;import com.vivo.study.spidemo.spi.HelloSPI;public class ImageHello implements HelloSPI { public void sayHello() { System.out.println("Image Hello"); }}package com.vivo.study.spidemo.spi.impl;import com.vivo.study.spidemo.spi.HelloSPI;public class TextHello implements HelloSPI { public void sayHello() { System.out.println("Text Hello"); }}在META-INF/services/目录里创建一个以com.vivo.study.spidemo.spi.HelloSPI的文件,这个文件里的内容就是这个接口的具体的实现类。 具体内容如下: com.vivo.study.spidemo.spi.impl.ImageHellocom.vivo.study.spidemo.spi.impl.TextHello3.使用 ServiceLoader 来加载配置文件中指定的实现 ...

September 9, 2019 · 2 min · jiezi

PostmanAPI-接口测试

Script Postman 提供了一个强大的基于 Nodejs 的运行环境,允许开发人员在请求或集合中填加动态行为,如编写测试用例,动态参数,请求之前传递数据等。Postman 提供了两个事件来填加 Javascript 代码来实现特定行为。分别为1.pre-request script 在请求发送之前2.test script 在响应完成之后 简单的测试用例 响应参数 {"statusCode":200,"result":["swimwear","bikinis","two-piece outfits","tops","dresses","tees"],"msg":""}测试脚本 // example using pm.response.to.havepm.test("response is ok", function () { pm.response.to.have.status(200); // 响应状态码必须为200});pm.test("response must be valid and have a body", function () { // assert that the status code is 200 pm.response.to.be.ok; // 响应体是否OK pm.response.to.be.json; // 响应体是否为 json 格式,它也会较验响应体是否存在,如果写了这个,上面这个断言就没有必要了});pm.test("response is correct", function () { var jsonData = pm.response.json(); pm.expect(jsonData.statusCode).to.equal(200); // 响应体 statusCode 字段值等于200,区分数据类型,如响应的字段串200,那这里就较验通不过 pm.expect(jsonData.result).to.be.an('array') // 响应体 result 字段必须是个数组});// json schema 较验var schema = { "items": { "type":"string" }}pm.test("响应参数格式不合法", function () { var jsonData = pm.response.json(); pm.expect(tv4.validate(jsonData.result, schema)).to.be.true; // scheme 较验是通过 tv4 这个类库来实现的,Postman 内置了它,并不需要额外填加})pm.response 表示获取到响应体pm.response.to.be 声名预定义的规则,如 pm.response.to.be.json 表示响应体为 json 格式。 ...

August 18, 2019 · 1 min · jiezi

对API进行版本控制的重要性和实现方式

我在API设计中收到的最常见问题之一就是如何对API进行版本控制。虽然并非所有API都完全相同,但我发现在API版本控制方面,某些模式和实践适用于大多数团队。我已经将这些内容收集起来,下面将提供一些关于版本控制策略的建议,该策略将帮助大多数API提供商,无论他们是向内部署API,还是对外的API。 API版本真的那么重要吗?API是你与API使用者之间建立的纽带。正常情况下,你们之间的纽带不会轻易的断开。纽带包括URI模式,有效负载结构,字段和参数名称,预期行为以及其他内容。这种方法的最大好处是显而易见的:API使用者的理解不会变更,应用程序可以保证持续有效。 但是,永久不变是不现实的。有时因为业务变化,你可能需要对API进行重大改变。发生这种情况时,最好的方式是,你确保不会做任何会导致API使用者修复代码的事情。 打破与不间断的变化非破坏性变更往往就像是“添加剂”,一般是添加新字段或嵌套资源到资源陈述,又或是增加新的端点,如PUT或PATCH。API使用者从一开始应该构建能够适应这些非破坏性更改的客户端代码。 突破性变化包括:1.名字段或资源路径,通常在API发布后为了统一命名规范。 2.更改有效负载结构,一般是适应以下内容: a.重命名或删除字段b.将字段从单个值更改为一对多关系(比如从一个帐户的一个电子邮件地址移动到这个帐户的电子邮件地址列表)c.修改了API的URL,导致返回结果不一致的情况。简而言之,一旦你将API对外公开发布,你就必须保持它是可用的并且不会影响到使用者的。如果您遇到多个项目,则需要对API进行版本控制,以防止破坏现有的API使用者。 定义API版本策略任何不断发展变化的API都需要API版本控制策略。你的API版本可以适应根据API使用者的期望而切换不同版本变得有所不同。我通常建议将以下API版本控制策略作为整体API管理系统的一部分。 1.如果你的API处于早期测试版本中,为了获得消费者的反馈,请建立您的API可能会发生变化的正确期望。在此阶段内,你会保留这个版本一段时间,因为你的API设计可能还会更改。作为消费者,API是不稳定的,因此他们应该预期到可能会发生的变化。 2.一旦发布,你的API应被视为契约,如果没有新版本,则不能被替换。 3.不间断的更改会导致次要版本出现问题,客户端会自动迁移到最新版本,不会出现任何负面副作用。 4.突破性的变化意味着客户必须迁移到此新版本,因为它包含一个或多个重大更改。你必须与API使用者建立适当的时间表并定期沟通,以确保他们能方便地迁移到新版本。但在某些情况下,这可能无法马上实现,所以你的团队将会被要求暂时性支持以前的API版本。 何时必须实现API版本控制一旦确定需要新版本的API,就需要知道如何处理它。实现API版本控制有三种常用方法。 1.资源版本控制 该版本是HTTP请求中Accept标头的一部分,例如,Accept:application/vnd.github.v3+json 发送到GET /customers。这考虑了许多版本控制的首选形式,因为资源在版本化的同时需要保持URI相同。如果未在Accept标头中提供,某些API会选择提供最新版本作为默认版本。 2.URI版本控制 该版本是URI的一部分,作为前缀或后缀,例如,/v1/customers 或/customers/v1。虽然URI版本控制不如基于内容的版本控制那么精确,但它往往是最常见的,因为它适用于不支持自定义标头的各种工具。缺点是资源URI随每个新版本而变化,有些人认为这与拥有永不改变的URI的意图背道而驰。 3.主机名版本控制 该版本是主机名的一部分而不是URI,例如,https://v2.api.myapp.com/cust...。当技术限制阻止基于URI或Accept标头到API的正确后端版本时,将使用此方法。 备注:无论您选择哪个选项,API版本都只应包含主要编号。不需要小数字(即  /v1/customers,不是/v1.1/customers)。 实现版本控制的工具使用工具和技术可以从根本上实现API版本控制过程。用市场上优秀的API编辑器将使技术开发团队能够在更短的时间内生成并切换更多的API版本,从而不断改进设计决策。 结合工具进行版本控制是大多数开发过程的重要组成部分。API设计领域中也有这种能版本控制的工具,实际上,全球范围内API服务领域中已经存在一些优秀的Web API设计工具。 现在,如EOLINKER、RAML、Swagger,都提供了出色的编辑工具来支持他们的语言。EOLINKER采用的是版本对比和重点标注提示,可以清晰的切换、对比。RAML、Swagger采用的是版本切换,方便程度可能略逊一点。而且只有前者是支持中文的,后两种只支持英文语言。这些API编辑器都能轻松地实现API版本的控制,使得更容易在更短的时间内切换运行版本。 附上EOLINKER的官方网址:https://www.eolinker.com 附上Swagger的官方网址:https://swagger.io/ 附上RAML的官方网址:https://raml.org/ 最后的想法请记住,API是与你的消费者链接的枢纽。打破旧的连接,就需要新版本。选择策略,制定计划,并与API使用者沟通该计划,这才是版本控制的最终目的。 参考资料:James Higginbotham,When and How Do You Version Your API? 原文地址:https://dzone.com/articles/wh...

August 7, 2019 · 1 min · jiezi

微服务架构之网关层Zuul剖析

文章来源:http://www.liangsonghua.me作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、微服务架构 一、Zuul简介Zuul相当于是第三方调用和服务提供方之间的防护门,其中最大的亮点就是可动态发布过滤器 二、Zuul可以为我们提供什么1、权限控制2、预警和监控3、红绿部署、(粘性)金丝雀部署,流量调度支持4、流量复制转发,方便分支测试、埋点测试、压力测试5、跨区域高可用,异常感知6、防爬防攻击7、负载均衡、健康检查和屏蔽坏节点8、静态资源处理9、重试容错服务三、Zuul网关架构 可以看到其架构主要分为发布模块、控制管理加载模块、运行时模块、线程安全的请求上下文模块。在Spring Cloud中,Zuul每个后端都称为一个Route,为了避免资源抢占,整合了Hystrix进行隔离和限流,基于线程的隔离机制,另外一种机制是信号量,后面文章会提到。Zuul默认使用ThreadLocal protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() { @Override protected RequestContext initialValue() { try { return contextClass.newInstance(); } catch (Throwable e) { e.printStackTrace(); throw new RuntimeException(e); } }}; 请求处理生命周期,”pre” filters(认证、路由、请求日记)->”routing filters”(将请求发送到后端)->”post” filters(增加HTTP头、收集统计和度量、客户端响应) 四、过滤器一些概念 1、类型Type,定义被运行的阶段,也就是preroutingposterror阶段2、顺序Execution Order,定义同类型链执行中顺序3、条件Criteria,定义过滤器执行的前提条件4、动作Action,定义过滤器执行的业务下面是一个DEMO class DebugFilter extends ZuulFilter { static final DynamicBooleanProperty routingDebug = DynamicPropertyFactory.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, true) static final DynamicStringProperty debugParameter = DynamicPropertyFactory.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "d") @Override String filterType() { return 'pre' } @Override int filterOrder() { return 1 } boolean shouldFilter() { if ("true".equals(RequestContext.getCurrentContext().getRequest().getParameter(debugParameter.get()))) { return true } return routingDebug.get(); } Object run() { RequestContext ctx = RequestContext.getCurrentContext() ctx.setDebugRouting(true) ctx.setDebugRequest(true) ctx.setChunkedRequestBody() return null; }五、代码剖析在Servlet API 中有一个ServletContextListener接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。接口中定义了两个方法 ...

July 11, 2019 · 3 min · jiezi

运维编排场景系列给实例加到SLS机器组

场景简介我们经常会有这样的运维场景,扩容一批机器需要配置SLS日志,对于已经配置好的SLS Logstore后,我们只需要将机器加到机器组里。 解决方案传统的解决方案是登录每台ecs实例并安装logtail,执行的命令为 wget http://logtail-release-{{ACS::RegionId}}.oss-{{ACS::RegionId}}-internal.aliyuncs.com/linux64/logtail.sh -O logtail.sh; chmod 755 logtail.sh; ./logtail.sh install {{ACS::RegionId}};echo {{ LogTailUserDefinedId }} > /etc/ilogtail/user_defined_id分解下Task,需要以下几步:1.检查实例是不是Running状态2.调用云助手CreateCommand创建上述命令3.调用InvokeCommand执行4.等待执行成功5.删除模板 再转换成OOS模板并创建命名为installSlsAgent { "FormatVersion": "OOS-2019-06-01", "Description": "Install Logtail agent on the ECS Instance.", "Parameters": { "InstanceId": { "Type": "String", "Description": "the Instance Id to install ilogtail", "AllowedPattern": "i-[A-Za-z0-9]*", "MinLength": 1, "MaxLength": 30 }, "LogTailUserDefinedId": { "Type": "String", "Description": "the user defined Id write to /etc/ilogtail/user_defined_id", "AllowedPattern": "[A-Za-z0-9\\-_]*", "MinLength": 1, "MaxLength": 30 }, "OOSAssumeRole": { "Type": "String", "Description": "The RAM role to be assumed by OOS.", "Default": "OOSServiceRole" } }, "RamRole": "{{OOSAssumeRole}}", "Tasks": [ { "Name": "checkInstanceReady", "Action": "ACS::CheckFor", "Description": "describe instances with specified parameters, refer them here: https://help.aliyun.com/document_detail/63440.html", "Properties": { "API": "DescribeInstances", "Service": "ECS", "PropertySelector": "Instances.Instance[].Status", "DesiredValues": [ "Running" ], "Parameters": { "InstanceIds": [ "{{ InstanceId }}" ] } }, "Outputs": { "InstanceIds": { "ValueSelector": "InstanceIdSets.InstanceIdSet[]", "Type": "List" } } }, { "Name": "createCommand", "Action": "ACS::ExecuteApi", "Description": "create the command to install logtail agent.", "Properties": { "API": "CreateCommand", "Service": "ECS", "Parameters": { "CommandContent": { "Fn::Base64Encode": "wget http://logtail-release-{{ACS::RegionId}}.oss-{{ACS::RegionId}}-internal.aliyuncs.com/linux64/logtail.sh -O logtail.sh; chmod 755 logtail.sh; ./logtail.sh install {{ACS::RegionId}}; echo {{ LogTailUserDefinedId }} > /etc/ilogtail/user_defined_id" }, "Name": "oos-{{ACS::TemplateName}}", "Type": "RunShellScript" } }, "Outputs": { "CommandId": { "Type": "String", "ValueSelector": "CommandId" } } }, { "Name": "invokeCommand", "Action": "ACS::ExecuteApi", "Description": "invoke the command to install ilogtail", "Properties": { "Service": "ECS", "API": "InvokeCommand", "Parameters": { "CommandId": "{{ createCommand.CommandId }}", "InstanceIds": [ "{{ InstanceId }}" ] } }, "Outputs": { "InvokeId": { "Type": "String", "ValueSelector": "InvokeId" } } }, { "Name": "untilInvocationDone", "Action": "ACS::WaitFor", "Description": "until invocation ready", "MaxAttempts": 5, "Properties": { "Service": "ECS", "API": "DescribeInvocations", "Parameters": { "InvokeId": "{{ invokeCommand.InvokeId }}" }, "DesiredValues": [ "Finished" ], "PropertySelector": "Invocations.Invocation[].InvokeStatus" } }, { "Name": "describeInvocationResult", "Action": "ACS::ExecuteApi", "Description": "get the command invocation result", "Properties": { "Service": "Ecs", "API": "DescribeInvocationResults", "Parameters": { "InvokeId": "{{ invokeCommand.InvokeId }}" } }, "Outputs": { "InvocationResult": { "Type": "String", "ValueSelector": "Invocation.InvocationResults.InvocationResult[].Output" }, "ExitCode": { "Type": "Number", "ValueSelector": "Invocation.InvocationResults.InvocationResult[].ExitCode" } } }, { "Name": "deleteCommand", "Action": "ACS::ExecuteAPI", "Description": "clean up the install ilogtail command", "Properties": { "Service": "ECS", "Risk": "Normal", "API": "DeleteCommand", "Parameters": { "CommandId": "{{ createCommand.CommandId }}" } } } ], "Outputs": { "InvocationResult": { "Type": "String", "Value": { "Fn::Base64Decode": "{{ describeInvocationResult.InvocationResult }}" } }, "ExitCode": { "Type": "String", "Value": "{{ describeInvocationResult.ExitCode }}" } }}以上模板我们很好的解决了单台机器执行Install sls Agent的任务,那么对于多台机器的执行怎么办呢?OOS的Loop功能可以很好的解决这个问题。并且OOS支持模板嵌套执行,那么我们只需要构建一个传入实例ID列表的 ...

July 9, 2019 · 3 min · jiezi

GrapeCity-Documents-for-Excel-与-Apache-POI-功能对比

GrapeCity Documents for Excel 是什么?GrapeCity Documents for Excel (简称为:GcExcel)是葡萄城推出的一款文档API组件,同时适用于 Java 和所有支持 .NET Standard 2.0 及以上标准的平台,以编码的方式,无需依赖任何 Microsoft Excel 组件,即可快速批量操作 Excel 文件,轻松满足您关于 Excel 电子表格的一切需求。 超快速、低占用率、更轻量,使用 GrapeCity Documents 可极大节省应用程序在生成、加载、编辑和保存大型文档时所占用的内存和时间,帮助企业以更高效的方式处理各种文档,实现更多定制化选项。 下载试用GrapeCity Documents for Excel (Java平台)下载试用GrapeCity Documents for Excel (.NET平台)Apache POI是什么?Apache POI 是由Java编写的一款免费开源的跨平台Java API,主要用于实现对Microsoft Office文档进行读、写的功能。POI为“Poor Obfuscation Implementation”首字母的缩写,意为“简单的模糊实现”。 GrapeCity Documents for Excel相对于Apache POI的主要优势1.公式数量支持GcExcel支持452种Excel公式,而在Apache POI中,支持的公式数量很少(虽然Apache POI网站罗列了280多种可评估的公式,但在API中仅显示为157种)。 2.导出PDFGcExcel支持导出为PDF格式,以及控制页面设置选项。 Apache POI不支持导出为PDF。 3.条件格式GcExcel支持更多条件格式规则,如自定义图标集、高于平均值(AboveAverage)、发生日期、Top 10和重复项,且这些条件格式规则与VSTO保持一致。但在Apache POI中,使用高级API支持条件格式会受到限制,例如,需要使用标记为内部用途的低级类来处理Top10、高于平均值(AboveAverage)等格式化。 4.图表类型支持GcExcel的图表界面与VSTO一致,支持约53种图表类型。 Apache POI对图表的支持非常有限,仅支持Line、Bar、Column、Scatter和Radar图表类型。 5.迷你图GcExcel完全支持添加和配置迷你图(Sparklines)。 Apache POI目前不支持Sparklines。 6.剪切、复制、粘贴形状GcExcel支持剪切、复制、粘贴形状,Apache POI不支持。 7.过滤器数据类型GcExcel广泛支持文本、数字、日期、颜色和图标等过滤器。 Apache POI仅支持基本的AutoFilter,需要使用低级类来实现应用过滤或创建任何其他高级过滤器。 ...

July 9, 2019 · 1 min · jiezi

随时发布REST-API文档的代码仓库中的持续集成与协作

本文主要内容:API文档提供了预测客户成功的关键路径;在代码附近的文档上进行协作可以更好地检查代码和文档文件,提高自动化效率,并专门针对文档进行质量测试;提供通用文档框架,标准,自动化和工具,以提高团队效率。编写文档有时候会非常枯燥乏味,但优秀的文档是增加API被采用的一个很好的前提。编写出色的文档与编写代码一样需要严谨。随着API的质量逐渐成为产品增长的指标,您的文档比以往任何时候都更加重要,优秀的文档很大程度上代表创建了成功的API。API定义和文档常常结合在一起,虽然今天的API规范越来越多地作为GitHub中的代码进行管理,但API文档并非如此。所以需要让我们对此进行更改,以便在GitHub和相关代码库中编写和管理API文档(包括相关网站)的标准。 通过尽可能靠近代码和API定义协作编写文档,您可以自动执行文档测试,构建和部署。API的文档通常构建为网站,因此如果在分段构建期间就可以进行链接检查等测试。这种方法提供了许多好处:经过测试的文档构建;支持连续发布;支持对文档内容和代码的评论;多个输出(包括经过测试的代码示例);文档的发布管理(如API的早期访问版本)或增量更改和添加到发布了API,并防止代码合并。 文档持续集成/交付对于REST API文档,三个框架以网页的形式提供文档输出,其中许多是交互式的。这些是OpenAPI(Swagger),RESTful API建模语言(RAML)和API蓝图。OpenAPI(以前称为Swagger)基于JSON输出,由于YAML是JSON的超集,因此您可以交换描述API的源文件。RAML是一种基于YAML的语言,用于描述REST API。API Blueprint使用Markdown,也可以遵循GitHub Flavored Markdown语法。 许多编程语言都有一个文档框架,可以很好地与代码本身集成。通常,这些是静态站点生成器,例如Jekyll for Ruby和Sphinx for Python。文档的源文件是Markdown或reStructured Text(RST)。使用这些静态站点生成器,您可以创建漂亮的文档站点。事实上,GitHub上有一系列链接到漂亮的文档网站,其中包括Stripe API文档站点和Basho文档 - 这些是获得美观和实用程序最高分的示例API站点。 由于可以使用脚本转换这些文档源文件和网站配置文件,因此您可以使用与代码相同的构建作业来持续构建文档。通过从静态目录复制平面文件来部署Jekyll站点,因此您可以存储脚本以使用其他构建文件在代码存储库中构建站点。您还可以使用简单的链接检查程序在部署站点之前测试任何损坏的链接。HTMLProofer库是一个为此而编写的Ruby库。 GitHub提供的部署机制称为GitHub Pages。您可以选择触发文档网站部署。您可以从gh-pages分支,主分支自动化构建,或始终从主分支上的/ docs目录部署文档。虽然您可以部署到自定义域名,但GitHub页面的一个限制是您不能直接通过HTTPS提供服务,但作为免费服务,它是一个很好的起点。对于生产网站,您可以从GitHub部署到具有所需安全要求的云服务器或VPS。而GitHub Enterprise是GitHub的内部部署,用于内部部署,提供类似的托管站点功能。 为什么要像代码一样处理文档当成果已经可以交付给开发人员时,那与开发人员一起写文档会很有效。API文档任务为处理代码等文档的原因和时间提供了一个很好的示例。特别是在设计API或向API添加功能时的关键设计点,开发人员可以像文档代码一样贡献文档。 例如,在自动化参考信息时,您可以获得即时性和准确性,并通过在GitHub,Bitbucket或GitLab等社交编码系统中协作编写来获得贡献,质量和同理心。此外,API的最大成功指标之一是文档的准确性和有用性。当您的团队处理代码等API文档时,以下是一些特定的好处,下面将对此进行更深入的探讨: 1.协作效率可以为开发人员和文章协作编写者两个角色提供有价值的信息。开发人员希望为类似于自己的受众撰写文章,为读者提供经验分享。协作编写者有一个特殊的诀窍来组织信息,文笔更好,并以适当的顺序揭示概念和参考信息。如果在同一个存储库中协同工作可以提供沟通的效率,增加教学和被教学的机会。 2.贡献收益当没有专门的技术写作团队时,你可以扩大贡献者的数量,即使API本身不是公共文档的公共文件。或者,您可以通过在GitHub或Bitbucket等版本控制系统中提供开源文档存储库,从最终用户自己获得更多贡献。当文档与代码位于同一存储库中时,任何感兴趣的开发人员(包括客户)都可以订阅拉取请求的通知。 3.跟踪文档中的错误要获得更高质量的文档,请提供直接在输出页面上报告文档错误的方法。正如莱纳斯定律所述,“给予足够的眼球,所有的错误都是浅薄的”。通过为这些眼球提供报告错误的位置,您可以为文档提供更多类似代码的质量跟踪。此外,通过问题跟踪制定的指标,您可以衡量文档的质量,并确保在需要解决这些错误时可使用指标来判断。 4.对文档和代码的评论在查看代码中包含的文档时,审阅者可以在文档不足时阻止对代码的更改。该审查指南为团队提供了使文档具有高质量的能力,即使它们必须与快速变化的代码同步。 将文档源文件放在像GitHub这样的开放系统中可以使内容具有开放贡献,类似于开源代码。在OpenStack中,大量的开源云项目,文档贡献过程与代码贡献过程相同。编写者和开发人员在访问仅文档存储库(仅代码存储库)时具有相同的协作工具和背景知识。 当您的API定义需要一定程度的守门或小心防护时,您还可以考虑将GitHub Enterprise用于内部API文档。您的团队仍然可以在内部获得协作优势,而无需向全世界打开您的文档和代码。 5.部署和发布处理代码等文档时,意识到您正在将构建与部署分离。通过这种方式,您可以对文档的早期草稿进行审核,并且在文档构建部署并发布到网站之前,它都可以随时进行修正。或者,您可以选择不断发布一些文档以跟上代码。例如,您可以选择编写叙述性文档和教程,这些文档和教程会随着每个补丁集添加而不断发布。但对于像OpenAPI规范这样的合同文档,只有在考虑在特定版本下发布API时,才能部署新文档。 6.测试和构建文档通过为评论提供匹配的分段构建和向读者交付的生产构建,您可以确信您对构建的文档的审核满足用户需求。请参阅以下部分中的示例。 docs的示例测试您可以测试文档源文件的质量以及是否构建。您还可以检查拼写或语法,但也可以通过人为进行检查。但测试文档的目的是减轻人类审阅者的审查负担,自动化能节省时间,以便可以编写,发布和发布更多文档文件。 链接检查对于许多静态站点生成器,有专门用于检查站点中所有链接的库。例如,在检查像docslikecode.com这样的Jekyll网站上的链接时,Travis CI工作很简单: #!/usr/bin/env bashset -e # halt script on errorbundle exec jekyll buildbundle exec htmlproofer ./_site通过这种类型的测试,人工审阅者不必花时间点击新补丁中的每个链接。如果需要更新外部链接,那么这个测试的结果会通知您。如果内部链接因修补程序本身而中断,则系统可以在人为查看修补程序之前修复它。 JSON格式使用REST API文档,请求和响应通常是JSON文件,对于阅读文档的人在将自己的环境与文档进行比较时非常有价值。在读者需要复制和粘贴请求的情况下,正确格式化示例至关重要。在OpenStack中,我们有一组包含JSON测试器和格式化程序的通用文档工具。通过对传入的修补程序对文档文件运行此测试,读者可以确定所包含的JSON文件是否格式正确。此外,当它们可以在本地运行简单命令以确保JSON格式正确时,它可以使贡献者更容易创建补丁。 验证的请求和响应高级文档测试可以检查文档中包含的示例请求和响应。用查看代码文件相同的方式查看文档文件时,准确性通常是最高值。因此,通过针对正常工作的API端点测试示例,您可以证明这些示例也适用于实际情况。这种类型的验证提供了每次都可用的成功文档示例,因为只有它们通过验证测试,它们才被发布。 建立检查自动化文档测试可以节省读者的时间,因为他们不必自己构建文档来查看输出。此外您可以测试损坏的链接或不正确格式化的JSON文件的构建。对于任何奇怪的问题,查看源文档和输出文档都很有帮助。 结论在与代码库相同的仓库中协同处理文档,可以更好地为客户提供服务,无论他们是组织的内部还是外部。通过将API文档视为代码,您可以找到自动化途径,提高效率,加快文档构建,在文档中构建成功示例。 自动化测试功能除了能减少开发的时间之外,自动化测试还有助于进行项目流程测试,最近因为一直在使用EOLINKER进行API研发管理,因此推荐自动化测试功能,因为它支持UI模式和高级模式,可以实现不同类型的自动化测试项目,比如登录验证就可以用UI模式,高级模式则用来测试较为复杂的项目,对比之前效率高了不少,对API自动化测试等方面有兴趣的小伙伴前往了解下哦!https://www.eolinker.com 作者:Anne Gentle,思科的技术产品经理; 原文标题:Always Be Publishing: Continuous Integration & Collaboration in Code Repositories for REST API Docs; ...

July 9, 2019 · 1 min · jiezi

分析RESTful-API安全性及如何采取保护措施

本文中讨论了API安全性和采用安全措施的重要性,如身份验证,API密钥,访问控制和输入验证。API设计的第一步是撰写接口文档根据TechTarget(海外IT专业媒体)的定义,RESTful API是一个应用程序接口,它使用HTTP请求来获取GET,PUT,POST和DELETE等数据。从技术层面上看,RESTful API(也称为RESTful Web服务)是一种基于代表性状态转移(REST)技术,这是一种通常用于Web服务开发的架构风格和通信方法。 但随着 RESTful API 应用范围的爆炸性扩大,安全性越来越成为API架构设计中最容易被忽视的部分。 为什么API安全性很重要?在设计和部署RESTful API时,有以下三个核心原因可以解释为什么安全性应该是一个很重要的考虑因素。 1.数据保护RESTful API是一种向外界传输价值的服务方式。因此,保护通过RESTful方式提供的数据始终应该是属于高优先级。 2.DOS攻击如果不采取正确的安全措施,(DOS)攻击可以使RESTful API进入非功能状态。考虑到很多基础的RESTful API是开放给所有人使用的情况,通过这种类似开源的方式有助于它更好推广给市场,让更多人投入使用,但同时意味着如果有人选择对API执行DOS攻击时,它也可能导致灾难性的结果。 3.商业影响如今有越来多的服务平台,提供着影响衣食住行的各种信息,从飞机航班时刻表到高铁余票查询,甚至只是超市里日常用品,都能给你提供价格、数量、时间等诸多信息,让你足不出户,买到最合心意的商品。在这样的大趋势下,这种利用API数据来获取更多信息,再提供给你的聚合服务平台将会越来越多。于是通过RESTful API传输的信息会被频繁调用,而其中的个人信息很容易被泄露。 保障安全采用的措施以下介绍一些RESTful API通用设计中的关键概念。 1.会话管理和认证除了使用TLS / HTTPS之外,最重要的RESTful API安全级别是以会话管理和身份验证为中心的。在本文讨论中重点将放在API密钥,OpenID Connect / OAuth2 / SAML和会话状态事项上。 2.API密钥API密钥的概念是为使用者提供了作为其HTTP请求的一部分的唯一字符串(密钥)。虽然不是一个完整的安全解决方案,但与匿名使用相比,使用API密钥可以更清楚地了解谁在使用API。 API密钥也可用于提供附加服务。例如,对于RESTfulAPI,附加的服务可以选择使用不同的级别。以一般、高、最高三个等级为例,在“一般”的级别,用户可以自由访问,但只能访问一组有限的数据。假如要访问更多组的数据,那就必须支付费用才能访问“高”的级别,以此类推,不受限制的访问所有的数据则要支付“最高”等级的费用,通过提供不同API密钥的方式,提供额外的服务。 使用API密钥的最常用的方式是将它们包含在请求头中。 例如,当调用某个小部件的头部时,67A73DD1BD1D90210BA的API设置为HTTP头部中的X-API-KEY键/值对: curl-H“X-API-键:67A73DD1BD1D90210BA” API密钥的另一个常见用法是将该密钥包含在URI中: https://www.example.com/v1/wi... 67a73dde1bdd1d90210ba 但这种方法的问题在于,API密钥在浏览器历史记录和对应服务器的日志中都会显示出来,意味着向所有有权访问这些数据的人公开唯一密钥。 3.OpenID Connect,OAuth2和SAMLOpenID Connect,OAuth2和SAML使用HTTP协议作为传输,用于安全目的。身份验证提供个人的验证,同时授权执行或撤消访问的任务。 从身份验证角度来看,存在以下选项: OpenID Connect:可以利用现有的身份提供商(如Google或Facebook)获取用于验证用户授权的令牌。OAuth2:可以通过授权执行伪认证(如下所述)。SAML:使用断言、协议、绑定和配置文件来管理身份验证,包括标识提供者,但不太适合移动应用程序验证。为提供授权服务,采用以下策略: OAuth2:提供安全的委托访问,通过允许第三方身份提供程序颁发令牌来代表用户执行操作。由于OAuth2必须了解被委派的用户,因此身份验证是以伪方式实现的(如上所述)。SAML:对可信服务执行断言,包括提供授权的令牌。4.会话状态事项RESTful API端点应始终保持无状态会话状态,这意味着会话的所有内容都必须保存在客户端。来自客户端的每个请求都必须包含服务器理解请求所必需的所有信息。为了简化流程,包括API令牌以及会话令牌,作为每个业务中都需要的一部分。 5.访问控制如上所述,对RESTful服务的授权可以将安全性引入到所提供的端点中,以便对那些可以向API发出HTTP删除请求的人有限制。 在下面的简单Java示例中,只有Admin和Manager角色(即组)的成员可以执行删除所有用户的DELETE请求,但是所有用户都可以执行GET请求以获取用户列表: 6.速率限制如上所述,API密钥是判断RESTful API的使用者身份级别一种很有用的策略。除了提供等级识别外,使用API密钥的另一个好处是能够限制API的使用,例子像Tibco Mashery、MuleSoft和Dell Boomi等API管理解决方案允许限制API请求,利用API密钥实现此功能。因此,试图执行DoS攻击(有意或无意)的时候将会达到一个设定的阈值,到达阈值后后续所有的请求都将被拒绝。 7.输入验证和HTTP返回代码在保护RESTful API时,应始终考虑对输入进行验证。例如,如果用户试图发布与地址相关的JSON数据集合,则RESTful端点内的服务应验证数据并使用HTTP返回代码来反映正确的状态。在下面简化的Java示例中,就是调用一个非常基本的AdvSersService来验证和保存地址: 在上面的示例中,使用isValidAddress()方法验证newAddress对象(从JSON编组到Address Java对象)。如果地址无效,则向用户返回HTTP 401(错误请求)代码,文本显示为“提供的地址无效。” 如果认为该地址有效,则convertAddress()将执行必要的操作,然后将包含地址内容的JSON格式的字符串返回给用户,以及一个HTTP 201(创建)返回代码。 结论保护RESTful API的安全应该始终处于API设计工作中最先被考虑的部分。如果不保护敏感数据,允许DOS攻击以及不考虑使用RESTful API的影响,那么即使它只是短暂的影响,相关的风险可能很容易使企业处于不利地位。 ...

July 1, 2019 · 1 min · jiezi

yii2-在控制器中验证请求参数

写api接口时一般会在控制器中简单验证参数的正确性。 使用yii只带验证器(因为比较熟悉)实现有两种方式(效果都不佳)。 针对每个请求单独写个 Model, 定义验证规则并进行验证。 缺点:写好多参数验证的 Model 类。使用 独立验证器 中提到的 $validator->validateValue() 方法直接验证变量值。缺点:写实例化很多验证器对象。有么有“一劳永逸”的做法,像在 Model 中通过 rules 方法定义验证规则并实现快速验证的呢?有! 使用方法(实现效果)namespace frontend\controllers\api;use yii\web\Controller;use common\services\app\ParamsValidateService;class ArticleController extends Controller{ // 文章列表 public function actionList() { $PVS = new ParamsValidateService(); $valid = $PVS->validate(\Yii::$app->request->get(), [ ['category_id', 'required'], ['category_id', 'integer'], ['keyword', 'string'], ]); if (!$valid) { $this->apiError(1001, $PVS->getErrorSummary(true)); } //... } // 新增文章 public function actionPost() { $PVS = new ParamsValidateService(); $valid = $PVS->validate(\Yii::$app->request->get(), [ [['category_id', 'title', 'content'], 'required'], ['category_id', 'integer'], [['title'], 'string', 'max' => 64], [['content'], 'string'], ]); if (!$valid) { $this->apiError(1001, $PVS->getErrorSummary(true)); } //... } // 文章删除 public function actionDelete() { $PVS = new ParamsValidateService(); $valid = $PVS->validate(\Yii::$app->request->get(), [ ['article_id', 'required'], ['article_id', 'integer'], ]); if (!$valid) { $this->apiError(1001, $PVS->getErrorSummary(true)); } //... }}实现方法定义参数验证模型定义参数验证模型 ParamsValidateModel,继承 yii\db\ActiveRecord,重写 attributes() 方法,主要功能: ...

June 18, 2019 · 2 min · jiezi

云原生生态周报-Vol-8-Gartner-发布云原生趋势

业界要闻 Gartner 发布云原生基础设施未来的八大趋势:权威分析机构 Gartner 在对 2020 年技术趋势的展望当中指出:“预计2020年所有领先的容器管理软件均内置服务融合技术,到2022年有75%的全球化企业将在生产中使用容器化的应用、还有50%的应用软件将容器化适应超融合环境”。Gartner 在报告中表示,未来基础设施技术演进的八大趋势包括: 多云与混合云; Service Mesh;基于 Kubernetes 的 fPaaS(即:函数计算 PaaS);裸金属容器和微虚拟机;第三方应用和 ISV 的大规模容器化;对有状态应用的完善支持;整个技术栈都会基于 CNCF 中的知名项目来构建。Gartner 2019年发布了《公有云容器服务竞争格局》,阿里云是唯一进入该报告的国内云厂商, 拥有国内最大公共云容器集群。 Kubernetes 五周年官方回顾:在本月,Kubernetes 项目迎来了自己的五周岁生日,整个 Kubernetes 社区开展了一系列纪念活动来,CNCF 官方博客也刊登了 Kubernetes 社区对自己的五年历程的总结与回顾: Kubernetes 项目的成功,首先归功于数以千计的开源开发者的智慧与劳动;Kubernetes 已经成长为这个星球上最庞大的单一开源项目之一。而在维护这个项目的过程中,数千位开发者的远程协同与严谨自动化的开源项目管理方式,带来了项目的稳定性与高质量,这个过程堪称软件工程史上的典型范例;Kubernetes 项目取得今天的成绩,还归功于其富有远见的设计思想与技术理念。Kubernetes 的核心原理与实现,终于使得“云原生”从虚无缥缈的概念,变成了运行在每一个数据中心里的代码与架构;Kubernetes 项目的发展永远不会停止,这是因为 Kubernetes 的 API,已经成为了新一代开发者编写与构建软件的基础假设与核心依赖:这个生长于 Kubernetes 之上的“云原生”生态系统,已经成为了推动整个社区不断向前发展的动力源泉。上游重要进展 Kubernetes 项目 kubelet cAdvisor JSON API 要正式下线了: SIG-Node 已经为 kubelet 加入了可以禁用这些接口的参数,同时,这些接口也被标记为“废弃” (deprecrated)。 实际上,社区上游 CAdvisor 的独立性一直在被削弱,因为 kubelet的metrics指标要聚焦,以便提高kubelet性能,允许更频繁的查看 Node metrics,详见:#68522Kubelet到API Server的连接被强制关闭会出错,目前社区已经合并了修复,但是短时内可能会出现node NotReady 的错误。Admission Hook 添加 ObjectSelector:Admission WebHook 一直以来都是开发者对 Kubernetes 进行扩展的重要手段。但是 WebHook 里很长一段时间以来只支持按照 Namespace 过滤 API 对象。而在本周,Admission Hook 的语义中终于添加了 ObjectSelector。这样,Hook 的开发者终于可以按照更细致的 Label 来过滤出该 Hook 关系的 API 对象了。开源项目推荐 ...

June 18, 2019 · 1 min · jiezi

正确甄别API-REST-API-RESTful-API-Web-Service之间的差异与联系

看到API你会想起什么?是接口、第三方调用、还是API文档?初看你可能会觉得这太熟悉了,这不是系统开发日常系列吗?但你仔细想一想,你会发现API的概念在你脑海里是如此的模糊。如果你通过搜索引擎检索API,你会看到类似这样的信息:API——Application Programming Interface(应用程序编程接口),这太抽象了。接下来,我将结合在开发中总结的一些经验,以通俗的方式聊聊API、REST API、RESTful API以及Web Service这四者之间的联系与区别。 1、API 与 REST API 什么是API?这里引述维基百科给出的定义:应用程序接口(英语:Application Programming Interface,缩写:API;又称为应用编程接口)是软件系统不同组成部分衔接的约定。这个对API的定义太过于广泛和抽象,而通俗的讲,API是一段应用程序与另一段应用程序相互“交流”的方式(协议)。在Web应用程开发中,API是我们通过网络进行数据检索的一种主要方式,API文档将告知你检索数据的URL列表、查询参数、请求方式以及响应状态,其目的是降低Web应用程序开发难度,共享两个应用程序之间的数据(文本、音频、视频、图片等),而屏蔽其内部复杂的实现细节。 REST是Representational State Transfer的缩写,直译过来就是:表述状态的转移。REST API是一组关于如何构建Web应用程序API的架构规则、标准或指导,或者说REST API是遵循API原则的一种架构风格。REST是专门针对Web应用程序而设计的,其目的在于降低开发的复杂度,提高系统的可伸缩性。下面是设计REST风格的系统架构时需要满足或者遵循的一些基本条件和原则: 1、在REST架构中,Web中所有的事物(文本、音频、视频、图片、链接)都可以被统一的抽象为资源(resource)2、在REST架构中,每一个资源都有与之对应的唯一资源标识符(resource identifier),当资源的状态发生改变时,资源标识符不会发生改变3、在REST架构中,所有的操作都是无状态的。REST架构遵循CRUD原则,所有的资源都可以通过GET、POST、PUT和DELETE这四种行为完成对应的操作。4、可缓存(可选项),在REST架构中需要缓存来有效的处理大批量的请求5、接口一致 现在,了解了API和REST API的基本概念,那这两者之间有什么异同?如果按照数学上集合的概念来解释API与REST API之间的联系与区别,API是REST API的超集,REST API 是API的子集;所有的REST API都是API,但不是所有的API都是REST API。更通俗的解释是:所有的男人都是人,但不是所有的人都是男人。 2、REST API 与RESTful API 在第一小节中,了解了什么是REST API,接下来聊聊REST API与RESTful API之间的异同。很多初学者很容易将这两者等同起来,认为RESTful API就是REST API,这可能是单纯的从字面上去理解了,当你深入的去了解两者的本质后,你会发现其实不然。REST API是Web API设计的一种规范或者指导原则,而RESTful API则是这中架构设计原则或者规范的一种具体实现方式。也就是说,RESTful API是REST API的非正式实现方式,因为实现REST API的方式有很多,RESTful API只是其中一种,且没有完全满足REST API的所有设计原则,每个开发者在实现REST 架构时的则重点都会有差别。 很多初学者容易将REST API与RESTful API两者的概念搞混淆,我想可能只是看字面意思,而没有关注它们本身的含义(就像认识中文字一样,有边读边,无边读中间,断章取义了)。这就好比很多人会把变性人等同于女人,变性人可能五官的表象看起来和女人一样,但变性人不能生育,它只是满足了定义一个女性的大多数条件(实现),但本质上不是女人。 接下来,通过一个简单的例子以加深对REST API和RESTful API的理解。下面将给出一个执行CURD操作的RESTful API设计案例: 说明:resource代指某种资源的名称,可以时student(学生)、teacher(老师)、book(书籍)等等,通常使用名词来表示;{id}则代指某种资源的唯一标识符(resource identifier)下面是一个具体的小例子,以学生管理为例,设计学生管理的API。学生资源包括ID,姓名和所学课程信息,学生资源信息如下: 现在,我们需要将学生数据保存到数据库,然后执行查询、修改和删除学生数据的操作。学生管理API的使用者调用的API如下: 1、创建学生资源:[POST] http://www.example.com/student2、获取所有学生资源:[GET] http://www.example.com/students3、获取ID=1001的学生资源:[GET] http://www.example.com/studen...4、修改ID=1001的学生资源:[PUT] http://www.example.com/studen...5、删除ID=1001的学生资源:[DELETE] http://www.example.com/studen... 前面的内容说到,API共享数据资源,而屏蔽内部实现,API的使用者(客户端)关注的是资源(读懂数据),并不需要了解API内部构造;API的提供者(服务端)只关注自己的内部实现,而不关系API使用者(客户端)的状态。为了加深对这一概念的理解,下面给出学生管理API的内部实现示例: ...

June 17, 2019 · 1 min · jiezi

API网关如何实现对服务下线实时感知

上篇文章《Eureka 缓存机制》介绍了Eureka的缓存机制,相信大家对Eureka 有了进一步的了解,本文将详细介绍API网关如何实现服务下线的实时感知。 一、前言在基于云的微服务应用中,服务实例的网络位置都是动态分配的。而且由于自动伸缩、故障和升级,服务实例会经常动态改变。因此,客户端代码需要使用更加复杂的服务发现机制。 目前服务发现主要有两种模式:客户端发现和服务端发现。 服务端发现:客户端通过负载均衡器向服务注册中心发起请求,负载均衡器查询服务注册中心,将每个请求路由到可用的服务实例上。客户端发现:客户端负责决定可用服务实例的网络地址,并且在集群中对请求负载均衡, 客户端访问服务登记表,也就是一个可用服务的数据库,然后客户端使用一种负载均衡算法选择一个可用的服务实例然后发起请求。客户端发现相对于服务端发现最大的区别是:客户端知道(缓存)可用服务注册表信息。如果Client端缓存没能从服务端及时更新的话,可能出现Client 与 服务端缓存数据不一致的情况。 二、网关与Eureka结合使用Netflix OSS 提供了一个客户端服务发现的好例子。Eureka Server 为注册中心,Zuul 相对于Eureka Server来说是Eureka Client,Zuul 会把 Eureka Server 端服务列表缓存到本地,并以定时任务的形式更新服务列表,同时zuul通过本地列表发现其它服务,使用Ribbon实现客户端负载均衡。 正常情况下,调用方对网关发起请求即刻能得到响应。但是当对生产者做缩容、下线、升级的情况下,由于Eureka这种多级缓存的设计结构和定时更新的机制,LoadBalance 端的服务列表B存在更新不及时的情况(由上篇文章《Eureka 缓存机制》可知,服务消费者最长感知时间将无限趋近240s),如果这时消费者对网关发起请求,LoadBalance 会对一个已经不存在的服务发起请求,请求是会超时的。 三、解决方案3.1 实现思路生产者下线后,最先得到感知的是 Eureka Server 中的 readWriteCacheMap,最后得到感知的是网关核心中的 LoadBalance。但是 loadBalance 对生产者的发现是在 loadBalance 本地维护的列表中。 所以要想达到网关对生产者下线的实时感知,可以这样做:首先生产者或者部署平台主动通知 Eureka Server, 然后跳过 Eureka 多级缓存之间的更新时间,直接通知 Zuul 中的 Eureka Client,最后将 Eureka Client 中的服务列表更新到 Ribbon 中。 但是如果下线通知的逻辑代码放在生产者中,会造成代码污染、语言差异等问题。 借用一句名言: “计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决” Gateway-SynchSpeed 相当于一个代理服务,它对外提供REST API来负责响应调用方的下线请求,同时会将生产者的状态同步到 Eureka Server 和 网关核心,起着 状态同步 和 软事物 的作用。 ...

June 10, 2019 · 2 min · jiezi

一次和前端的相互甩锅的问题记录

背景我们在APP上有个功能,需要获取用户当前定位,然后当用户关闭了GPS后,没有获取到用户定位,会触发一个bug,弹窗内容如下。 问题分析这个问题的直接原因就是移动端的值取不到,导致没有给变量赋值,就将"undefined"传给了后端,后端的这个值定义的Integer,类型转换失败,报错。 深层原因是异常处理机制有问题,于是后端和前端开始撕逼了 前端观点: 后端代码太不健壮了, 就算前端传错了,也应该具备容错性;此外APP是有版本的,就算hotfix,用户也不一定升级,上一版本用户还是会有问题,所以这种问题尽量是后端来修复。 后端观点:前端没有异常兜底机制,用户不应该看到任何这种程序异常。对于有定制需求的异常,报特定异常,没有应该显示通用异常,比如弹窗"服务不可用"。另外这种属于http请求层面的约束,前端不遵从约束,还来怪我。我后端框架层面就给你拦截了,没到业务代码。 双方说的都好有道理,谁也说服不了谁。但是关于目标大家达成一致:坚决不能让用户看到这种类型的弹窗异常。 既然说服不了对方,就只能从更深入的分析问题,看看更合理的解法 通用异常的处理方式http通常错误有 4开头:客户端参数有问题,需要后端提供debug信息。理论上应该只是联调的时候会出现,但是实际上不一定(这不就打脸了吗)5开头:服务器端有错误,客户端有统一提供的异常处理2开头:业务异常,如果有UI要求,后端返回一个code码,前端根据code码,展示UI。如果没有UI要求,前端直接展示后端返回的错误消息。为了统一异常处理,一般公司的做法都是API统一返回一套数据格式, { "error_code": "xx", // code码,1代表正常,其他表示异常。 "error_msg": "xx" // msg,错误提示消息 "data": "xx" // 正常数据}我们也是,并且将4开头的都统一处理成这套统一的数据格式。 那么前端处理异常的逻辑 这次的问题就是走到2的分支了。 前后端都没做错,问题是后端对于异常模型的抽象有问题,客户端参数有问题,需要后端提供debug信息,而不是给用户展示的错误信息。 其实服务端对于异常就分三种 客户端参数有问题的异常(前端需要debug信息和错误msg信息)需要用户知道的业务异常,前端需要根据code展示的(前端需要code码)通用的服务端异常,包装成消息给前端。(前端需要错误msg信息)解法分析清楚了问题后,就有了解法。 解法1:错误消息和debug消息分离,返回的API统一格式中增加一种字段。debug_msg 给开发看的,error_msg 还是给用户看的 解法2:定义几个枚举code。作为开发的约定 codeerror_msg参数错误含义010000系统错误[010000]请求方法不支持010001系统错误[010001]缺少路径参数010002系统错误[010002]缺少必须的请求参数010003系统错误[010003]类型不匹配 010004系统错误[010004]请求体异常010005系统错误[010005]// 参数校验异常(body 或 param)010006系统错误[010006]参数绑定异常(表单)解法1定义比较清晰,但是为了这种corner case增加了一个字段的开销,网络传输代价高了。另外还需要前端配合更改,改动成本比较大。 解法2兼容了现在的实现,前端不用更改,但是对于客户端参数有问题这种错误提醒不是很友好,不能向前端显示具体的参数错误了。只能打日志。 和前端讨论了下,确定了解法2。 总结所以这个问题,最后的解法 前端获取不到定位时,将undefined这个变量赋值后端针对移动端这个服务,改动了异常处理机制@RestControllerAdvicepublic class ApiStandardException { private static final String ERROR_MSG = "系统错误"; @ExceptionHandler(value = Exception.class) public ResponseEntity<Result> handle(final Exception ex, final WebRequest request) throws Exception { try { if (ex instanceof HttpRequestMethodNotSupportedException) { // 请求方法不支持 LOG.warn("Request method is not supported"); throw new BusinessException(WebRequestErrorEnum.METHOD_ERR.getCode(), ERROR_MSG); } else if (ex instanceof MissingPathVariableException) { LOG.warn("MISSING_PATHVAR" + ex.getMessage()); // 缺少路径参数 throw new BusinessException(WebRequestErrorEnum.MISSING_PARAM.getCode(), ERROR_MSG); } else if (ex instanceof MissingServletRequestParameterException) { // 缺少必须的请求参数 } // 省略其他异常处理

June 9, 2019 · 1 min · jiezi

1小时玩转OCR实战

在日常生活工作中,我们难免会遇到一些问题,比如自己辛辛苦苦写完的资料,好不容易打印出来却发现源文件丢了。难的收集了一些名片,却要很麻烦的一个一个录入信息。快递公司的业务越来越好,但每天却需要花费很多时间登记录入运单,效率非常的低。 那么,有没有什么技术能帮助我们解决这些难题呢?有的,那就是OCR文字识别技术。 什么是OCR? OCR 是实时高效的定位与识别图片中的所有文字信息,返回文字框位置与文字内容。支持多场景、任意版面下整 图文字的识别,以及中英文、字母、数字的识别。通俗的来说,就是将图片上的文字内容,智能识别成为可编辑的文本,例如: OCR的技术原理是什么? OCR本质是图像识别。其原理也和其他的图像识别问题基本一样。包含两大关键技术:文本检测和文字识别。先将图像中的特征的提取并检测目标区域,之后对目标区域的的字符进行分割和分类。 以深度学习兴起的时间为分割点,直至近五年之前,业界最为广泛使用的仍然是传统的OCR识别技术框架,而随着深度学习的崛起,基于这一技术的OCR识别框架以另外一种新的思路迅速突破了原有的技术瓶颈(如文字定位、二值化和文字分割等),并已在工业界得到广泛应用。 首先文本定位,接着进行倾斜文本矫正,之后分割出单字后,并对单字识别,最后基于统计模型(如隐马尔科夫链,HMM)进行语义纠错。 OCR技术的难点是什么? 复杂背景、艺术字体、低分辨率、非均匀光照、图像退化、字符形变、多语言混合、文本行复杂版式、检测框字符残缺,等等。 如何克服这些难点的? 从几个方面入手。一是使用场景,另一方面是从技术上的改进腾讯优图实验室在文本检测技术方进行了深度优化,提出了Compact Inception,通过设计合理的网络结构来提升各尺度的文字检测/提取能力。同时引入RNN多层自适应网络和Refinement结构来提升检测完整性和准确性。 腾讯云OCR目前支持什么功能? 基于腾讯优图实验室世界领先的深度学习技术,目前我们已支持: 身份证识别,银行卡识别,名片识别,营业执照识别,行驶证驾驶证识别,车牌号识别,通用印刷体识别,手写体识别。 通用印刷体的技术难点,使用场景 大家都知道身份证识别可广泛应用在金融行业中,有用户的身份认证中,可以减少用户的信息输入,提升效率,提高用户体验,营业执照的识别完全省去了手工录入的繁琐,还可以为企业省去大量的人力资源成本,这些场景大家都已经比较熟悉。 对于通用印刷体,腾讯优图实验室自主设计一整套全方位多尺度文字识别引擎,可攻破模糊,散焦,透视,文字部分遮挡的问题,识别准确率高达90%以上,处于业界领先水平。使用场景广泛,例如对任意版面上图像的文字识别,可广泛应用在印刷文档、广告图、医疗、物流等行业中的识别。 对于通用印刷体有没有什么好的例子? 例如这个广告,内容多字体,中英文与数字混合,背景也比较随意。咱们的OCR通过透视矫正、去模糊等,能大幅还原图像真实度,极大提升算法的鲁棒性。 再例如识别文字密集,行间距小,透视畸变等的海报。人工识别需要不仅耗费时间,肉眼也比较难识别。但腾讯云OCR 设计了小而精的特征提取网络,配合先进的预处理技术,识别准确率高达93%以上。 有时候也会遇到识别率不理想的情况,如何可以提高识别准确率? 首先会确认下当前的场景,造成准确度不高的原因。评估可提高的空间设计,之后做出相应的修改,列入预处理等等。 关于腾讯云手写体识别方面的案例 腾讯是国内首家将手写体识别应用在复杂场景下的服务商,数字识别准确率高达90%以上,单字识别速度在15ms以内,复杂汉字准确率超过80%。 腾讯云手写体OCR已运用到的运单识别场景,解决了物流行业每日快递单人工输入工作量极大且极易出错,非常低效等问题。 运单识别与传统人工识别的区别 如果传统人工识别按照3min/单,1000单需要6.25个人/天,保证运单时效则需要耗费大量人力,考虑人力成本则影响运单及时性,成本和服务难两全。 我们的运单识别速度可以达到毫秒级/单,并支持24小时识别服务 ,业务增长时只需要投入计算用服务器资源即可,弹性较大。 与传统识别相比,不仅成本可以降低,提高准确性,还可以保护用户的隐私泄露风险。 腾讯云OCR在行业中落地案例 新版手Q就用到了咱们的技术,在扫一扫、聊天窗口和空间图片大图预览共三个入口上支持了提取图片中文字的功能。 方便用户阅读、编辑、保存图片上的文字,从而可以对提取出的文字进行翻译、搜索。在多种场景下可以极大提升用户对图片上文字的阅读和记录效率。 企业微信中的名片识也用到了咱们OCR技术。用户只需拍照或选择名片图片,就能准确快速地识别出名片中的文字,并自动提取为对应的字段,极大简化了名片录入流程,也避免了手动录入过程可能出现的错误。 福利时间:腾讯云大学6月10日(周一)晚19:00-20:30邀请到大数据与人工智能产品中心大咖,开设免费线上直播课程,现场讲解API落地使用!讲师将会在直播课程中为大家解答疑惑。 适合人群: 0-3岁开发者OCR与人脸核身使用者人脸识别从业者对人脸识别感兴趣的小伙伴非开发者0基础不要紧,现场手把手教学,一小时你能收获的不止有实战指导,更有鹅厂大咖的经验分享! 大咖讲师: 腾讯云AI视觉产品项目经理 张诚 课程内容:1、 腾讯云OCR文字识别产品简介2、 OCR通用印刷体识别动手实验室3、 腾讯云慧眼·人脸核身产品简介4、 人脸核身H5动手实验室 如何观看课程?扫描下方海报二维码即可预约课程,开播前15分钟将短信通知! 重要提示:提前关注微信【学习君】,入群还可以领取更多鹅厂内部技术资料

June 5, 2019 · 1 min · jiezi

接口API实现短信自动化发送

短信验证码接口示例,如何接入短信API接口实现短信自动发送功能; 网站如何实现自动发送短信验证码的功能,短信验证码技术文档需要哪些参数,网站点击触发验证码的功能实现的方法,通过调用API接口的方式实现短信触发和秒发短信;实现验证码的校验功能; 以java为例,短信验证码接口的调用示例如下: (Demo下载链接:https://www.kewail.com/experi...) package com.kewail.sdk.sms;import com.kewail.sdk.sms.yun.SmsSingleSender; importcom.kewail.sdk.sms.yun.SmsSingleSenderResult; importcom.kewail.sdk.sms.yun.SmsVoiceVerifyCodeSender; importcom.kewail.sdk.sms.yun.SmsVoiceVerifyCodeSenderResult; public classSmsSDKDemo { public static void main(String[] args) { try { //请根据实际accesskey 和 secretkey 进行开发,以下只作为演示 sdk 使用 String accesskey = "xxx";String secretkey ="xxxxx"; //填写你的手机号码 String phoneNumber ="136252412xx"; //初始化单发手机短信 SmsSingleSender singleSender = newSmsSingleSender(accesskey, secretkey); SmsSingleSenderResultsingleSenderResult; //手机短信普通单发,注意前面必须为【】符号包含,置于头或者尾部。singleSenderResult = singleSender.send(0, "86", phoneNumber,"【Kewail科技】尊敬的用户:您的验证码:123456,工作人员不会索取,请勿泄漏。", "", "");System.out.println(singleSenderResult); //语音验证码发送//SmsVoiceVerifyCodeSender smsVoiceVerifyCodeSender = newSmsVoiceVerifyCodeSender(accesskey,secretkey);//SmsVoiceVerifyCodeSenderResult smsVoiceVerifyCodeSenderResult =smsVoiceVerifyCodeSender.send("86",phoneNumber, "444144",2,"");//System.out.println(smsVoiceVerifyCodeSenderResult); } catch (Exception e) { e.printStackTrace(); } } } ...

June 5, 2019 · 1 min · jiezi

MEMORYBASICINFORMATION的成员

May 31, 2019 · 0 min · jiezi

以太坊中文文档翻译智能合约

本文原文链接点击这里获取Etherscan API 中文文档(完整版)完整内容排版更好,推荐读者前往阅读。 智能合约(Contracts)智能合约相关的 API,接口的参数说明请参考Etherscan API 约定, 文档中不单独说明。 Newly verified Contracts are synced to the API servers within 5 minutes or less 获取已经验证代码合约的ABIVerified Contract Source Codes https://api.etherscan.io/api?module=contract&action=getabi&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413&apikey=YourApiKeyTokenA simple sample for retrieving the contractABI using Web3.js and Jquery to interact with a contract var Web3 = require('web3'); var web3 = new Web3(new Web3.providers.HttpProvider()); var version = web3.version.api; $.getJSON('http://api.etherscan.io/api?module=contract&action=getabi&address=0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359', function (data) { var contractABI = ""; contractABI = JSON.parse(data.result); if (contractABI != ''){ var MyContract = web3.eth.contract(contractABI); var myContractInstance = MyContract.at("0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359"); var result = myContractInstance.memberId("0xfe8ad7dd2f564a877cc23feea6c0a9cc2e783715"); console.log("result1 : " + result); var result = myContractInstance.members(1); console.log("result2 : " + result); } else { console.log("Error" ); } });获取已经验证代码合约的源码https://api.etherscan.io/api?module=contract&action=getsourcecode&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413&apikey=YourApiKeyToken点击获取Etherscan API 中文文档(完整版)示意图: ...

May 26, 2019 · 1 min · jiezi

如何在调用Marketing-Cloud-contact创建API时增加对扩展字段的支持

需求:扩展字段“微信ID”是我创建出来的extension field,我想用Marketing Cloud提供的contact creation API,在创建contact时也能支持这个扩展字段。换言之,我希望在调用contact create API时,给Extension field维护值,contact创建成功后,Extension field会被调用API时传入的值填充。 首先在Chrome开发者工具里找到这个字段的技术名称technical name:YY1_WECHATID_MPS 在Contact创建页面上把扩展字段配置出来, 创建一个新的contact实例, 给这个扩展字段维护一个值,比如i042416, 通过chrome开发者工具的network标签页观察创建时的payload: 然后在nodejs代码里依法将扩展字段的名称和值维护进去即可: 完整源代码:https://github.com/i042416/we...要获取更多Jerry的原创文章,请关注公众号"汪子熙":

May 25, 2019 · 1 min · jiezi

如何使用Marketing-Cloud的扩展字段作为搜索条件进行搜索

需求:我在Marketing Cloud的contact模型上用custom field这个应用创建了一个Extension field,名称为微信ID。 现在客户的需求是使用这个字段作为过滤条件进行搜索。 首先在界面上执行一次搜索,在Chrome开发者工具network标签里将这次搜索中前端发给后台的HTTP请求明细记录下来: 然后在postman里照样维护一份: payload:--batch_bd03-9977-8095Content-Type: application/httpContent-Transfer-Encoding: binaryGET InteractionContacts?sap-client=100&$skip=0&$top=15&$filter=YY1_WECHATID_MPS%20eq%20%27i042410%27&$select=ImageURL%2cName%2cContactLevelName%2cCountryName%2cCity%2cEMailAddress%2cPhoneNumber%2cMobilePhoneNumber%2cCorporateAccountName%2cInteractionContactUUID%2cRelationship%2cType&$inlinecount=allpages HTTP/1.1sap-cancel-on-close: trueCache-Control: max-age=360sap-contextid-accept: headerAccept: application/jsonAccept-Language: enDataServiceVersion: 2.0MaxDataServiceVersion: 2.0x-csrf-token: fsZ87xxAy55LLhk56CCjCw== --batch_bd03-9977-8095-- 测试结果如下: 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

May 25, 2019 · 1 min · jiezi

从-Spark-Streaming-到-Apache-Flink-实时数据流在爱奇艺的演进

作者:陈越晨 整理:刘河 本文将为大家介绍Apache Flink在爱奇艺的生产与实践过程。你可以借此了解到爱奇艺引入Apache Flink的背景与挑战,以及平台构建化流程。主要内容如下: 爱奇艺在实时计算方面的的演化和遇到的一些挑战爱奇艺使用Flink的User Case爱奇艺Flink平台化构建流程爱奇艺在Flink上的改进未来工作爱奇艺简介 爱奇艺在2010年正式上线,于2018年3月份在纳斯达克上市。我们拥有规模庞大且高度活跃的用户基础,月活跃用户数5.65亿人,在在线视频领域名列第一。在移动端,爱奇艺月度总有效时长59.08亿小时,稳居中国APP榜第三名。 一、爱奇艺在实时计算方面的演化和遇到的一些挑战1. 实时计算在爱奇艺的演化过程 实时计算是基于一些实时到达、速率不可控、到达次序独立不保证顺序、一经处理无法重放除非特意保存的无序时间序列的数据的在线计算。 因此,在实时计算中,会遇到数据乱序、数据延时、事件时间与处理时间不一致等问题。爱奇艺的峰值事件数达到1100万/秒,在正确性、容错、性能、延迟、吞吐量、扩展性等方面均遇到不小的挑战。 爱奇艺从2013年开始小规模使用storm,部署了3个独立集群。在2015年,开始引入Spark Streaming,部署在YARN上。在2016年,将Spark Streaming平台化,构建流计算平台,降低用户使用成本,之后流计算开始在爱奇艺大规模使用。在2017年,因为Spark Streaming的先天缺陷,引入Flink,部署在独立集群和YARN上。在2018年,构建Streaming SQL与实时分析平台,进一步降低用户使用门槛。 2. 从Spark Streaming到Apache Flink 爱奇艺主要使用的是Spark Streaming和Flink来进行流式计算。Spark Streaming的实现非常简单,通过微批次将实时数据拆成一个个批处理任务,通过批处理的方式完成各个子Batch。Spark Streaming的API也非常简单灵活,既可以用DStream的java/scala API,也可以使用SQL定义处理逻辑。但Spark Streaming受限于微批次处理模型,业务方需要完成一个真正意义上的实时计算会非常困难,比如基于数据事件时间、数据晚到后的处理,都得用户进行大量编程实现。爱奇艺这边大量使用Spark Streaming的场景往往都在于实时数据的采集落盘。 Apache Flink框架的实时计算模型是基于Dataflow Model实现的,完全支持Dataflow Model的四个问题:What,支持定义DAG图;Where:定义各类窗口(固定窗口、滑动窗口和Session窗口);When:支持灵活定义计算触发时间;How:支持丰富的Function定义数据更新模式。和Spark Streaming一样,Flink支持分层API,支持DataStream API,Process Function,SQL。Flink最大特点在于其实时计算的正确性保证:Exactly once,原生支持事件时间,支持延时数据处理。由于Flink本身基于原生数据流计算,可以达到毫秒级低延时。 在爱奇艺实测下来,相比Spark Streaming,Apache Flink在相近的吞吐量上,有更低的延时,更好的实时计算表述能力,原生实时事件时间、延时数据处理等。 二、在爱奇艺使用Flink的一些案例下面通过三个Use Case来介绍一下,爱奇艺具体是怎么使用Flink的,包括海量数据实时ETL,实时风控,分布式调用链分析。 1. 海量数据实时ETL 在爱奇艺这边所有用户在端上的任何行为都会发一条日志到nginx服务器上,总量超过千万QPS。对于具体某个业务来说,他们后续做实时分析,只希望访问到业务自身的数据,于是这中间就涉及一个数据拆分的工作。 在引入Flink之前,最早的数据拆分逻辑是这样子的,在Ngnix机器上通过“tail -f /xxx/ngnix.log | grep "xxx"”的方式,配置了无数条这样的规则,将这些不同的数据按照不同的规则,打到不同的业务kafka中。但这样的规则随着业务线的规模的扩大,这个tail进程越来越多,逐渐遇到了服务器性能瓶颈。 于是,我们就有了这样一个设想,希望通过实时流计算将数据拆分到各个业务kafka。具体来说,就是Nginx上的全量数据,全量采集到一级Kafka,通过实时ETL程序,按需将数据采集到各个业务Kafka中。当时,爱奇艺主的实时流计算基本均是基于Spark Streaming的,但考虑到Spark Streaming延迟相对来说比较高,爱奇艺从这个case展开开始推进Apache Flink的应用。 海量数据实时ETL的具体实现,主要有以下几个步骤: 解码:各个端的投递日志格式不统一,需要首先将各个端的日志按照各种解码方式解析成规范化的格式,这边选用的是JSON风控:实时拆分这边的数据都会过一下风控的规则,过滤掉很大一部分刷量日志。由于量级太高,如果将每条日志都过一下风控规则,延时会非常大。这边做了几个优化,首先,将用户数据通过DeviceID拆分,不同的DeviceID拆分到不同的task manager上,每个task manager用本地内存做一级缓存,将redis和flink部署在一起,用本地redis做二级缓存。最终的效果是,每秒redis访问降到了平均4k,实时拆分的P99延时小于500ms。拆分:按照各个业务进行拆分采样、再过滤:根据每个业务的拆分过程中根据用户的需求不同,有采样、再过滤等过程 2. 实时风控 防机器撞库盗号攻击是安全风控的一个常见需求,主要需求集中于事中和事后。在事中,进行超高频异常检测分析,过滤用户异常行为;在事后,生成IP和设备ID的黑名单,供各业务实时分析时进行防刷使用。 以下是两个使用Flink特性的案例: CEP:因为很多黑产用户是有固定的一些套路,比如刚注册的用户可能在短时间内会进行一两项操作,我们通过CEP模式匹配,过滤掉那些有固定套路的黑产行为多窗口聚合:风控这边会有一些需求,它需要在不同的一些时间窗口,有些时间窗口要求比较苛刻,可能是需要在一秒内或亚秒内去看一下某个用户有多少次访问,然后对他进行计数,计数的结果超过某些阈值就判断他是异常用户。通过Flink低延时且支持多窗口的特点,进行超高频的异常检测,比如对同一个用户在1秒内的请求进行计数,超过某个阈值的话就会被识别成黑产。3. 分布式追踪系统 ...

May 22, 2019 · 1 min · jiezi

我发誓这真的是最后一篇关于ECDH的文儿API安全加强篇四

首先是前段时间我在公众号里被人批(dui)评(gang)了,大概意思就是:你别老整那ECDH又是椭圆又是素数啥的,你就说这玩意实际项目中怎么用就完了,我们不想听那些,那些我们都懂都精通,而且你还太监了,你自己看看是不是太监了,ECDH写到上一篇明显还没完,结果到现在了还没下文,你自己说是不是太监了,你自己说。 其次是实际上本篇内容实际上和ECDH没有半毛钱关系,通篇都是DH(少了EC两个字母),不过在项目中实际应用的业务逻辑写法、道理都是一样晒儿的。你现在可以暂时认为DH就是ECDH的“ 少了两个字母版本 ”。用DH的最主要原因是啥呢,因为时间有限,我优先写了DH的常用语言库文件,目前可用,ECDH的一根毛都没有写,所以只能用DH演示。 最后是再次强调一遍,作为一篇正经的文章,我需要再次科普一下DH是啥意思。 很多都以为DH是Daemon Hunter(恶魔猎手)的简称,然而并不是。Daemon Hunter是真实名称叫做伊利丹,是个瞎子同时又是法玛丽奥(就是老鹿)的兄dei。他暗恋白虎(就是那种真的白虎)泰兰德,然而泰兰德却嫁给了老鹿,事情大概就是这么一回事。 在我们这里DH则是Diffie-Hellman的简称,二位大爷的照片我以前贴过,现在不得不再贴一遍: 上图告诉我们头发长短与职业无关,douyin上那些自以为get到程序员梗的短视频真的是LOWB到一塌糊涂。 在正式开始前之前,我还是要说明一下用DH的初衷是什么或者说这个东西是来解决什么问题的。接着上篇的故事(点击这里)说: 你老板说项目非常牛逼,数据要加密,用牛逼的加密算法你就用RSA非对称加密开发测试操作猛如虎然后,一上线:CPU炸了,成绩1-5然后你找老板审批升级服务器费用,老板给了你300块并让你放心花大胆花你首先把RSA下线了,然后偷偷换成了AES对称加密,CPU不炸了然后三百块偷偷放到了自己腰包里但是AES的对称密钥你写死到客户端,被逆向就完了;如果通过服务器下发,听起来更加扯淡想了想,你拿着三百块钱组了个局儿,你带着钱,我带着陈旭,老赵带着柱子,再加上大彪,正好六人局局上我向你透露出一种方案:将AES对称密钥通过非对称方式协商出来。DH这种神奇的算法可以让你服务器和客户端在不传输该对称密钥的情况下就可以通过心有灵犀地方式各自计算出一个对称密钥,而且可以一样,避免了该密钥在网络上流通,而且你可以随意更换,过期时间定为1分钟,可谓是狠毒至极!我们引入DH就是为了解决上面的问题。然而,DH或ECDH并不能解决中间人攻击问题,这个要搞明白了。 所以,在正式开始之前,我必须先安利我和东北大嫖客还有巨蛀以及阿尼特写的DH库,github链接是这个,下面我将利用这些DH库们进行demo演示。 https://github.com/ti-dh(明眼人已经看出来我是来骗star的)目前这个库提供了纯PHP、C实现的PHP扩展、Java版,列个表格吧: 先说下服务端和客户端进行协商地整体流程,非常非常简单: 整个协商流程中,只有第二步和第三步会发生数据交互。第二步是API下发p、g、server-num给客户端;第三步是客户端向API提交client-num数据;最后一步,对称加解密用的key就已经计算出来用于生产环境了。 下面我用世界上最好的语言演示一下如何使用这个鬼东西,客户端我们用什么演示呢?客户端也依然使用世界上最好的语言来演示。首先,你们把上面github里的库文件集成到你们API里,我这里集成完毕后代码如下: API demo code:<?phpclass DhController extends BaseController{ private $dh = null; // 将DH库初始化进来呀... public function init() { $this->dh = new Dh(); } // 这就是上图中的第二步:客户端访问这个API获取g p 和 server-num public function getdhbasedataAction() { $ret = $this->dh->getdhbasedata(); echo json_encode( $ret ); } // 这就是上图中的第三步:客户端通过这个api提交client-num参数 public function postdhclientdataAction() { if ( $this->getRequest()->isPost() ) { if ( empty( $_POST['client_number'] ) || !is_numeric( $_POST['client_number'] ) ) { exit( json_encode( array( 'code' => -1, 'message' => 'wrong parameters', ) ) ); } $ret = $this->dh->postdhclientdata( $_POST ); echo json_encode( array( 'key' => $ret, ) ); } }}Client demo code:<?phprequire __DIR__ . '/vendor/autoload.php';use \Curl\Curl;$curl = new Curl();// 初始化客户端数据,随机一个即可~$client_number = mt_rand( 100000, 999999 );// 1、第一步,获取服务器的p、g和server_number$ret = $curl->get( 'https://xxxx.ooo/dh/getdhbasedata' );$ret = json_decode( $ret, true );$p = $ret['p'];$g = $ret['g'];$server_number = $ret['server_number'];// 2、第二步,根据服务器获取到的数据计算出client-number$process_client_number = gmp_powm( $g, $client_number, $p );// 3、第三步,将计算过后的client-number发送给服务器// 那个demo里已经有完美的演示了,多看代码$ret = $curl->post( 'https://xxxx.ooo/dh/postdhclientdata', array( 'client_number' => gmp_strval( $process_client_number ),) );$ret = json_decode( $ret, true );// 4、第四步,根据server-number,client-number和p 计算出公共密钥K$key = gmp_powm( $server_number, $client_number, $p );echo PHP_EOL."DH非对称密钥产生交换:".PHP_EOL;echo 'client计算出的public key : '.$key.PHP_EOL;echo 'server计算出的public key : '.$ret['key'].PHP_EOL.PHP_EOL;客户端文件保存client.php,然后php client.php执行一下,结果你们感受一下: ...

May 15, 2019 · 1 min · jiezi

深度-API-设计最佳实践的思考

阿里妹导读:API 是模块或者子系统之间交互的接口定义。好的系统架构离不开好的 API 设计,而一个设计不够完善的 API 则注定会导致系统的后续发展和维护非常困难。接下来,阿里巴巴研究员谷朴将给出建议,什么样的 API 设计是好的设计?好的设计该如何做? 作者简介:张瓅玶 (谷朴),阿里巴巴研究员,负责阿里云容器平台集群管理团队。本科和博士毕业于清华大学。 前言API 设计面临的挑战千差万别,很难有处处适用的准则,所以在讨论原则和最佳实践时,无论这些原则和最佳实践是什么,一定有适应的场景和不适应的场景。因此我们在下文中不仅提出一些建议,也尽量去分析这些建议在什么场景下适用,这样我们也可以有针对性地采取例外的策略。 为什么去讨论这些问题? API 是软件系统的核心,而软件系统的复杂度 Complexity 是大规模软件系统能否成功最重要的因素。但复杂度 Complexity 并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多很多小的设计考量一点点叠加起来的(John Ousterhout老爷子说的Complexity is incremental【8】)。 成功的系统不是有一些特别闪光的地方,而是设计时点点滴滴的努力积累起来的。 范围本文偏重于一般性的API设计,并更适用于远程调用(RPC或者HTTP/RESTful的API),但是这里没有特别讨论RESTful API特有的一些问题。 另外,本文在讨论时,假定了客户端直接和远程服务端的API交互。在阿里,由于多种原因,通过客户端的 SDK 来间接访问远程服务的情况更多一些。这里并不讨论 SDK 带来的特殊问题,但是将 SDK 提供的方法看作远程 API 的代理,这里的讨论仍然适用。 API 设计准则:什么是好的 API在这一部分,我们试图总结一些好的 API 应该拥有的特性,或者说是设计的原则。这里我们试图总结更加基础性的原则。所谓基础性的原则,是那些如果我们很好地遵守了就可以让 API 在之后演进的过程中避免多数设计问题的原则。 提供清晰的思维模型 provides a good mental model 为什么这一点重要?因为 API 的设计本身最关键的难题并不是让客户端与服务端软件之间如何交互,而是设计者、维护者、API使用者这几个程序员群体之间在 API 生命周期内的互动。一个 API 如何被使用,以及API本身如何被维护,是依赖于维护者和使用者能够对该 API 有清晰的、一致的认识。这非常依赖于设计者提供了一个清晰易于理解的模型。这种状况实际上是不容易达到的。 就像下图所示,设计者心中有一个模型,而使用者看到和理解的模型可能是另一个模式,这个模式如果比较复杂的话,使用者使用的方式又可能与自己理解的不完全一致。 对于维护者来说,问题是类似的。 而好的 API 让维护者和使用者能够很容易理解到设计时要传达的模型。带来理解、调试、测试、代码扩展和系统维护性的提升 。 图片来源:https://medium.com/@copyconstruct/effective-mental-models-for-code-and-systems-7c55918f1b3e ...

May 9, 2019 · 4 min · jiezi

关于PHP加解密的青年抬高篇API安全加强篇二

为什么标题总是要带上“API安全”关键字呢?因为我想我乐意。 实际上这一篇和上一篇均可以看作是《关于PHP加解密的懒汉入门篇(API安全加强篇一)》》")的后续,只不过侧重点在于安全上。 如果说,你没有看上篇,你一定回去看,不然一定会断篇儿! 为了避免文章陷入过于抽象复杂的理论讲解,所以这次还得借助元首和东线的将领们以及“反派角色”朱可夫同志。 人民好演员列表:男一元首: 男二古德里安: 路人甲曼施坦因: 路人乙冯*博克 “反派”男一 上篇我们知道元首和古德里安翻脸了,然后两个人通过非对称加密技术diss彼此,朱可夫没有私钥只能在路边儿打酱油。 根据事实,我们知道古德里安又重返了东线战场。当初把人撸了下来,现在又得让人去东线救火,反正这脸我是拉不下来,但元首拉的下来。 回到上篇结果提到的问题,就是:对称加密的安全性要人命,非对称加密的性能非常要人命。用我党地话来说就是“不能多快好省”,不符合“可持续发展”,不满足“社会主义主流价值观”。 这篇主要就是说“多快好省”的绿色方案。 让古德里安回东线肯定得是秘密下令的,加密是肯定。但是这个地方一定要值得注意:那就是元首一定得是用古德里安同志的给他公钥进行加密,然后再发送出去,此时这个密文虽然在东线用飞机撒的满地都是,但是只有古德里安同志自己能用藏在自己裤裆里的私钥进行解密后才能得到明文,也就是说这事儿也只有古德里安和元首两个人知道了。 除此之外,还有两种情况,可能爱思考的青年已经考虑到了: 元首是不是可以利用自己的公钥对密文进行加密。但这种做法的最终结果就是这个密文只能用元首的私钥进行解密,但是元首的私钥在元首的裤裆里,别人是无法知道的。元首作为高智商罪犯,这种低级错误是不可能犯的。元首用自己的私钥对密文进行加密。这个时候就意味着只有持有元首公钥的东线将领们才可以解密这个密文,然而假如元首并不想让其他人知道他天才一般的部署,这种方式就显得有点儿2了。综上,这种情况下,最正确的方式就是元首利用古德里安的公钥对密文进行加密,然而再撒的满天飞,这会儿只有古德里安能用自己的私钥进行解密。此时,无论是自己家的曼施坦因、冯*博克,还是“反派”的朱可夫,都只能默默当路人甲。 在上述案例中(注意,客户端不要理解为狭义角度的手机客户端!) 元首充当API服务器的角色。古德里安充当客户端的角色。曼施坦因、冯*博克充当路人甲客户端角色。朱可夫充当中间劫持者的角色。我们回归到现实中,也就是真正搬砖撸代码的现实中。这个时候,如果要对服务器和客户端传输的数据进行非对称加密,那么就得有如下条件: 客户端有自己一对公钥私钥,客户端持有服务器的公钥服务器有自己一对公钥私钥,服务器持有客户端的公钥那么问题来了,服务器只有少数一台,客户端成千上万。这会儿摆在搬砖侠们面前的只有两个选择: 客户端的公钥和私钥共用一对,这样服务器只要一个公钥就算是拥有了所有客户端的公钥客户端的公钥和私钥都是特立独行的,是颜色不一样的烟火。这会儿服务器就苦逼了,必须维护一坨彼此不同的客户端,同时还要建立和不同客户端的对应关系那么,好了,下面让各位搬砖侠们吃口屎保持一下冷静,我们看看支付宝是怎么做的。当你的系统接入支付宝的时候,支付宝会要求你生成一对你的公私钥,然后私钥你自己藏好了,公钥上传到支付宝(这个过程相当于支付宝有了你的公钥),然后再你上传完你的公钥后,支付宝会返回给你支付宝的公钥。其中当你使用RSA普通版本的时候,所有商户得到的支付宝公钥都是同一个,当你使用RSA2的时候,每个商户收到的支付宝公钥都是不尽相同的。 所以说,怎么做都行,一切都看你选择。 说起支付宝,你们接入的时候都一定看到有个叫做签名验证的功能,我认为这个很重要,是必须值得一提的一件事情。回到元首这里来,我们说元首给古德里安发消息“滚到东线,去库尔斯克棱角部”,正确的做法应该是用古德里安的公钥进行加密,此时该消息只能被古德里安的私钥解密,其他人都只能干瞪眼。如果元首抽风了,用自己的私钥加密了密文,这会儿会是什么情况咧?那就是持有元首公钥的人都可以看到“滚到东线,去库尔斯克棱角部”这条机密消息了,很多人都会发朋友圈或者私聊类似于“听说古德里安要回来了”。其实,用自己私钥解密,然后利用自己公钥解密是一个二逼的行为,但是,这个过程可以用来验签是没有任何问题的。什么是验签? 假如有一天,希姆莱想提前篡位,冒充元首给古德里安发号施令了。此时,古德里安只需要用元首的公钥验证一下命令的签名,一验返回了false,那就说明这坨命令不是来自于元首,这种数据就应该直接扔掉即可! 所以,上面叨逼叨叨逼叨这么久,为的就是得出一个结论,你们理(bei)解(song)一下: 公钥加密,私钥解密私钥加密,公钥验签然后我们再往前追溯一下,我们的为什么要用非对称加密?是为了防止对称加密措施密钥的泄漏,而非对称加密不存在密钥泄漏的情况。 但是,非对称加解密的性能以及部署使用方式,非土豪所能及也!那么,有没有办法既能得到鱼,又能得到熊掌咧? 最近开了一个微信公众号:高性能API社区,所有文章都先发这里

May 9, 2019 · 1 min · jiezi

Jenkins API 使用

Jenkins 是一款流行的开源持续集成工具,可以用来做一些软件开发的自动化工作,如打包,测试,自动部署等。 Jenkins 中有 view 和 job 的概念, view 相当于组, job 则是具体的任务。view 下面可以创建 job ,但 job 可以不在任何 view 下。 这里主要介绍 Jenkins 提供的 HTTP API ,至于如何使用 Jenkins 请参看 Jenkins User Documentation。 API鉴权Jenkins 使用 Baisc Auth 的权限验证方式,需要传入 username 和 api token 。其中 api token 需要在用户的设置界面去创建。 但在 Job 的远程触发中,可以设置用于远程触发的 token (在 Job 的配置页面设置),这样在触发 Job 时就不需要传入 Basic Auth 了。远程触发的 token 使用 urlencode 的方式放在请求的 body 中,其原始数据为: token=<Token Value> 下面给出两种方式触发 Job 的例子: ...

April 21, 2019 · 3 min · jiezi

如何通过Python创建Twitter应用程序和API接口

来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:9min本文说明了如何运用Python API使用Twitter库连接到Twitter帐户。具体来说,此API允许用户提取与特定Twitter帐户相关的大量数据,以及通过 Python 管理 Twitter 的帖子(例如一次发布多个推文)。 即使你是 Python 的初学者, 使用 Twitter Python 依赖包在分析方面也非常有用。例如,虽然Web开发人员可能更倾向于使用PHP等语言来连接API,但Python可以更灵活地分析数据的趋势和统计数据。因此,数据科学家和其他分析师会发现Python更适合这个目的。我们将从Python连接到Twitter API的一些基本步骤开始,然后查看如何流式传输所需的数据。需要注意的是,虽然Twitter库(以及其他Python库,如Tweepy和Twython)可以使用数据执行大量不同的任务,但我们将专注于本文中的一些更基本(和有用)的查询,解决以下问题:使用适当的凭据将Python连接到Twitter API下载与特定帐户关联的推文下载帐户的所有关注和关注用户的列表一次发布多条推文在Twitter上自定义搜索特定术语的实例。1.将Python连接到Twitter API本教程使用iPython作为Python接口连接到Twitter。为了连接到API,我们需要获取Consumer Key,Consumer Secret和Access Token Secret。要获得这些,您需要在apps.twitter.com上登录您的帐户。到那里后,系统会提示您创建一个应用程序:创建应用程序后,您将在Keys and Access Tokens部分下找到相关的密钥和令牌。首先,我们在终端中安装python-twitter库,如下所示:pip install python twitter完成后,我们导入Twitter库并输入凭据,如下所示:import twitterapi = twitter.Api(consumer_key=‘your_consumer_key’, consumer_secret=‘your_consumer_secret’, access_token_key=‘your_access_token_key’, access_token_secret=‘your_access_token_secret’)print(api.VerifyCredentials())输入正确的凭证后,与API的连接即告完成,我们现在可以通过Python平台控制我们的Twitter帐户!2.下载用户时间线现在我们已经将Python连接到Twitter API,我们可以继续开始远程使用不同的Twitter功能。例如,如果我们希望下载推文的用户时间线,我们使用如下方法(并指定相应帐户的屏幕名称),然后使用该功能显示结果:statuses = api.GetUserTimeline(screen_name=‘Michael Grogan’)print([s.text for s in statuses])一旦我们输入了上述内容,我们就会在Python界面中看到相应的时间轴:3.下载以下和以下联系人Twitter库还使我们能够下载特定用户正在关注的帐户列表,以及作为该特定用户的关注者的帐户。为此,我们使用前者,后者使用:users = api.GetFriends()print([u.name for u in users])followers = api.GetFollowers()print([f.name for f in followers])请注意,我们还可以设置我们希望获取的用户数的上限。例如,如果我们希望为任何特定帐户获取100个关注者,我们可以通过向total_count函数添加变量来实现,如下所示:followers = api.GetFollowers(total_count=100)print([f.name for f in followers])4.发布多个推文使用Twitter API的一个巧妙之处是能够一次发布多条推文。例如,我们可以使用该命令同时发布以下两条推文(同样,使用该功能进行确认)。一旦我们转到相关的Twitter帐户,我们就会看到这两条推文都已发布:status = api.PostUpdate(‘How to calculate the Variance Inflation Factor in R: http://www.michaeljgrogan.com/ordinary-least-squares-an-analysis-of-stock-returns/ #rstats #datascience #programming’)print(status.text)status = api.PostUpdate(’#BigData Scientists Earn 10X to 15X More Money Compared to Engineers, CAs http://bit.ly/1NoAgto #datascience’)print(status.text)5.搜索推文Twitter库中包含的getsearch()函数是一个特别强大的工具。此功能允许我们在Twitter上搜索特定术语。请注意,这适用于已输入特定术语的所有用户,而不仅仅是我们在Python中提供凭据的帐户。例如,让我们在Python中搜索术语“bigdata”。我们设置的参数是自2016年11月21日起包含该术语的推文,我们选择限制流式传输的推文数量为10:api.GetSearch(term=‘bigdata’, since=2016-11-21, count=10)请注意,我们可以通过各种方式自定义GetSearch()函数,具体取决于我们希望如何提取数据。例如,如果没有指定日期,这将花费更长的时间来流式传输,我们也可以选择在2016年11月21日之前收集包含术语“bigdata”的推文,如下所示:api.GetSearch(term=‘bigdata’, until=2016-11-21, count=10)值得注意的是,此函数在我们在until变量下指定的日期之前下载最多7天的数据。此外,我们不仅限于仅通过术语搜索GetSearch。例如,假设我们希望通过地理位置搜索推文 - 特别是自11月18日以来在纽约时代广场1英里范围内发送的推文(请注意,距离可以使用mi或km分别以英里或公里格式化):api.GetSearch(geocode=“40.758896,-73.985130,1mi”, since=2016-11-18)运行该函数后,我们看到Python返回以下推文(当然,还有什么更好的地方可以找到Donald Trump!):GetSearch()如何使用这些数据?如前所述,Python对流式社交网络数据极具吸引力的一个特殊原因是能够对我们收集的信息进行深入的数据分析。例如,我们已经看到了如何使用位置搜索推文GetSearch。随着机器学习在分析社交媒体趋势的数据科学家中风靡一时,在这一领域变得非常流行的一种特殊技术是网络分析。这种技术实际上可以显示分散的数据(或节点)以形成紧密的网络,通常某些节点被证明是一个焦点。例如,假设我们要分析全球十个不同地点的1000条最受欢迎的推文。在随机的某一天,尽管我们看到网络中不同推文之间存在一些相关性,但我们可能仍会发现伦敦推文上的主题标签与纽约推文的主题标签差别很大。然而,在美国大选之夜或英国退欧这样的重大世界事件中,当Twitter对这一特定主题发展趋势时,发现网络往往更加紧密,因此,在这种情况下,情感分析的机会更多。一个场景,例如,很明显谁将赢得总统职位,或英国投票退出欧盟。人们通常会看到网络以不同的方式聚集,这取决于趋势推文,因为可以获得更多的实时信息。这只是Python的优势之一。虽然使用API连接到Twitter(可以在许多编程语言中完成)是一回事,但是能够使用分析以有意义的方式对数据进行排序是另一回事。可以通过Python使用机器学习技术来分析来自社交网络的流数据并从该数据进行有意义的预测。结论模块文档提供了可用于Python下载,过滤和操作数据的不同功能的非常详细的描述。最后,虽然我们还研究了使用API直接发布到Twitter的方法,但上述技术在分析趋势时尤其有用,例如标签流行度,按位置搜索术语的频率等等。在这方面,通过Python与Twitter交互对于那些希望对收集的信息实施数据分析技术的人特别有用。当然,与Twitter的API交互可以使用多种语言完成,具体取决于您的最终目标。如果目标是Web开发或设计,那么PHP或Ruby可能是您最好的选择。但是,如果您的目标是使用从Twitter获得的数据进行有意义的分析,那么Python就是不二之选。 ...

April 15, 2019 · 1 min · jiezi

使用Express开发小说API接口服务1.0(三)

使用Express开发小说API接口服务1.0(三)线上访问地址https://api.langpz.com/之前发现追书神器API详情页竟然没有下一章和上一章的返回值,只能自己动手封装一下。app.js 增加错误处理// catch 404 and forward to error handlerapp.use(function (req, res, next) { const err = new Error(‘Not Found’); err.status = 404; next(err);});// error handlerapp.use(function (err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get(’env’) === ‘development’ ? err : {}; // render the error page res.status(err.status || 500); res.render(’error’);});这些代码放到module.exports = app; 上面就可以了。列表页增加返回ID找到routes/chapter.js 29行替换 res.send(JSON.stringify({ “flag”: 1,“id”: body._id, “chapters”: body.chapters, “msg”: “OK” }));详情页增加上一章和下一章的返回值let express = require(’express’);let request = require(‘request’);let common = require(’../common/common.json’); // 引用公共文件let router = express.Router();/** 获取小说文章内容 返回小说文章内容 param link {String} 是小说文章列表接口 chapters[0].link http://chapter2.zhuishushenqi.com/chapter/${link}*/router.get(’/’, function (req, res, next) { if (!req.query.link) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请传入link…” })); } // req.query.link 编码转义 let link = encodeURIComponent(req.query.link); request.get(${common.CHAPTER}/chapter/${link}, function (err, response, body) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } // 解析返回的数据 body = JSON.parse(body); if (body.ok){ // 再次请求列表页获取上一页和下一页 if(req.query.id){ // req.query.id 编码转义 let id = encodeURIComponent(req.query.id); let n = parseInt(req.query.n); if (isNaN(n)){ n = 0; } request.get(${common.API}/atoc/${id}?view=chapters, function (err, response, body2) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } if (body2 == “wrong param”){ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入错误的ID…” })); }else{ // 解析返回的数据 body2 = JSON.parse(body2); // 检查页码是否超过小说的章节数 if(n > body2.chapters.length - 1){ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入的页码过大” })); }else{ // 如果有上一页或者下一页就返回link否则返回false let prev,next; body2.chapters[n - 1] ? prev = body2.chapters[n - 1].link : prev = false; body2.chapters[n + 1] ? next = body2.chapters[n + 1].link : next = false; if (body2.chapters.length > 0) { res.send(JSON.stringify({ “flag”: 1,“id”: id, “chapter”: body.chapter, “prev”: prev,“next”: next, “msg”: “OK” })); } } } }); }else{ res.send(JSON.stringify({ “flag”: 1, “chapter”: body.chapter, “msg”: “OK” })); } }else{ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入link有错误” })); } });});module.exports = router;访问http://localhost:3000/article?link=http://www.69shu.com/txt/1463…新增n和id参数。n 代表是第几页。id 是书籍ID。github仓库访问地址https://github.com/lanpangzhi/novel-api ...

April 10, 2019 · 2 min · jiezi

以API驱动的开发流程:选择一个出色的API规范可以帮你节省时间和省去麻烦

在API开发的实践中有一项重要的过程就是规划API规范。API规范通常包含一组API(通常我们称之为服务)的定义,就像房子的蓝图一样。一份API规范中应该包含一个微服务应用提供了哪些API,这些API上承载的数据的定义。API规范可以帮助提前做好应用的结构设计,所以API规范的编写是开发前的关键步骤,它可以帮助你在编写代码之前降低设计缺陷或问题。API驱动的开发流程在以API驱动的开发过程中,开发者首先创建某一种机器和人都能识别,并且长期存在的API规范。遵循API规范,可以让你在开发API时提前捕获任何小故障或不一致之地方。虽然这个过程将使开发工作增加少量开销,但这项工作必不可少,因为它可以大大加速后面应用开发和测试维护的时间。预先规划好的API规范可以为你的开发节省数月甚至数年的时间,并且提高已开发好的API的复用性。否则,除了要在API设计上多花时间,有时候甚至必须从头构建一个新的API。 API驱动的开发流程鼓励将设计阶段与开发阶段分开,并迭代地进行处理。API的实现快速演进,但API规范保持稳定,可以确保API的使用者不会受到变化带来的影响。这也意味着使用标准化规范构建设计时,就可以通过模拟并获得用户反馈信息来测试该规范。API规范有很多类型,对于REST API,较为流行的API规范有Swagger, RAML等。对于SOAP,通常使用基于W3C标准的,XML格式的WSDL规范。 对于REST和SOAP区别感兴趣的读者可以参考我的另一篇文章比较REST和SOAP之间的差别。作为国内API管理领域的领导者,灵长科技开发的通用企业应用接口管理系统(CEAMS)为Node.js开发者提供了包括Web IDE在内的全套微服务应用开发以及API管理工具,以及相应的后台微服务框架:CDIF的支持。在CEAMS上,系统也为开发者提供了一份创新的内置API规范模型。这份API规范模型可以看作是一个JSON版本的WSDL规范,可以被用来描述在CEAMS系统上运行的云端微服务应用提供的JSON API接口,以及在这些API上传输的JSON数据的定义。在CEAMS系统上,系统将要求开发者从规划API开始,通过系统提供的JSON编辑器创建和编辑这份API规范:用户首先为自己的API起一个名字,同时为它自由地创建符合JSON Schema标准的,任意嵌套复杂度的API请求和返回的JSON数据结构定义。从这份API规范出发,系统将自动为开发者创建应用的框架JavaScript代码、API文档、API调用方的接入代码(目前支持Java)、以及在线的API测试工具等等。这些工具可以帮助开发者在很大程度上减少编写代码、文档和手动测试的精力,保持文档和代码永远一致,减轻对接和同步的工作量。同时,系统将根据开发者编写的API规范中提供的JSON schema数据定义,在运行时自动对输入的API请求数据做数据验证,并过滤掉不合法的请求,帮助提高微服务应用的数据安全性和稳定性。API设计背后的逻辑思路很简单:保持足够的灵活性和复用能力。这也就意味着当你构建API时要提前计划——不仅仅是规划项目路线图以缩短开发周期,而是为未来一两年可能存在的需求做出安排,好的API应该支持多种内容类型并保持灵活性开发者要保持一颗正确的心态——因为你将长期关注你所构建的API。选择一个出色的API规范可以节省时间以及省去开发过程中不必要的麻烦,同时,选择一个全面专业的开发平台,同样也会提升开发运维效率。灵长科技自主开发的智能连接和数据集成平台CEAMS,是为Node.js技术生态中的API和微服务应用开发者量身定做的微服务应用开发和以及API运维管理系统。将系统连接、数据集成、业务逻辑全部通过松耦合集成于一体的开发平台。系统的目标客户主要是系统和数据集成开发者。开发者利用CEAMS系统,可以通过统一的规范模式,快速地与各类IT系统,数据库,云计算服务和智能设备高效对接,平台不仅帮助开发者简化了许多与底层设备对接的复杂操作,并基于提供大量的自动化工具。CEAMS系统已在国内包括长沙警务云等多个IT项目中得到成功应用,并在单个项目中成功支持数十位开发者同时在线开发和管理自身的后台微服务应用。目前,CEAMS系统已开放免费下载使用,感兴趣的用户可以和我们联系或加入灵长科技技术支持QQ群:618450152 获取关于产品的进一步信息。

April 5, 2019 · 1 min · jiezi

构建API的最佳编程语言是什么?

构建API的最佳编程语言是什么?来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:4min你是否正在设计第一个Web应用程序?也许你过去已经建立了一些,但是目前也正在寻找语言的变化以提高你的技能,或尝试新的东西。有了所有信息,就很难决定为下一个产品或项目选择哪种编程语言。因为任何编程语言最终都可以用于编写API,所以有些编程语言比其他编程语言更好,更有效。今天我们将讨论在选择编程语言以构建适用于你的Web应用程序的 API时应该考虑的因素。在编程语言方面,舒适性至关重要这适用于任何具有某种语言经验的开发人员。如果你已经掌握了某种语言的经验,那么你最终将能够更轻松地开发,理解所涉及的概念,并且能够立即取得更多进展。这也转化为改进的代码和性能,因为你可以花更多的时间在上面而不是学习一种全新的编程语言。例如,如果我已经在Python中开发了几年,但我可以选择使用PHP或 Python作为项目的编程语言,我只选择 Python,因为已经花了很多时间学习Python。这非常重要,因为在尝试执行新操作时,你希望限制项目中未知的数量。这将有助于学习并帮助你取得更好的成果。如果你是一位零编程经验的全新开发人员,则以下部分可帮助你缩小搜索范围。支持开发API的库和框架在消除潜在的编程语言以构建API的过程中要问的下一个问题是:该语言是否为有助于开发API的库或框架提供了大量不同的选项?继续上一节中的Python示例, Django REST框架是专门构建在Django之上的。 Django是一个用于Python 的 Web开发框架,可以更快,更轻松地在编程语言中创建API。这些库和框架允许通过包含处理构建API中的大量重复工作的函数和对象来加速开发过程。一旦你花了一些时间研究可用于语言的库和框架的可用内容,那么现在是时候检查社区的活跃程度了。支持和社区在这个过程中要问自己的下一个问题是:这个编程语言的框架和库是否仍然受支持?如果支持,开发者社区的活跃程度如何?他们是否对其软件和功能进行了持续或定期更新?更新是否有助于提高安全性和可用性?鉴于没有多少人使用该语言,将来也没有更新错误修复,你可能不想继续使用它。另一件需要注意的是用户社区。有足够的资源供你学习吗?文档的清晰度和可用性如何?是否有经验丰富的开发人员有关于必要主题的博客文章需要学习?Stack Overflow是否有问题和答案?是否有任何硬资源如杂志或教科书向你展示如何使用这些语言和框架?构建API的潜在语言根据我的经验,有许多更好的编程语言。这是一些这些语言的示例框架,你可以使用它来开始开发下一个API:LanguageFrameworkJavaSpringJavaScript(Node)ExpressPythonDjangoPHPLaravelRubyRuby on Rails综上,你选择的编程语言取决于几个因素:你使用该语言时的体验;可用于API构建的框架以及支持和社区的活跃程度。不要害怕尝试新事物!你可以随时学习,但如果担心速度和开发的简易性,请使用这些标准来帮助选择使用语言。愿码·全思维工程师社群全球招募愿码全思维工程师社群面向全球招募,如果你不甘平凡、如果你敢于突破自我、如果你希望有一份可观的睡后收入,Come on,关注公众号回复愿码两个字申请加入社群。

April 4, 2019 · 1 min · jiezi

php 使用Curl传递json资料给对方及显示对方回传的json(Json格式/ API串接/ HttpRequest)

本教学使用环境介绍伺服器端:Ubuntu 18.04 LTS资料库:Mariadb 10.1.34(Mysql)语言版本:php 7.3本机端:MacOS High Sierrafunction httpRequest($api, $data_string) { $ch = curl_init($api); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, “POST”); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array( ‘Content-Type: application/json’, ‘Content-Length: ’ . strlen($data_string)) ); $result = curl_exec($ch); curl_close($ch); return json_decode($result);}将以下资料变成json格式传输传给对方接应的 <https-api-url>$data = array( “id” => $id, “field” => $field);$data = httpRequest(’<https-api-url>’, json_encode($data));要印出对方回的 json key and value 内容时echo $data->{‘message’};如果对方回的是json array,使用foreach接应即可就能够印出回圈,对方回传多少笔就印多少笔foreach ($data as $value) { echo $value[‘message’];}可以使用sizeof查看object的长度,轻松做判断echo sizeof($data); // int如果对方回的不是json只是直接传 body 过来将上面的function中的return json_decode($result);改为return $result;然后直接印出即可echo $data;Line ID:ianmacQQ:1258554508 ...

April 2, 2019 · 1 min · jiezi

GraphQL 和 Apollo 为什么能帮助你更快地完成开发需求?

在大前端应用的开发过程中,如何管理好数据是一件很有挑战的事情。后端工程师需要聚合来自多个数据源的数据,再分发到大前端的各个端中,而大前端工程师需要在实现用户体验好的视图 (optimistic UI1) 的基础上,需要考虑如何管理端上的状态。在团队中使用 GraphQL 能够很好的解决数据管理的痛点。本文接下来会介绍 GraphQL 声明式(declarative)获取数据的方法,这将简化数据管理的难度,并且提升网络性能。还会介绍 Apollo2 如何通过一系列对开发者体验好的工具,提高工程师的开发效率。译注1:optimistic UI 是一种 UI 设计模式。例如,你在微信上发送消息会直接显示,而不用等到消息的网络请求成功或失败后再显示。optimistic UI 的数据管理很复杂,需要先显示模拟数据,再等待网络请求成功或失败后,再显示真正的数据。通过 Apollo 可以轻易地实现 optimistic UI。译注2:Apollo 是实现,GraphQL 是标准,和 JS/ECMA 的关系一样。开发者体验Apollo 可以帮助团队更快地实现功能上线,因为它对开发者的体验非常好。Apollo 目标是"让各端的数据管理变得简单"(simplify data management across the stack)。通过 Apollo Client、Apollo Server 和 Apollo Engine,以前需要花上一些功夫实现的功能,比如全栈缓存、数据规范化、optimistic UI,现在变得很简单。请求你所要的数据GraphQL 强类型查询语言的特性,使得开发者可以利用牛逼的工具来请求 GraphQL 接口。借助 GraphQL 内省系统(introspection system),开发者可以查询 GraphQL schema 3信息,包括字段和类型。内省系统拥有一些非常炫酷的功能,比如自动生成的文档、代码自动补全等等。译注3:schema 用于描述你所要数据的结构和字段,如: { dogs { id breed image { url } activities { name } } }GraphQL PlaygroundPrisma 团队开发的 GraphQL Playground 工具是一款非常优秀的 IDE,它可以把自定义的 schema 和查询历史自动地生成文档。只要看一下,你就知道 GraphQL API 中有哪些能获取到的数据,而不用研究后端代码或了解数据来源。Apollo Server 2.0 内置了 GraphQL Playground,更方便你浏览 schema 和执行查询命令。Apollo DevToolsApollo DevTools 是 Chrome 的扩展程序,可以查询 Apollo 的前端缓存(Cache),记录查询(Queries)和变更(Mutations)。你还可以使用 Apollo DevTools 中的 GraphiQL 来方便地测试前端查询。简化前端代码如果你使用过 REST 和状态管理库,如 Redux,为了发一个网络请求,你需要写 action creators、reducers、mapStateToProps 并集成中间件。使用 Apollo Client,你再也不用关系这些东西。Apollo Client 解决了一切,你只需要专注于查询,而不需要写一堆状态管理的代码。import ApolloClient from “apollo-boost”;const client = new ApolloClient({ uri: “https://dog-graphql-api.glitch.me/graphql"});有团队声称他们切换成 Apollo Client 后,删除了上千行状态管理代码和一堆复杂逻辑。这得益于 Apollo Client 不仅支持远程数据管理,还支持本地数据管理, Apollo 缓存就是当前应用全局状态的单一事实来源。现代化的工具Apollo platform4 可以让团队使用现代化的工具,帮忙他们快速发现错误、查看 API、开发具有挑战的缓存功能。译注4:Apollo platform 是云平台。Apollo 在本文中有两层含义,首先 Apollo 是 GraphQL 的一个开源实现,其次 Apollo 是开发 Apollo platform 、Apollo Client 、Apollo Server 等产品的公司。Apollo Engine 是 GraphQL 生态系统中唯一可以为你的 API 提供监控和分析的工具。Engine 可以显示每个 resolver5 的埋点指标,可以帮忙你定位错误, 可以分析 schema 中请求的每个字段的分布频率。 你还可以将这些数据传输到你正在用的其他分析平台,如 DataDog,并在某些数据超过报警阙值设置时进行报警。译注5:resolver 处理返回字段的函数声明式数据获取使用 GraphQL 的一个主要优点是它有声明式数据获取的能力,不需要前端请求多个接口,不需要手动的聚合数据,只需要你精确地描述你所要的数据,然后 GraphQL 就会将你要的数据返回给你。而使用 REST ,你需要调用每一个接口,并过滤出你要的数据,然后将过滤后的数据构造成组件所需要的结构。GET /api/dogs/breedsGET /api/dogs/imagesGET /api/dogs/activitiesREST 的方法不仅不好使,而且容易出错,难以跨平台重用逻辑。对比一下 GraphQL 声明式的方式:const GET_DOGS = gql query { dogs { id breed image { url } activities { name } } };在上面,我们定义了我们想要从服务端获取的对象的结构。GraphQL 负责组合和过滤数据,同时返回我们想要的数据字段和结构。如何使用 GraphQL 查询?Apollo Client 构建了 GraphQL 声明式请求数据的方法。在 React 应用中,获取数据、跟踪加载和错误状态以及更新 UI 的所有逻辑,都封装在一个 Query 组件中。这种封装方式使数据获取组件和展示组件很容易的组合在一起。让我们看看,如何在 React 应用中使用 Apollo Client 获取 GraphQL 数据:const Feed = () => ( {/* 数据获取组件 Query*/} <Query query={GET_DOGS}> {/* 展示组件:由 Error、Fetching、DogList 等组成的函数组件 /} {({ loading, error, data }) => { if (error) return <Error /> if (loading || !data) return <Fetching />; return <DogList dogs={data.dogs} /> }} </Query>);Apollo Client 管理整个请求的周期,从请求开始到请求结束,包括为你跟踪加载和错误状态。这里不用设置中间件,不用写模板代码,不用重构的数据结构,不用关心请求缓存。你所需要做的就是描述你组件要的数据,然后让 Apoolo Client 去完成那些繁重的工作。当你使用 Apollo Client 时,你会发现你能删除很多不需要的数据管理方面的代码。具体能够删除多少行代码,要根据你项目的情况来判断,但有些团队声称他们删除了数千行代码。要了解更多 Apollo Client 的牛逼功能,例如 optimistic UI、重新获取、分页获取,请查看我们的状态管理指南。提升网络性能在许多情况下,在现有的 REST 接口层之上增加 GraphQL API 层,可以提高你 App 的网络性能,特别是在网络差的情况下。虽然,你应该通过网络性能监控来衡量 GraphQL 如何影响你的 App,但大家通常认为 GraphQL 通过避免客户端与服务端的往返通讯(round trips),和减少请求数据的大小来提升网络性能的。更少的请求数据因为从服务端返回的响应中只包含你指定的查询数据,所以 GraphQL 相对于 REST 可以显著地减少请求数据的大小。让我们看看前面文章中的例子:const GET_DOGS = gql query { dogs { id breed image { url } activities { name } } };GraphQL 服务响应中只包括 dogs 对象的 id、breed、image、activities 字段,即便 REST 层的接口 dogs 是带有 100 个字段的对象也是如此,所有多余的字段都将在返回给客户端之前过滤掉。避免往返通讯(round trips)由于每个 GraphQL 请求只返回一个响应,使用 GraphQL 可以帮助你避免客户端到服务端的往返通讯。使用 REST,请求一个资源就是一次往返通讯,往返通讯会快速地增加。如果你请求要列表中的每一项,每一项都需要一次往返,每一项中的每个资源也需要一次往返,总次数就是二者的乘积6,这就导致了请求时间过长。 译注6:极端 REST 例子,列表长度 N,每一项 3 个资源,请求次数就是 3NGET /api/dogs/breedsGET /api/dogs/imagesGET /api/dogs/activities使用 GraphQL,每个查询代表一次往返通讯。如果你希望进一步的减少往返,你可以实现查询批处理(query batching),将多个查询封装到单个请求中。产品案例虽然 GraphQL 规范是由 Facebook 在 2015 年公布的,但是自 2012 年以来,GraphQL 就是 Facebook 移动应用开发的重要组成部分。在 Apollo 团队,我们发现 GraphQL 为我们现有方案中遇到的很多问题提供了出色的解决方案,现在我们用它来优化我们的技术基础设施。几年来,我们和开源社区、客户、合作伙伴一起,为开源项目 Apollo 带了了诸多创新。我们很骄傲,Apollo 适用于各类公司,从创业公司到大型企业。除了我们自己的经验,我们还收到了积极地在生产环境中使用 Apollo GraphQL 的企业客户的广泛反馈、贡献和支持。一些最值得借鉴的案例是:The New York Times(https://open.nytimes.com/the-…: 学习 The New York Times 如何从 Relay 切换到 Apollo,实现他们 App 的 SSR 和持久化 queries。Airbnb(https://medium.com/airbnb-eng…: Airbnb 在 Apollo 平台上下了重注,去强化他们微服务的数据层。Express(https://dev-blog.apollodata.c…:使用易上手的 Apollo 分页功能,优化我们团队的关键应用。Major League Soccer(https://dev-blog.apollodata.c…: 团队的数据管理工具从 Redux 切换到了 Apollo,帮忙他们删除了几乎所有的 Redux 代码。Expo(https://dev-blog.apollodata.c…: 使用 Apollo 开发 React Native App 使得团队可以专注于改善产品功能,而不是写数据抓取的逻辑。KLM(https://youtu.be/T2njjXHdKqw): 学习 KLM 团队如何使用 GraphQL 和 Apollo 扩展他们的 Angular app。 ...

March 29, 2019 · 2 min · jiezi

用一个通俗的例子讲清楚API

随着移动互联网的发展, 基于互联网的应用正变得越来越普及,在这个过程中,更多的平台将自身的资源开放给开发者来调用。对外提供的API 调用使得平台之间的内容关联性更强,同时这些开放的平台也为用户、开发者和中小网站带来了更大的价值。那么API究竟是何方神圣?首先我们在百度百科查询到的解释是:API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。听起来在理解上还是有些困惑,如何通俗理解什么是API?当我们在智能移动端使用应用程序时,应用程序通过网络将数据发送到服务器,然后服务器会检索分析数据,执行特定的操作并将其重新发送回你的手机。最后应用程序解析完这些数据,并以“可读的方式”向你提供所需的信息,要完成这一系列过程,API就是关键。我们可以再举一个大家熟悉的例子来进一步解释API。想象这样一个场景,当你来到一家餐馆,拿起菜单进行点餐时,首先看到的时菜单上罗列的众多菜品信息,厨房将是你点餐“系统”的一部分,但其中的关键环节是如何将点菜的信息传达至厨房,并将食物送到餐桌上,这个时候服务员就起到作用了,服务员就是一个信使(API),他接收你的点菜信息,并告诉厨房(系统)该做什么,在这种情况下, 服务员以“食物”作为信息反馈。为了进一步加深大家对API的理解,我们再列一个实际的API示例,在线搜索航班的过程就像在餐馆点餐一样,其中也有很多种选择,包括城市,往返航班时间等。假设你正在预订航空公司网站上的航班,你可以选择往返出发的城市和时间、客舱等级以及其他服务。为了准确预订航班,你可以预览查询航空公司网站,访问他们的数据库,看一看在你出发的时间是否有空位,以及航行费用等信息。不过,如果你不想用使用航空公司的网站,还有没有其他可以直接访问航空信息的渠道呢?那么比如像Kayak、Expedia等在线旅行服务平台就成为获取航班信息的另一种渠道,因为这些平台整合了来自多个航空公司数据库的信息,你想要获取的信息将会更加全面,这其中的逻辑又是怎样的呢?其实在上面的示例中,旅游服务平台与航空公司网站的API是相联系的。API就是一个接口,与餐馆的服务员作用一样,另外,在线旅游服务平台可以要求从航空公司网站的数据库中调取航班信息,以便用户预订座位、行李选项等。API在获取航空公司网站对用户请求的反馈之后,就会将相关信息发送回在线旅游服务平台,最终在线旅游服务平台上显示的信息就是用户所需要的最新航班信息。不仅如此,API还提供了一层安全性。我们在使用手机的过程中,手机的数据在没有授权的情况下,是不会暴露给其他服务器的,同样道理,服务器也从来不会轻易将数据提供给你的手机。相反,每个系统都只与小的数据包相联系,并且只共享必要的数据——比如我们在线订购外卖,我们首先会告诉店家想吃什么,他们也会告诉我们用什么作为回报,完成这些,才能得到想要的外卖。当前,API的价值已经越来越重要,除了像谷歌、eBay、Salesforce.com、Amazon及Expedia这样的大公司是少数通过API赚钱。绝大多数公司的大部分业务收入来源于API,所以也就有了“API经济”,其中指的就是API市场。很不过多年以来,“API”通常描述的是应用程序的任何类型的通用连接接口。然而,随着技术的发展和市场的需求增大,现代API具有的一些特性,使其价值和实用性变得越来越高:•现代API遵循标准(通常是HTTP和REST),这些标准对开发人员友好,易于访问和广泛理解;•API被视为产品而不是代码。它们是为特定的受众(例如,移动开发人员)而设计的,它们被记录下来,并且以用户可以对其维护和生命周期有特定期望的方式进行版本控制;•因为它们更加标准化,所以在安全性和管理方面有更强的规范性,并对性能和规模进行监视和管理;•与其他任何产品化软件一样,现代API具有自己的软件开发生命周期(SDLC),包括设计、测试、构建、管理和版本控制。此外,现代API的使用和版本控制都有很好的文档。当前,越来越多的企业会选择利用 API,将公司生态系统中的优质合作伙伴汇聚在一起,并重新挖掘隐藏在背后的巨大经济价值,所以,“API经济”也已经成为各大企业数字化发展的必经之路,IDC曾在2018年的报告当中将API经济作为全球第二的技术趋势,显示在接下来的三年里,亚太地区(不含日本)45% 的组织将会采用核心云API战略,因此,以API为核心驱动的新经济亟待爆发。

March 28, 2019 · 1 min · jiezi

「重大更新」Autodesk Forge IFC 模型转换提取服务管线变更

当前 IFC文档通过 Forge 模型转换提服务 (Model Derivative API) 进行转换时是使用 Navisworks 进行 IFC 文档解析,同时一个更好的、基于 Revit 引擎的 IFC 文档解析也上线了。文档解析器的变更对于 IFC 转换数据格式、内容有重大影响 (例如,结构树结构、模型转向等),所以请更新即刻的您代码来应对这些变更。这个转换将会分成两个阶段:阶段一:自今日(美国时间 2019/03/26)开始的几个月内,IFC 文档的模型转换提取管线仍默认使用 Navisworks 进行 IFC 文档解析,但建议您开始依下面的样例使用 Revit 管线进行测试:curl -X ‘POST’ \ -H ‘Content-Type: application/json; charset=utf-8’ \ -H ‘Authorization: Bearer PtnrvrtSRpWwUi3407QhgvqdUVKL’ -v ‘https://developer.api.autodesk.com/modelderivative/v2/designdata/job' \ -d ‘{ “input”: { “urn”: “dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6bW9kZWxkZXJpdmF0aXZlL21vZGVsLmlmYw”, }, “output”: { “formats”: [ { “type”: “svf”, “views”: [ “3d” ], “advanced”: { “switchLoader”: true } } ] } }‘阶段二:在几个月之后,Forge 模型转换提取服务将默认使用 Revit 管线处理 IFC 文档的转换工作,所以我们强列建议您尽快开始使用 Revit 管线测试您的模型。注意1: 转换后的数据结构变更是可以预期的,所以请您尽快因应此变更来更新您的项目代码。注意2: 请密切关注Forge官方博客的公告,待 Revit IFC 管线的验证工作完成后,我们将发对此发布公告注意3:切换至阶段二后,旧有的 Navisworks 管线可以使用下面的方式进行调用:curl -X ‘POST’ \ -H ‘Content-Type: application/json; charset=utf-8’ \ -H ‘Authorization: Bearer PtnrvrtSRpWwUi3407QhgvqdUVKL’ -v ‘https://developer.api.autodesk.com/modelderivative/v2/designdata/job' \ -d ‘{ “input”: { “urn”: “dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6bW9kZWxkZXJpdmF0aXZlL21vZGVsLmlmYw”, }, “output”: { “formats”: [ { “type”: “svf”, “views”: [ “3d” ], “advanced”: { “switchLoader”: true } } ] } }‘如在测试时发现认何问题,请随时通过 forge.help@autodesk.com 反馈,谢谢! ...

March 27, 2019 · 1 min · jiezi

如何使用 eolinker 扫描 GitLab 代码注释自动生成 API 文档?

前言:一般写完代码之后,还要将各类参数注解写入API文档,方便后续进行对接和测试,这个过程通常都很麻烦,如果有工具可以读取代码注释直接生成API文档的话,那会十分方便。此前一直都是在使用eolinker的,但自从去年他们家“注释生成文档”的功能下线后,我就一直活在水深火热当中——真的不想写文档啊,真的好累啊。然而这两天上线后,突然发现这个功能重新上线了!必须给大家安利一波!官网地址:https://www.eolinker.com根据官方的解释,这个功能简单来说就是读取 gitlab(以前应该还能读本地代码) 的 php 代码(截至发文增加支持读取java,更方便了)注释生成 API 文档。下面是官方的操作介绍:1.先在EOLINKER新建项目,随后进入项目概况页,可以在概况页中找到“扫描代码注解生成文档”模块。2.在同步之前我们打开设置看下需要填写什么信息。总共是10个选项,我们来分别看下需要怎么填写:1.代码仓库类型,现在默认只有gitlab,在官方群问了他们的PM,后面应该还会支持github。2.代码仓库地址,gitlab有线上版本和用户自己搭建私有云版本,线上版本可以填写https://gitlab.com,如果是自己部署的gitlab写域名或者IP端口。3.项目ID,gitlab中新建项目后会有一个project ID,填入即可。4.访问私钥,通过gitlab的Access Tokens功能可获取,后面会详细介绍如何获取。5.需要扫描的分支,默认为master。我们也可以新建一个分支。6.需要扫描的API目录路径,建立一个目录作为API目录。7.需要扫描的数据结构目录路径,建立一个目录作为数据结构目录。8.目标语言,目前默认只有PHP,比较可惜只有一个语言,不过我跟他们客服聊天,说是后面更新的语言支持会增加java。9.注解格式,默认为Swagger 2.0,代码注释编写的格式可以按照下面的形式来写,或者参考官方文档http://zircote.com/swagger-ph…比如model的比如controller的10.数据同步方式,目前可选增量更新、全量更新、仅添加新的API三种形式。以上就是需要填写的全部信息。要正确填写这些信息,接下来我们就要转到gitlab进行设置。由于官方没有介绍过Gitlab,那还是由我先介绍下比较合适:gitLab 是一个用于仓库管理系统的开源项目,使用git作为代码管理工具,并在此基础上搭建起来的web服务。gitlab跟github有点类似,都是基于web的git仓库,关于注册gitlab新建账号如何操作的部分我就不多说了,但如果你已经有github账号的话,是可以用github账号登录gitLab的。1.首先要新建项目,这里我新建了一个名为demo code的project。2.新建后已经有一个master的分支,然后在分支下分别建立两个新的目录:我命名为controllers和models,一个作为API目录路径,一个作为数据结构目录路径。3.将写好的php代码上传至分别的目录。可以直接用命令行或者直接将文件上传。4.成功上传代码后,跟着就是获取密钥。在gitlab中,生成密钥需要用到Access Tokens功能。先进入设置页面,通过左边菜单中的Access Tokens功能,填写对应的项目名称,再根据需要,勾选开放的权限,看不懂也可以按照我下面的截图进行勾选,点击绿框后就可以获取个人的密钥了。如下图:5.进行到这一步,我们已经把所有的信息都拿到了,再回到EOLINKER将信息填入,请看下图,注意数据同步方式我选择的是增量更新。那我为什么会选择增量更新呢?而三种数据同步更新区别是什么呢?增量更新:判断已有API的详细信息,添加新的API信息。用注解的数据替换掉现有的数据。部分注解没有的数据,比如mock、参数值可能性、详细文档等等,均会保留。全量更新:在添加新的API的基础上,全量替换现有API内的信息,以注解的为准,不保留注解没有的数据。仅添加新的API:判断接口名称是否已经存在,不存在则插入。听起来很绕,我们来举个例子。Gitlab上的接口只有参数,而导入EOLINKER后会有mock、详细文档等数据。假如现在你的gitlab仓库有ABCD四个接口数据,在EOLINKER有A一个接口数据。下面举个例子介绍下三种数据同步更新的区别, GitLab中的接口只有参数,而导入 EOLINKER 后会有 mock、详细文档等数据。假如现在你的 GitLab 仓库有 ABCD 四个接口,在 EOLINKER 有 A 一个接口。采用“增量更新”后,EOLINKER 上将新增 BCD 三个接口;如果仓库A接口的数据有所更新,那么在保持原有本地A接口的 mock、详细文档数据的同时,本地亦将新增相应更新的数据;采用“全量更新”后,EOLINKER 上将有 ABCD 四个接口;此时本地A 接口所有数据都不保留,而会与仓库中A接口的数据保持一致;采用“仅添加新的 API”后,EOLINKER将以接口名称来判断是否需要添加新的API,因此EOLINKER上将新增 BCD 三个接口;即便 GitLab 上的参数已经改变,但本地原有的A接口数据不变;因此,无论是什么情况都推荐采用增量更新。不过即便你还是误操作了,EOLINKER都会自动生成API历史版本,方便我们回滚文档,操作失误也不怕了。1.根据官方的说明,在设置完成点击立即同步后,文档即会开始进行同步,而同步生成文档所需的时间,则根据代码注释的数据量来决定。2.API文档和对应的分组都被自动生成了,如下图。3.那我们就可以直接编辑修改文档了,实在是方便了很多。补充一句:按照他们的更新速度,目前也已经支持读取gitlab上java代码了,操作步骤跟读取php的步骤类似,这里就不展开说了,还不知道请回头再看一遍文章hhh。总结如果可以通过扫描代码注释自动生成API文档,写完代码注解后就不用再一条一条的写接口文档,现在又有一个理由可以不再使用swagger了。新增的这个功能可以减轻大部分不必要的工作量,虽然现在只能支持gitlab上的php代码和java代码,但后续肯定还会继续支持更多的平台和编程语言代码,持续使用起来将会更加方便和快捷,希望eolinker能够给我们带来更多的惊喜。官网地址:https://www.eolinker.com

March 26, 2019 · 1 min · jiezi

图像格式转化在人脸识别应用中的实践

ArcFace 2.0 API目前支持多种图像格式:BGR24、NV21、NV12、I420、YUYV(Android、IOS只支持其中的部分)。接下来将开始介绍这几种图像格式以及部分转换方式。一、相关图像颜色空间介绍1.RGB颜色空间RGB颜色空间以Red、Green、Blue三种基本色为基础,进行不同程度的叠加,产生丰富而广泛的颜色,所以俗称三基色模式。常见的RGB格式有:RGB_565、RGB_888、ARGB_8888、ARGB_4444等。2.YUV颜色空间在YUV颜色空间中,Y用来表示亮度,U和V用来表示色度。常见的YUV格式有以下几大类:planar: Y、U、V全部连续存储,如I420、YV12packed: Y、U、V交叉存储,如YUYVsemi-planar: Y连续存储,U、V交叉存储,如NV21、NV12二、相关图像格式介绍1.BGR24图像格式BGR24图像格式是一种采用24bpp(bit per pixel)的格式。每个颜色通道B、G、R各占8bpp。排列方式如:B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G R B G2.NV21图像格式NV21图像格式属于 YUV颜色空间中的YUV420SP格式,每四个Y分量共用一组U分量和V分量,Y连续排序,U与V交叉排序。排列方式如:Y Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YV U V U V U V UV U V U V U V U3.NV12图像格式NV12图像格式属于 YUV颜色空间中的YUV420SP格式,每四个Y分量共用一组U分量和V分量,Y连续排序,U与V交叉排序(NV12和NV21只是U与V的位置相反)。排列方式如:Y Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YU V U V U V U VU V U V U V U V4.I420图像格式I420图像格式属于 YUV颜色空间中的YUV420P格式,每四个Y分量共用一组U分量和V分量,Y、U、V各自连续排序。排列方式如:Y Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YU U U U U U U U V V V V V V V V 5.YV12图像格式YV12图像格式属于 YUV颜色空间中的YUV420P格式,每四个Y分量共用一组U分量和V分量,Y、U、V各自连续排序(YV12和I420只是U与V的位置相反)。排列方式如:Y Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YY Y Y Y Y Y Y YV V V V V V V V U U U U U U U U 6.YUYV图像格式YUYV图像格式属于 YUV颜色空间中的YUV422格式,每两个Y分量公用一组U分量和V分量,Y、U、V交叉排序。排列方式如:Y U Y V Y U Y V Y U Y V Y U Y VY U Y V Y U Y V Y U Y V Y U Y VY U Y V Y U Y V Y U Y V Y U Y VY U Y V Y U Y V Y U Y V Y U Y V三、图像格式转换由于图像的格式多种多样,转换的方法也不胜枚举,以下只列出部分的图像转换参考代码。1.从Bitmap中获取ARGB_8888图像格式数据(Android平台)Bitmap支持多种格式:ALPHA_8,RGB_565,ARGB_4444,ARGB_8888,RGBA_F16,HARDWARE。我们目前主要选择ARGB_8888进行格式转换。我们可使用Bitmap类中的public void getPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height)方法获取int[]类型的argb数据或public void copyPixelsToBuffer (Buffer dst)方法获取byte[]类型的ARGB_8888数据。2.ARGB_8888转换为NV21根据一个比较常见的rgb转yuv的算法:int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;即可编写ARGB转NV21的方法。int[]类型的ARGB_8888数据转换为NV21:private static byte[] argbToNv21(int[] argb, int width, int height) { int yIndex = 0; int uvIndex = width * height; int argbIndex = 0; byte[] nv21 = new byte[width * height * 3 / 2]; for (int j = 0; j < height; ++j) { for (int i = 0; i < width; ++i) { //对于int型color数据,格式为0xAARRGGBB,可进行与运算后移位取对应A R G B, //但是该YUV转换公式中不需要ALPHA,因此我们只需要取R G B 即可。 int r = (argb[argbIndex] & 0xFF0000) >> 16; int g = (argb[argbIndex] & 0x00FF00) >> 8; int b = argb[argbIndex] & 0x0000FF; //获取该像素点的R G B,并转换为Y U V,但byte范围是0x00~0xFF,因此在赋值时还需进行判断 int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16; int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128; int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128; nv21[yIndex++] = (byte) (y < 0 ? 0 : (y > 0xFF ? 0xFF : y)); if ((j & 1) == 0 && (argbIndex & 1) == 0 && uvIndex < nv21.length - 2) { nv21[uvIndex++] = (byte) (v < 0 ? 0 : (v > 0xFF ? 0xFF : v)); nv21[uvIndex++] = (byte) (u < 0 ? 0 : (u > 0xFF ? 0xFF : u)); } ++argbIndex; } } return nv21; }byte[]类型的ARGB_8888数据转换为NV21(原理同方法1): private static byte[] argbToNv21(byte[] argb, int width, int height) { int yIndex = 0; int uvIndex = width * height; int argbIndex = 0; byte[] nv21 = new byte[width * height * 3 / 2]; for (int j = 0; j < height; ++j) { for (int i = 0; i < width; ++i) { argbIndex++; int r = argb[argbIndex++]; int g = argb[argbIndex++]; int b = argb[argbIndex++]; /** * byte在强制转换为int时高位会自动以符号位扩充,如: * 0x80(byte类型,十六进制) -> 10000000(byte类型,二进制) -> 11111111_11111111_11111111_10000000(int类型,二进制) -> -128(int类型,十进制) * 0x7F(byte类型,十六进制) -> 01111111(byte类型,二进制) -> 00000000_00000000_00000000_01111111(int类型,二进制) -> 127(int类型,十进制) * 因此需要取低八位获取原byte的无符号值 */ r &= 0x000000FF; g &= 0x000000FF; b &= 0x000000FF; int y = ((66 * r + 129 * g + 25 * b + 128 >> 8) + 16); int u = ((-38 * r - 74 * g + 112 * b + 128 >> 8) + 128); int v = ((112 * r - 94 * g - 18 * b + 128 >> 8) + 128); nv21[yIndex++] = (byte) (y > 0xFF ? 0xFF : (y < 0 ? 0 : y)); if ((j & 1) == 0 && ((argbIndex >> 2) & 1) == 0 && uvIndex < nv21.length - 2) { nv21[uvIndex++] = (byte) (v > 0xFF ? 0xFF : (v < 0 ? 0 : v)); nv21[uvIndex++] = (byte) (u > 0xFF ? 0xFF : (u < 0 ? 0 : u)); } }3.ARGB_8888转换为BGR_24举个例子,对于4x2的图片,ARGB_8888格式内容为:A1 R1 G1 B1 A2 R2 G2 B2 A3 R3 G3 B3 A4 R4 G4 B4A5 R5 G5 B5 A6 R6 G6 B6 A7 R7 G7 B7 A8 R8 G8 B8那么若需要转化为BGR_24,内容将变成:B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4B5 G5 R5 B6 G6 R6 B7 G7 R7 B8 G8 R8BGR_24内容为3个byte一组,ARGB_8888内容为4个byte一组。因此,对于第一组ARGB_8888(A1 R1 G1 B1)和第一组BGR_24(B1 G1 R1),其对应关系为:bgr24[0] = argb8888[3];bgr24[1] = argb8888[2];bgr24[2] = argb8888[1];对应的转换代码: public static byte[] argb8888ToBgr24(byte[] argb8888) { if (argb8888 == null){ throw new IllegalArgumentException(“invalid image params!”); } int groupNum = argb8888.length / 4; byte[] bgr24 = new byte[groupNum * 3]; int bgr24Index = 0; int argb8888Index = 0; for (int i = 0; i < groupNum; i++) { bgr24[bgr24Index + 0] = argb8888[argb8888Index + 2]; bgr24[bgr24Index + 1] = argb8888[argb8888Index + 1]; bgr24[bgr24Index + 2] = argb8888[argb8888Index + 0]; bgr24Index += 3; argb8888Index += 4; } return bgr24; } 4.NV12和NV21的互换NV21和NV12只是U与V的数据位置不同,因此,NV21转换为NV12的代码同样适用于NV12转换为NV21。可参考如下代码:public static byte[] nv21ToNv12(byte[] nv21, int width, int height) { if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) { throw new IllegalArgumentException(“invalid image params!”); } final int ySize = width * height; int totalSize = width * height * 3 / 2; byte[] nv12 = new byte[nv21.length]; //复制Y System.arraycopy(nv21, 0, nv12, 0, ySize); //UV互换 for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) { nv12[uvIndex] = nv21[uvIndex + 1]; nv12[uvIndex + 1] = nv21[uvIndex]; } return nv12; }5.NV21转YV12NV21转化为YV12的过程主要是将其UV数据的交叉排序修改为连续排序。可参考如下代码:public static byte[] nv21ToYv12(byte[] nv21, int width, int height) { if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) { throw new IllegalArgumentException(“invalid image params!”); } final int ySize = width * height; int totalSize = width * height * 3 / 2; byte[] yv12 = new byte[nv21.length]; int yv12UIndex = ySize; int yv12VIndex = ySize * 5 / 4; //复制Y System.arraycopy(nv21, 0, yv12, 0, ySize); //复制UV for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) { yv12[yv12UIndex++] = nv21[uvIndex]; yv12[yv12VIndex++] = nv21[uvIndex + 1]; } return yv12; }6.YUYV转NV12在YUYV格式中,两个Y共用一组U和V,而NV12是四个Y公用一组U和V,因此,若需要将YUYV转化为NV12,需要舍弃一半的U和V。可参考如下代码: public static byte[] yuyvToNv12(byte[] yuyv, int width, int height) { if (yuyv == null || yuyv.length == 0) { throw new IllegalArgumentException(“invalid image params!”); } int ySize = yuyv.length / 2; byte[] nv12 = new byte[yuyv.length * 3 / 4]; int nv12YIndex = 0; int nv12UVIndex = ySize; boolean copyUV = false; int lineDataSize = width * 2; for (int i = 0, yuyvIndex = 0; i < height; i++, yuyvIndex += lineDataSize) { if (copyUV) { for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) { //复制Y nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset]; nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2]; //复制UV nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 1]; nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 3]; } } else { for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) { //复制Y nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset]; nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2]; } } copyUV = !copyUV; } return nv12; }7.I420和YV12的互换I420和YV12只是U与V的数据位置不同,因此,I420转换为YV12的代码同样适用于YV12转换为I420。可参考如下代码:public static byte[] i420ToYv12(byte[] i420) { if (i420 == null || i420.length == 0 || i420.length % 6 != 0) { throw new IllegalArgumentException(“invalid image params!”); } int ySize = i420.length * 2 / 3; int uvSize = i420.length / 6; byte[] yv12 = new byte[i420.length]; //复制Y System.arraycopy(i420, 0, yv12, 0, ySize); //UV互换 System.arraycopy(i420, ySize, yv12, ySize + uvSize, uvSize); System.arraycopy(i420, ySize + uvSize, yv12, ySize, uvSize); return yv12; } ...

March 12, 2019 · 7 min · jiezi

RESTful API 中的 Status code 是否要遵守规范

缘起事情是这样的,我在知乎受到邀请回答一个问题,主要是问 ID 找不到到底要不要用 Status 404 。我回答的还是比较早的,那时候只有一两个回答。我本来以为这是没啥争议的,在一个学术的地方讨论学术问题,当然是要遵守规范了,结果过了几个小时大跌眼镜。自造 code 党竟然支持率第一,还好平时见的也很多的全 200 党没有受到支持,不然真的吐血了。为什么要遵守规范一般那种说特殊情况特殊处理,不要拘泥于规范的人,大多都是自己没搞清楚某些知识,拿这句话当作偷懒的借口。其实一般做项目没那么多特殊情况。为了更好的适应各种库大部分完善的 HTTP 请求库,都会依照 RFC 的规范去设计错误处理的流程,虽然处理方式各有不同,但一定会在文档说明错误处理的部分的。使用 RFC 标准能最大限度的兼容各种 HTTP 客户端。你说现在你用的HTTP客户端不处理 Status Code,但是你没法保证将来不重构,重构的时候还是不处理。一般调用 api 使用 js 或者 python 的概率比较大,我们看看知名的库。在 js 里,最近比较流行的 axios 默认会把 200 系列外的 code 归到异常里。在 python 里,最流行的 http client 是 requests ,它更为详尽的预处理了 status code 。为了开发者更好上手另外在管理团队的方面,我们的原则是尽量的减少一个项目的“规范”,这样才能更容易去遵守。能用标准的地方,一定不要自己定一个更复杂的规则。无论是服务端的维护者还是 API 的消费者是会换人流动的,每个进入项目的人熟悉一大堆无谓的自定义项目规范都要成本。更简单的办法是参考大厂其实给项目定规范,最不靠谱的是自己拍脑袋,稍好一点的是去知乎或论坛问,更好一点的是去 google 搜,最简单的是直接去看大厂的产品或者规范啊。 API 本来就是个公开暴露的东西,还有比这更好找参考的吗?我们来看看:Google 遵守规范Github 遵守规范Microsoft 遵守规范 顺便说一句,微软的 API 规范真的很具有指导意义。Twitter 遵守规范阿里云 遵守规范腾讯云 不遵守规范 全部 200 事实上腾讯的技术比较混乱,每个项目都不一样。但最新要执行的统一规范是全部 返回 200 用返回值中的错误码表明错误。百度云 遵守规范我的建议很多人也许用着很简陋的 Web 框架,导致误以为返回了错误码,就不能返回 Response Body 了。其实你返回 204 外的任何 Status Code,最好都伴随着返回 Body 。在项目规范里,可以规定 Status Code 遵照 RFC 标准,或者选定一个集合出来,把一些不常用的去掉。然后如果不是200系列的代码,必须伴随着这样的一个错误结构:{ “error”: “UserNotFound”, “message”: “该用户没有找到” }这样错误分为了三层结构,第一层是 Status Code,使用者能大概知道是什么问题。第二层Error 是一个 Key 使用约定好的无空格的英文,给使用者做判断用,使用者可以根据 Key 自定义接下来的操作。第三层是 message ,有些 Key 使用者可以决定直接把 Message 显示个终端客户。如果是微服务项目,需要要求每个服务不管用什么语言,都要把错误统一成这个样子。如果开发者告诉你框架不支持,那这一定不是个好框架,改重构了。好的框架不仅能让你自定义错误内容,还能做到所谓的“框架自己出错的返回”也由你自定义。比如路由没有找到之类的。最后我实在不明白为什么一个最扯淡的答案,要自造一个 600 的 status code ,可以得票第一。知乎用户到底有没有一点独立的判断精神啊,只要装的一本正经,再摆出来一点资历,哪怕是胡说八道,大家也纷纷点赞。也许真的不适合在知乎去回答技术问题了。 ...

February 23, 2019 · 1 min · jiezi

宜人贷蜂巢API网关技术解密之Netty使用实践

宜人贷蜂巢团队,由Michael创立于2013年,通过使用互联网科技手段助力金融生态和谐健康发展。自成立起一直致力于多维度数据闭环平台建设。目前团队规模超过百人,涵盖征信、电商、金融、社交、五险一金和保险等用户授信数据的抓取解析业务,辅以先进的数据分析、挖掘和机器学习等技术对用户信用级别、欺诈风险进行预测评定,全面对外输出金融反欺诈、社交图谱、自动化模型定制等服务或产品。目前宜人贷蜂巢基于用户授权数据实时抓取解析技术,并结合顶尖大数据技术,快速迭代和自主的创新,已形成了强大而领先的聚合和输出能力。为了适应完成宜人贷蜂巢强大的服务输出能力,蜂巢设计开发了自己的API网关系统,集中实现了鉴权、加解密、路由、限流等功能,使各业务抓取团队关注其核心抓取和分析工作,而API网关系统更专注于安全、流量、路由等问题,从而更好的保障蜂巢服务系统的质量。今天带着大家解密API网关的Netty线程池技术实践细节。API网关作为宜人贷蜂巢数据开放平台的统一入口,所有的客户端及消费端通过统一的API来使用各类抓取服务。从面向对象设计的角度看,它与外观模式类似,包装各类不同的实现细节,对外表现出统一的调用形式。本文首先,简要地介绍API网关的项目框架,其次对比BIO和NIO的特点,再引入Netty作为项目的基础框架,然后介绍Netty线程池的原理,最后深入Netty线程池的初始化、ServerBootstrap的初始化与启动及channel与线程池的绑定过程,让读者了解Netty在承载高并发访问的设计路思。项目框架图1 - API网关项目框架图中描绘了API网关系统的处理流程,以及与服务注册发现、日志分析、报警系统、各类爬虫的关系。其中API网关系统接收请求,对请求进行编解码、鉴权、限流、加解密,再基于Eureka服务注册发现模块,将请求发送到有效的服务节点上;网关及抓取系统的日志,会被收集到elk平台中,做业务分析及报警处理。BIO vs NIOAPI网关承载数倍于爬虫的流量,提升服务器的并发处理能力、缩短系统的响应时间,通信模型的选择是至关重要的,是选择BIO,还是NIO?Streamvs Buffer & 阻塞 vs 非阻塞BIO是面向流的,io的读写,每次只能处理一个或者多个bytes,如果数据没有读写完成,线程将一直等待于此,而不能暂时跳过io或者等待io读写完成异步通知,线程滞留在io读写上,不能充分利用机器有限的线程资源,造成server的吞吐量较低,见图2。而NIO与此不同,面向Buffer,线程不需要滞留在io读写上,采用操作系统的epoll模式,在io数据准备好了,才由线程来处理,见图3。图2 – BIO 从流中读取数据图3 – NIO 从Buffer中读取数据SelectorsNIO的selector使一个线程可以监控多个channel的读写,多个channel注册到一个selector上,这个selector可以监测到各个channel的数据准备情况,从而使用有限的线程资源处理更多的连接,见图4。所以可以这样说,NIO极大的提升了服务器接受并发请求的能力,而服务器性能还是要取决于业务处理时间和业务线程池模型。图4 – NIO 单一线程管理多个连接而BIO采用的是request-per-thread模式,用一个线程负责接收TCP连接请求,并建立链路,然后将请求dispatch给负责业务逻辑处理的线程,见图5。一旦访问量过多,就会造成机器的线程资源紧张,造成请求延迟,甚至服务宕机。图5 – BIO 一连接一线程对比JDK NIO与诸多NIO框架后,鉴于Netty优雅的设计、易用的API、优越的性能、安全性支持、API网关使用Netty作为通信模型,实现了基础框架的搭建。Netty线程池考虑到API网关的高并发访问需求,线程池设计,见图6。图6 – API网关线程池设计Netty的线程池理念有点像ForkJoinPool,不是一个线程大池子并发等待一条任务队列,而是每条线程都有一个任务队列。而且Netty的线程,并不只是简单的阻塞地拉取任务,而是在每个循环中做三件事情:先SelectKeys()处理NIO的事件然后获取本线程的定时任务,放到本线程的任务队列里最后执行其他线程提交给本线程的任务每个循环里处理NIO事件与其他任务的时间消耗比例,还能通过ioRatio变量来控制,默认是各占50%。可见,Netty的线程根本没有阻塞等待任务的清闲日子,所以也不使用有锁的BlockingQueue来做任务队列了,而是使用无锁的MpscLinkedQueue(Mpsc 是Multiple Producer, Single Consumer的缩写)NioEventLoopGroup初始化下面分析下Netty线程池NioEventLoopGroup的设计与实现细节,NioEventLoopGroup的类层次关系见图7图7 –NioEvenrLoopGroup类层次关系其创建过程——方法调用,见下图图8 –NioEvenrLoopGroup创建调用关系NioEvenrLoopGroup的创建,具体执行过程是执行类MultithreadEventExecutorGroup的构造方法/** * Create a new instance. * * @param nThreads the number of threads that will be used by this instance. * @param executor the Executor to use, or {@code null} if the default should be used. * @param chooserFactory the {@link EventExecutorChooserFactory} to use. * @param args arguments which will passed to each {@link #newChild(Executor, Object…)} call */protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object… args) { if (nThreads <= 0) { throw new IllegalArgumentException(String.format(“nThreads: %d (expected: > 0)”, nThreads)); } if (executor == null) { executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); } children = new EventExecutor[nThreads]; for (int i = 0; i < nThreads; i ++) { boolean success = false; try { children[i] = newChild(executor, args); success = true; } catch (Exception e) { throw new IllegalStateException(“failed to create a child event loop”, e); } finally { if (!success) { for (int j = 0; j < i; j ++) { children[j].shutdownGracefully(); } for (int j = 0; j < i; j ++) { EventExecutor e = children[j]; try { while (!e.isTerminated()) { e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); } } catch (InterruptedException interrupted) { // Let the caller handle the interruption. Thread.currentThread().interrupt(); break; } } } } } chooser = chooserFactory.newChooser(children); final FutureListener<Object> terminationListener = new FutureListener<Object>() { @Override public void operationComplete(Future<Object> future) throws Exception { if (terminatedChildren.incrementAndGet() == children.length) { terminationFuture.setSuccess(null); } } }; for (EventExecutor e: children) { e.terminationFuture().addListener(terminationListener); } Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length); Collections.addAll(childrenSet, children); readonlyChildren = Collections.unmodifiableSet(childrenSet);}其中,创建细节见下:线程池中的线程数nThreads必须大于0;如果executor为null,创建默认executor,executor用于创建线程(newChild方法使用executor对象);依次创建线程池中的每一个线程即NioEventLoop,如果其中有一个创建失败,将关闭之前创建的所有线程;chooser为线程池选择器,用来选择下一个EventExecutor,可以理解为,用来选择一个线程来执行task;chooser的创建细节,见下DefaultEventExecutorChooserFactory根据线程数创建具体的EventExecutorChooser,线程数如果等于2^n,可使用按位与替代取模运算,节省cpu的计算资源,见源码@SuppressWarnings(“unchecked”)@Overridepublic EventExecutorChooser newChooser(EventExecutor[] executors) { if (isPowerOfTwo(executors.length)) { return new PowerOfTowEventExecutorChooser(executors); } else { return new GenericEventExecutorChooser(executors); }} private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser { private final AtomicInteger idx = new AtomicInteger(); private final EventExecutor[] executors; PowerOfTowEventExecutorChooser(EventExecutor[] executors) { this.executors = executors; } @Override public EventExecutor next() { return executors[idx.getAndIncrement() & executors.length - 1]; } } private static final class GenericEventExecutorChooser implements EventExecutorChooser { private final AtomicInteger idx = new AtomicInteger(); private final EventExecutor[] executors; GenericEventExecutorChooser(EventExecutor[] executors) { this.executors = executors; } @Override public EventExecutor next() { return executors[Math.abs(idx.getAndIncrement() % executors.length)]; } }newChild(executor, args)的创建细节,见下MultithreadEventExecutorGroup的newChild方法是一个抽象方法,故使用NioEventLoopGroup的newChild方法,即调用NioEventLoop的构造函数 @Override protected EventLoop newChild(Executor executor, Object… args) throws Exception { return new NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]); }在这里先看下NioEventLoop的类层次关系NioEventLoop的继承关系比较复杂,在AbstractScheduledEventExecutor 中, Netty 实现了 NioEventLoop 的 schedule 功能, 即我们可以通过调用一个 NioEventLoop 实例的 schedule 方法来运行一些定时任务. 而在 SingleThreadEventLoop 中, 又实现了任务队列的功能, 通过它, 我们可以调用一个NioEventLoop 实例的 execute 方法来向任务队列中添加一个 task, 并由 NioEventLoop 进行调度执行.通常来说, NioEventLoop 肩负着两种任务, 第一个是作为 IO 线程, 执行与 Channel 相关的 IO 操作, 包括调用 select 等待就绪的 IO 事件、读写数据与数据的处理等; 而第二个任务是作为任务队列, 执行 taskQueue 中的任务, 例如用户调用 eventLoop.schedule 提交的定时任务也是这个线程执行的.具体的构造过程,见下创建任务队列tailTasks(内部为有界的LinkedBlockingQueue)创建线程的任务队列taskQueue(内部为有界的LinkedBlockingQueue),以及任务过多防止系统宕机的拒绝策略rejectedHandler其中tailTasks和taskQueue均是任务队列,而优先级不同,taskQueue的优先级高于tailTasks,定时任务的优先级高于taskQueue。ServerBootstrap初始化及启动了解了Netty线程池NioEvenrLoopGroup的创建过程后,下面看下API网关服务ServerBootstrap的是如何使用线程池引入服务中,为高并发访问服务的。API网关ServerBootstrap初始化及启动代码,见下serverBootstrap = new ServerBootstrap();bossGroup = new NioEventLoopGroup(config.getBossGroupThreads());workerGroup = new NioEventLoopGroup(config.getWorkerGroupThreads());serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .option(ChannelOption.TCP_NODELAY, config.isTcpNoDelay()) .option(ChannelOption.SO_BACKLOG, config.getBacklogSize()) .option(ChannelOption.SO_KEEPALIVE, config.isSoKeepAlive()) // Memory pooled .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childHandler(channelInitializer); ChannelFuture future = serverBootstrap.bind(config.getPort()).sync();log.info(“API-gateway started on port: {}”, config.getPort());future.channel().closeFuture().sync();API网关系统使用netty自带的线程池,共有三组线程池,分别为bossGroup、workerGroup和executorGroup(使用在channelInitializer中,本文暂不作介绍)。其中,bossGroup用于接收客户端的TCP连接,workerGroup用于处理I/O、执行系统task和定时任务,executorGroup用于处理网关业务加解密、限流、路由,及将请求转发给后端的抓取服务等业务操作。Channel与线程池的绑定ServerBootstrap初始化后,通过调用bind(port)方法启动Server,bind的调用链如下AbstractBootstrap.bind ->AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister其中,ChannelFuture regFuture = config().group().register(channel);中的group()方法返回bossGroup,而channel在serverBootstrap的初始化过程指定channel为NioServerSocketChannel.class,至此将NioServerSocketChannel与bossGroup绑定到一起,bossGroup负责客户端连接的建立。那么NioSocketChannel是如何与workerGroup绑定到一起的?调用链AbstractBootstrap.initAndRegister -> AbstractBootstrap. init-> ServerBootstrap.init ->ServerBootstrapAcceptor.ServerBootstrapAcceptor ->ServerBootstrapAcceptor.channelReadpublic void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); for (Entry<ChannelOption<?>, Object> e: childOptions) { try { if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) { logger.warn(“Unknown channel option: " + e); } } catch (Throwable t) { logger.warn(“Failed to set a channel option: " + child, t); } } for (Entry<AttributeKey<?>, Object> e: childAttrs) { child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue()); } try { childGroup.register(child).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { forceClose(child, future.cause()); } } }); } catch (Throwable t) { forceClose(child, t); }}其中,childGroup.register(child)就是将NioSocketChannel与workderGroup绑定到一起,那又是什么触发了ServerBootstrapAcceptor的channelRead方法?其实当一个 client 连接到 server 时, Java 底层的 NIO ServerSocketChannel 会有一个SelectionKey.OP_ACCEPT 就绪事件, 接着就会调用到 NioServerSocketChannel.doReadMessages方法@Overrideprotected int doReadMessages(List<Object> buf) throws Exception { SocketChannel ch = javaChannel().accept(); try { if (ch != null) { buf.add(new NioSocketChannel(this, ch)); return 1; } } catch (Throwable t) { … } return 0;}javaChannel().accept() 会获取到客户端新连接的SocketChannel,实例化为一个 NioSocketChannel, 并且传入 NioServerSocketChannel 对象(即 this), 由此可知, 我们创建的这个NioSocketChannel 的父 Channel 就是 NioServerSocketChannel 实例 .接下来就经由 Netty 的 ChannelPipeline 机制, 将读取事件逐级发送到各个 handler 中, 于是就会触发前面我们提到的 ServerBootstrapAcceptor.channelRead 方法啦。至此,分析了Netty线程池的初始化、ServerBootstrap的启动及channel与线程池的绑定过程,能够看出Netty中线程池的优雅设计,使用不同的线程池负责连接的建立、IO读写等,为API网关项目的高并发访问提供了技术基础。总结至此,对API网关技术的Netty实践分享就到这里,各位如果对中间的各个环节有什么疑问和建议,欢迎大家指正,我们一起讨论,共同学习提高。参考http://tutorials.jenkov.com/j…http://netty.io/wiki/user-gui...http://netty.io/http://www.tuicool.com/articl...https://segmentfault.com/a/11...https://segmentfault.com/a/11…作者:蜂巢团队 宜信技术学院 ...

February 20, 2019 · 3 min · jiezi

Google Translate的API调用

Google Translate的API调用由于经常用到谷歌翻译,而每次切换到网页又觉得耗费时间,所以决定自己写一个小工具来用,于是就去查询了一番谷歌翻译的API,但是看到都说是API已经开始收费了,但还是有人通过网页爬出了网页翻译的API但是利用起来比较繁琐。之后又找到了一个简单的API,如下:fun translate(text: String, source: String = “auto”, target: String = “zh-CN”): Pair<String, String> { val textChecked = if (text.isBlank()) “null” else URLEncoder.encode(text, “utf8”) val userAgent = “Mozilla/5.0” val url = “https://translate.googleapis.com/translate_a/single?client=gtx&sl=$source&tl=$target&dt=t&q=$textChecked" val connection = URL(url).openConnection().apply { setRequestProperty(“User-Agent”, userAgent) } val raw = connection.getInputStream().use { it.readBytes() }.toString(Charset.forName(“utf8”)) val p1 = raw.indexOf(”","") val p2 = raw.indexOf("",", p1 + 1) val result = raw.substring(4, p1) val query = raw.substring(p1 + 3, p2) return Pair(result, query)}测试函数如下:fun main() { println(translate(“别让这么应景的天空放晴啊”)) println(translate(“空気を読んだ空晴れないでよ”)) println(translate(“别降下这么看场合的雨啊”)) println(translate(“空気を読んだ雨降らないでよ”)) println(translate(“He sits no sure that sits too high”)) println(translate(“高处不胜寒”, target = “en-US”))}调用结果如下:(别让这么应景的天空放晴啊, 别让这么应景的天空放晴啊)(我看空中的天空请不要晴天, 空気を読んだ空晴れないでよ)(别降下这么看场合的雨啊, 别降下这么看场合的雨啊)(看风雨时不要下雨, 空気を読んだ雨降らないでよ)(他不确定是不是太高了, He sits no sure that sits too high)(High altitude, 高处不胜寒)translate函数参数分别是:需要翻译的字符串,原始语言(默认为auto,即自动检测),目标语言(默认zh-CN,即简体中文)。translate函数返回结果为:翻译后的字符串,需要翻译的字符串(原始字符串)。 ...

February 4, 2019 · 1 min · jiezi

来自Google资深工程师的API设计最佳实践

来自Google资深工程师Joshua Bloch的分享:API设计最佳实践为什么API设计如此重要?API是一个公司最重要的资产。为什么API的设计对程序员如此重要?API一旦发布,出于兼容性考虑,不能轻易地随心所欲进行修改,比如删除参数。从API的上帝 - 设计者的视角出发,有助于提高代码质量。一个优秀的API应该满足下列标准:易学易用,甚至是自描述的,不需文档也能让新手快速上手。不易造成误解。后续维护者易于理解,满足开闭原则 - 能够很容易进行扩展。如何设计一个好的API首先应该从收集需求出发。注意结合API实现的复杂度一起考虑。作为第一步,首先给出需求规格文档,一页即可:别指望你的API能让所有人满意。也不要指望发布后,它不会出任何错误——那是不可能的。API也应该遵循单一职责:如果你发觉很难根据你的API实现的功能给它取个合适的名字,这是一个不好的信号,很多时候说明你的API里面做了太多事情——试着把它们拆成多个API。信息封装 - 公有类尽量避免暴露公有字段出去,最大化信息隐藏API命名艺术 - API的名称也是一门语言。API和文档的关系合理使用继承和子类,不要滥用里氏替换原则采用fail fast 策略,尽可能早地抛出错误消息:API的数据都应该允许使用者通过字符串的方式访问慎用重载选用合适的API参数和返回类型API里的参数顺序也很有讲究避免冗长的参数列表,参数如果超过3个,使用者就需要通过阅读文档才能消费了。尽量返回不需要调用者进行异常处理的参数,比如空数组或集合,而不是nullAPI设计里的和异常处理相关的最佳实践API重构的最佳实践API设计和Thread-local相关的最佳实践要获取更多Jerry的原创文章,请关注公众号"汪子熙":

January 31, 2019 · 1 min · jiezi

小聊 API

参考 How NOT to design APIs 进行总结作者的朋友的项目正在使用 Beds24 这套系统,这套系统主要就是用来做预定的,连接的是BookingAirBnB上的房源信息。而这个项目的功能就是从一些订房平台上获取可供预定的房间和日期。但是这个提供的接口服务存在着很多问题,所以作者就拿他作为反面教材愉快地吐槽了一番。我们先来看一下一个典型的getAvailabilities接口,接口连接在这里。我们暂且就称为接口A。接口 A 通过参数获取可用房间和时间,所有可选参数如下所示:{ “checkIn”: “20151001”, “lastNight”: “20151002”, “checkOut”: “20151003”, “roomId”: “12345”, “propId”: “1234”, “ownerId”: “123”, “numAdult”: “2”, “numChild”: “0”, “offerId”: “1”, “voucherCode”: “”, “referer”: “”, “agent”: “”, “ignoreAvail”: false, “propIds”: [ 1235, 1236 ], “roomIds”: [ 12347, 12348, 12349 ]}我们就来细细评鉴一下这个参数中有哪些不合理的地方。1.日期我们可以看到,在请求报文中,checkIn,lastnight,checkOut,都使用了黏在一起的年月日形式(YYYYMMDD),虽然这种形式的可读性也不差,但是对于跨语言解析的便利性上就不好说了。其实作为时间,用 ISO8601 (YYYY-MM-DD),这样的通用标准形式会更加便捷。不论是从 encode 还是 decode 来说,比如 javascript 就能用 Date.parse(YYYY-MM-DD) 直接解析出所代表的 unix timestamp。2.ID与数字如果一个数字代表是 ID,即用来描述对象唯一的数字。那么最好是用 string 类型的,比如请求结构体roomIds,propIds。如果一个数字是用来描述数量或者值的,那不要使用 string,否则会有歧义并且不好计算。如上头的numAdult,numChild。虽然上面数字与 ID 的区分并不是强制要求,但是注意,不论你使用哪一种形式,保持统一。你可以看到:roomId和roomIds,明明只是一个是另一个的集合,缺使用了两种表示形式,这不免让人误解。在说完了 Request 部分,我们看下返回的 Response。我们按照{ “checkIn”: “20190501”, “checkOut”: “20190503”, “ownerId”: “25748”, “numAdult”: “2”, “numChild”: “0”}作为请求,得到了如下的 Response:{ “10328”: { “roomId”: “10328”, “propId”: “4478”, “roomsavail”: “0” }, “13219”: { “roomId”: “13219”, “propId”: “5729”, “roomsavail”: “0” }, “14900”: { “roomId”: “14900”, “propId”: “6779”, “roomsavail”: 1 }, “checkIn”: “20190501”, “lastNight”: “20190502”, “checkOut”: “20190503”, “ownerId”: 25748, “numAdult”: 2}3.数据结构我们用心体会一下这个 response 是干嘛的…返回的内容包括了两块:客户的房间资产列表我们请求的参数以及相应的检索返回如果你是前端的话,肯定心里已经开始 mmp 了。这个 response 把所有数据都捏在了顶层数据结构中。如果我们只想获取房间列表信息的话,我还必须遍历所有数据,并自己做一个筛选??这显然十分不合理,我们再来看看房间列表。“14900”: { “roomId”: “14900”, “propId”: “6779”, “roomsavail”: 1 },他把 roomId 作为了map的 key。这看上去有点重复,我们很少会在 json 传输过程中使用把关键结果数据作为key的字典类型(dictionary)。对于数据处理方来讲,只对 value中的数据进行处理是最方便的。你额外来一个关键信息,还是作为 key,要不是 value 中包含了 roomId,真的很 cd。另外,我们看到roomsavail,roomsavail在值为0,和值为1的时候的数据类型是不一样的(应保持一致),这个也需要注意。既然我们吐槽完毕,那就看看推荐的数据结构应该是怎么样的:{ “properties”: [ { “id”: 4478, “rooms”: [ { “id”: 12328, “available”: false } ] }, { “id”: 5729, “rooms”: [ { “id”: 13219, “available”: false } ] }, { “id”: 6779, “rooms”: [ { “id”: 14900, “available”: true } ] } ], “checkIn”: “2019-05-01”, “lastNight”: “2019-05-02”, “checkOut”: “2019-05-03”, “ownerId”: 25748, “numAdult”: 2}增加了 properties 数据块,很明确区分了数据内容,方便取用。4.错误处理万能的 200如果你知道我在说什么的话,你应该也经历过这样的问题~我们看看 Beds24 接口的错误返回码:他们定义了一套自己的错误码和错误返回信息。但是这些都是封装在200返回码的 Response 中的。这里我想说一下,我不太同意原作者的观点,这也是国内和国外的风格差异。遵循开发效率优先的原则,我认为错误处理的返回应该对外 API,统一返回200,并注明错误在自定义 errorCode 中。这样的好处是,API对接方可以很容易区分是连接层的问题还是业务层的问题。而内部接口竟可能使用 HttpStatusCode,前端同学可以更方便获取异常。4.调用须知原文中,作者对接口调用方给到的调用建议有些意见,并不涉及太多技术细节,只是提出了接口设计原则,设计要求和思路。我们就来简单过一下…调用须知是接口提供方给到的一些调用意见和规范,我们看下 Beds24的调用须知:Calls should be designed to send and receive only the minimum required data.Only one API call at a time is allowed, You must wait for the first call to complete before starting the next API call.Multiple calls should be spaced with a few seconds delay between each call.API calls should be used sparingly and kept to the minimum required for reasonable business usage.Excessive usage within a 5 minute period will cause your account to be blocked without warning.We reserve the right to disable any access we consider to be making excessive use of the API functions at our complete discretion and without warning.我们大概分析下,1和4的意见还是挺在理的,尽可能保证请求数据的最小化,合理化。第二条:每次只允许单个 api 的请求,只有当上一个请求结束后才能开始下一个请求。我觉得应该是文档好久没更新了…在当下并发与并行横行的年代,这样的接口限制有点过时。如果你是在搭建REST API,那记住,这个API是无状态的。这也是 REST API 在云应用中被广泛使用的原因,调用者无需在意是否一次一请求,一旦程序出现问题,或者上一个请求超时、异常,完全可以立即重新请求。要求调用者每次只能请求一次也是一个无理要求。第三条:复合请求每次要排队,每次间隔几秒钟。第五条:五分钟内过高的请求频率会导致账户冻结。第六条:接口提供方保留冻结接口使用者的账户的权利。有点霸王条约,而且条件说明不清晰,包括冻结条件,同时在接口请求限制上有点严格。5.文档展现推荐使用接口展示的工具,Swagger或者Apiary。Beds24的接口文档可谓是十分原汁原味了,没有主要的 index 引导,很容易让开发者看得云里雾里。6.安全这里原作者主要吐槽了 Beds24 接口不需要鉴权也能够调用的 BUG。既然说到了安全,我们就稍微延展一下。如今常见的 API 调用鉴权方式主要有以下几种:Basic AuthorizationOauth TokenSessionBasic Authorization鉴权的方式相对简单,就是在 Http Header 增加经过base64运算后的 username + password。相信你能够发现,这样的鉴权方式没有过期时间,传输内容安全验证的措施,所以如果你的 api 需要上述两个方面的保证的话,不建议使用这种鉴权方式。Session通过一个唯一的 key,验证在共享内存中的用户状态。单实例应用常用的解决方案。唯一的问题在于,共享内存中的隔离机制,因为是共享 session 的,所以 session 的读取安全性很重要。Oauth Token不同与 Session, Oauth Token(下面简称 token)不需要在共享内存中记录用户状态。token 本身就会包含用户的基本信息、权限范围、有效时间,当然这些都是经过加密的。调用者将 token 放在 http header 中进行请求。一般提供 Oauth 验证方式的接口,都会附加一个类似刷新 refresh token 的接口,用来解决 token 过期的问题。*关于详细的鉴权方式和实现大家可以 Google一下,或者 github 一下。提供一个优质、可用、易读的 API 也是每个接口提供方的责任。记得前段时间Ant Design的彩蛋事件,所引出的“开源即责任”,更何况是收费模式下的接口服务呢?文章末尾,作者好心劝告了大家,如果遇到类似Beds24的接口,请远离,除非这个接口提供的服务是不可替代的,因为这样的接口会让你投入更多的理解、纠错成本。当然,我们作为开发者,自己也要谨记在设计 API 的时候不要出现上述的一些问题。 ...

January 29, 2019 · 2 min · jiezi

Postman 快速入门之脚本语法

Postman是访问各种API的客户端。它的强大之处在于其允许在对某一个request访问的之前和之后分别运行自定义的一段Javascript脚本,这样直接就完成了一个chain request的效果,可以将数条request连结成一个流程完成一体化测试。这在很多的API操作中都是极其有用的,所以这里有必要总结一些常用语句。脚本执行流程pre-request脚本,是在对API进行请求之前的脚本,一般用于动态生成参数、JSON数据包、链接地址等。test脚本,其实更应该叫post-request,实在完成API访问并得到其response回应之后运行的脚本,一般用于获取response的内容,用于之后对于别的资源的请求,如获取页面标题和内容等。运行脚本要求需要注意,pre-request脚本,在里面直接写代码就可以了,但是test脚本需要在某个指定的函数pm.test(…)中执行才会被识别,且作为test脚本运行。如下图:pm.test()中第一个参数是测试描述(会在测试结果栏显示,应和其它测试描述做以区分),第二个参数是一个函数,具体执行代码都在这个函数中运行。另外,pm对象是Postman的主要对象,所有的内置函数,数据调用等,都需要通过它来实现。脚本调试如果要看已经设置的Enviroment变量的话,可以点开右上方小眼睛看到,如下图,我设置了3个环境变量:调试时要打印的话,一般都是用console.log(…),这样就能在console中看到。如果你的Postman是Chrome app的话,直接在chrome浏览器的开发者工具里调试就行。如果是Mac等桌面软件,则需要打开内置的console才能看到调试信息。位置在左下角,如下图:常用语句 Code Snippets一般会在脚本编写栏的右边都会有常用语句片段,点击以下就会出来代码,但是一开始不太了解的话点出来其实也看不懂。如下图:官方文档解释的各种函数调用链接在这里:Postman Sandbox以下是我自己总结的常用代码片段:// 获取response返回内容var rsb = responseBody; // 是字符串格式// 获取环境变量var v = pm.environment.get(“变量名称”);// 设置环境变量 只能存储字符串,如果是对象的话则无法在下次运行时获取到内容// 如需要存储JSON数据,可以用JSON.stringify(..)存储,再用JSON.parse(..)转化为对象使用pm.environment.set(“变量名称”, 变量内容);// 清除某个环境变量pm.environment.unset(“环境变量名”);// 获取全局变量和普通变量var gb = pm.globals.get(“全局变量名”);var nm = pm.variables.get(“普通变量名”);// Javascript 获取变量类型console.log( typeof pm.enviroment );测试结果除了上面的具体功能代码外,经常还需要返回一个结果,告诉Postman这个测试结果是Pass还是Fail,默认是pass。这里返回值就不是简单的return语句可以,必须要通过Postman自带的对象或方法才可以,一般是通过pm.expect()或tests[]这两个地方返回测试结果。这些方法名看起来都很容易理解,一般都会叫pm.expect()或.to.be()或.to.have()这样的,字面意思就是期待什么或要求它必须是什么或必须有什么,才能通过测试。另外,同样的测试结果,实际上还有更简单的写法,即不通过pm对象,而是内置tests对象,常用操作如下:# 反应时间必须少于200毫秒tests[“Response time is less than 200ms”] = responseTime < 200;# 判断反应代号是否等于某一个指定的代号tests[“Status code name has string”] = responseCode.name.has(“Created”);看这个用法,猜测tests是一个JSON格式的对象,tests[…]括号内的字符串是测试的描述, =后面是判断语句,然后将True或False赋予为tests[..]的值,然后postman轮训所有tests对象里的参数,并返回pass与否的结果。这里是官方总结的常用测试脚本方法:Test examples以下是我总结的常用的返回测试结果的内置函数:# “期待”返回结果必须包含某一段内容pm.expect(从response里获取的字符串).to.include(“必须包含的内容”);# 返回body值必须完全等于某一段内容pm.response.to.have.body(“必须等于的内容”);# 反应时间必须少于200毫秒pm.expect(pm.response.responseTime).to.be.below(200);# 必须返回某一个状态 如"Created"pm.response.to.have.status(“状态名”);

January 26, 2019 · 1 min · jiezi

Postman Script 脚本语法总结

Postman的强大之处在于其允许在对某一个request访问的之前和之后分别运行自定义的一段Javascript脚本,这样直接就完成了一个chain request的效果,可以将数条request连结成一个流程完成一体化测试。这在很多的API操作中都是极其有用的,所以这里有必要总结一些常用语句。参考Postman官方:Intro to scriptsScript workflow 脚本执行流程pre-request脚本,是在对API进行请求之前的脚本,一般用于动态生成参数、JSON数据包、链接地址等。test脚本,其实更应该叫post-request,实在完成API访问并得到其response回应之后运行的脚本,一般用于获取response的内容,用于之后对于别的资源的请求,如获取页面标题和内容等。Requirements 运行脚本要求需要注意,pre-request脚本,在里面直接写代码就可以了,但是test脚本需要在某个指定的函数pm.test(…)中执行才会被识别,且作为test脚本运行。如下图:pm.test()中第一个参数是测试描述(会在测试结果栏显示,应和其它测试描述做以区分),第二个参数是一个函数,具体执行代码都在这个函数中运行。另外,pm对象是Postman的主要对象,所有的内置函数,数据调用等,都需要通过它来实现。Code Snippets 常用语句一般会在脚本编写栏的右边都会有常用语句片段,点击以下就会出来代码,但是一开始不太了解的话点出来其实也看不懂。如下图:官方文档解释的各种函数调用链接在这里:Postman Sandbox以下是我自己总结的常用代码片段:// 获取response返回内容var rsb = responseBody; // 是字符串格式// 获取环境变量var v = pm.environment.get(“变量名称”);// 设置环境变量 只能存储字符串,如果是对象的话则无法在下次运行时获取到内容// 如需要存储JSON数据,可以用JSON.stringify(..)存储,再用JSON.parse(..)转化为对象使用pm.environment.set(“变量名称”, 变量内容);// 清除某个环境变量pm.environment.unset(“环境变量名”);// 获取全局变量和普通变量var gb = pm.globals.get(“全局变量名”);var nm = pm.variables.get(“普通变量名”);// Javascript 获取变量类型console.log( typeof pm.enviroment );Pre-request Script 预处理脚本Pre-request script是在request之前准备request信息用的脚本。参考Postman官方:Pre-request scripts常用的准备工作有:读取环境变量,再放到request body或headers中。拼接组合提交request请求所需要的参数,比如authentication code。常用的语句如下:// 获取环境变量var v = pm.environment.get(“变量名称”);// 设置环境变量 只能存储字符串,如果是对象的话则无法在下次运行时获取到内容// 如需要存储JSON数据,可以用JSON.stringify(..)存储,再用JSON.parse(..)转化为对象使用pm.environment.set(“变量名称”, 变量内容);Test Script 测试脚本测试脚本是在request之后,对Response的返回值进行下一步处理的脚本。参考Postman官方:Test scripts参考Postman官方:Test examples常用的处理有:读取response回复的数据,存为环境变量根据response回复的状态成功与否,判断下一步做什么常用的语句如下:// 获取response headers中某一个值ctype = postman.getResponseHeader(“Content-Type”);// 获取response body的全部内容text = pm.response.text();// 获取response返回的全部JSONjson_data = pm.response.json();// 获取json中某一个值,比如name的值:myName = json_data.name;Test Result 测试结果除了上面的具体功能代码外,经常还需要返回一个结果,告诉Postman这个测试结果是Pass还是Fail,默认是pass。这里返回值就不是简单的return语句可以,必须要通过Postman自带的对象或方法才可以,一般是通过pm.expect()或tests[]这两个地方返回测试结果。完整的测试示范:// 测试response的状态是否是200成功:pm.test(“Status code is 200”, function () { pm.response.to.have.status(200);});这些方法名看起来都很容易理解,一般都会叫pm.expect()或.to.be()或.to.have()这样的,字面意思就是期待什么或要求它必须是什么或必须有什么,才能通过测试。另外,同样的测试结果,实际上还有更简单的写法,即不通过pm对象,而是内置tests对象。常用操作如下:# 反应时间必须少于200毫秒tests[“Response time is less than 200ms”] = responseTime < 200;# 判断反应代号是否等于某一个指定的代号tests[“Status code name has string”] = responseCode.name.has(“Created”);看这个用法,猜测tests是一个JSON格式的对象,tests[…]括号内的字符串是测试的描述, =后面是判断语句,然后将True或False赋予为tests[..]的值,然后postman轮训所有tests对象里的参数,并返回pass与否的结果。这里是官方总结的常用测试脚本方法:Test examples以下是我总结的常用的返回测试结果的内置函数:# “期待”返回结果必须包含某一段内容pm.expect(从response里获取的字符串).to.include(“必须包含的内容”);# 返回body值必须完全等于某一段内容pm.response.to.have.body(“必须等于的内容”);# 反应时间必须少于200毫秒pm.expect(pm.response.responseTime).to.be.below(200);# 必须返回某一个状态 如"Created"pm.response.to.have.status(“状态名”);Debugging 脚本调试如果要看已经设置的Enviroment变量的话,可以点开右上方小眼睛看到,如下图,我设置了3个环境变量:调试时要打印的话,一般都是用console.log(…),这样就能在console中看到:如果你的Postman是Chrome app的话,直接在chrome浏览器的开发者工具里调试就行。如果是Mac等桌面软件,则需要打开内置的console才能看到调试信息。位置在左下角,如下图:Postman SandboxPostman的Sandbox是Postman内部默认引入的第三方JS库。参考:Postman Sandbox 官方脚本可引用库说明参考:Postman Sandbox API 官方引用的脚本库详解Sandbox引用的第三方库有:生成MD5字符串:var hashed = CryptoJS.MD5(“待加密的字符串”);文件转base64字符串:s = ‘Hello’;// 先转化成UTF-8编码的字符串utf8 = CryptoJS.enc.Utf8.parse( s );// 用CryptoJS第三方库(Postman已经内置了)进行编码b64 = CryptoJS.enc.Base64.stringify(utf8)console.log(b64);// aGVsbG8=Postman SDK不同于Sandbox,这是Postman内部较高级的SDK,可以引用一些方便的方法。参考官方网址:Tutorial: Postman SDK ConceptsURL转换成JSON格式,并获取指定的参数值:var sdk = require(‘postman-collection’);query = ( new sdk.Url(callback) ).toJSON().query;for (var i in query) { console.log(query[i].key +": “+ query[i].value);} ...

January 26, 2019 · 1 min · jiezi

一篇文章搞定百度OCR图片文字识别API

研究百度OCR的API,主要是向做对扫描版的各种PDF进行文字识别并转Word文档的需求。这里用Postman客户端进行测试和演示。因为Postman是对各种API操作的最佳入门方式。一旦在Postman里实现了正确的调用,剩下的就只是一键生成代码,和一些细节的修改了。参考百度云官方文档:文字识别API参考下载官方文档PDF:OCR.zh.pdf授权字符串 Access TokenToken字符串永远是你使用别人API的第一步,简单说,就是只有你自己知道的密码,在你每次向服务器发送的请求里面加上这个字符串,就相当于完成了一次登录。如果没有Token授权认证,API的访问可能会像浏览网页一样简单。Access Token一般是调用API最重要也最麻烦的地方了:每个公司都不一样,各种设置安全问题让你的Token复杂化。而百度云的Token,真的是麻烦到一定地步了。参考:百度API的鉴权认证机制 (建议你不要参考,因为它的流程图会先把你镇住的)简单说,获取百度云token字符串的主要流程就是:创建一个应用,获得只有自己知道的id和密码用POST方式把id和密码发给百度的一个链接:https://aip.baidubce.com/oauth/2.0/token其中,需要你向这个地址传送三个参数:grant_type = client_credentials 这个是固定的client_id = xxx 这个是你在百度云管理后台创建OCR应用的时候,那个应用的API Keyclient_secret = xxx 这个是你的应用的Secret Key等待服务器返还给你一个包含token字符串的数据记住这个token字符串,并用来访问每一次的API来看看怎么利用Postman操作,如下图所示:填好以后点击Send发送,就会获得一个JSON数据,如下图:然后你用你的程序(Python, PHP, Node.js等,随便),获取这个JSON中的access_token,即可用到正式的API请求中,做为授权认证。正式调用API: 以"通用文字识别"为例API链接:https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic提交方式:POST调用方式有两种:方式一:直接在URL填写信息直接把API所需的认证信息放在URL里是最简单最方便的。方式二:Headers填写信息方式建议忽略这种方式,需要填写很多request的标准headers,太麻烦。Headers设置:Content-Type = application/x-www-form-urlencoded只要填这一项就够了。Body数据传送的各项参数:access_token = xxx 把之前获取到的token字符串填到这里来image = xxx 把图片转成base64字符串填到这里,不需要开头的data:image/png;base64,url = xxx 也可以不用传图片而是传一个图片的链接。但是百年无效,不要用!language_type = CHN_ENG 识别语言类型。默认中英。Body的数据如图所示:然后就可以点Send发送请求了。成功后,可以得到百度云返回的一个JSON数据,类似下图:返回的是一行一行的识别字符。百度云的识别率是相当高的,几乎100%吧。毕竟是国内本土的机器训练出来的。API常用地址以下是百度云的OCR常用API地址,每个API所需的参数都差不多,略有不同。所有的API和地址以及详细所需的参数,参考官方文档,很简单。一个弄明白了就其他的都明白了。API请求地址调用量限制通用文字识别https://aip.baidubce.com/rest…50000次/天免费通用文字识别(含位置信息版)https://aip.baidubce.com/rest…500次/天免费通用文字识别(高精度版)https://aip.baidubce.com/rest…500次/天免费通用文字识别(高精度含位置版)https://aip.baidubce.com/rest…50次/天免费网络图片文字识别https://aip.baidubce.com/rest…500次/天免费

January 26, 2019 · 1 min · jiezi

Github API v4: GraphQL

GraphQL势不可挡,有着即将取代REST的API架构。主要好处就是“你要什么,api就给你什么。而不是你要不要都给你返回一大堆没用的。”而且:GraphQL只需要一个网址URL!https://api.github.com/graphql不像REST,你需要各种各有的URL去申请不同的内容。GraphQL一个URL全够了。而且一般不是很复杂的情况下,你几乎只要request一次这个地址,就能拿到你全部需要的数据了(能按照你的需求返回给你各种嵌套的、格式化的数据)网上看了很多文章解释之后发现还是什么都没懂。所以这篇分享不打算按照常规路线,先用一大堆结构图、语法给你弄懵。这里我想先让它运作起来,有个"Hello world",然后再去深究背后的逻辑和语法。初试GraphQL要说去了解一个API,最好的方式就是用Postman或Insomnia这种REST客户端去连接玩耍了,不需要任何编程,只是手动点一点就ok。”有意思“的是,因为GraphQL太新潮,这两大客户端对它的支持各不相同,使用的参数、格式也大相径庭。下面通过最简单的案例总结一下。Insomnia 访问Github GraphQL 的案例Insomnia对GraphQL的支持相当好,可以说已经领先别人一步。以下为操作步骤:新建request, 设置为POST方式访问https://api.github.com/graphql申请授权认证:Github的API v4不能陌生访问,必须使用自己的账号申请一个token密码串,然后在每次连接API时使用。操作很简单,登录Github后,找到Settings -> Developer settings -> Personal access tokens -> Generate new token,生成一个新的token授权密码串,复制保存到本地备份。添加授权认证:在Insomnia软件里,有两种授权方式都可以达到同样认证效果:明文的Query参数里设置(即在url后面加上参数),或者Header表头里设置。Query里设置的话,格式为:?access_token=xxxxxxxxxxxHeader里设置的话,格式是:名称为Authorization,值为token xxxxxxxxxx。在栏目里面的Body位置选择GraphQL格式:输入Github指定格式的查询语句(看似JSON格式实则不是):query { viewer { login }}点击Send发送请求,如果一切正常,则会得到查询的返回值:Postman访问GraphQL失败不像Insomnia,Postman暂时没有支持GraphQL的选项,但是可以通过类似的操作达到一样的效果。流程是一样的,只是每个地方设置格式都不同,这也是我不断尝试才找到的总结方案(可惜网上相关教程太少)。这里只说不同的地方吧:授权比Insomnia多一种方式,可以在Authorization栏目里面直接选OAuth 2.0然后输入token密码串。最重要的是Body部分,查询语句完全不能使用Github指定的格式。只能选择Body格式为Raw -> JSON(application/json)。然后加上查询语句,格式如下(必须完全符合JSON语法):{ “query”: “query {viewer { login } }"}GitHub GraphQL Explorer这个是Github制作的网页练习机,免去了一切授权等处理,让你只专注于查询语句的调用练习。每次点击运行,都会实时返回对应的数据。如果记不住的、不懂的,还可以点击旁边的Docs,会显示出一个搜索框,显示你需要的内容的文档。GraphQL API Explorer | GitHub Developer Guide常用查询结构下面展示一些我测试过的查询结构,希望能起到帮助作用。为了增强阅读性,节省文字长度,返回值就先不粘上来了。而且返回值几乎就是查询语句的结构,没什么特别新鲜的。查询指定的repo中的issues和comments其中指定了某一个repo(通过owner和name指定),也指定了某一条issue(通过number指定),并要求返回最近的10条comments。

January 26, 2019 · 1 min · jiezi

百度翻译API实战

参考官方文档:定制化翻译API技术文档所需传输参数百度翻译的API所需的除了需要翻译的内容和指定语言外,比较麻烦的是需要制作3个授权认证相关的参数。正式调用APIAPI地址:https://fanyi-api.baidu.com/api/trans/vip/translate千万要看清楚这个地址中的vip,而不是官方文档里的private。真是个大坑呢。提交方式:GET 或 POST参数设置(Params或者Body都可以):在Postman中选择Bulk-edit,加入以下内容:q:{{query}}from:ento:zhappid:{{appid}}salt:{{salt}}sign:{{sign}}选择环境变量,将这几个环境变量加进去:并且根据自己的内容填进去。除了填写这些,我们还需要一些自动的脚本来处理数据,因为百度的认证比较麻烦。在Postman里面选择Pre-script,把脚本加进去:// URL request example: // “https://fanyi-api.baidu.com/api/trans/vip/translate?q=apple&from=en&to=zh&appid=2015063000000001&salt=1435660288&sign=f89f9594663708c1605f3d736d01d2d4"var query = pm.environment.get(“query”);var appid = pm.environment.get(“appid”);var salt = (new Date).getTime();var key = pm.environment.get(‘secret_key’);var sign_string = appid + query + salt + key;var sign = CryptoJS.MD5(sign_string).toString();// set encoded query textpm.environment.set(“query”, encodeURI(query));// Set a random number to “salt"pm.environment.set(“salt”, salt);// set hashed “sign” value for authenticationpm.environment.set(“sign”, sign);然后就可以点击Send发送了。以下是百度翻译返回的内容:

January 26, 2019 · 1 min · jiezi

一篇文章玩转世界最强音乐Spotify API操作

Spotify的API简单分为两个入口:授权入口:https://accounts.spotify.com/…API入口:https://api.spotify.com/v1/….其中所有的授权相关验证都通过授权入口进行,而所有正常获取数据的API请求都从API入口进行。Requests Rate Limit 请求限制次数参考官网:Authorization Guide参考官方Github Issues:Getting higher usage rate limits其中只说了不同请求方式的限制次数有不同,但是没写具体有多少:Implicit Grant:次数最少,不能刷新token,数量未知。Rate limits for requests are improved but there is no refresh token provided.Client Credentials:次数较多一些,数量未知. The advantage here … is that a higher rate limit is applied.Authorization Code:次数最多,具体数量未知。网友回答:With 400 users, this would work out at one request every 3.6 seconds, which I’m sure wouldn’t trigger Spotify’s rate limiting policy. 也就是说一个app大约100 requests/second,或6000 requests/minute。参考另一篇官网文档:https://developer.spotify.com…“Rate limiting is applied on an application basis (based on client id), regardless of how many users are using it.“即无关多少个用户,限制次数只是和每个app挂钩的。Developer 创建开发者身份前往Spotify开发者网址:https://developer.spotify.com…登录后点击create a client id,生成一个专用的client_id和client_secret。同时必须认真设置Redirect URIs,因为日后授权验证时要完全匹配才行。Scopes 权限选择在进行授权申请之前,要先确定这个app需要哪些权限,确定好了再到授权过程中通过参数进行声明。Spotify对权限进行了详细的分类,全部的权限如下:参考官方:Authorization Scopes在之后我们对/authorize页面进行GET申请授权时候,需要在URL里加入scope参数,里面的值就是我们所选择的一些权限申请。每条权限名用 空格分开只读型的常用权限有:user-library-readuser-follow-read user-top-read user-read-privateplaylist-read-privateuser-read-playback-state修改型的常用权限有:user-follow-modifyuser-library-modifyplaylist-modify-publicplaylist-modify-private全部组合起来的请求URL是这样的:https://accounts.spotify.com/authorize?…&…&…&scope=user-library-read user-follow-read user-top-read user-read-private playlist-read-private user-read-playback-state user-follow-modify user-library-modify playlist-modify-public playlist-modify-privateAuthentication 授权授权的最终目的是获取一个名为access_token的值,然后用这个access_token去获取各种个样的API信息。参考官方:Authorization GuideSpotify为了严格区分不同的用途和权限,把这个access_token的获取方法分为了三种流程,各自的权限、存活期都不同。三种流程特点如下:Authorization Code Flow: 授权码。主要方法,可刷新token。需要弹出网页让用户登录并点击授权。Client Credentials Flow: app级token。获取非常简单。但不可获取用户资源,也不可刷新。过期需再申请。Implicit Grant Flow: 临时授权。可获取用户资源,不可刷新。存活期比较短。Flow-1: Authorization Code Flow这是推荐的授权流程,可以获得全部权限,比较安全,且可以刷新token。但是交互步骤多了一点,需要弹出页面手动点击“授权”按钮才行。具体步骤:向/authorize发送GET请求,包括client_id和redirect_uri等Spotify弹出页面,用户手动登录并点击允许授权Spotify把页面跳转至自己设定的callback网址,并明文传输一个Code码用Code码向/token发送POST请求,并在header中包括一个动态生成并base64编码的Authorization字符串,格式为Authorization: Basic *<base64 encoded client_id:client_secret>*从Spotify获得access_token和refresh_token在access_token过期后,用refresh_token向/token发送POST请求,获得新的access_token。测试过程中,我们是用Postman。好在Postman提供了最简单的方法,一步到位:使用内置Oauth2.0设置。设置方法是:在Collection上右键点开Edit选择AuthenticationType选择Oauth 2.0Add to根据需求选择把认证添加到headers还是url点击Get new access token,添加token flowToken name填上url或header中的参数的指定名称,一般为access_token。Grant type上选择Authentication Code填入所有相关内容,注意callback必须与spotify后台中设置一致点击request token然后Postman会弹出一个页面,需要你登录Spotify并点击允许授权。只需这一次,以后每次Postman都会帮你登录了然后Postman就会生成一个全局的access_token之后每个页面的Authentication栏,都直接选择Inherit auth from parent继承自父级即可自动完成验证。完成后,每个页面的Header或URL中,都会自动增加一个access_token值。authentication的值里,已经默认加上了Bearer前缀及后面的base64编码字符串。注意:同名的参数如果以前存在,则会被覆盖。内置oAuth 2.0的设置是灰色的,在这里不可直接编辑。Flow-2: Client Credentials Flow这种流程只需要用Dashboard后台中的client_id和client_secret可以直接获取access_token。SDKspotipy这个不好用,因为功能太少,文档不全,基本的Oauth还要自己手动解决,没什么现成的方法。$ pip install spotipy –userspotify.py这个也构建不完全,基于async异步,但是很难走通。文档也没说明具体用法。$ pip3 install spotify –user ...

January 26, 2019 · 1 min · jiezi

一篇文章玩转全网音乐信息库MusicBrainz API

MusicBrainz 没有/没有/没有 复杂的OAuth认证,直接访问即可!MusicBrainz WebAPI目前Musicbrainz提供两种WebAPI:XML Web ServiceJSON Web Service (Beta)Rate LimitingMusicBrainz 的API一般都无用户权限认证,允许任何匿名访问,直接打开浏览器访问即可。但是,如果为了增加访问限制的数量,官方建议在request请求的头部加上user-agent。参考:Rate Limiting格式如下:User-Agent: <AppName>/<version> ( contact-email )or User-Agent: <AppName>/<version> ( contact-url )etc.,User-Agent: MyAwesomeTagger/1.2.0 ( http://myawesometagger.example.com )User-Agent: MyAwesomeTagger/1.2.0 ( me@example.com )根据user-agent的种类,限制情况如下:使用python-musicbrainz/0.7.3库访问:限制50次/秒。完全匿名访问:限制50次/秒。其它访问:Source IP address:取决于访问速率,一旦过高,将被完全限制,直到速度降为到1次/秒。Global: 300次/秒。MusicBrainz XML APIMusicbrainz的WebAPI是XML格式的。目前v1版本正准备被淘汰,v2版本也很好用。参考官方:Development / XML Web Service / Version 2查询格式:http://musicbrainz.org/ws/2/<资源>/?query=<属性1>:<值> AND <属性2>:<值>&limit=<显示数>如,搜索artist:http://musicbrainz.org/ws/2/artist/?query=name:bigbang%20AND%20country:NO&limit=10如,搜索album:http://musicbrainz.org/ws/2/release/?query=name:edendale如,搜索track:http://musicbrainz.org/ws/2/recording/?query=name:pristine具体查询详细参考:Development / XML Web Service / Version 2 / Search关于XML解析Python:xmltodictlxmlxpat…经过试用,目前尚未找到能“正确”解析的工具,总是出现一些问题。MusicBrainz JSON API (Beta)参考官方:Development/JSON Web ServiceMusicbrainz提供了一个正在beta开发中的JSON API,要远方便与XML。因为XML的解析实在是太麻烦了。具体的方法是:在v2版本的API上加上一个fmt参数即可。格式为:..&fmt=json示例:http://musicbrainz.org/ws/2/artist/?query=name:bigbang&fmt=json注意:目前JSON API正在开发中,所以是unstable的。inc参数参考:inc - Development / XML Web Service / Version 2当你request API的时候,默认返回的数据很多都是不全的。MusicBrainz可以让你有选择性的增加返回的数据。需要用到的就是url里的inc参数。格式为…&inc=AAA+BBB+CCC示例:http://musicbrainz.org/ws/2/recording/?query=bigbang&inc=artist-credits+isrcs+releases&fmt=jsonscore属性在我们请求WebAPI搜索的时候,每个返回的搜索结果都会有一个score属性。这个是匹配度的值,100分,99分,65分等等。如果搜索的信息完全匹配,则为100。这个搜索算法,是Lucene引擎的算法。参考:More information on the “Score” attribute in the search of musicbrainz参考:Lucene scoring not accurateMusicBrainz Python SDK注意:目前python-musicbrainz项目是调用的v1版本API,显示的数据不是很全。参考官方Github:alastair/python-musicbrainzngs参考官方API文档:musicbrainzngs 0.6 documentation »参考官方示例:Usage安装:pip install musicbrainzngs登录:import musicbrainzngs as mb# 登录mb.auth(“用户名”, “密码”)# 随便写个app信息mb.set_useragent(“Example music app”, “0.1”, “http://example.com/music")# [可选] 指定查询服务器mb.set_hostname(“beta.musicbrainz.org”)就是这么简单,没有复杂的Oauth验证。常用操作:# 搜索一个artistartists = mb.search_artists(artist=“big bang”, type=“group”, country=“Norway”)MusicBrainz Database 数据库下载使用MusicBrainz的数据库是完全免费公开下载使用的。参考官方说明:MusicBrainz Database参考官方说明:MusicBrainz Database/Download参考官方说明:MusicBrainz Database / SchemaMusicBrainz数据库结构图(关系型):使用方法有很多种:Virtual Machine 虚拟机JSON文件Postgresql数据库安装Postgresql数据库参考官方:Database Installation下载数据库的方式有http、ftp、rsync等,其中最方便的是http。下载地址一般为:http://ftp.musicbrainz.org/pu…要查看最新日期为什么,可以直接到http://ftp.musicbrainz.org/pub/musicbrainz/data/fullexport查看下面的子目录有哪些。Postgresql数据库下载使用:# 下载最新日期的数据库文件 “mbdump.tar.bz2” 大约2.7GBwget http://ftp.eu.metabrainz.org/pub/musicbrainz/data/fullexport/20181205-001547/mbdump.tar.bz2tar -xjvf mbdump.tar.bz2cd mbdump/mkdir ../finished# 创建空数据库createdb -U postgres –owner=postgres –encoding=UNICODE db_musicbrainz# 登录数据库psql -U postgres db_musicbrainz\i admin/sql/CreateTables.sqlBEGIN\q# 导入数据for FILE in * ; do cmd=”\copy $FILE from ./$FILE" echo $cmd | psql -U postgres db_musicbrainz && mv $FILE ../finished/done echo date Donecd .. ...

January 25, 2019 · 1 min · jiezi

音乐服务商Music Story API操作全程

参考官网:http://developers.music-story…Music Story 是一个非开源收费版的类似MusicBrainz的音乐库。它的最大优点是能提供各种单曲、专辑、歌手的Connector,即显示资源在各种平台上的链接。比如有一首歌,它能提供API告诉你它在Youtube、Spotify、MusicBrainz等平台的资源链接。有了这个Connector后,我们就可以做很多有趣的事情。但是这个API是半收费的,即免费版用户每个月只能request 2000次。显然有点少。所以我只打算用它的connector,而其它所有信息还是以免费的MusicBrainz为主。注册开发者身份注册开发者身份:http://user.music-story.com/这个API的注册极其简单,注册个用户,就能立马得到oAuth 1.0的两套key-token密钥对。显示token的地址为:http://user.music-story.com/c…第一个Consumer密钥对,密码自动会发到邮箱。第二个Access密钥对,要手动点一下生成才行。全部生成好,最好复制到本地文本里面,以供下次使用。授权验证Music Story采用Oauth 1.0验证,需要这些步骤:通过两套secret密钥向http://api.music-story.com/oauth/request_token提交授权申请获取oauth_signature值每次查询API时,都在URL中加入&oauth_token=<VOTRE TOKEN D’ACCES>&oauth_signature=<SIGNATURE OAUTH>这两个参数即可。在Postman中测试的话很方便,无需复杂操作即可完成。在Authentication页面选择Oauth 1.0方式授权,填入密钥信息即可。读取API按歌名搜索一首歌:http://api.music-story.com/en/track/search?title=now is not a good time结果会返回它的ID等信息。不包括详细信息。比如一首歌的id是22739747299518299,那么搜索它的信息:http://api.music-story.com/en/track/22739747299518299搜索一首歌的conenctor,比如youtube:http://api.music-story.com/en/track/22739747299518299/youtube同理,albums和Artists也都是这么操作。

January 25, 2019 · 1 min · jiezi

Authlib强大OAuth框架入门

基于Python的Authlib是集所有主流WebAPI权限认证协议的客户端、服务端、底层实现、高层架构于一身的强大工具库。参考官方:Authlib: Python AuthenticationAuthlib几乎是能将RFC所有相关的API认证协议都包括进来了,甚至从协议的底层实现、高层架构,从客户端到服务端都能实现的,当之无愧称为Monolithic project 的一个项目。目前Authlib支持的Authentication协议有:OAuth 1.0OAuth 2.0JWTmany more…安装:$ pip install AuthlibOauth2.0客户端验证参考官方:Authlib - Client Guide - OAuth 2 SessionAuthlib中用来登录验证OAuth2.0的对象叫Session,其中包括了所有相关的验证所需方法函数。from authlib.client import OAuth2Session# 生成session,用来之后的创建URL、获取token、刷新token等所有动作session = OAuth2Session( client_id=‘Your Client ID’, client_secret=‘Your Client Secret’, scope=‘user:email’, # 这个授权范围根据每个API有所不同 redirect_uri=‘https://MyWebsite.com/callback')# 生成URLuri, state = session.create_authorization_url( ‘https://目标网址的授权入口/authorize’)print(uri)# 「手动」打开浏览器访问URI,登录API帐户,点击授权,然后获取最终的URL# 或者自己有服务器的话,可以自己接收#。。。# 将callback返回的URL复制下来,因为其中包含授权codeauthorization_response = ‘https://MyWebsite.com/callback?code=42..e9&state=d..t'# 用获得的Code去访问access_token入口tokens = session.fetch_access_token( access_token_url=‘https://目标网址的授权入口/api/access_token’, authorization_response=authorization_response)print(tokens) #返回字典格式: {‘access_token’: ’e..ad’, ’token_type’: ‘bearer’, ‘scope’: ‘user:email’}# 过期后,刷新token。需重建session对象:session = OAuth2Session( client_id=‘Your Client ID’, client_secret=‘Your Client Secret’, scope=‘user:email’, state=state, redirect_uri=‘https://MyWebsite.com/callback')new_tokens = session.refresh_token( access_token_url, refresh_token=tokens[‘refresh_token’])print(’[Refreshed tokens]:’, new_tokens)设置的callback或redirect_url不存在怎么获取Code?在调试过程中,如果我们向上面一样手动去打开浏览器复制URL,再复制回应过来的URL是很麻烦的。Oauth2的逻辑就是:要求各种客户自己在自己的浏览器里登录帐户,然后给你的App授权。所以这一步redirect_url是躲不过的。但是我们测试过程中,还没来得及专门建一个服务器或网页来接收这个callback回调怎么办呢?有办法!方法一:直接截取我们可以在第一次登录并授权后,复制cookies,然后在测试中直接使用requests带着cookies登录信息去访问,就不再需要手动打开浏览器了:raw_cookies = """ 这里是你复制过来的cookies “““cookies = dict([line.split("=”, 1) for line in raw_cookies.strip().split(”; “)])try: r = requests.get(uri, cookies=cookies, allow_redirects=True)except requests.exceptions.ConnectionError as e: print( ‘[Final URL]: ‘, e.request.url ) authorization_response = e.request.url由于你最开始设置的callback是公网上的某个网址,应该是不存在的(只要你没有设置的话)。所以,这里去request的时候,肯定会报错,且是ConnectionError。所以我们可以将最终报错的URL获取到,这个里面就包含了我们想要的Code码。方法二:更改本地hosts如果本地已经搭建了测试服务器,比如Nginx或Flask,这种方法更简单。比如在供应商中设定的redirect_url为http://example.com/callback,那么只需简单编辑hosts:# /etc/hosts127.0.0.1 example.com那么,只要本机设置了Nginx或Flask等服务器,只需要获取127.0.0.1/callback即可得到需要的内容。 ...

January 25, 2019 · 1 min · jiezi

掌握Web API,开发常见的页面交互功能(进阶一)

学习目标:掌握API和Web API的概念掌握常见的浏览器提供的API的调用方式能通过API开发常见的页面交互功能能够利用搜索引擎解决问题Web APIAPI的概念API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。任何开发语言都有自己的APIAPI的特征输入和输出(I/O)API的使用方法(console.log())Web API的概念浏览器提供的一套操作浏览器功能和页面元素的API(BOM和DOM)此处的Web API特指浏览器提供的API(一组方法),Web API在后面的课程中有其它含义掌握常见的浏览器提供的API的调用方式MDN-Web APIJavaScript的组成ECMAScript - JavaScript的核心定义了javascript的语法规范JavaScript的核心,描述了语言的基本语法和数据类型,ECMAScript是一套标准,定义了一种语言的标准与具体实现无关BOM - 浏览器对象模型一套操作浏览器功能的API通过BOM可以操作浏览器窗口,比如:弹出框、控制浏览器跳转、获取分辨率等DOM - 文档对象模型一套操作页面元素的APIDOM可以把HTML看做是文档树,通过DOM提供的API可以对树上的节点进行操作BOMBOM的概念BOM(Browser Object Model) 是指浏览器对象模型,浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构。BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象。我们在浏览器中的一些操作都可以使用BOM的方式进行编程处理,比如:刷新浏览器、后退、前进、在浏览器中输入URL等BOM的顶级对象windowwindow是浏览器的顶级对象,当调用window下的属性和方法时,可以省略window注意:window下一个特殊的属性 window.name对话框alert()prompt()confirm()页面加载事件onloadwindow.onload = function () { // 当页面加载完成执行 // 当页面完全加载所有内容(包括图像、脚本文件、CSS 文件等)执行}onunloadwindow.onunload = function () { // 当用户退出页面时执行}定时器setTimeout()和clearTimeout()在指定的毫秒数到达之后执行指定的函数,只执行一次// 创建一个定时器,1000毫秒后执行,返回定时器的标示var timerId = setTimeout(function () { console.log(‘Hello World’);}, 1000);// 取消定时器的执行clearTimeout(timerId);setInterval()和clearInterval()定时调用的函数,可以按照给定的时间(单位毫秒)周期调用函数// 创建一个定时器,每隔1秒调用一次var timerId = setInterval(function () { var date = new Date(); console.log(date.toLocaleTimeString());}, 1000);// 取消定时器的执行clearInterval(timerId);location对象location对象是window对象下的一个属性,时候的时候可以省略window对象location可以获取或者设置浏览器地址栏的URLURL统一资源定位符 (Uniform Resource Locator, URL)URL的组成scheme://host:port/path?query#fragmentscheme:通信协议常用的http,ftp,maito等host:主机服务器(计算机)域名系统 (DNS) 主机名或 IP 地址。port:端口号整数,可选,省略时使用方案的默认端口,如http的默认端口为80。path:路径由零或多个’/‘符号隔开的字符串,一般用来表示主机上的一个目录或文件地址。query:查询可选,用于给动态网页传递参数,可有多个参数,用’&‘符号隔开,每个参数的名和值用’=‘符号隔开。例如:name=zsfragment:信息片断字符串,锚点.location有哪些成员?使用chrome的控制台查看查MDNMDN成员assign()/reload()/replace()hash/host/hostname/search/href……history对象back()forward()go()navigator对象userAgent通过userAgent可以判断用户浏览器的类型platform通过platform可以判断浏览器所在的系统平台类型.DOM文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口。在网页上,组织页面(或文档)的对象被组织在一个树形结构中,用来表示文档中对象的标准模型就称为DOM。Document Object Model的历史可以追溯至1990年代后期微软与Netscape的“浏览器大战”,双方为了在JavaScript与JScript一决生死,于是大规模的赋予浏览器强大的功能。微软在网页技术上加入了不少专属事物,既有VBScript、ActiveX、以及微软自家的DHTML格式等,使不少网页使用非微软平台及浏览器无法正常显示。DOM即是当时蕴酿出来的杰作。DOM又称为文档树模型文档:一个网页可以称为文档节点:网页中的所有内容都是节点(标签、属性、文本、注释等)元素:网页中的标签属性:标签的属性DOM经常进行的操作获取元素动态创建元素对元素进行操作(设置其属性或调用其方法)事件(什么时机做相应的操作)获取页面元素根据id获取元素var div = document.getElementById(‘main’);console.log(div);// 获取到的数据类型 HTMLDivElement,对象都是有类型的// HTMLDivElement <– HTMLElement <– Element <– Node <– EventTarget根据标签名获取元素var divs = document.getElementsByTagName(‘div’);for (var i = 0; i < divs.length; i++) { var div = divs[i]; console.log(div);}根据name获取元素*var inputs = document.getElementsByName(‘hobby’);for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; console.log(input);}根据类名获取元素var mains = document.getElementsByClassName(‘main’);for (var i = 0; i < mains.length; i++) { var main = mains[i]; console.log(main);}根据选择器获取元素var text = document.querySelector(’#text’);console.log(text);var boxes = document.querySelectorAll(’.box’);for (var i = 0; i < boxes.length; i++) { var box = boxes[i]; console.log(box);}总结掌握getElementById()getElementsByTagName()了解getElementsByName()getElementsByClassName()querySelector()querySelectorAll()事件事件:触发-响应机制Event接口表示在DOM中发生的任何事件,一些是用户生成的(例如鼠标或键盘事件),而其他由API生成。事件三要素事件源:触发(被)事件的元素事件类型:事件的触发方式(例如鼠标点击或键盘点击)事件处理程序:事件触发后要执行的代码(函数形式)事件的基本使用var box = document.getElementById(‘box’);box.onclick = function() { console.log(‘代码会在box被点击后执行’); };属性操作非表单元素的属性href、title、id、src、classNamevar link = document.getElementById(’link’);console.log(link.href);console.log(link.title);var pic = document.getElementById(‘pic’);console.log(pic.src);innerHTML和innerTextvar box = document.getElementById(‘box’);box.innerHTML = ‘我是文本<p>我会生成为标签</p>’;console.log(box.innerHTML);box.innerText = ‘我是文本<p>我不会生成为标签</p>’;console.log(box.innerText);HTML转义符" “‘ ‘& &< < //less than 小于 &gt; // greater than 大于空格 © ©表单元素属性value 用于大部分表单元素的内容获取(option除外)type 可以获取input标签的类型(输入框或复选框等)disabled 禁用属性checked 复选框选中属性selected 下拉菜单选中属性自定义属性操作getAttribute() 获取标签行内属性setAttribute() 设置标签行内属性removeAttribute() 移除标签行内属性与element.属性的区别: 上述三个方法用于获取任意的行内属性。样式操作var box = document.getElementById(‘box’);box.style.width = ‘100px’;box.style.height = ‘100px’;box.style.backgroundColor = ‘red’;类名操作var box = document.getElementById(‘box’);box.className = ‘clearfix’;创建元素的三种方式document.write()document.write(‘新设置的内容<p>标签也可以生成</p>’);innerHTMLvar box = document.getElementById(‘box’);box.innerHTML = ‘新内容<p>新标签</p>’;document.createElement()var div = document.createElement(‘div’);document.body.appendChild(div);性能问题innerHTML方法由于会对字符串进行解析,需要避免在循环内多次使用。可以借助字符串或数组的方式进行替换,再设置给innerHTML优化后与document.createElement性能相近节点操作var body = document.body;var div = document.createElement(‘div’);body.appendChild(div);var firstEle = body.children[0];body.insertBefore(div,firstEle);body.removeChild(firstEle);var text = document.createElement(‘p’);body.replaceChild(text, div);节点层级重点讲父子属性,兄弟属性画图讲解var box = document.getElementById(‘box’);console.log(box.parentNode);console.log(box.childNodes);console.log(box.children);console.log(box.nextSibling);console.log(box.previousSibling);console.log(box.firstChild);console.log(box.lastChild);注意childNodes和children的区别,childNodes获取的是子节点,children获取的是子元素nextSibling和previousSibling获取的是节点,获取元素对应的属性是nextElementSibling和previousElementSibling获取的是元素nextElementSibling和previousElementSibling有兼容性问题,IE9以后才支持总结节点操作,方法 appendChild() insertBefore() removeChild() replaceChild()节点层次,属性 parentNode childNodes children nextSibling/previousSibling firstChild/lastChild事件详解注册/移除事件的三种方式var box = document.getElementById(‘box’);box.onclick = function () { console.log(‘点击后执行’);};box.onclick = null;box.addEventListener(‘click’, eventCode, false);box.removeEventListener(‘click’, eventCode, false);box.attachEvent(‘onclick’, eventCode);box.detachEvent(‘onclick’, eventCode);function eventCode() { console.log(‘点击后执行’);}事件的三个阶段捕获阶段当前目标阶段冒泡阶段事件对象.eventPhase属性可以查看事件触发时所处的阶段事件对象的属性和方法event.type 获取事件类型clientX/clientY 所有浏览器都支持,窗口位置pageX/pageY IE8以前不支持,页面位置event.target || event.srcElement 用于获取触发事件的元素event.preventDefault() 取消默认行为阻止事件传播的方式标准方式 event.stopPropagation();IE低版本 event.cancelBubble = true; 标准中已废弃常用的鼠标和键盘事件onmouseup 鼠标按键放开时触发onmousedown 鼠标按键按下触发onmousemove 鼠标移动触发onkeyup 键盘按键按下触发onkeydown 键盘按键抬起触发 ...

January 23, 2019 · 2 min · jiezi

Kong Api 网关使用 docker 部署

Kong 镜像: https://hub.docker.com/_/kong官网给定的用户安装手册上并没有设置 PG 的密码,导致如下问题无法启动nginx: [error] init_by_lua error: /usr/local/share/lua/5.1/kong/init.lua:277: [PostgreSQL error] failed to >retrieve server_version_num: connection refusedstack traceback:[C]: in function ‘assert’/usr/local/share/lua/5.1/kong/init.lua:277: in function ‘init’init_by_lua:3: in main chunk后在 issues 中找到问题原因及解决方法(里面还有个docker-compose):https://github.com/Kong/docke…使用 docker 的安装Kong 使用 postgresql 或 cassandra 存储数据,这里我们选用 PG# 创建 pg 数据库 容器docker run -d –name kong-database -p 5432:5432 -e “POSTGRES_USER=kong” -e “POSTGRES_DB=kong” -e “POSTGRES_PASSWORD=your_pg_password” \postgres:9.6# kong 数据迁移到 pgdocker run –rm --link kong-database:kong-database \ #将 kong-database 容器的地址引入注册到本容器-e “KONG_DATABASE=postgres” -e “KONG_PG_HOST=kong-database” -e “KONG_PG_PASSWORD=your_pg_password” \ # 官方文档未给出此参数 PG 可能不支持无密登录了-e “KONG_CASSANDRA_CONTACT_POINTS=kong-database” \kong kong migrations bootstrap#创建 kong 容器docker run -d –name kong --link kong-database:kong-database -e “KONG_DATABASE=postgres” -e “KONG_PG_HOST=kong-database” -e “KONG_PG_PASSWORD=your_pg_password” -e “KONG_CASSANDRA_CONTACT_POINTS=kong-database” -e “KONG_PROXY_ACCESS_LOG=/dev/stdout” -e “KONG_ADMIN_ACCESS_LOG=/dev/stdout” -e “KONG_PROXY_ERROR_LOG=/dev/stderr” -e “KONG_ADMIN_ERROR_LOG=/dev/stderr” -e “KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl” -p 8000:8000 \ # http 代理端口-p 8443:8443 \ # https 代理端口-p 8001:8001 \ # http 管理接口-p 8444:8444 \ # https 管理接口kong#查看是否启动docker ps#如未启动通过日志查看问题docker logs kong#如正常启动 可访问管理Api(替换成你的IP)curl -X GET http://192.168.20.6:8001因为 kong 服务是在容器中,所以设 KONG_ADMIN_LISTEN 为全局监听才能通过宿主机IP代理访问,宿主机则需对相应的映射端口做访问限制,如本机/内网访问,且不应该对外网可访问,不然谁都可以改你的Api网关策略了。自定义 kong 配置文件kong docker 镜像的配置文件路径为 /etc/kong/kong.conf如需自定义配置文件,自行挂载即可。kong 配置项手册:https://docs.konghq.com/1.0.x……-v /opt/kong/kong.conf:/etc/kong/kong.conf…管理网关的API的使用教程这里就不写了,自行觅食吧~简单的看看下面这篇可以的Kong 集成 Jwt 插件:https://www.cnkirito.moe/kong…kong服务网关API:https://www.jianshu.com/p/ef6… ...

January 17, 2019 · 1 min · jiezi

Kubernetes API 与 Operator,不为人知的开发者战争

如果我问你,如何把一个 etcd 集群部署在 Google Cloud 或者阿里云上,你一定会不假思索的给出答案:当然是用 etcd Operator!实际上,几乎在一夜之间,Kubernetes Operator 这个新生事物,就成了开发和部署分布式应用的一项事实标准。时至今日,无论是 etcd、TiDB、Redis,还是 Kafka、RocketMQ、Spark、TensorFlow,几乎每一个你能叫上名字来的分布式项目,都由官方维护着各自的 Kubernetes Operator。而 Operator 官方库里,也一直维护着一个知名分布式项目的 Operator 汇总。https://github.com/operator-framework/awesome-operators短短一年多时间,这个列表的长度已经增长了几十倍。 而且更有意思的是,如果你仔细翻阅这个 Operator 列表,你就不难发现这样一个有趣的事实:现今 Kubernetes Operator 的意义,恐怕已经远远超过了“分布式应用部署”的这个原始的范畴,而已然成为了容器化时代应用开发与发布的一个全新途径。所以,你才会在这个列表里看到,Android SDK 的开发者们,正在使用 Operator “一键”生成和更新 Android 开发环境;而 Linux 系统工程师们,则在使用Operator “一键”重现性能测试集群。如果说,Docker 镜像的提出,完成了应用静态描述的标准化。那么 Kubernetes Operator 的出现,终于为应用的动态描述提出了一套行之有效的实现规范。更为重要的是,对于 TiDB、Kafka、RocketMQ 等分布式应用的开发者来说,这些应用运行起来之后的动态描述,才是对一个分布式应用真正有意义的信息。而在此之前,用户如果要想将 TiDB、Kafka 这样的分布式应用很好的使用起来,就不得不去尝试编写一套复杂的管理脚本,甚至为此学习大量与项目本身无关的运维知识。更为麻烦的是,这些脚本、知识、和经验,并没有一个很好的办法能够有效的沉淀下来。而任何一种技术的传授,如果严重依赖于口口相传而不是固化的代码和逻辑的话,那么它的维护成本和使用门槛,就可以说是“灾难级”的。所以说,Kubernetes Operator 发布之初最大的意义,就在于它将分布式应用的使用门槛直接降到了最低。那么这个门槛具体有多低呢?一般来说,无论这个分布式应用项目有多复杂,只要它为用户提供了 Operator,那么这个项目的使用就只需要两条命令即可搞定,以 Kafka 为例:这两条命令执行完成后,一个 Kafka 集群运行所需的节点,以及它们所依赖的 ZooKeeper 节点,就会以容器的方式自动出现在你的 Kubernetes 集群里了。不过,简化运维和部署,其实只是 Operator 在用户层面的表象。而在更底层的技术层面,Operator 最大的价值,在于它为“容器究竟能不能管理有状态应用”这个颇具争议话题,画上了一个优雅的句号。要知道,在2014-2015年的时候,伴随着 Docker 公司和 Docker 项目的走红,整个云计算生态几乎都陷入了名为“容器”的狂热当中。然而,相比于 “容器化”浪潮的如火如荼,这个圈子却始终对“有状态应用”讳莫如深。事实上,有状态应用(比如, 前面提到的Kafka )跟无状态应用(比如,一个简单的Jave Web网站)的不同之处,就在于前者对某些外部资源有着绑定性的依赖,比如远程存储,或者网络设备,以及,有状态应用的多个示例之间往往有着拓扑关系。这两种设计,在软件工程的世界里可以说再普通不过了,而且我们几乎可以下这样一个结论:所有的分布式应用都是有状态应用。但是,在容器的世界里,分布式应用却成了一个“异类”。我们知道,容器的本质,其实就是一个被限制了“世界观”的进程。在这种隔离和限制的大基调下,容器技术本身的“人格基因”,就是对外部世界(即:宿主机)的“视而不见”和“充耳不闻”。所以我们经常说,容器的“状态”一定是“易失”的。其实,容器对它的“小世界”之外的状态和数据漠不关心,正是这种“隔离性”的主要体现。但状态“易失”并不能说是容器的缺陷:我们既然对容器可以重现完整的应用执行环境的“一致性”拍手称赞,那就必然要对这种能力背后的限制了然于心。这种默契,也正是早期的 Docker 公司所向披靡的重要背景:在这个阶段,相比于“容器化”的巨大吸引力,开发者是可以暂时接受一部分应用不能运行在容器里的。而分布式应用容器化的困境,其实就在于它成为了这种“容器化”默契的“终极破坏者”。一个应用本身可以拥有多个可扩展的实例,这本来是容器化应用令人津津乐道的一个优势。但是一旦这些实例像分布式应用这样具有了拓扑关系,以及,这些实例本身不完全等价的时候,容器化的解决方案就再次变得“丑陋”起来:这种情况下,应用开发者们不仅又要为这些容器实例编写一套难以维护的管理脚本,还必须要想办法应对容器重启后状态丢失的难题。而这些容器状态的维护,实际上往往需要打破容器的隔离性、让容器对外部世界有所感知才能做到,这就使得容器化与有状态,成为了两种完全相悖的需求。不过,从上面的叙述中相信你也应该已经察觉到,分布式应用容器化的难点,并不在于容器本身有什么重大缺陷,而在于我们一直以来缺乏一种对“状态”的合理的抽象与描述,使得状态可以和容器进程本身解耦开来。这也就解释了为什么,在 Kubernetes 这样的外部编排框架逐渐成熟起了之后,业界才逐渐对有状态应用管理开始有了比较清晰的理解和认识。而我们知道, Kubernetes 项目最具价值的理念,就是它围绕 etcd 构建出来的一套“面向终态”编排体系,这套体系在开源社区里,就是大名鼎鼎的“声明式 API”。“声明式 API”的核心原理,就是当用户向 Kubernetes 提交了一个 API 对象的描述之后,Kubernetes 会负责为你保证整个集群里各项资源的状态,都与你的 API 对象描述的需求相一致。更重要的是,这个保证是一项“无条件的”、“没有期限”的承诺:对于每个保存在 etcd 里的 API 对象,Kubernetes 都通过启动一种叫做“控制器模式”(Controller Pattern)的无限循环,不断检查,然后调谐,最后确保整个集群的状态与这个 API 对象的描述一致。比如,你提交的 API 对象是一个应用,描述的是这个应用必须有三个实例,那么无论接下来你的 API 对象发生任何“风吹草动”,控制器都会检查一遍这个集群里是不是真的有三个应用实例在运行。并且,它会根据这次检查的结果来决定,是不是需要对集群做某些操作来完成这次“调谐”过程。当然,这里控制器正是依靠 etcd 的 Watch API 来实现对 API 对象变化的感知的。在整个过程中,你提交的 API 对象就是 Kubernetes 控制器眼中的“金科玉律”,是接下来控制器执行调谐逻辑要达到的唯一状态。这就是我们所说的“终态”的含义。而 Operator 的设计,其实就是把这个“控制器”模式的思想,贯彻的更加彻底。在 Operator 里,你提交的 API 对象不再是一个单体应用的描述,而是一个完整的分布式应用集群的描述。这里的区别在于,整个分布式应用集群的状态和定义,都成了Kubernetes 控制器需要保证的“终态”。比如,这个应用有几个实例,实例间的关系如何处理,实例需要把数据存储在哪里,如何对实例数据进行备份和恢复,都是这个控制器需要根据 API 对象的变化进行处理的逻辑。从上述叙述中,你就应该能够明白, Operator 其实就是一段代码,这段代码 Watch 了 etcd 里一个描述分布式应用集群的API 对象,然后这段代码通过实现 Kubernetes 的控制器模式,来保证这个集群始终跟用户的定义完全相同。而在这个过程中,Operator 也有能力利用 Kubernetes 的存储、网络插件等外部资源,协同的为应用状态的保持提供帮助。所以说,Operator 本身在实现上,其实是在 Kubernetes 声明式 API 基础上的一种“微创新”。它合理的利用了 Kubernetes API 可以添加自定义 API 类型的能力,然后又巧妙的通过 Kubernetes 原生的“控制器模式”,完成了一个面向分布式应用终态的调谐过程。而 Operator 本身在用法上,则是一个需要用户大量编写代码的的开发者工具。 不过,这个编写代码的过程,并没有像很多人当初料想的那样导致 Operator 项目走向小众,反而在短短三年的时间里, Operator 就迅速成为了容器化分布式应用管理的事实标准。时至今日,Operator 项目的生态地位已经毋庸置疑。就在刚刚结束的2018年 KubeCon 北美峰会上,Operator 项目和大量的用户案例一次又一次出现在聚光灯前,不断的印证着这个小小的“微创新”对整个云计算社区所产生的深远影响。不过,在 Operator 项目引人瞩目的成长经历背后,你是否考虑过这样一个问题:Kubernetes 项目一直以来,其实都内置着一个管理有状态应用的能力叫作 StatefulSet。而如果你稍微了解 Kubernetes 项目的话就不难发现,Operator 和 StatefulSet,虽然在对应用状态的抽象上有所不同,但它们的设计原理,几乎是完全一致的,即:这两种机制的本质,都是围绕Kubernetes API 对象的“终态”进行调谐的一个控制器(Controller)而已。可是,为什么在一个开源社区里,会同时存在这样的两个核心原理完全一致、设计目标也几乎相同的有状态应用管理方案呢?作为 CoreOS 公司后来广为人知的“左膀右臂”之一(即:etcd 和 Operator),Operator 项目能够在 Kubernetes 生态里争取到今天的位置,是不是也是 CoreOS 公司的开源战略使然呢?事实上,Operator 项目并没有像很多人想象的那样出生就含着金钥匙。只不过,在当时的确没有人能想到,当 CoreOS 的两名工程师带着一个业余项目从一间平淡无奇的公寓走出后不久,一场围绕着 Kubernetes API 生态、以争夺“分布式应用开发者”为核心的的重量级角逐,就徐徐拉开了序幕。*2016 年秋天,原 CoreOS 公司的工程师邓洪超像往常一样,来到了同事位于福斯特城(Foster City)的公寓进行结对编程。每周四相约在这里结对,是这两位工程师多年来约定俗成的惯例。不过,与以往不同的是,相比于往常天马行空般的头脑风暴,这一次,这两位工程师的脑子里正在琢磨着的,是一个非常“接地气”的小项目。我们知道,Kubernetes 项目实现“容器编排”的核心,在于一个叫做“控制器模式”的机制,即:通过对 etcd 里的 API 对象的变化进行监视(Watch),Kubernetes 项目就可以在一个叫做 Controller 的组件里对这些变化进行响应。而无论是 Pod 等应用对象,还是 iptables、存储设备等服务对象,任何一个 API 对象发生变化,那么 Kubernetes 接下来需要执行的响应逻辑,就是对应的 Controller 里定义的编排动作。所以,一个自然而然的想法就是,作为 Kubernetes 项目的用户,我能不能自己编写一个 Controller 来定义我所期望的编排动作呢?比如:当一个 Pod 对象被更新的时候,我的 Controller 可以在“原地”对 Pod 进行“重启”,而不是像 Deployment 那样必须先删除 Pod,然后再创建 Pod。这个想法,其实是很多应用开发者以及 PaaS 用户的强烈需求,也是一直以来萦绕在 CoreOS 公司 CEO Alex Polvi 脑海里的一个念头。而在一次简单的内部讨论提及之后,这个念头很快就激发出了两位工程师的技术灵感,成为了周四结对编程的新主题。而这一次,他们决定把这个小项目,起名叫做:Operator。所以顾名思义,Operator 这个项目最开始的初衷,是用来帮助开发者实现运维(Operate)能力的。但 Operator 的核心思想,却并不是“替开发者做运维工作”,而是“让开发者自己编写运维工具”。更有意思的是,这个运维工具的编写标准,或者说,编写 Operator 代码可以参考的模板,正是 Kubernetes 的“控制器模式(Controller Pattern)”。前面已经说过, Kubernetes 的“控制器模式”,是围绕着比如 Pod 这样的 API 对象,在 Controller 通过响应它的增删改查来定义对 Pod 的编排动作。而 Operator 的设计思路,就是允许开发者在 Kubernetes 里添加一个新的 API 对象,用来描述一个分布式应用的集群。然后,在这个 API 对象的 Controller 里,开发者就可以定义对这个分布式应用集群的运维动作了。举个例子, 假设下面这个 YAML 文件定义的,是一个 3 节点 etcd 集群的描述:有了这样一个 etcdCluster 对象,那么开发者接下来要做的事情,就是编写一个 etcdCluster Controller,使得当任何用户提交这样一个 YAML 文件给 Kubernetes 之后,我们自己编写的 Controller 就会响应 etcdCluster “增加”事件,为用户创建出 3 个节点的 etcd 集群出来。然后,它还会按照我们在 Controller 编写的事件响应逻辑,自动的对这个集群的节点更新、删除等事件做出处理,执行我定义的其他运维功能。像这样一个 etcdCluster Controller,就是 etcd Operator 的核心组成部分了。而作为 etcd 的开发者,CoreOS 的两位工程师把对 etcd 集群的运维工作编写成 Go 语言代码,一点都不困难。可是,要完成这个 Operator 真正困难在于:Kubernetes 只认识 Pod、Node、Service 等这些 Kubernetes 自己原生的 API 对象,它怎么可能认识开发者自己定义的这个 etcdCluster 对象呢?在当时, Kubernetes 项目允许用户自己添加 API 对象的插件能力,叫做 Third Party Resource,简称:TPR。TPR 允许你提交一个 YAML 文件,来定义你想要的的新 API 对象的名字,比如:etcdCluster;也允许你定义这个对象允许的合法的属性,比如:int 格式的 size 字段, string 格式的 version 字段。然后,你就可以提交一个具体的 etcdCluster 对象的描述文件给 Kubernetes,等待该对应的 Controller 进行处理。而这个 Controller,就是 Operator 的主干代码了。所以接下来,CoreOS 的两位工程师轻车熟路,在 Operator 里对 etcdCluster 对象的增、删、改事件的响应位置,写上了创建、删除、更新 etcd 节点的操作逻辑。然后,调试运行,看着一个 etcd 集群按照 YAML 文件里的描述被创建起来。大功告成!就这样,在一个普通的周四下午,世界上第一个 Operator 诞生在了湾区的一所公寓当中。而对于 CoreOS 的两位工程师来说,编写这个小工具的主要目的,就是借助 Kubernetes 的核心原理来自动化的管理 etcd 集群,更重要的是,不需要使用 Kubernetes 里自带的 StatefulSet。你可能已经知道,Kubernetes 里本身就内置了一个叫做 StatefulSet 的功能,是专门用来管理有状态应用的。而 StatefulSet 的核心原理,其实是对分布式应用的两种状态进行了保持:分布式应用的拓扑状态,或者说,节点之间的启动顺序;分布式应用的存储状态,或者说,每个节点依赖的持久化数据。可是,为了能够实现上述两种状态的保持机制,StatefulSet 的设计就给应用开发者带来了额外的束缚。比如,etcd 集群各节点之间的拓扑关系,并不依赖于节点名字或者角色(比如 Master 或者 Slave)来确定,而是记录在每个 etcd 节点的启动参数当中。这使得 StatefulSet 通过“为节点分配有序的 DNS 名字”的拓扑保持方式,实际上没有了用武之地,反而还得要求开发者在节点的启动命令里添加大量的逻辑来生成正确的启动命令,非常不优雅。类似的,对于存储状态来说,etcd 集群对数据的备份和恢复方法,也跟 StatefulSet 依赖的的远程持久化数据卷方案并没有太大关系。不难看到, StatefulSet 其实比较适用于应用本身节点管理能力不完善的项目,比如 MySQL。而对于 etcd 这种已经借助 Raft 实现了自管理的分布式应用来说, StatefulSet 的使用方法和带来的各种限制,其实是非常别扭的。而带着工程师特有的较真儿精神,邓洪超和他的同事借助 Kubernetes 原生的扩展机制实现的,正是一个比 StatefulSet 更加灵活、能够把控制权重新交还给开发者的分布式应用管理工具。他们把这个工具起名叫做 Operator,并在几个月后的 KubeCon 上进行了一次 Demo ,推荐大家尝试使用 Operator 来部署 etcd 集群。没有人能想到的是,这个当时还处于 PoC 状态的小项目一经公布,就立刻激发起了整个社区的模仿和学习的热潮。很快,大量的应用开发者纷纷涌进 Kubernetes 社区,争先恐后的宣布自己的分布式项目可以通过 Operator 运行起来。而敏锐的公有云提供商们很快看出了这其中的端倪:Operator 这个小框架,已然成为了分布式应用和有状态应用“上云”的必经之路。Prometheus,Rook,伴随着越来越多的、以往在容器里运行起来困难重重的应用,通过 Operator 走上了 Kubernetes 之后,Kubernetes 项目第一次出现在了开发者生态的核心位置。这个局面,已经远远超出了邓洪超甚至 CoreOS 公司自己的预期。更重要的是,不同于 StatefulSet 等 Kubernetes 原生的编排概念,Operator 依赖的 Kubernetes 能力,只有最核心的声明式 API 与控制器模式;Operator 具体的实现逻辑,则编写在自定义 Controller 的代码中。这种设计给开发者赋予了极高的自由度,这在整个云计算和 PaaS 领域的发展过程中,都是非常罕见的。此外,相比于 Helm、Docker Compose 等描述应用静态关系的编排工具,Operator 定义的乃是应用运行起来后整个集群的动态逻辑。得益于 Kubernetes 项目良好的声明式 API 的设计和开发者友好的 API 编程范式,Operator 在保证上述自由度的同时,又可以始终如一的展现出清晰的架构和设计逻辑,使得应用的开发者们,可以通过复制粘贴就快速搭建出一个 Operator 的框架,然后专注于填写自己的业务逻辑。在向来讲究“用脚投票”的开发者生态当中,Operator 这样一个编程友好、架构清晰、方便代码复制粘贴的小工具,本身就已经具备了某些成功的特质。然而,Operator 的意外走红,并没有让 CoreOS 公司“一夜成名”,反而差点将这个初出茅庐的项目,扼杀在萌芽状态。在当时的 Kubernetes 社区里,跟应用开发者打交道并不是一个非常常见的事情。而 Operator 项目的诞生,却把 Kubernetes 项目第一次拉近到了开发者的面前,这让整个社区感觉了不适应。而作为 Kubernetes 项目 API 治理的负责人,Google 团队对这种冲突的感受最为明显。对于 Google 团队来说,Controller 以及控制器模式,应该是一个隐藏在 Kubernetes 内部实现里的核心机制,并不适合直接开放给开发者来使用。退一步说,即使开放出去,这个 Controller 的设计和用法,也应该按照 Kubernetes 现有的 API 层规范来进行,最好能成为 Kubernetes 内置 Controller Manager 管理下的一部分。可是, Operator 却把直接编写 Controller 代码的自由度完全交给了开发者,成为了一个游离于 Kubernetes Controller Manager 之外的外部组件。带着这个想法,社区里的很多团队从 Operator 项目诞生一开始,就对它的设计和演进方向提出了质疑,甚至建议将 Operator 的名字修改为 Custom Kubernetes Controller。而无巧不成书,就在 Google 和 CoreOS 在 Controller 的话语权上争执不下的时候, Kubernetes 项目的发起人之一 Brendan Burns 突然宣布加入了微软,这让 Google 团队和 Operator 项目的关系一下子跌倒了冰点。你可能会有些困惑:Brendan Burns 与 Kubernetes 的关系我是清楚的,但这跟 Operator 又有什么瓜葛吗?实际上,你可能很难想到,Brendan Burns 和他的团队,才是 TPR (Third Party Resource)这个特性最初的发起人。所以,几乎在一夜之间,Operator 项目链路上的每一个环节,都与 Google 团队完美的擦肩而过。眼睁睁的看着这个正冉冉升起的开发者工具突然就跟自己完全没了关系,这个中滋味,确实不太好受。于是,在 2017年初,Google 团队和 RedHat 公司开始主动在社区推广 UAS(User Aggregated APIServer),也就是后来 APIServer Aggregator 的雏形。APIServer Aggregator 的设计思路是允许用户编写一个自定义的 APIServer,在这里面添加自定义 API。然后,这个 APIServer 就可以跟 Kubernetes 原生的 APIServer 绑定部署在一起统一提供服务了。不难看到,这个设计与 Google 团队认为自定义 API 必须在 Kubernetes 现有框架下进行管理的想法还是比较一致的。紧接着,RedHat 和 Google 联盟开始游说社区使用 UAS 机制取代 TPR,并且建议直接从 Kubernetes 项目里废弃 TPR 这个功能。一时间,社区里谣言四起,不少已经通过 TPR 实现的项目,也开始转而使用 UAS 来重构以求自保。 而 Operator 这个严重依赖于 TPR 的小项目,还没来得及发展壮大,就被推向了关闭的边缘。面对几乎要与社区背道而驰的困境,CoreOS 公司的 CTO Brandon Philips 做出了一个大胆的决定:让社区里的所有开发者发声,挽救 TPR 和 Operator。2017 年 2月,Brandon Philips 在 GitHub 上开了一个帖子(Gist), 号召所有使用 TPR 或者 Operator 项目的开发者在这里留下的自己的项目链接或者描述。这个帖子,迅速的成为了当年容器技术圈最热门的事件之一,登上了 HackerNews 的头条。有趣的是,这个帖子直到今天也仍然健在,甚至还在被更新,你可以点击这个链接去感受一下当时的盛况。https://gist.github.com/philips/a97a143546c87b86b870a82a753db14c而伴随着 Kubernetes 项目的迅速崛起,短短一年时间不到,夹缝中求生存的 Operator 项目,开始对公有云市场产生了不可逆转的影响,也逐步改变了开发者们对“云”以及云上应用开发模式的基本认知。甚至就连 Google Cloud 自己最大的客户之一 Snapchat ,也成为了 Operator 项目的忠实用户。在来自社区的巨大压力下,在这个由成千上万开发者们自发维护起来的 Operator 生态面前,Google 和 RedHat 公司最终选择了反省和退让。有意思的是,这个退让的结果,再一次为这次闹剧增添了几分戏剧性。就在 Brandon Phillips 的开发者搜集帖发布了不到三个月后,RedHat 和 Google 公司的工程师突然在 Kubernetes 社区里宣布:TPR 即将被废弃,取而代之的是一个名叫 CRD,Custom Resource Definition 的东西。于是,开发者们开始忧心忡忡的按照文档,将原本使用 TPR 的代码都升级成 CRD。而就在这时,他们却惊奇的发现,这两种机制除了名字之外,好像并没有任何不同。所谓的升级工作,其实就是将代码里的 TPR 字样全局替换成 CRD 而已。难道,这只是虚惊一场?其实,很少有人注意到,在 TPR 被替换成 CRD 之后,Brendan Burns 和微软团队就再也没有出现在“自定义 API”这个至关重要的领域里了。而 CRD 现在的负责人,都是来自 Google 和 RedHat 的工程师。在这次升级事件之后不久,CoreOS 公司在它的官方网站上发布了一篇叫做:TPR Is Dead! Kubernetes 1.7 Turns to CRD 的博客(https://coreos.com/blog/custom-resource-kubernetes-v17),旨在指导用户从 TRP 升级成 CRD。不过,现在回头再看一眼这篇文章,平淡无奇的讲述背后,你能否感受到当年这场“开发者战争”的蛛丝马迹呢?其实,Operator 并不平坦的晋级之路,只是 Kubernetes API 生态风起云涌的冰山一角。几乎在每个星期,甚至每一天,都有太多围绕着 Kubernetes 开发者生态的角逐,在这个无比繁荣的社区背后,以不为人知的方式开始或者谢幕。而这一切纷争的根本原因却无比直白。Kubernetes 项目,已经被广泛认可为云计算时代应用开发者们的终端入口。这正是为何,无论是 Google、微软,还是 CoreOS 以及 Heptio,所有这个生态里的大小玩家,都在不遗余力的在 Kubernetes API 层上捍卫着自己的话语权,以期在这个未来云时代的开发者入口上,争取到自己的一席之地。而在完成了对收 CoreOS 的收购之后,RedHat 终于在这一领域拿到了可以跟 Google 和微软一较高低的关键位置。2018年,RedHat 不失时机的发布了 Operator Framework,希望通过 Operator 周边工具和生态的进一步完善,把 Operator 确立成为分布式应用开发与管理的关键依赖。而伴随着 Operator 越来越多的介入到应用开发和部署流程之后, Kubernetes API 一定会继续向上演进,进一步影响开发者的认知和编程习惯。这,已经成为了云计算生态继续发展下去的必然趋势。而作为这个趋势坚定不移的贯彻者,无论是 Istio,还是 Knative,都在用同样的经历告诉我们这样的道理:只有构建在 Kubernetes 这个云时代基础设施事实标准上的开发者工具,才有可能成为下一个开发者领域的 “Operator” 。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 11, 2019 · 4 min · jiezi

Yii2.0 RESTful API 之版本控制

Yii2.0 RESTful API 之版本控制之前我写过两篇关于 Yii2.0 RESTful API 如何搭建,以及 认证 等处理,但是没有涉及到版本管理,今天就来谈谈版本管理如何实现。索性就从头开始一步一步搭建吧,但是关于一些概念以及使用本篇就不一一解释了,可以参考 第一篇 Yii2.0 RESTful API 基础配置教程 进行配置安装Yii2.0通过 Composer 安装这是安装Yii2.0的首选方法。如果你还没有安装 Composer,你可以按照这里的说明进行安装。安装完 Composer,运行下面的命令来安装 Composer Asset 插件:composer global require “fxp/composer-asset-plugin:^1.2.0"安装高级的应用程序模板,运行下面的命令:composer create-project yiisoft/yii2-app-advanced yii-api 2.0.14拷贝backend目录,命名为api打开api\config\main.php 修改id,controllerNamespace:return [ ‘id’ => ‘app-api’, ‘basePath’ => dirname(DIR), ‘controllerNamespace’ => ‘api\controllers’,]初始化高级模板在初始化之前不妨先看下这篇文章cd advancedphp init打开common\config\main.php开启url路由美化规则’urlManager’ => [ ’enablePrettyUrl’ => true, ‘showScriptName’ => false, ‘rules’ => [ ],],打开common\config\bootstrap.php添加以下别名Yii::setAlias(’@api’, dirname(dirname(DIR)) . ‘/api’);ok,以上工作准备完毕,接下来进入正题,关于版本更多介绍可以参考 权威指南 ,这里不过多解释(PS:主要我也不会……)我的理解:Yii2 的版本你可以理解为不同的模块,每一个版本就是一个新的模块,比如常见的v1,v2等。模块的搭建关于如何生成模块,我们可以使用GII来进行生成.配置 GII打开 api/config/main-local.php 文件 修改如下:if (!YII_ENV_TEST) { // configuration adjustments for ‘dev’ environment $config[‘bootstrap’][] = ‘debug’; $config[‘modules’][‘debug’] = [ ‘class’ => ‘yii\debug\Module’, ]; $config[‘bootstrap’][] = ‘gii’; $config[‘modules’][‘gii’] = [ ‘class’ => ‘yii\gii\Module’, ‘allowedIPs’ => [‘127.0.0.1’, ‘’] ];}我这里因为使用的是 Homestead ,默认是不允许访问 GII 的,所以得加上 ‘allowedIPs’ => [‘127.0.0.1’, ‘’] ,否则会出现 Forbidden (#403), 你可以根据自己的需要来进行配置,或者不配置生成Modules浏览器中输入 http://your host/gii ,可以看到 Module Generator ,点击 StartModules Class 中输入:api\modules\v1\ModuleModule ID 中输入v1,(一般会自动输入)点击 Preview最后点击 Generate 进行生成配置模块打开 api/config/main.php 文件,修改 modules’modules’ => [ ‘v1’=>[ ‘class’=>‘api\modules\v1\Module’, ],],接着修改 urlManager’urlManager’ => [ ’enablePrettyUrl’ => true, ’enableStrictParsing’ => true, ‘showScriptName’ => false, ‘rules’ => [ [‘class’ => ‘yii\rest\UrlRule’, ‘controller’ => ‘v1/default’, ’extraPatterns’=>[ ‘GET index’=>‘index’, ], ], ],],基于以上,Yii2.0 RESTFul API 就实现了版本管理,我们可以通过如下地址进行访问:http://localhost/v1/defaults多说一点,我上方的地址是已经映射到api/web目录,请根据自己的实际情况进行配置打开刚生成的 modules 文件目录,可以看到里面存在一个 v1 的目录,可以看到该目录还有一个controllers,以及一个 views 目录,我们刚才访问的 defaults 其实就是这两个文件,和传统的web项目一样控制器渲染视图好了,你可能知道了,我们以后的控制器代码就放到 modules/v1/controllers 里了刚才仅仅是默认GII为我们生成的代码,因为我们是API,所以 views 目录,我们一般情况下用不到。新建一个 rest 的控制器在 modules\v1\controllers 下新建 UserController<?phpnamespace api\modules\v1\controllers;use yii\rest\Controller;/** * User controller for the v1 module /class UserController extends Controller{ /* * @return string */ public function actionIndex() { return ’this is v1/user’; }}修改 api/config/main.php 中的urlManager’urlManager’ => [ ’enablePrettyUrl’ => true, ’enableStrictParsing’ => true, ‘showScriptName’ => false, ‘rules’ => [ [‘class’ => ‘yii\rest\UrlRule’, ‘controller’ => ‘v1/default’, ’extraPatterns’=>[ ‘GET index’=>‘index’, ], ], [‘class’ => ‘yii\rest\UrlRule’, ‘controller’ => ‘v1/user’, ’extraPatterns’=>[ ‘GET index’=>‘index’, ], ], ],],试着访问下http://localhost/v1/users/indexok,以上就是 Yii2.0 版本管理的实现方式格式化响应修改 api/config/main.php 在components 数组中添加 response’response’ => [ ‘class’ => ‘yii\web\Response’, ‘on beforeSend’ => function ($event) { $response = $event->sender; $response->data = [ ‘success’ => $response->isSuccessful, ‘code’ => $response->getStatusCode(), ‘message’ => $response->statusText, ‘data’ => $response->data, ]; $response->statusCode = 200; },],至此关于 Yii2.0 RESTFul API 我一共完成了 3 篇文章,分别为:Yii2.0 RESTful API 基础配置教程Yii2.0 RESTful API 认证教程Yii2.0 RESTful API 之版本控制写得实在不怎么样,您如果看了有收获,不妨留言给个评论,或者您觉得写得有问题,或者不明白,也可以留言,我们可以一块探讨研究。 ...

January 9, 2019 · 2 min · jiezi

Yii2.0 RESTful API 之速率限制

Yii2.0 RESTful API 之速率限制什么是速率限制?权威指南翻译过来为限流,为防止滥用,你应该考虑对您的 API 限流。 例如,您可以限制每个用户 10 分钟内最多调用 API 100 次。 如果在规定的时间内接收了一个用户大量的请求,将返回响应状态代码 429 (这意味着过多的请求)。要启用速率限制,首先需要实现认证类,而关于认证的章节我在 Yii2.0 RESTful API 认证教程 进行了详细的阐述,本篇就不过多介绍,再次基础上进行操作启用速率限制翻阅权威指南,我们可以看到要启用速率限制首先 认证类 需要继承 yiifiltersRateLimitInterface生成两个关键字段php yii migrate/create add_allowance_and_allowance_updated_at_to_user修改 刚才的迁移文件/** * {@inheritdoc} /public function safeUp(){ $this->addColumn(‘user’, ‘allowance’, $this->integer()); $this->addColumn(‘user’, ‘allowance_updated_at’, $this->integer());}/* * {@inheritdoc} */public function safeDown(){ $this->dropColumn(‘user’, ‘allowance’); $this->dropColumn(‘user’, ‘allowance_updated_at’);}执行迁移php yii migrate编写认证类,并继承 RateLimitInterfacenamespace api\models;use Yii;use yii\base\NotSupportedException;use yii\behaviors\TimestampBehavior;use yii\db\ActiveRecord;use yii\filters\RateLimitInterface;use yii\web\IdentityInterface;class User extends ActiveRecord implements IdentityInterface,RateLimitInterface{ . . .}实现 RateLimitInterface 所需要的方法public function getRateLimit($request, $action){ return [1, 1]; // $rateLimit requests per second}public function loadAllowance($request, $action){ return [$this->allowance, $this->allowance_updated_at];}public function saveAllowance($request, $action, $allowance, $timestamp){ $this->allowance = $allowance; $this->allowance_updated_at = $timestamp; $this->save();}控制器中实现调用use yii\filters\auth\CompositeAuth;use yii\filters\auth\HttpBearerAuth;use yii\filters\auth\QueryParamAuth;use yii\filters\RateLimiter;public function behaviors(){ $behaviors = parent::behaviors(); $behaviors[‘rateLimiter’] = [ ‘class’ => RateLimiter::className(), ’enableRateLimitHeaders’ => true, ]; $behaviors[‘authenticator’] = [ ‘class’ => CompositeAuth::className(), ‘authMethods’ => [ //Http::className(), HttpBearerAuth::className(), QueryParamAuth::className(), ], ]; //$behaviors[‘rateLimiter’][’enableRateLimitHeaders’] = true; return $behaviors;}ok,请求下你的 action,多次请求如果出现 429,那么表示速率限制启用成功以上就是关于 Yii2.0 速率限制的使用,速率限制需要和认证配合着使用,关于认证的,查阅Yii2.0 RESTful API 认证教程 ,这篇文章,推荐您,先看完认证,先做完认证的功能,然后在启用速率限制 关于 Yii2.0 RESTFul API到此我觉得就结束了,核心功能就是这些,剩下的就是具体的实战了,多练、多敲,一共四篇文章,分别为:Yii2.0 RESTful API 基础配置教程Yii2.0 RESTful API 认证教程Yii2.0 RESTful API 之版本控制Yii2.0 RESTful API 之速率限制 ...

January 9, 2019 · 1 min · jiezi

GraphQL 入门详解

简介定义一种用于API调用的数据查询语言核心思想传统的api调用一般获取到的是后端组装好的一个完整对象,该对象的结构相对固定,而且前端可能只需要用其中的某些字段,大部分数据的查询和传输工作都浪费了。graphQL提供一种全新数据查询方式,可以只获取需要的数据,使api调用更灵活、高效和低成本。特点需要什么就获取什么数据支持关系数据的查询API无需定义各种路由,完全数据驱动无需管理API版本,一个版本持续演进支持大部分主流开发语言和平台强大的配套开发工具使用方法下面我们通过搭建一个SpaceX的新闻网站来直观学习graphQL的基本使用方法,所有数据由 官方API 获得。服务端服务端采用node + express。新建一个node项目,安装如下依赖:$ npm i graphql express-graphql express axios创建入口文件 server.js,里面创建express服务。使用graphQL我们只需要设置一个路由,所有的请求都由这个graphQL的request handler处理:const express = require(’express’);const graphqlHTTP = require(’express-graphql’);const schema = require(’./schema’);const app = express();app.use(’/graphql’, graphqlHTTP({ schema, graphiql: true}));const PORT = process.env.PORT || 5000;app.listen(PORT,()=>console.log(Server started on port ${PORT}));graphqlHTTP是grapql的http服务,用于处理graphql的查询请求,它接收一个options参数,其中schema是一个 GraphQLSchema实例,我们待会儿定义。graphiql设置为true可以在浏览器中使用GraphiQL直接进行调试,之后会看到。更多express-graphql的用法请参考 Github express-graphql。schema接着我们定义schema,schema意为‘模式’,其中定义了数据模型的结构、字段的类型、模型间的关系,是graphQL的核心。新建schema.js文件,定义两个数据模型:LaunchType(发射)和 RocketType(火箭)。注意字段的数据类型需要使用GraphQL定义的,不能使用js中的基本数据类型。const { GraphQLObjectType, GraphQLInt, GraphQLString, GraphQLBoolean, GraphQLList, GraphQLSchema } = require(‘graphql’);const LaunchType = new GraphQLObjectType({ name: ‘Launch’, fields: () => ({ flight_number: { type: GraphQLInt }, mission_name: { type: GraphQLString }, launch_date_local: { type: GraphQLString }, launch_success: { type: GraphQLBoolean }, rocket: { type: RocketType }, })});const LaunchType = new GraphQLObjectType({ name: ‘Rocket’, fields: () => ({ rocket_id: { type: GraphQLString }, rocket_name: { type: GraphQLString }, rocket_type: { type: GraphQLString } })});有了数据模型之后,我们需要从数据库或者第三方API获取数据,在此我们从spacex的官方API获取。我们需要定义一个root query,root query将做为所有查询的入口,处理并返回数据,更多请参考 GraphQL Root fields & resolvers。在 schema.js中增加代码:const axios = require(‘axios’);…const RootQuery = new GraphQLObjectType({ name: ‘RootQueryType’, fields: { launches: { type: new GraphQLList(LaunchType), resolve(parent, args) { return axios.get(‘https://api.spacexdata.com/v3/launches').then(res => res.data); } } }});module.exports = new GraphQLSchema({ query: RootQuery});查询列表完成这一步,api服务基本搭建完成!我们看一下效果,在浏览器中输入 http://localhost:5000/graphql 将打开 Graphiql(生产环境建议禁用):我们可以只查询所有的 flight_number:或者更多的属性:是不是很简单很神奇!单个查询我们也可以通过传入参数查询单条信息:const RootQuery = new GraphQLObjectType({ name: ‘RootQueryType’, fields: { … launch: { type: LaunchType, args: { flight_number: { type: GraphQLInt } }, resolve(parent, args) { return axios.get(https://api.spacexdata.com/v3/launches/${args.flight_number}) .then(res => res.data); } } }});结果:前端刚刚我们都是用GraphiQL在浏览器调用接口,接下来我们看一下在前端页面中怎么调用graphql服务。前端我们使用react。在项目根目录初始化react项目:$ npx create-react-app client为了便于调试,在package.json中增加scripts:“start”: “node server.js”,“server”: “nodemon server.js”,“client”: “npm start –prefix client”,“dev”:“concurrently "npm run server" "npm run client" “样式我们使用bootswatch中的一款主题:GraphQL的客户端有多种实现,本次项目使用 Apollo,最流行的GraphQL Client。更多client请参考 GraphQL Clients。安装依赖安装如下依赖:$ cd client$ npm i apollo-boost react-apollo graphql其中 apollo-boost 是apollo client本身,react-apollo 是react视图层的集成,graphql 用于解析graphql的查询语句。设置client修改App.js内容如下:import React, { Component } from ‘react’;import ApolloClient from ‘apollo-boost’;import { ApolloProvider } from ‘react-apollo’;import ‘./theme.css’;import ‘./App.css’;import logo from ‘./spacex-logo-light.png’const client = new ApolloClient({ uri: ‘http://localhost:5000/graphql’});class App extends Component { render() { return ( <ApolloProvider client={client}> <div className=“container”> <img src={logo} id=“logo” /> </div> </ApolloProvider> ); }}export default App;和redux使用<Provider>传递store类似,react-apollo 通过 <ApolloProvider>将apollo client向下传递。实现query接着我们来实现显示launches的component,新增文件 components/Launches.js:import React, { Component, Fragment } from ‘react’;import gql from ‘graphql-tag’;import { Query } from ‘react-apollo’;import LaunchItem from ‘./LaunchItem’;const LAUNCHES_QUERY = gql query LaunchesQuery { launches { flight_number, mission_name, launch_date_local,, launch_success, } };export class Launches extends Component { render() { return ( <Fragment> <h1 className=“display-4 my-3”>Launches</h1> <Query query={LAUNCHES_QUERY}> { ({ loading, error, data }) => { if (loading) return <h4>Loading…</h4> if (error) console.log(error); return ( <Fragment> { data.launches.map(launch => <LaunchItem key={launch.flight_number} launch={launch}/>) } </Fragment> ) } } </Query> </Fragment> ) }}export default Launchesquery语句通过 graphql-tag 定义,传入 <Query> 执行获取数据并传入 LaunchItem 显示。components/LaunchItem.js:import React from ‘react’export default function LaunchItem({ launch: { flight_number, mission_name, launch_date_local, launch_success } }) { return ( <div className=“card card-body mb-3”> <div className=“col-md-9”> <h4>Mission: {mission_name}</h4> <p>Date: {launch_date_local,}</p> </div> <div className=“col-md-3”> <button className=“btn btn-secondary”>Launch Details</button> </div> </div> )}运行由于本地调试,client和server分别运行在不同的端口,所以需要先进行跨域处理,使用 cors。server.jsconst cors = require(‘cors’);…app.use(cors());效果好了,大功告成,我们来看一下效果:结语今天就主要介绍GraphQL工程的搭建和GraphQL Query的使用,更多关于GraphQL的内容比如 Mudtation下次有空会跟大家逐步讲解。本文灵感来源:Youtube@Traversy Media,感谢本文完整demo(增加部分功能):Github@MudOnTire ...

January 4, 2019 · 2 min · jiezi

又拍云 API 使用的那些小事

又拍云提供了丰富的 API 调用,为了减少用户在初次接入时可能会遇到的坑”,本文将对又拍云常用的 API 使用方法做个简单的梳理,力求让业务接入变得更简单,更高效。目前我们的 API 主要有四大类,分别是:云存储 API,云处理 API,人工智能 API,后台管理 API。前三类 API 都有主流编程语言对应的 SDK 或可视化的集成工具。SDK 的使用遵循说明文档配置环境,然后对方法进行引用,一般不会遇到太多的问题。不过有时我们需要抛开 SDK,自行根据文档来实现某个接口的某个功能时可能会遇到一些“坑”,调试的过程中有时候甚至会怀疑人生。调试 API 接口,除了直接用编程语言来调试,还推荐使用 Curl 命令或 Postman 来辅助调试。云存储 API云存储 API 是调用比较多的 API,在这个 API 中又以文件上传的接口调用最为频繁,我们就主要以这个接口为例,来做下详细讲述。API 地址:http://v0.api.upyun.com支持协议:HTTP ,HTTPS认证方式:基本认证 ,签名认证主要功能:上传文件,下载文件,删除(创建)文件(目录),获取文件信息,获取文件列表。常见问题一:上面是用 PUT 方法来上传文件,但是返回状态码 401,通过返回的 body 我们可以看出提示是比较清晰的:user not exists。这种情况一般是没有创建操作员或者创建了操作员但是没有授权给对应的服务。操作员这个概念会出现在云存储 API,云处理 API 和人工智能 API 中,所以这是个常见的 401 错误。常见问题二:依旧是401错误,不过返回的 body 信息不同:user need permission。这是提示操作员没有权限,因为我们对操作员的权限控制有三个,分别是:读取,写入,删除。上传属于“写入”操作,所以需要把“可写入”权限勾选上。同理,如果在文件删除过程中出现该问题,那么就需要把“可删除”勾选上。常见问题三:调试工具正常,但是代码嵌入网页上传时候控制台报错。这是因为网站用的是 HTTPS 协议,但是请求的是 HTTP 协议的接口,浏览器是拒绝在 HTTPS 的网站内请求 HTTP 协议的地址的,所以把请求地址更改为:https://v0.api.upyun.com/ 即可。常见问题四:上面三个问题都是用的基本认证,但是认证头并没有加密,只是简单的进行了 Base64 编码,如果在前端暴露出来,将会造成操作员账号泄露。就算只在后端使用,也存在一定的泄露风险。所以生产环境中一般使用签名认证。文件上传有三种方法,二进制 PUT 上传(适用于后端或者客户端上传)表单 POST 上传(适用于浏览器上传)FTP 上传有时候我们出于某种需求,可能需要在前端使用 PUT 的方式进行浏览器上传。同样的,调试工具正常,但是代码嵌入网页上传时候控制台报错,接口返回 401 状态码,body 信息:need date header。这是因为除了表单 POST 上传,其他用签名来认证的存储操作(PUT 上传,删除文件,云处理,内容识别等)都需要携带 Date 请求头,生成 Token 的时候 Date 的值也是参与运算的。但是,浏览器根据请求规范拒绝了自定义“unsafe header “Date””,这怎么办?答案是为了兼容前端的一些请求场景,我们提供了自定义的 Date 请求头 —— X-Date,进行请求头设置和 Token 计算的时候用 X-Date 即可。当然也有人说那为什么不取消这个必须参数 Date 呢?这个校验头是出于安全考虑,从时间的维度上来尽可能的保障请求的合法性。云处理 API云处理 API 也是又拍云 API 中的一大类。云处理也需要操作员账号、密码以及权限等,所以上面云存储 API 中遇到账号问题也会在该 API 中出现,这里就不再赘述。API 地址:http://p0.api.upyun.com,http://p1.api.upyun.com支持协议:HTTP,HTTPS认证方式:签名认证主要功能:音视频异步(同步)处理,异步拉取,文件压缩及解压缩,文档转换。云处理 API 的请求一般在服务端进行,前端只负责触发一个任务,所以可以避开浏览器的种种限制。这里常常遇到的问题无外乎是签名错误了。其实文档说明比较清楚了,签名的计算方式是:Authorization: UPYUN <Operator>:<Signature><Signature> = Base64 (HMAC-SHA1 (<Password>,<Method>&<URI>&<Date>&<Content-MD5>))一个成功的请求实例:因为 Content-MD5 是非必选项,所以我们请求头中不需要添加 Content-MD5 请求头,那么计算签名的时候也就不需要把 body 的 MD5 参与进去了,如下:Authorization: UPYUN <Operator>:<Signature><Signature> = Base64 (HMAC-SHA1 (<Password>,<Method>&<URI>&<Date>))如果此时我们把 body 体accept=json&service=tsqtestpic&notify_url=http://1.1.1.1&source=/x.mp4&tasks=W3siYXZvcHRzIjoiL3IvMy9zLzY0MHgzMjAvc3AvMTgwIiwicmV0dXJuX2luZm8iOnRydWUsInNhdmVfYXMiOiIveC5naWYiLCJ0eXBlIjoidmlkZW8ifV0= 的 MD5 值d22d7d15f3111a4d1a3ea6359f35e78c 也参与 Signature 的计算,请求最后会报签名错误。也就是说如果请求头中没有使用 Content-MD5,那么计算签名时候就不能让 Content-MD5 参与计算,反之则需要 Content-MD5 参与计算,这个规则在 云存储 API,人工智能 API 都是一致的。人工智能API人工智能 API 的调用规则与云存储和云处理的类似,也是使用的签名认证,签名的生成方式也一致。API 地址:http://p0.api.upyun.com,http://banma.api.upyun.com支持协议:HTTP,HTTPS认证方式:签名认证主要功能:图片识别,点播识别,文本识别在调用内容识别API接口时,需要注意内容识别分为“有存储”和“无存储”两种鉴别方式。“有存储”指的是待鉴别的图片或者视频是存储在自己的存储服务中,此参与 Authorization 生成的是该存储的操作员账号和操作员密码,“无存储”指的是待鉴别的内容并没有在自己的存储中,此时的提交方式可以是内容本身或 URL,这种方式的 Authorization 生成用的是内容识别控制台中的 ClientKey 和 ClientSecret。后台管理API后台管理 API 是又拍云服务管理后台开放接口,使得用户可以对后台进行程序化配置,甚至开发出自己的简易管理后台。API 地址:http://api.upyun.com支持协议:HTTP,HTTPS认证方式:Token 认证Token 认证过程相对简单些,只需要添加一个请求头 Authorization: Bearer <Token> 来用对应的方法请求对应的接口即可,需要注意的是接口的 POST 和 PUT 请求的 body 都是 json 字符串,所以请求时 Content-Type 需要设置为 application/json。以上主要对又拍云 API 的使用方法和常见问题做了简单的总结,结合接口的返回信息和文档说明,接入是一件比较简单的事情。在使用场景方面比如 PUT 上传时可以加上预处理作图请求头,图片会处理后再保存;POST 上传的同时可以加云处理的任务(如:色情识别,封面截图,转码适配等)。总体来说,这四大 API 的组合使用基本上可以满足用户的需求。推荐阅读:受到 1 万点暴击,二狗子被 DDoS 攻击的惨痛经历五分钟读懂什么是容器云 ...

January 3, 2019 · 1 min · jiezi

宜信支付结算系统API自动化测试实践

API 测试的基本步骤通常来讲,API 测试的基本步骤主要包括以下三大步骤:1、准备测试数据;2、通过通用的或自己开发的API测试工具发起对被测API的request;3、验证返回结果的response。常用的API测试工具有命令行工具cURL、图形界面工具Postman或SoapUI,支持API性能测试的JMeter等。API复杂场景举例通过使用基础的测试工具,可以做简单场景的API测试;而项目进行过程中,为了解决实际的一些问题,我们会设计更加复杂的测试场景,下面列举几个实际项目中的典型场景。场景一:API串联调用以协议支付为例,我们知道,三方公司接入网联后,用协议支付取代代扣,而协议支付的流程中需要用户输入银行返回的验证码完成绑卡。从接口层面上看,顺序是先调用协议签约API,返回状态成功且获取到短信验证码后,再使用此短信验证码作为输入参数调用代扣API。协议签约和代扣两个API是顺序调用,而且在两次调用中间有获取手机上的短信验证码,这些过程都需要通过程序自动化实现以提高效率。场景二:API接口加密为保证API接口安全,系统间和系统内模块间互相访问需要进行加密处理,常用的加密方式有DES、AES、RSA、MD5等,各系统的加密方式并不一样(接口调用方和接口提供方约定好即可),意味着API测试需要支持多种自动化加密方式程。某些系统还会返回加密的响应报文,也需要识别并解密。场景三:异步API测试异步API指请求发出后后收到一个同步响应,但并不是最终处理结果,最终结果通过回调或者主动查询获得。对于这样的API,同步响应的验证只是第一步,后续还得继续验证DB中的值、MQ中的值、以及异步回调是否成功等。对于异步回调,我们可以模拟回调地址来验证成功与否;而对于主动查询,我们就得通过查看DB中的状态值来验证了,但是查询到结果的时间点不确定,几分钟到几小时都有可能,这就得有一个定时DB查询任务去验证。场景四:API测试中的外部依赖APIA调用APIB且B不可用,此时如何测试APIA需要考虑。比如支付系统对三方支付通道、对银行的依赖,并不是所有的三方都支持测试环境,解决此问题的核心思路是搭建MockServer,而且尽量做到通用性,我们开发了一套Mock系统 -aMock,通过页面录入接口信息,保存在数据库内,通过Nginx访问配置好的Mock接口,后台统一处理请求信息,然后通过URL和报文特性去匹配特定的响应信息。API测试平台我们的API测试平台是要基于业务场景的,即要支持各业务的共性需求,又要针对不同业务的个性特点加以开发来丰富API测试能力;而且,对用例也要有很好的规划,结果有清楚的展示,测试平台架构如下图:BIT:业务接口测试(BusinessInterfaceTest)SUT:被测系统(SystemUnderTest)TestCaseManagement:测试用例管理,包括从测试用例到测试用例集,再到测试任务的数据关系的建立和维护。测试用例是最小单位,测试用例集是从某一维度对用例进行的归集,测试任务即测试执行,可立即触发也可定时执行,只能执行测试用例集。Util:工具类封装,主要提供数据加解密,数据类型转换,配置文件读写,数据字典的缓存服务等。Validator:接口响应字段和数据库字段的验证封装。RiskManagement:风控处理,因为会涉及支付真实资金,需要内置风控规则来保证资金安全,风险可控。Timer:定时任务服务,包括1)串联API用例中,前置用例的状态判断;2)异步API的数据库校验;3)超时API用例的失效状态判断;4)定时执行的任务计划。MockServer:用例依赖的外部系统Mock服务。Portal:API测试平台门户网站,包括测试用例的录入,维护,测试任务的执行,结果查看,导出等都通过门户进行操作。DB:存储测试用例数据以及相应的测试任务、测试报告数据,还有项目配置等。目前API测试平台上各项目维护用例总结1200多条,以回归用例为主,且还在不断增加中,随着用例的不断添加,平台也历经了一系列优化,下面就谈谈这个过程中的一些思考。测试数据准备对于大量API用例的执行,需要数据驱动测试,也就是说可以将测试数据和测试代码分离解耦,这样便于测试数据的维护同时也保证了数据的准确性,用例设计格式是这样<accountName>${accountName}</accountName><accountNo>${accountNo}</accountNo><identNo>${identNo}</identNo>几个关键数据节点由DataProvider提供,为了增加测试覆盖度,数据库相似的测试数据有多条,比如多条四要素(银行卡号、手机号、身份证号、姓名)数据,当大量用例需要读取时,可采用缓存方式存储并读取到cList里面,通过循环遍历,使每条测试数据都可以被均匀读取,下面是替换关键数据节点的一段代码,将cList数据依次赋给对应变量。测试执行的逻辑控制很多情况下的测试是场景化API测试,涉及用例的顺序调用。如下图,“签约-成功-kftn-协议”依赖于“签约-成功-kftn短信”的执行;在添加用例时配置好关联关系。执行时,会根据用例属性将此两条依据有无前置条件划分为两类,分别存放于两个list里,无前置条件的用例可以马上执行,有前置条件的用例,设置TestStatus为0,等待定时任务轮询触发执行。分类执行代码如下图定时任务每分钟执行一次,下面一段是判断前置API的执行状态,只有“0000”代表成功,当前API才能执行,执行时,需要读取前置用例的结果数据并传入;如果前置API失败,则停止任务执行,多条API用例顺序执行也是同样的道理;即使有外部依赖的用例,比如短信验证码,我们也可以通过写一段手机APP程序自动上传短信验证码到服务器,然后触发延迟获取验证码,得到后通过DB记录用例执行的状态和结果,并传给下一个API使用,就完成了多用例的顺序执行。此外,测试任务的执行封装成restful接口,可以更加灵活地和目前团队正在开发中的CICD系统结合一起。测试结果的验证通过分析业务,API的结果校验大致分为两种类型,响应校验和数据库校验。响应校验是针对response报文字段的校验,可精确匹配也可通过正则表达式模糊匹配;数据库校验是基于定时任务的,需要在用例里面根据约定格式设置校验方法,比如下面的sql检验条件,会在准生产环境通过指定单号以及其他条件去查询返回字段,并判断status是否为7,从而判断用例是否成功。PreOnline.3|,|SELECTtb.outer_batch_no,tb.status,bs.send_statusFROMbs_outpay.trans_batchtbleft joinbs_outpay.es_business_sendbsontb.business_batch_no=bs.entity_uuidandbs.entity_status<>2 WHEREtb.outer_batch_noin (?) order bytb.CREATED_TIMEDESC|,|{“status”:“7”}用例状态分为成功、失败、处理中、超时四种状态,分别通过配置相应SQL查询条件去映射,成功和失败是终态,处理中则是需要定时任务继续查询,超时,是我们内部设定的一个状态,目前是超过一个小时未返回终态设为超时,此API用例失效并报警,需要人工参与查看。所有这些规则都是在用例建立和编辑的时候添加的,如下图,一条用例包括了响应校验(值校验、key校验)和数据库校验,通过这种比较灵活的设计,基本能够满足复杂API测试场景。需要指出一点是,很多应用不允许外部测试平台直接访问数据库,我们的解决办法是写一个数据查询服务部署在系统环境中,只提供查询功能,并且有加密验证保证通讯双方(测试平台和数据查询服务之间)可信。通常来说,测试平台或框架可以从某种层面上理解为工具链的串联,开发此平台的过程中,我们使用的技术栈有springmvc+herbinate做逻辑控制、amazingUI做用例管理、echart做结果展示,还使用Jenkins做任务调度等,用户就是各业务线测试人员,他们不需要了解具体代码的实现,但是需要对系统结构以及用例规则有很好的理解,这样才能设计出符合测试场景的用例。任何测试平台的设计还是要基于业务的,后续我们对API平台的推进策略是,继续增加场景化功能以支持更多业务类型的测试,比如清结算系统中日终、日间的跑批任务,对账文件的数据检验等,增加大并发能力并和性能测试工具相结合。-END-作者:孙鹰 宜信技术学院官网:http://college.creditease.cn/…

January 3, 2019 · 1 min · jiezi

阿里研究员谷朴:API 设计最佳实践的思考

API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素。但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多很多小的设计考量一点点叠加起来的(也即John Ousterhout老爷子说的Complexity is incremental【8】)。成功的系统不是有一些特别闪光的地方,而是设计时点点滴滴的努力积累起来的。因此,这里我们试图思考并给出建议,一方面,什么样的API设计是__好__的设计?另一方面,在设计中如何能做到?API设计面临的挑战千差万别,很难有处处适用的准则,所以在讨论原则和最佳实践时,无论这些原则和最佳实践是什么,一定有适应的场景和不适应的场景。因此我们在下面争取不仅提出一些建议,也尽量去分析这些建议在什么场景下适用,这样我们也可以有针对性的采取例外的策略。范围本文偏重于__一般性的API设计__,并更适用于远程调用(RPC或者HTTP/RESTful的API),但是这里没有特别讨论RESTful API特有的一些问题。另外,本文在讨论时,假定了客户端直接和远程服务端的API交互。在阿里,由于多种原因,通过客户端的SDK来间接访问远程服务的情况更多一些。这里并不讨论SDK带来的特殊问题,但是将SDK提供的方法看作远程API的代理,这里的讨论仍然适用。API设计准则:什么是好的API在这一部分,我们试图总结一些好的API应该拥有的特性,或者说是设计的原则。这里我们试图总结更加基础性的原则。所谓基础性的原则,是那些如果我们很好的遵守了就可以让API在之后演进的过程中避免多数设计问题的原则。A good API__提供清晰的思维模型 provides a good mental model__:API是用于程序之间的交互,但是一个API如何被使用,以及API本身如何被维护,是依赖于维护者和使用者能够对该API有清晰的、一致的认识。这种状况实际上是不容易达到的。简单 is simple:“Make things as simple as possible, but no simpler.” 在实际的系统中,尤其是考虑到系统随着需求的增加不断的演化,我们绝大多数情况下见到的问题都是__过于复杂__的设计,而非过于简单,因此强调简单性一般是恰当的。容许多个实现 allows multiple implementations:这个原则看上去更具体,但是这是我非常喜欢的一个原则。这是Sanjay Ghemawat常常提到的一个原则。一般来说,在讨论API设计时常常被提到的原则是解耦性原则或者说松耦合原则。然而相比于松耦合原则,这个原则更加有可操作性:如果一个API自身可以有多个__完全不同的实现__,一般来说这个API已经有了足够好的抽象,和自身的某一个具体实现无关,那么一般也不会出现和外部系统耦合过紧的问题。因此这个原则更本质一些。最佳实践本部分则试图讨论一些更加详细、具体的建议,可以让API的设计更容易满足前面描述的基础原则。想想优秀的API例子:POSIX File API如果说API的设计实践只能列一条的话,那么可能最有帮助的和最可操作的就是这一条。本文也可以叫做“通过File API体会API设计的最佳实践”。所以整个最佳实践可以总结为一句话:“想想File API是怎么设计的。”首先回顾一下File API的主要接口(以C为例,很多是Posix API,选用比较简单的I/O接口为例【1】:int open(const char path, int oflag, …/,mode_t mode /);int close (int filedes);int remove( const char fname );ssize_t write(int fildes, const void buf, size_t nbyte);ssize_t read(int fildes, void buf, size_t nbyte);File API为什么是经典的好API设计?File API已经有几十年历史(从1988年算起将近40年),尽管期间硬件软件系统的发展经历了好几代,这套API核心保持了稳定。这是极其了不起的。API提供了非常清晰的概念模型,每个人都能够很快理解这套API背后的基础概念:什么是文件,以及相关联的操作(open, close, read, write),清晰明了;支持很多的不同文件系统实现,这些系统实现甚至于属于类型非常不同的设备,例如磁盘、块设备、管道(pipe)、共享内存、网络、终端terminal等等。这些设备有的是随机访问的,有的只支持顺序访问;有的是持久化的有的则不是。然而所有不同的设备不同的文件系统实现都可以采用了同样的接口,使得上层系统不必关注底层实现的不同,这是这套API强大的生命力的表现。例如同样是打开文件的接口,底层实现完全不同,但是通过完全一样的接口,不同的路径以及Mount机制,实现了同时支持。其他还有Procfs, pipe等。int open(const char path, int oflag, …/,mode_t mode /);例如这里的cephfs和本地文件系统,底层对应完全不同的实现,但是上层client可以不用区分对待,采用同样的接口来操作,只通过路径不同来区分。基于上面的这些原因,我们知道File API为什么能够如此成功。事实上,它是如此的成功以至于今天的-nix操作系统,everything is filed based.尽管我们有了一个非常好的例子File API,但是__要设计一个能够长期保持稳定的API是一项及其困难的事情__,因此仅有一个好的参考还不够,下面再试图展开去讨论一些更细节的问题。Document well 写详细的文档写详细的文档,并保持更新。 关于这一点,其实无需赘述,现实是,很多API的设计和维护者不重视文档的工作。在一个面向服务化/Micro-service化架构的今天,一个应用依赖大量的服务,而每个服务API又在不断的演进过程中,准确的记录每个字段和每个方法,并且保持更新,对于减少客户端的开发踩坑、减少出问题的几率,提升整体的研发效率至关重要。Carefully define the “resource” of your API 仔细的定义“资源”如果适合的话,选用“资源”加操作的方式来定义。今天很多的API都可以采用这样一个抽象的模式来定义,这种模式有很多好处,也适合于HTTP的RESTful API的设计。但是在设计API时,一个重要的前提是对Resource本身进行合理的定义。什么样的定义是合理的?Resource资源本身是对一套API操作核心对象的一个抽象Abstraction。抽象的过程是__去除细节的过程__。在我们做设计时,如果现实世界的流程或者操作对象是具体化的,抽象的Object的选择可能不那么困难,但是对于哪些细节应该包括,是需要很多思考的。例如对于文件的API,可以看出,文件File这个Resource(资源)的抽象,是“可以由一个字符串唯一标识的数据记录”。这个定义去除了文件是如何标识的(这个问题留给了各个文件系统的具体实现),也去除了关于如何存储的组织结构(again,留给了存储系统)细节。虽然我们希望API简单,但是更重要的是__选择对的实体来建模__。在底层系统设计中,我们倾向于更简单的抽象设计。有的系统里面,域模型本身的设计往往不会这么简单,需要更细致的考虑如何定义Resource。一般来说,域模型中的概念抽象,如果能和现实中的人们的体验接近,会有利于人们理解该模型。选择对的实体来建模__往往是关键。结合域模型的设计,可以参考相关的文章,例如阿白老师的文章【2】。Choose the right level of abstraction 选择合适的抽象层与前面的一个问题密切相关的,是在定义对象时需要选择合适的Level of abstraction(抽象的层级)。不同概念之间往往相互关联。仍然以File API为例。在设计这样的API时,选择抽象的层级的可能的选项有多个,例如:文本、图像混合对象“数据块” 抽象”文件“抽象这些不同的层级的抽象方式,可能描述的是同一个东西,但是在概念上是不同层面的选择。当设计一个API用于与数据访问的客户端交互时,“文件File“是更合适的抽象,而设计一个API用于文件系统内部或者设备驱动时,数据块或者数据块设备可能是合适的抽象,当设计一个文档编辑工具时,可能会用到“文本图像混合对象”这样的文件抽象层级。又例如,数据库相关的API定义,底层的抽象可能针对的是数据的存储结构,中间是数据库逻辑层需要定义数据交互的各种对象和协议,而在展示(View layer)的时候需要的抽象又有不同【3】。Prefer using different model for different layers 不同层建议采用不同的数据模型这一条与前一条密切关联,但是强调的是不同层之间模型不同。在服务化的架构下,数据对象在处理的过程中往往经历多层,例如上面的View-Logic model-Storage是典型的分层结构。在这里我们的建议是不同的Layer采用不同的数据结构。John Ousterhout 【8】书里面则更直接强调:Different layer, different abstraction。例如网络系统的7层模型,每一层有自己的协议和抽象,是个典型的例子。而前面的文件API,则是一个Logic layer的模型,而不同的文件存储实现(文件系统实现),则采用各自独立的模型(如快设备、内存文件系统、磁盘文件系统等各自有自己的存储实现API)。当API设计倾向于不同的层采用一样的模型的时候(例如一个系统使用后段存储服务与自身提供的模型之间,见下图),可能意味着这个Service本身的职责没有定义清楚,是否功能其实应该下沉?不同的层采用同样的数据结构带来的问题还在于API的演进和维护过程。一个系统演进过程中可能需要替换掉后端的存储,可能因为性能优化的关系需要分离缓存等需求,这时会发现将两个层的数据绑定一起(甚至有时候直接把前端的json存储在后端),会带来不必要的耦合而阻碍演进。Naming and identification of the resource 命名与标识当API定义了一个资源对象,下面一般需要的是提供命名/标识(Naming and identification)。在naming/ID方面,一般有两个选择(不是指系统内部的ID,而是会暴露给用户的):用free-form string作为ID(string nameAsId)用结构化数据表达naming/ID何时选择哪个方法,需要具体分析。采用Free-form string的方式定义的命名,为系统的具体实现留下了最大的自由度。带来的问题是命名的内在结构(如路径)本身并非API强制定义的一部分,转为变成实现细节。如果命名本身存在结构,客户端需要有提取结构信息的逻辑。这是一个需要做的平衡。例如文件API采用了free-form string作为文件名的标识方式,而文件的URL则是文件系统具体实现规定。这样,就容许Windows操作系统采用"D:\Documents\File.jpg"而Linux采用"/etc/init.d/file.conf"这样的结构了。而如果文件命名的数据结构定义为{ disk: string, path: string}这样结构化的方式,透出了"disk"和"path"两个部分的结构化数据,那么这样的结构可能适应于Windows的文件组织方式,而不适应于其他文件系统,也就是说泄漏了实现细节。如果资源Resource对象的抽象模型自然包含结构化的标识信息,则采用结构化方式会简化客户端与之交互的逻辑,强化概念模型。这时牺牲掉标识的灵活度,换取其他方面的优势。例如,银行的转账账号设计,可以表达为{ account: number routing: number}这样一个结构化标识,由账号和银行间标识两部分组成,这样的设计含有一定的业务逻辑在内,但是这部分业务逻辑是__被描述的系统内在逻辑而非实现细节,并且这样的设计可能有助于具体实现的简化以及避免一些非结构化的字符串标识带来的安全性问题等。因此在这里结构化的标识可能更适合。另一个相关的问题是,何时应该提供一个数字unique ID? 这是一个经常遇到的问题。有几个问题与之相关需要考虑:是否已经有结构化或者字符串的标识可以唯一、稳定标识对象?如果已经有了,那么就不一定需要numerical ID;64位整数范围够用吗?数字ID可能不是那么用户友好,对于用户来讲数字的ID会有帮助吗?如果这些问题都有答案而且不是什么阻碍,那么使用数字ID是可以的,否则要慎用数字ID。Conceptually what are the meaningful operations on this resource? 对于该对象来说,什么操作概念上是合理的?在确定下来了资源/对象以后,我们还需要定义哪些操作需要支持。这时,考虑的重点是“概念上合理(Conceptually reasonable)”。换句话说,operation + resource 连在一起听起来自然而然合理(如果Resource本身命名也比较准确的话。当然这个“如果命名准确”是个big if,非常不容易做到)。操作并不总是CRUD(create, read, update, delete)。例如,一个API的操作对象是额度(Quota),那么下面的操作听上去就比较自然:Update quota(更新额度),transfer quota(原子化的转移额度)但是如果试图Create Quota,听上去就不那么自然,因额度这样一个概念似乎表达了一个数量,概念上不需要创建。额外需要思考一下,这个对象是否真的需要创建?我们真正需要做的是什么?For update operations, prefer idempotency whenever feasible 更新操作,尽量保持幂等性Idempotency幂等性,指的是一种操作具备的性质,具有这种性质的操作可以被多次实施并且不会影响到初次实施的结果“the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.”【3】很明显Idempotency在系统设计中会带来很多便利性,例如客户端可以更安全的重试,从而让复杂的流程实现更为简单。但是Idempotency实现并不总是很容易。Create类型的idempotency创建的Idempotency,多次调用容易出现重复创建,为实现幂等性,常见的做法是使用一个__client-side generated de-deduplication token(客户端生成的唯一ID)__,在反复重试时使用同一个Unique ID,便于服务端识别重复。Update类型的Idempotency更新值(update)类型的API,应该避免采用"Delta"语义,以便于实现幂等性。对于更新类的操作,我们再简化为两类实现方式Incremental(数量增减),如IncrementBy(3)这样的语义SetNewTotal(设置新的总量)IncrementBy 这样的语义重试的时候难以避免出错,而SetNewTotal(3)(总量设置为x)语义则比较容易具备幂等性。当然在这个例子里面,也需要看到,IncrementBy也有有点,即多个客户请求同时增加的时候,比较容易并行处理,而SetTotal可能导致并行的更新相互覆盖(或者相互阻塞)。这里,可以认为更新增量和设置新的总量这两种语义是不同的优缺点,需要根据场景来解决。如果必须优先考虑并发更新的情景,可以使用更新增量的语义,并辅助以Deduplication token解决幂等性。Delete类型idempotency:Delete的幂等性问题,往往在于一个对象被删除后,再次试图删除可能会由于数据无法被发现导致出错。这个行为一般来说也没什么问题,虽然严格意义上不幂等,但是也无副作用。如果需要实现Idempotency,系统也采用了Archive->Purge生命周期的方式分步删除,或者持久化Purge log的方式,都能支持幂等删除的实现。Compatibility 兼容API的变更需要兼容,兼容,兼容!重要的事情说三遍。这里的兼容指的是向后兼容,而兼容的定义是不会Break客户端的使用,也即__老的客户端能否正常访问服务端的新版本(如果是同一个大版本下)不会有错误的行为__。这一点对于远程的API(HTTP/RPC)尤其重要。关于兼容性,已经有很好的总结,例如【4】提供的一些建议。常见的__不兼容__变化包括(但不限于)删除一个方法、字段或者enum的数值方法、字段改名方法名称字段不改,但是语义和行为的变化,也是不兼容的。这类比较容易被忽视。更具体描述可以参加【4】。另一个关于兼容性的重要问题是,如何做不兼容的API变更?通常来说,不兼容变更需要通过一个__Deprecation process,在大版本发布时来分步骤实现__。关于Deprecation process,这里不展开描述,一般来说,需要保持过去版本的兼容性的前提下,支持新老字段/方法/语义,并给客户端足够的升级时间。这样的过程比较耗时,也正是因为如此,我们才需要如此重视API的设计。有时,一个面向内部的API升级,往往开发的同学倾向于选择高效率,采用一种叫”同步发布“的模式来做不兼容变更,即通知已知的所有的客户端,自己的服务API要做一个不兼容变更,大家一起发布,同时更新,切换到新的接口。这样的方法是非常不可取的,原因有几个:我们经常并不知道所有使用API的客户发布过程需要时间,无法真正实现“同步更新”不考虑向后兼容性的模式,一旦新的API有问题需要回滚,则会非常麻烦,这样的计划八成也不会有回滚方案,而且客户端未必都能跟着回滚。因此,对于在生产集群已经得到应用的API,强烈不建议采用“同步升级”的模式来处理不兼容API变更。Batch mutations 批量更新批量更新如何设计是另一个常见的API设计决策。这里我们常见有两种模式:客户端批量更新,或者服务端实现批量更新。如下图所示。API的设计者可能会希望实现一个服务端的批量更新能力,但是我们建议要尽量避免这样做。除非对于客户来说提供原子化+事务性的批量很有意义(all-or-nothing),否则实现服务端的批量更新有诸多的弊端,而客户端批量更新则有优势:服务端批量更新带来了API语义和实现上的复杂度。例如当部分更新成功时的语义、状态表达等即使我们希望支持批量事物,也要考虑到是否不同的后端实现都能支持事务性批量更新往往给服务端性能带来很大挑战,也容易被客户端滥用接口在客户端实现批量,可以更好的将负载由不同的服务端来承担(见图)客户端批量可以更灵活的由客户端决定失败重试策略Be aware of the risks in full replace 警惕全体替换更新模式的风险所谓Full replacement更新,是指在Mutation API中,用一个全新的Object/Resource去替换老的Object/Resource的模式。API写出来大概是这样的UpdateFoo(Foo newFoo);这是非常常见的Mutation设计模式。但是这样的模式有一些潜在的风险作为API设计者必须了解。使用Full replacement的时候,更新对象Foo在服务端可能已经有了新的成员,而客户端尚未更新并不知道该新成员。服务端增加一个新的成员一般来说是兼容的变更,但是,如果该成员之前被另一个知道这个成员的client设置了值,而这时一个不知道这个成员的client来做full-replace,该成员可能就会被覆盖。更安全的更新方式是采用Update mask,也即在API设计中引入明确的参数指明哪些成员应该被更新。UpdateFoo { Foo newFoo; boolen update_field1; // update mask boolen update_field2; // update mask}或者update mask可以用repeated “a.b.c.d“这样方式来表达。不过由于这样的API方式维护和代码实现都复杂一些,采用这样模式的API并不多。所以,本节的标题是“be aware of the risk“,而不是要求一定要用update mask。Don’t create your own error codes or error mechanism 不要试图创建自己的错误码和返回错误机制API的设计者有时很想创建自己的Error code,或者是表达返回错误的不同机制,因为每个API都有很多的细节的信息,设计者想表达出来并返回给用户,想着“用户可能会用到”。但是事实上,这么做经常只会使API变得更复杂更难用。Error-handling是用户使用API非常重要的部分。为了让用户更容易的使用API,最佳的实践应该是用标准、统一的Error Code,而不是每个API自己去创立一套。例如HTTP有规范的error code 【7】,Google Could API设计时都采用统一的Error code等【5】。为什么不建议自己创建Error code机制?Error-handling是客户端的事,而对于客户端来说,是很难关注到那么多错误的细节的,一般来说最多分两三种情况处理。往往客户端最关心的是"这个error是否应该重试(retryable)“还是应该继续向上层返回错误,而不是试图区分不同的error细节。这时多样的错误代码机制只会让处理变得复杂有人觉得提供更多的自定义的error code有助于传递信息,但是这些信息除非有系统分别处理才有意义。如果只是传递信息的话,error message里面的字段可以达到同样的效果。More更多的Design patterns,可以参考[5] Google Cloud API guide,[6] Microsoft API design best practices等。不少这里提到的问题也在这些参考的文档里面有涉及,另外他们还讨论到了像versioning,pagination,filter等常见的设计规范方面考虑。这里不再重复。参考文献【1】File wiki https://en.wikipedia.org/wiki/Computer_file【2】阿白,域模型设计系列文章,https://yq.aliyun.com/articles/6383【3】Idempotency, wiki https://en.wikipedia.org/wiki/Idempotence【4】Compatibility https://cloud.google.com/apis/design/compatibility【5】API Design patterns for Google Cloud, https://cloud.google.com/apis/design/design_patterns【6】API design best practices, Microsoft https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design【7】Http status code https://en.wikipedia.org/wiki/List_of_HTTP_status_codes【8】A philosophy of software design, John Ousterhout本文作者:jessie筱姜 阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 29, 2018 · 2 min · jiezi

[LeetCode] 364. Nested List Weight Sum II

ProblemGiven a nested list of integers, return the sum of all integers in the list weighted by their depth.Each element is either an integer, or a list – whose elements may also be integers or other lists.Different from the previous question where weight is increasing from root to leaf, now the weight is defined from bottom up. i.e., the leaf level integers have weight 1, and the root level integers have the largest weight.Example 1:Input: [[1,1],2,[1,1]]Output: 8 Explanation: Four 1’s at depth 1, one 2 at depth 2.Example 2:Input: [1,[4,[6]]]Output: 17 Explanation: One 1 at depth 3, one 4 at depth 2, and one 6 at depth 1; 13 + 42 + 6*1 = 17.Solutionclass Solution { public int depthSumInverse(List<NestedInteger> nestedList) { int preSum = 0, sum = 0; Queue<NestedInteger> queue = new LinkedList<>(); for (NestedInteger i: nestedList) queue.offer(i); while (!queue.isEmpty()) { int size = queue.size(); int curSum = 0; while (size– > 0) { NestedInteger i = queue.poll(); if (i.isInteger()) curSum += i.getInteger(); else { for (NestedInteger j: i.getList()) queue.offer(j); } } preSum += curSum; sum += preSum; } return sum; }}APIs:/** * // This is the interface that allows for creating nested lists. * // You should not implement it, or speculate about its implementation * public interface NestedInteger { * // Constructor initializes an empty nested list. * public NestedInteger(); * * // Constructor initializes a single integer. * public NestedInteger(int value); * * // @return true if this NestedInteger holds a single integer, rather than a nested list. * public boolean isInteger(); * * // @return the single integer that this NestedInteger holds, if it holds a single integer * // Return null if this NestedInteger holds a nested list * public Integer getInteger(); * * // Set this NestedInteger to hold a single integer. * public void setInteger(int value); * * // Set this NestedInteger to hold a nested list and adds a nested integer to it. * public void add(NestedInteger ni); * * // @return the nested list that this NestedInteger holds, if it holds a nested list * // Return null if this NestedInteger holds a single integer * public List<NestedInteger> getList(); * } */ ...

December 28, 2018 · 2 min · jiezi

超火js库: Lodash API例子 (持续更新~~~)

lodash.js是一款超火的js库,在npm上平均周下载量达到了惊人的12,374,096,github start36K!大量框架都用到了lodash,包括拥有123kstart的vue本文对比lodash英文文档,加上一些小栗子和个人的经验~~,希望能帮到你们lodash采用了immutable的思想,永远不会改变原数据,而是会返回一个新的结果String 字符串操作camelCase 转换驼峰命名_.camelCase([string=’’])console.log(.camelCase(‘Foo Bar’))// => ‘fooBar’console.log(.camelCase(’–foo-bar–’))// => ‘fooBar’console.log(.camelCase(’FOO_BAR’))// => ‘fooBar’console.log(.camelCase(’/_FOO_BAR_*\9’))// ‘fooBar9’console.log(.camelCase(‘fooBarbar_bar’))// fooBarbarBar字符串中非数字和字母都会被过滤掉,然后再转换为驼峰capitalize 转换大写.capitalize([string=’’])console.log(.capitalize(‘FRED’));// => ‘Fred’联想: 同string.prototype.toLocaleUpperCase();deburr 清理符号.capitalize([string=’’])deburr转换 Latin-1 Supplement和Latin Extended-A 为普通拉丁字母并且移除变音符号_.deburr(‘déjà vu’);// => ‘deja vu’一般用不到…endsWith 判断是否是某个字符串结尾_.endsWith([string=’’], [target], [position=string.length])console.log(.endsWith(‘abcdef3’, ‘c’, 3))// trueconsole.log(.endsWith(‘abcdef3’, ‘c’, 2))// false主要是第三个参数,不填表示检查整个字符串,有值代表从左截取几个字符,从截取的字符中进行判断ECMAScript 6中已经加入string.prototype.endsWith()方法escape 转义html实体字符_.escape([string=’’])会将&装换成&amp, < -> &lt, > -> &gt ’’ -> &quot。其他转义字符,如:×(乘号),÷(除号)等不会转义,请用he这样的专业处理转义的库console.log(.escape(a as &lt;a&gt; &amp;'"" *))// a as &lt;a&gt; &amp;&#39;&quot;&quot; *escapeRegExp 转义正则表达式特殊字符.escapeRegExp([string=’’])正则表达式中的特殊字符都会加’‘处理console.log(.escapeRegExp(’lodash’))// [lodash]()kebabCase 转换成kebabCase格式总结: 存在四种case格式CamelCase: TheQuickBrownFoxJumpsOverTheLazyDogSnakeCase: the_quick_brown_fox_jumps_over_the_lazy_dogKebabCase: the-quick-brown-fox-jumps-over-the-lazy-dogStudlycaps: tHeqUicKBrOWnFoXJUmpsoVeRThElAzydOG查看case的具体文档其他转换case语法通camelCaselowerCase 转换小写.lowerCase([string=’’]).lowerCase(’–Foo-Bar–’);// => ‘foo bar’ .lowerCase(‘fooBar’);// => ‘foo bar’ .lowerCase(’FOO_BAR’);// => ‘foo bar’通capitalize联想: string.prototype.toLocaleLowerCaselowerFirst 转换第一个字符为小写console.log(.lowerFirst(‘DS’))// dSconsole.log(.lowerFirst(’__DS’))// _DS无法过滤非字母字符pad 填充字符.pad([string=’’], [length=0], [chars=’ ‘])有三个参数: 原字符串,长度,填充字符如果原字符串长度短于给定的长度,则原字符串左右两边会填充指定字符(默认为空格),如果不能平均分配则会被截断。.pad(‘abc’, 8);// => ’ abc ’ .pad(‘abc’, 8, ‘-’);// => ‘-abc-_’ _.pad(‘abc’, 3);// => ‘abc’ ...

December 27, 2018 · 1 min · jiezi

想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜

本文由云+社区发表前言业务已基于Redis实现了一个高可用的排行榜服务,长期以来相安无事。有一天,产品说:我要一个按周排名的排行榜,以反映本周内用户的活跃情况。于是周榜(按周重置更新的榜单)诞生了。为了满足产品多变的需求,我们一并实现了小时榜、日榜、周榜、月榜几种周期榜。本以为可长治久安了,又有一天,产品体验业务后说:我想要一个最近7天榜,反映最近一段时间的用户活跃情况,不想让历史的高分用户长期占据榜首,可否?于是,滚动榜(最近N期榜)的需求诞生了。周期榜周期榜实现还是很容易的,给每个周期算出一个序号,作为榜单名后缀,进入新的周期自然切换读写新榜单,平滑过度。以日榜为例,根据时间戳ts计算每日序号s=ts/86400,以日序号s作为后缀即可实现零点后自动读写新日榜。小时榜与此雷同,不再赘述。 对于周榜,可以选定某一个周一(或周日,看需求)的时间戳为基准,计算基准到当前经过的周数为周序号,以此作为榜单后缀。 对于月榜,稍有不同,因为月份天数不固定,所以不能按照上述方法计算。但我们可以根据时间戳取得年、月信息,以年月做标志(如201810)后缀,即可实现月榜。滚动榜方案探讨滚动榜需要考虑多个周期榜数据的聚合与自动迭代更新,实现起来就没那么容易了。下面分析几个方案。方案1:每日一个滚动榜,当日离线补齐数据还以日榜为例,最近N天榜就是把前N-1天到当天的每一个日榜榜单累加即可,比如最近7天榜,就是前6天到当天的每一个日榜中相同元素数据累加。因此,最直观的一个方案是:首先记录每天的排行榜R,那么第i天的最近N天榜Si=∑N−1n=0Ri−n,其中,Ri−x表示第i天的前x天的日榜。实现上,可以每日生成一个滚动榜S和当天日榜R,加分时同时写入S和R,每日零点后跑工具将前N-1天数据累加写入当日滚动榜S。 这个方案的优点是直观,实现简单。但缺点也很明显,一是每日一个滚动榜,消耗内存较多;二是数据更新不实时,需要等待离线作业完成累加后S中的数据才完全正确;三是时间复杂度高,7天榜还好,只需要读过去6天数据,如果是100天榜,该方案需要读过去99天榜,显然不可接受。方案2:全局一个滚动榜,当日离线补齐数据基于方案1,如果业务无需查询历史的S,可以只使用全局一个S,无需每日创建一个Si。加分操作还是同时加当日的Ri和全局唯一的S,但每日零点的离线作业改为从S中减去Ri−(N−1)的数据(即将最早一天的数据淘汰,从而实现S的计数滚动)。 此方案减少了内存使用,同时离线任务每次只需读取一个日榜做减法,时间复杂度为O(1);但仍需要离线作业完成才能保证数据正确性,还是无法做到平滑过渡。方案3:每日一个滚动榜,实时更新要做到每日零点后榜单实时生效,而不需要等待离线作业的完成,一种方案是预写未来的榜单。不难得出,当日分数会计入往后N-1天的滚动榜中。因此,可以写当天的滚动榜Si的同时,写往后N-1天的榜单Si+1到Si+N−1。 该方案不仅能脱离离线作业做到实时更新,且可以省略每天的日榜。但缺点也不难看出,对于7天滚动榜,每次写操作需要更新7个榜单,写入量小时还勉强能接受,如果写操作量大或者需要的是30天、60天滚动榜,此方案可行性几乎为零。方案4:实时更新,常数次写操作有不有办法做到既能实时更新,写榜数量也不随N的增加而增加呢?不难看出,第i天滚动榜Si=∑N−1n=0Ri−n,而第i+1天的滚动榜Si+1=∑N−1n=0R(i+1)−n=∑N−2n=0Ri−n+Ri+1。显然,Si+1=Si−Ri−(N−1)+Ri+1。由于Ri+1在刚达到零点时必然为空且可以在次日实时加到Si+1上,因此如果我们能提前准备好Si−Ri−(N−1)这部分数据,那么在零点进入i+1天后,Ri+1自然就是可用状态了。以3天滚动榜为例,次日滚动榜初始态为当日滚动榜减去n-2天的日榜数据。 +——————————————-+ | |+—-+—+ +——–+ +——–+ || | | | | | || R(i-2) | | R(i-1) | | R(i) | || | | | | | |+—-+—+ +—-+—+ +—+—-+ | | | | | | | | | | | | | | | v+ v- | | | | + +——–+ +——–+ | +—–> | | + | | | + | S(i) | +—+> | S(i+1) | +—————–+> | | | | +——–+ +——–+那么,如何提前准备好Si−Ri−(N−1)这部分数据呢?可以如下处理:对一个元素加分时,加当日周期榜Ri、滚动榜Si;还需根据其在今日滚动榜中的分数s、及n-1天日榜中的分数r,计算出其在明日滚动榜中的初始分数s-r写入明日滚动榜中;即3个写操作;如果一个元素在当日没有任何加分操作,那么不会触发写入初始分数操作,所以还需要一个离线工具补齐。与方案1、2不同的是,该离线工具可提前一天运行,即当日运行离线工具补齐次日的滚动榜数据即可。简而言之:第一步是运行离线工具生成次日的滚动榜;第二步是在写操作时同时更新次日的滚动榜。 该方案也是每日一个滚动榜。相对方案3而言,是空间换时间。如果空间不足且无保留历史的需求,可在离线工具中清理历史数据。 +————–+ | | | AddScore | | | +-+—-+—–+-+ | | | v | |+——–+ +——–+ +——-++ | || | | | | | | || R(i-2) | | R(i-1) | | R(i) | | || | | | | | | |+——–+ +——–+ +——–+ | | | v +——–+ | ++——-+ | | | | | | S(i) +<–+ | S(i+1) | | | | | +——–+ +—-+—+ ^ | | +——+—–+ | | | Tool | | | +————+方案4的实现以下是实现参考。此处仅列出核心的lua脚本。Redis命令调用脚本的参数定义为:eval script 4 当日日榜key 当日滚动榜key 即将淘汰的日榜key 明日滚动榜key 榜单元素名 加分数lua脚本script如下:–加今日日榜分数redis.call(‘ZINCRBY’, KEYS[1], ARGV[2], ARGV[1])–加今日滚动榜分数local rs = redis.call(‘ZINCRBY’, KEYS[2], ARGV[2], ARGV[1])local curRoundScore = 0if (rs) then curRoundScore = tonumber(rs)end–取即将淘汰的日榜分数rs = redis.call(‘ZSCORE’, KEYS[3], ARGV[1])local oldCycleScore = 0if (rs) then oldCycleScore = tonumber(rs)end–计算次日滚动榜初始分数local nextRoundScore = curRoundScore - oldCycleScoreif nextRoundScore < 0 then nextRoundScore = 0end–设置次日滚动榜分数redis.call(‘ZADD’, KEYS[4], nextRoundScore, ARGV[1])–返回今日分数rs = redis.call(‘ZREVRANK’, KEYS[2], ARGV[1])return {curRoundScore, rs}关于榜单key计算准确度的探讨 我们的业务是在排行榜接入层逻辑中计算榜单后缀的,这种方案对逻辑层多台机器的时间一致性要求较高,如果逻辑层服务器时钟不一致,可能在时间切换点上出现不同机器读写不同榜单的问题。如果业务对时间精确度要求严格,可以考虑通过lua脚步在redis端计算后缀。.关于内存容量限制的探讨 基于ZSet实现的排行榜,每个元素约需要100字节内存。如果榜单长度为1000万,则每个榜单约需要1G内存。滚动榜的计算需要每日保留一个日榜,如果滚动周期较长,则可能单机内存容量不足以容纳所有需要的榜单。 考虑到历史日榜数据是不会变更的,因此不在lua脚本中读取历史日榜数据也无一致性问题。故可以将榜单打散到多个Redis实例,在接入层做逻辑读取历史日榜的分数,再以参数形式传入给lua脚本处理。总结在榜单长度不大且并发量不高的场景下,使用关系数据库+Cache的方案实现排行榜有更高的灵活性。而在海量数据与高并发的场景下,Redis是一个更好的选择。本文基于Redis实现的滚动榜,不论滚动周期多长,都只需要常数(3)次数的写操作,有较好的性能和可扩展性。且通过离线+在线的双预生成机制,确保了榜单实时生效,可用性较强。此文已由作者授权腾讯云+社区发布 ...

December 13, 2018 · 2 min · jiezi

一起来看 rxjs

更新日志2018-05-26 校正2016-12-03 第一版翻译过去你错过的 Reactive Programming 的简介你好奇于这名为Reactive Programming(反应式编程)的新事物, 更确切地说,你想了解它各种不同的实现(比如 [Rx*], [Bacon.js], RAC 以及其它各种各样的框架或库)学习它比较困难, 因为比较缺好的学习材料(译者注: 原文写就时, RxJs 还在 v4 版本, 彼时社区对 RxJs 的探索还不够完善). 我在开始学习的时候, 试图找过教程, 不过能找到的实践指南屈指可数, 而且这些教程只不过隔靴搔痒, 并不能帮助你做真正了解 RxJs 的基本概念. 如果你想要理解其中一些函数, 往往代码库自带的文档帮不到你. 说白了, 你能一下看懂下面这种文档么:Rx.Observable.prototype.flatMapLatest(selector, [thisArg])按照将元素的索引合并的方法, 把一个 “observable 队列 " 中的作为一个新的队列加入到 “observable 队列的队列” 中, 然后把 “observable 队列的队列” 中的一个 “observable 队列” 转换成一个 “仅从最近的 ‘observable 队列’ 产生的值构成的一个新队列.“这是都是什么鬼?我读了两本书, 一本只是画了个大致的蓝图, 另一本则是一节一节教你 “如何使用 Reactive Libarary” . 最后我以一种艰难的方式来学习 Reactive Programming: 一遍写, 一遍理解. 在我就职于 Futurice 的时候, 我第一次在一个真实的项目中使用它, 我在遇到问题时, 得到了来自同事的支持.学习中最困难的地方是 以 Reactive(反应式) 的方式思考. 这意思就是, 放下你以往熟悉的编程中的命令式和状态化思维习惯, 鼓励自己以一种不同的范式去思考. 至今我还没在网上找到任何这方面的指南, 而我认为世界上应该有一个说明如何以 Reactive(反应式) 的方式思考的教程, 这样你才知道要如何开始使用它. 在阅读完本文后之后. 请继续阅读代码库自带的文档来指引你之后的学习. 我希望, 这篇文档对你有所帮助.“什么是 Reactive Programming(反应式编程)?“在网上可以找到大量对此糟糕的解释和定义. Wikipedia 的 意料之中地泛泛而谈和过于理论化. Stackoverflow 的 圣经般的答案也绝对不适合初学者. Reactive Manifesto 听起来就像是要给你公司的项目经理或者是老板看的东西. 微软的 Rx 术语 “Rx = Observables + LINQ + Schedulers” 也读起来太繁重, 太微软了, 以至于你看完后仍然一脸懵逼. 类似于 “reactive” 和 “propagation” 的术语传达出的含义给人感觉无异于你以前用过的 MV* 框架和趁手的语言已经做到的事情. 我们现有的框架视图当然是会对数据模型做出反应, 任何的变化当然也是要冒泡的. 要不然, 什么东西都不会被渲染出来嘛.所以, 让我们撇开那些无用的说辞, 尝试去了解本质.Reactive programming(反应式编程) 是在以异步数据流来编程当然, 这也不是什么新东西. 事件总线或者是典型的点击事件确实就是异步事件流, 你可以对其进行 observe(观察) 或者做些别的事情. 不过, Reactive 是比之更优秀的思维模型. 你能够创建任何事物的数据流, 而不只是从点击和悬浮事件中. “流” 是普遍存在的, 一切都可能是流: 变量, 用户输入, 属性, 缓存, 数据结构等等. 比如, 想象你的 Twitter 时间线会成为点击事件同样形式的数据流.熟练掌握该思维模型之后, 你还会接触到一个令人惊喜的函数集, 其中包含对任何的数据流进行合并、创建或者从中筛选数据的工具. 它充分展现了 “函数式” 的魅力所在. 一个流可以作为另一个流的输入. 甚至多个流可以作为另一个流的输入. 你可以合并两个流. 你可以筛选出一个仅包含你需要的数据的另一个流. 你可以从一个流映射数据值到另一个流.让我们基于 “流是 Reactive 的中心” 这个设想, 来细致地做看一下整个思维模型, 就从我们熟知的 “点击一个按钮” 事件流开始.每个流是一个按时序不间断的事件序列. 它可能派发出三个东西: (某种类型的)一个数值, 一个错误, 或者一个 “完成” 信号. 说到 “完成” , 举个例子, 当包含了这个按钮的当前窗口/视图关闭时, 也就是 “完成” 信号发生时.我们仅能异步地捕捉到这些事件: 通过定义三种函数, 分别用来捕捉派发出的数值、错误以及 “完成” 信号. 有时候后两者可以被忽略, 你只需定义用来捕捉数值的函数. 我们把对流的 “侦听” 称为订阅(subscribing), 我们定义的这三种函数合起来就是观察者, 流则是被观察的主体(或者叫"被观察者”). 这正是设计模式中的观察者模式.描述这种方式的另一种方式用 ASCII 字符来画个导图, 在本教程的后续的部分也能看到这种图形.–a—b-c—d—X—|->a, b, c, d 代表被派发出的值X 代表错误| 代表"完成"信号—> 则是时间线这些都是是老生常谈了, 为了不让你感到无聊, 现在来点新鲜的东西: 我们将原生的点击事件流进行变换, 来创建新的点击事件流.首先, 我们做一个计数流, 来指明一个按钮被点击了多少次. 在一般的 Reactive 库中, 每个流都附带了许多诸如map, filter, scan 等的方法. 当你调用这些方法之一(比如比如clickStream.map(f))时, 它返回一个基于 clickStream 的新的流. 它没有对原生的点击事件做任何修改. 这种(不对原有流作任何修改的)特性叫做immutability(不可变性), 而它和 Reactive(反应式) 这个概念的契合度之高好比班戟和糖浆(译者注: 班戟就是薄煎饼, 该称呼多见于中国广东地区. 此句意为 immutability 与 Reactive 两个概念高度契合). 这样的流允许我们进行链式调用, 比如clickStream.map(f).scan(g): clickStream: —c—-c–c—-c——c–> vvvvv map(c becomes 1) vvvv —1—-1–1—-1——1–> vvvvvvvvv scan(+) vvvvvvvvvcounterStream: —1—-2–3—-4——5–>map(f) 方法根据你提供的函数f替换每个被派发的元素形成一个新的流. 在上例中, 我们将每次点击都映射为计数 1. scan(g) 方法则在内部运行x = g(accumulated, current), 以某种方式连续聚合处理该流之前所有的值, 在该例子中, g 就是简单的加法. 然后, 一次点击发生时, counterStream 就派发一个点击数的总值.为了展示 Reactive 真正的能力, 我们假设你想要做一个 “双击事件” 的流. 或者更厉害的, 我们假设我们想要得到一个 “三击事件流” , 甚至推广到更普遍的情况, “多击流”. 现在, 深呼吸, 想象一下按照传统的命令式和状态化思维习惯要如何完成这项工作? 我敢说那会烦死你了, 它必须包含各种各样用来保持状态的变量, 以及一些对周期性工作的处理.然而, 以 Reactive 的方式, 它会非常简单. 事实上, 这个逻辑只不过是四行代码. 不过让我们现在忘掉代码.无论你是个初学者还是专家, 借助导图来思考, 才是理解和构建流最好的方法.图中的灰色方框是将一个流转换成另一个流的方法. 首先, 每经过 “250毫秒” 的 “事件静默” (简单地说, 这是在 buffer(stream.throttle(250ms)) 完成的. (现在先)不必担心对这点的细节的理解, 我们主要是演示下 Reactive 的能力.), 我们就得到了一个 “点击动作” 的列表, 即, 转换的结果是一个列表的流, 而从这个流中我们应用 map() 将每个列表映射成对应该队列的长度的整数值. 最后, 我们使用 filter(x >= 2) 方法忽略掉所有的 1. 如上: 这 3 步操作将产生我们期望的流. 我们之后可以订阅(“侦听”)它, 并按我们希望的处理方式处理流中的数据.我希望你感受到了这种方式的美妙. 这个例子只是一次不过揭示了冰山一角: 你可以将相同的操作应用到不同种类的流上, 比如 API 返回的流中. 除此以外, 还有许多有效的函数.“为什么我应该采用反应式编程?“Reactive Programming (反应式编程) 提升了你代码的抽象层次, 你可以更多地关注用于定义业务逻辑的事件之间的互相依赖, 而不必写大量的细节代码来处理事件. RP(反应式编程)的代码会更简洁明了.在现代网页应用和移动应用中, 这种好处是显而易见的, 这些场景下, 与数据事件关联的大量 UI 事件需要被高频地交互. 10 年前, 和 web 页面的交互只是很基础地提交一个长长的表单给后端, 然后执行一次简单的重新渲染. 在这 10 年间, App 逐渐变得更有实时性: 修改表单中的单个字段能够自动触发一次到后端的保存动作, 对某个内容的 “点赞” 需要实时反馈到其他相关的用户……现今的 App 有大量的实时事件, 它们共同作用, 以带给用户良好的体验. 我们要能简洁处理这些事件的工具, 而 Reactive Programming 方式我们想要的.举例说明如何以反应式编程的方式思考现在我们进入到实战. 一个真实的手把手教你如何以 RP(反应式编程) 的方式来思考的例子. 注意这里不是随处抄来的例子, 不是半吊子解释的概念. 到这篇教程结束为止, 我们会在写出真正的功能性代码的同时, 理解我们做的每个动作.我选择了 JavaScript 和 RxJS 作为工具, 原因是, JavaScript 是当下最为人熟知的语言, 而 [Rx*] 支持多数语言和平台 (.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa, Groovy 等等). , 无论你的工具是什么, 你可以从这篇教程中收益.实现一个"建议关注"盒子在 Twitter 上, 有一个 UI 元素是建议你可以关注的其它账户.我们将着重讲解如何模仿出它的核心特性:在页面启动时, 从 API 中加载账户数据, 并展示三个推荐关注者在点击"刷新"时, 加载另外的三个推荐关注的账户, 形成新三行在点击一个账户的 “x” 按钮时, 清除该账户并展示一个新的每一行显示账户的头像和到他们主页的链接我们可以忽略其它的特性和按钮, 它们都是次要的. 另外, Twitter 最近关闭了非认证请求接口, 作为替代, 我们使用 [Github 的 API] 来构建这个关注别人 UI.(注: 到本稿的最新的校正为止, github 的该接口对非认证用户启用了一段时间内访问频次限制)如果你想尽早看一下完整的代码, 请点击[样例代码].请求和回复你如何用 Rx 处理这个问题?首先, (几乎) 万物皆可为流 .这是 “Rx 口诀”. 让我们从最容易的特性开始: “在页面启动时, 从 API 中加载账户数据”. 这没什么难得, 只需要(1) 发一个请求, (2) 读取回复, (3) 渲染回复的中的数据. 所以我们直接把我们我们的请求当做流. 一开始就用流也许颇有"杀鸡焉用牛刀"的意味, 但为了理解, 我们需要从基本的例子开始.在应用启动的时候, 我们只需要一个请求, 因此如果我们将它作为一个数据流, 它将会只有一个派发的值. 我们知道之后我们将有更多的请求, 但刚开始时只有一个.–a——|->其中 a 是字符串 ‘https://api.github.com/users'这是一个将请求的 URL 的流. 无论请求何时发生, 它会告诉我们两件事: 请求发生的时刻和内容. 请求执行之时就是事件派发之时, 请求的内容就是被派发的值: 一个 URL 字符串.创建这样一个单值流对 [Rx*] 来说非常简单, 官方对于流的术语, 是 “Observable”(可被观察者), 顾名思义它是可被观察的, 但我觉得这名字有点傻, 所以我称呼它为 流.var requestStream = Rx.Observable.just(‘https://api.github.com/users');但现在, 这只是一个字符串流, 不包含其他操作, 所以我们需要要在值被派发的时候做一些事情. 这依靠对流的订阅.requestStream.subscribe(function(requestUrl) { // 执行该请求 jQuery.getJSON(requestUrl, function(responseData) { // … });}注意我们使用了 jQuery Ajax 回调(我们假定你应已对此有了解)来处理请求操作的异步性. 但稍等, Rx 就是处理 异步 数据流的. 难道这个请求的回复不就是一个在未来某一刻会带回返回数据的流么? 从概念上讲, 它看起来就是的, 我们来尝试写一下.requestStream.subscribe(function(requestUrl) { // 执行该请求 var responseStream = Rx.Observable.create(function (observer) { jQuery.getJSON(requestUrl) .done(function(response) { observer.onNext(response); }) .fail(function(jqXHR, status, error) { observer.onError(error); }) .always(function() { observer.onCompleted(); }); }); responseStream.subscribe(function(response) { // 对回复做一些处理 });}Rx.Observable.create() 所做的是自定义一个流, 这个流会通知其每个观察者(或者说其"订阅者” )有数据产生 (onNext()) 或发生了错误 (onError()). 我们需要做的仅仅是包装 jQuery Ajax Promise. 稍等, 这难道是说 Promise 也是一个 Observable?是的. Observable 就是一个 Promise++ 对象. 在 Rx 中, 通过运行 var stream = Rx.Observable.fromPromise(promise) 你就可以把一个 Promise 转换成一个 Observable. 仅有的区别在于 Observables 不符合 Promises/A+ 标准, 但他们在概念上是不冲突的. 一个 Promise 就是一个仅派发一个值的 Observable. Rx 流就是允许多次返回值的 Promise.这个例子很可以的, 它展示了 Observable 是如何至少有 Promise 的能力. 因此如果你喜欢 Promise, 请注意 Rx Observable 也可以做到同样的事.现在回到我们的例子上, 也许你已经注意到了, 我们在一个中 subscribe() 调用了另一个 subscribe(), 这有点像回调地狱. 另外, responseStream 的创建也依赖于 requestStream. 但正如前文所述, 在 Rx 中有简单的机制来最流作变换并支持从其他流创建一个新的流, 接下来我们来做这件事.到目前为止, 你应该知道的对流进行变换的一个基础方法是 map(f), 将 “流 A” 中的每一个元素作 f()处理, 然后在 “流 B” 中生成一一对应的值. 如果我们这样处理我们的请求和回复流, 我们可以把请求 URL 映射到回复的 Promise (被当做是流) 中.var responseMetastream = requestStream .map(function(requestUrl) { return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); });这下我们创建了一个叫做 元流 (流的流) 的奇怪的东西. 不必对此感到疑惑, 元流, 就是其中派发值是流的流. 你可以把它想象成 指针): 每个被派发的值都是对其它另一个流的 指针 . 在我们的例子中, 每个请求的 URL 都被映射为一个指针, 指向一个个包含 URL 对应的返回数据的 promise 流.这个元流看上去有点让人迷惑, 而且对我们根本没什么用. 我们只是想要一个简单的回复流, 其中每个派发的值都应是一个 JSON 对象, 而不是一个包含 JSON 对象的 Promise. 现在来认识 Flatmap: 它类似于 map(), 但它是把 “分支” 流中派发出的的每一项值在 “主干” 流中派发出来, 如此, 它就可以对元流进行扁平化处理.(译者注: 这里, “分支” 流指的是元流中每个被派发的值, “主干” 流是指这些值有序构成的流, 由于元流中的每个值都是流, 作者不得不用 “主干” 和 “分支” 这样的比喻来描述元流与其值的关系). 在此, Flatmap 并不是起到了"修正"的作用, 元流也并不是一个 bug, 相反, 它们正是 Rx 中处理异步回复流的工具.var responseStream = requestStream .flatMap(function(requestUrl) { return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); });漂亮. 因为回复流是依据请求流定义的, 设想之后有更多的发生在请求流中的事件, 不难想象, 就会有对应的发生在回复流中的的回复事件:requestStream: –a—–b–c————|->responseStream: —–A——–B—–C—|->(小写的是一个请求, 大写的是一个回复)现在我们终于得到了回复流, 我们就可以渲染接收到的数据responseStream.subscribe(function(response) { // 按你设想的方式渲染 response 为 DOM});整理一下到目前为止的代码, 如下:var requestStream = Rx.Observable.just(‘https://api.github.com/users');var responseStream = requestStream .flatMap(function(requestUrl) { return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); });responseStream.subscribe(function(response) { // 按你设想的方式渲染 response 为 DOM});刷新按钮现在我们注意到, 回复中的 JSON 是一个包含 100 个用户的列表. [Github 的 API] 只允许我们指定一页的偏移量, 而不能指定读取的一页中的项目数量, 所以我们只用到 3 个数据对象, 剩下的 97 个只能浪费掉. 我们暂时忽略这个问题, 之后我们会看到通过缓存回复来处理它.每次刷新按钮被点击的时候, 请求流应该派发一个新的 URL, 因此我们会得到一个新的回复. 我们需要两样东西: 一个刷新按钮的点击事件流(口诀: 万物皆可成流), 并且我们需要改变请求流以依赖刷新点击流. 好在, RxJs 拥有从事件监听器产生 Observable 的工具.var refreshButton = document.querySelector(’.refresh’);var refreshClickStream = Rx.Observable.fromEvent(refreshButton, ‘click’);既然刷新点击事件自身不带任何 API URL, 我们需要映射每次点击为一个实际的 URL. 现在我们将请求流改成刷新点击流, 这个流被映射为每次带有随机的偏移参数的、到 API 的请求.var requestStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });如果我直接这样写, 也不做自动化测试, 那这段代码其实有个特性没实现. 即请求不会在页面加载完时发生, 只有当刷新按钮被点击的时候才会. 但其实, 两种行为我们都需要: 刷新按钮被点击的时候的请求, 或者是页面刚打开时的请求.两种场景下需要不同的流:var requestOnRefreshStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });var startupRequestStream = Rx.Observable.just(‘https://api.github.com/users');但我们如何才能"合并"这两者为同一个呢? 有一个 merge() 方法. 用导图来解释的话, 它看起来像是这样的.stream A: —a——–e—–o—–>stream B: —–B—C—–D——–> vvvvvvvvv merge vvvvvvvvv —a-B—C–e–D–o—–>那我们要做的事就变得很容易了:var requestOnRefreshStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });var startupRequestStream = Rx.Observable.just(‘https://api.github.com/users');var requestStream = Rx.Observable.merge( requestOnRefreshStream, startupRequestStream);也有另外一种更干净的、不需要中间流的写法:var requestStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; }) .merge(Rx.Observable.just(‘https://api.github.com/users'));甚至再短、再有可读性一点:var requestStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; }) .startWith(‘https://api.github.com/users');startWith() 会照你猜的那样去工作: 给流一个起点. 无论你的输入流是怎样的, 带 startWith(x) 的输出流总会以 x 作为起点. 但我这样做还不够 [DRY], 我把 API 字符串写了两次. 一种修正的做法是把 startWith() 用在 refreshClickStream 上, 这样可以从"模拟"在页面加载时一次刷新点击事件.var requestStream = refreshClickStream.startWith(‘startup click’) .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });漂亮. 如果你现在回头去看我说 “有个特性没实现” 的那一段, 你应该能看出那里的代码和这里的代码的区别仅仅是多了一个 startWith().使用流来建立"3个推荐关注者"的模型到现在为止, 我们只是写完了一个发生在回复流的 subscribe() 中的 推荐关注者 的 UI. 对于刷新按钮, 我们要解决一个问题: 一旦你点击了"刷新”, 现在的三个推荐关注者仍然没有被清理. 新的推荐关注者只在请求内回复后才能拿到, 不过为了让 UI 看上去令人舒适, 我们需要在刷新按钮被点击的时候就清理当前的推荐关注者.refreshClickStream.subscribe(function() { // 清理 3 个推荐关注者的 DOM 元素});稍等一下. 这样做不太好, 因为这样我们就有两个会影响到推荐关注者的 DOM 元素的 subscriber (另一个是 responseStream.subscribe()), 这听起来不符合 Separation of concerns. 还记得 Reactive 口诀吗?在 “万物皆可为流” 的指导下, 我们把推荐关注者构建为一个流, 其中每个派发出来的值都是一个包含了推荐关注人数据的 JSON 对象. 我们会对三个推荐关注者的数据分别做这件事. 像这样来写:var suggestion1Stream = responseStream .map(function(listUsers) { // 从列表中随机获取一个用户 return listUsers[Math.floor(Math.random()*listUsers.length)]; });至于获取另外两个用户的流, 即 suggestion2Stream 和 suggestion3Stream, 只需要把 suggestion1Stream 复制一遍就行了. 这不够 [DRY], 不过对我们的教程而言, 这样能让我们的示例简单些, 同时我认为, 思考如何在这个场景下避免重复编写 suggestion[N]Stream 也是个好的思维练习, 就留给读者去考虑吧.我们让渲染的过程发生在回复流的 subscribe() 中, 而是这样做:suggestion1Stream.subscribe(function(suggestion) { // 渲染第 1 个推荐关注者});回想之前我们说的 “刷新的时候, 清理推荐关注者”, 我们可以简单地将刷新单击事件映射为 “null” 数据(它代表当前的推荐关注者为空), 并且在 suggestion1Stream 做这项工作, 如下:var suggestion1Stream = responseStream .map(function(listUsers) { // 从列表中随机获取一个用户 return listUsers[Math.floor(Math.random()*listUsers.length)]; }) .merge( refreshClickStream.map(function(){ return null; }) );在渲染的时候, 我们把 null 解释为 “没有数据”, 隐藏它的 UI 元素.suggestion1Stream.subscribe(function(suggestion) { if (suggestion === null) { // 隐藏第 1 个推荐关注者元素 } else { // 显示第 1 个推荐关注者元素并渲染数据 }});整个情景是这样的:refreshClickStream: ———-o——–o—-> requestStream: -r——–r——–r—-> responseStream: —-R———R——R–> suggestion1Stream: —-s—–N—s—-N-s–> suggestion2Stream: —-q—–N—q—-N-q–> suggestion3Stream: —-t—–N—t—-N-t–>其中 N 表示 null(译者注: 注意, 当 refreshClickStream 产生新值, 即用户进行点击时, null 的产生总是立刻发生在 refreshClickStream 之后; 而 refreshClickStream => requestStream => responseStream, responseStream 中的值, 是发给 API 接口的异步请求的结果, 这个结果的产生往往会需要花一点时间, 必然在 null 之后, 因此可以达到 “为了让 UI 看上去令人舒适, 我们需要在刷新按钮被点击的时候就清理当前的推荐关注者” 的效果).稍微完善一下, 我们会在页面启动的时候也会渲染 “空” 推荐关注人. 为此可以 startWith(null) 放在推荐关注人的流里:var suggestion1Stream = responseStream .map(function(listUsers) { // 从列表中随机获取一个用户 return listUsers[Math.floor(Math.random()*listUsers.length)]; }) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);最后我们得到的流:refreshClickStream: ———-o———o—-> requestStream: -r——–r———r—-> responseStream: —-R———-R——R–> suggestion1Stream: -N–s—–N—-s—-N-s–> suggestion2Stream: -N–q—–N—-q—-N-q–> suggestion3Stream: -N–t—–N—-t—-N-t–>关闭推荐关注人, 并利用已缓存的回复数据目前还有一个特性没有实现. 每个推荐关注人格子应该有它自己的 ‘x’ 按钮来关闭它, 然后加载另一个数据来代替. 也许你的第一反应是, 用一种简单方法: 在点击关闭按钮的时候, 发起一个请求, 然后更新这个推荐人:var close1Button = document.querySelector(’.close1’);var close1ClickStream = Rx.Observable.fromEvent(close1Button, ‘click’);// close2Button 和 close3Button 重复此过程var requestStream = refreshClickStream.startWith(‘startup click’) .merge(close1ClickStream) // 把关闭按钮加在这里 .map(function() { var randomOffset = Math.floor(Math.random()500); return ‘https://api.github.com/users?since=' + randomOffset; });然而这没不对. (由于 refreshClickStream 影响了所有的推荐人流, 所以)该过程会关闭并且重新加载_所有的_推荐关注人, 而不是仅更新我们想关掉的那一个. 这里有很多方式来解决这个问题, 为了玩点炫酷的, 我们会重用之前的回复数据中别的推荐人. API 返回的数据每页包含 100 个用户, 但我们每次只用到其中的 3 个, 所以我们有很多有效的刷新数据可以用, 没必要再请求新的.再一次的, 让我们用流的思维来思考. 当一个 ‘close1’点击事件发生的时候, 我们使用 responseStream中 最近被派发的 回复来从回复的用户列表中随机获取一个用户. 如下: requestStream: –r—————> responseStream: ——R———–>close1ClickStream: ————c—–>suggestion1Stream: ——s—–s—–>在 [Rx] 中, 有一个合成器方法叫做 combineLatest, 似乎可以完成我们想做的事情. 它把两个流 A 和 B 作为其输入, 而当其中任何一个派发值的时候, combineLatest 会把两者最近派发的值 a 和 b 按照 c = f(x,y) 的方法合并处理再输出, 其中 f 是你可以定义的方法. 用图来解释也许更清楚:stream A: –a———–e——–i——–>stream B: —–b—-c——–d——-q—-> vvvvvvvv combineLatest(f) vvvvvvv —-AB—AC–EC—ED–ID–IQ—->在该例中, f 是一个转换为全大写的函数我们可以把 combineLatest() 用在 close1ClickStream 和 responseStream 上, 因此一旦 “关闭按钮1” 被点击(导致 close1ClickStream 产生新值), 我们都能得到最新的返回数据, 并在 suggestion1Stream中产生一个新的值. 由于 combineLatest() 的对称性的, 任何时候, 只要 responseStream 派发了一个新的回复, 它也将合并最新的一次 ‘关闭按钮1被点击’ 事件来产生一个新的推荐关注人. 这个特性非常有趣, 因为它允许我们简化我们之前的 suggestion1Stream , 如下:var suggestion1Stream = close1ClickStream .combineLatest(responseStream, function(click, listUsers) { return listUsers[Math.floor(Math.random()*listUsers.length)]; } ) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);在上述思考中, 还有一点东西被遗漏. combineLatest() 使用了两个数据源中最近的数据, 但是如果这些源中的某个从未派发过任何东西, combineLatest() 就不能产生一个数据事件到输出流. 如果你再细看上面的 ASCII 图, 你会发现当第一个流派发 a 的时候, 不会有任何输出. 只有当第二个流派发 b 的时候才能产生一个输出值.有几种方式来解决该问题, 我们仍然采取最简单的一种, 就是在页面启动的时候模拟一次对 ‘关闭按钮1’ 按钮的点击:var suggestion1Stream = close1ClickStream.startWith(‘startup click’) // 把对"关闭按钮1"的点击的模拟加在这里 .combineLatest(responseStream, function(click, listUsers) {l return listUsers[Math.floor(Math.random()*listUsers.length)]; } ) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);总结整理现在我们的工作完成了. 完整的代码如下所示:var refreshButton = document.querySelector(’.refresh’);var refreshClickStream = Rx.Observable.fromEvent(refreshButton, ‘click’);var closeButton1 = document.querySelector(’.close1’);var close1ClickStream = Rx.Observable.fromEvent(closeButton1, ‘click’);// close2 和 close3 是同样的逻辑var requestStream = refreshClickStream.startWith(‘startup click’) .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });var responseStream = requestStream .flatMap(function (requestUrl) { return Rx.Observable.fromPromise($.ajax({url: requestUrl})); });var suggestion1Stream = close1ClickStream.startWith(‘startup click’) .combineLatest(responseStream, function(click, listUsers) { return listUsers[Math.floor(Math.random()listUsers.length)]; } ) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);// suggestion2Stream 和 suggestion3Stream 是同样的逻辑suggestion1Stream.subscribe(function(suggestion) { if (suggestion === null) { // 隐藏第 1 个推荐关注者元素 } else { // 显示第 1 个推荐关注者元素并渲染数据 }});你可以在这里查看完整的[样例代码]很惭愧, 这只是一个微小的代码示例, 但它的信息量很大: 它着重表现了, 如何对关注点进行适当的隔离, 从而对不同流进行管理, 甚至充分利用了返回数据的流. 这样的函数式风格使得代码像声明式多于像命令式: 我们并不用给出一个要执行的的结构化序列, 我们只是通过定义流之间的关系来表达系统中每件事物是什么. 举例来说, 通过 Rx, 我们告诉计算机 suggestion1Stream 就是 点击关闭按钮1 的流, 与最近一个API返回的(用户中随机选择的一个)的用户的流, 刷新时产生 null 的流, 和应用启动时产生 null 的流的合并流.回想一下那些你熟稔的流程控制的语句(比如 if, for, while), 以及 Javascript 应用中随处可见的基于回调的控制流. (只要你愿意, )你甚至可以在上文的 subscribe() 中不写 if 和 else, 而是(在 observable 上)使用 filter()(这一块我就不写实现细节了, 留给你作为练习). 在 Rx 中, 有很多流处理方法, 比如 map, filter, scan, merge, combineLatest, startWith, 以及非常多用于控制一个事件驱动的程序的流的方法. 这个工具集让你用更少的代码而写出更强大的效果.接下来还有什么?如果你愿意用 [Rx] 来做反应式编程, 请花一些时间来熟悉这个 函数列表, 其中涉及如何变换, 合并和创建 Observables (被观察者). 如果你想以图形的方式理解这些方法, 可以看一下 弹珠图解 RxJava. 一旦你对理解某物有困难的时候, 试着画一画图, 基于图来思考, 看一下函数列表, 再继续思考. 以我的经验, 这样的学习流程非常有用.一旦你熟悉了如何使用 [Rx] 进行变成, 理解冷热酸甜, 想吃就吃…哦不, 冷热 Observables 就很有必要了. 反正就算你跳过了这一节, 你也会回来重新看的, 勿谓言之不预也. 建议通过学习真正的函数式编程来磨练你的技巧, 并且熟悉影响各种议题, 比如"影响 [Rx] 的副作用"什么的.不过, 实现了反应式编程的库并非并非只有 [Rx]. [Bacon.js] 的运行机制就很直观, 理解它不像理解 [Rx] 那么难; [Elm Language] 在特定的应用场景有很强的生命里: 它是一种会编译到 Javascript + HTML + CSS 的反应式编程语言, 它的特色在于 [time travelling debugger]. 这些都很不错.Rx 在严重依赖事件的前端应用中表现优秀. 但它不只是只为客户端应用服务的, 在接近数据库的后端场景中也大有可为. 实际上, [RxJava 正是激活 Netflex 服务端并发能力的关键]. Rx 不是一个严格限于某种特定类型应用的框架或者是语言. 它其实是一种范式, 你可以在任何事件驱动的软件中实践它.本文作者:richardo2016阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 6, 2018 · 8 min · jiezi

使用API自动生成工具优化前端工作流

在工作中,我们的前端工作流一般开始于前后端协商好Api文档之后,再针对这个Api文档做mock模拟数据,然后用做好的mock进行开发,后端开发完毕之后再改一下API数据的BaseURL切换到正式API进行联调;如下本文介绍的一个工具(或者说方法),来将这个工作流优化一下,也是我平时工作正在用的方法,当做自己的笔记,也跟大家一起分享一下这个方法的主要思路就是开发人员在某个api工具中按要求填好文档,然后导出swagger.json配置文件,再把这个配置文件导入到easy-mock中,再用工具自动生成前端api的js文件以供调用。本文中所使用的工具:sosoApi、Easy-mock、Swagger、Easy-mock-api-template、axios1. 使用Api管理平台导出swagger.json文件一般我们前后端通过各种平台或者工具来管理Api,比如免费的可视化Api管理平台 sosoApi、Yapi等,一般来说这些工具都可以生成swagger.json的Api,我们可以用它来直接生成一个漂亮的可视化Api文档,也可以用它来作为配置文件导入其他工具中,比如Easy-mock;比如在sosoApi中就可以导出为swagger文档(swagger.json):我们先导出一个swagger.json备用;2. 使用swagger.json导入easy-mockMock平台我们可以使用Easy-mock,轻量又简洁,虽然没有Api的分组功能,但平时应付应付不太大的应用、个人应用等场景足够了;Easy-mock官网的服务被不少人直接拿到开发环境用,经常被挤爆,这个情况可以用本地部署来解决这个问题,参考 windows本地安装部署 Easy Mock 。我们将Api管理平台中导出的swagger.json文件在新建project的时候导入:这样刚刚Api平台中配置的Api就被同步到我们的Easy-mock配置中了,比如sosoApi的示例项目导出的结果就是:这时我们就可以用它来进行数据mock了,怎么样,是不是很轻松easy-mock项目面板上面会有个 Project ID,这个记下来后面要用;3. 使用easy-mock-cli生成js格式Api有了easy-mock之后一般情况下我们要写前端的api文件了,一般api工具用axios,这里提供一个封装:// utils/fetch.jsimport axios from ‘axios’ const service = axios.create({ baseURL: ‘https://easy-mock.com/project/5bf6a23c92b5d9334494e884', timeout: 5000}) // request拦截器service.interceptors.request.use( config => {…}, err => {…}) // respone拦截器service.interceptors.response.use( res => {…}, err => {…}) export default service我们可以用easy-mock-cli来生成api,模板文件如果不想用原来的模板的话,可以使用我fork之后改写的一个模板easy-mock-api-template,生成的Api文件是这样的:// api/index.jsimport fetch from ‘utils/fetch’; /* 活动查询 /const activityQuery = ({ activityDate }) => fetch({ method: ‘get’, url: ‘/activity/query’, params: { activityDate }}); /* 活动保存 /const activitySave = () => fetch({ method: ‘post’, url: ‘/activity/save’}); /* 活动提交 */const activitySubmit = ({ activityId, content }) => fetch({ method: ‘post’, url: ‘/activity/submit’, data: { activityId, content }}); export { activityQuery, // 活动查询 activitySave, // 活动保存 activitySubmit // 活动提交};然后在文件中就可以:import * as Api from ‘api/index.js’; // 调用Api.activitySubmit({ activityId: 2 }) .then(…)简单介绍一下配置文件,更复杂的配置要参考原来的文档;// .easy-mock.js 配置文件 { host: ‘http://localhost:8080/’, // easy-mock的源,没有本地部署的话不用写,本地部署则填本地服务地址 output: “../”, // 生成 API 的基础目录 template: “../”, // 指定模板,这里用本地写的模板 projects: [ // 可以有多个模板来源 { “id”: “你要创建的 Easy Mock 项目的 id”, // 刚刚记下来的 Project ID “name”: “api” // 生成的output目录下的文件名 } ]}然后npm run create-api就可以在根目录下生成一个api/index.js文件了网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出参考:用swagger.json自动生成axios api访问代码 - 简书Easy-mock-cli/README.md推介阅读:windows本地安装部署 Easy Mock - 掘金 ...

November 23, 2018 · 1 min · jiezi

使用postman调试jwt开发的接口

我的github博客:https://zgxxx.github.io/上一篇博客文章https://segmentfault.com/a/11… 介绍了laravel使用dingo+jwt开发API的几个步骤,那么在实际操作中,我们需要测试API$api = app(‘Dingo\Api\Routing\Router’);$api->version(‘v1’, [’namespace’ => ‘App\Http\Controllers\V1’], function ($api) { $api->post(‘register’, ‘AuthController@register’); $api->post(’login’, ‘AuthController@login’); $api->post(’logout’, ‘AuthController@logout’); $api->post(‘refresh’, ‘AuthController@refresh’); $api->post(‘me’, ‘AuthController@me’); $api->get(’test’, ‘AuthController@test’);});设置了这几个路由,对应的url类似这样:http://www.yourweb.com/api/me 使用postman来调试这些API。请求API的大致流程我们使用jwt代替session,首先是通过登录(jwt的attempt方法验证账号密码),成功后会返回一个JWT,我们把这个字符串统一叫做token,这个token需要我们客户端保存起来,然后后面需要认证的接口就在请求头里带上这个token,后台验证正确后就会进行下一操作,如果token错误,或者过期就返回401或500错误,拒绝后面的操作。前端可以保存在localStorage,小程序可以 使用wx.setStorageSync保存所以请求头信息Authorization:Bearer + token很重要,但是有个问题,这个token是有一个刷新时间和过期时间的:’ttl’ => env(‘JWT_TTL’, 60),‘refresh_ttl’ => env(‘JWT_REFRESH_TTL’, 20160),refresh_ttl是过期时间,默认14天,很好理解,就像一些网站一样,你好几个月没去登录,你的账号会自动退出登录,因为过期了,需要重新输入账号密码登录。ttl刷新时间默认60分钟,也就是说你拿这个一小时前的token去请求是不行的,会报The token has been blacklisted的错误,意思是说这个旧的token已经被列入黑名单,无法使用token是会被别人盗取的,所以token需要每隔一段时间就更新一次这时候有个问题,每隔一小时就更新,那岂不是要每小时就重新登录一遍来获取新token?当然不需要,我们可以写个中间件来实现无痛刷新token,用户不会察觉我们已经更新了token。<?phpnamespace App\Http\Middleware;use Closure;use Tymon\JWTAuth\Exceptions\JWTException;use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;use Tymon\JWTAuth\Exceptions\TokenExpiredException;use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;class RefreshToken extends BaseMiddleware{ /** * @author: zhaogx * @param $request * @param Closure $next * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed * @throws JWTException */ public function handle($request, Closure $next) { // 检查此次请求中是否带有 token,如果没有则抛出异常。 $this->checkForToken($request); // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException 异常 try { // 检测用户的登录状态,如果正常则通过 if ($this->auth->parseToken()->authenticate()) { return $next($request); } throw new UnauthorizedHttpException(‘jwt-auth’, ‘未登录’); } catch (TokenExpiredException $exception) { // 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中 try { // 刷新用户的 token $token = $this->auth->refresh(); // 使用一次性登录以保证此次请求的成功 \Auth::guard(‘api’)->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()[‘sub’]); } catch (JWTException $exception) { // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。 throw new UnauthorizedHttpException(‘jwt-auth’, $exception->getMessage()); } } return $next($request)->withHeaders([ ‘Authorization’=> ‘Bearer ‘.$token, ]); }}一旦中间件检测到token过时了,就自动刷新token,然后在响应头把新的token返回来,我们客户端可以根据响应头是否有’Authorization’来决定是否要替换token在使用postman调试这些API的时候就有个问题,postman又没有前端代码,我怎么及时更新这个token,难道每次请求都要去看响应头,发现Authorization后手动去复制黏贴吗,当然也不需要,postman有个强大的环境变量,其实也是前端js的东西。postman自动刷新请求头token登录后自动获取token首先点击设置环境这个按钮,点击Add按钮添加一个变量,我们设置key值为access_token,接着我们在登录接口的Tests中去赋值这个变量var data = JSON.parse(responseBody); if (data.result.access_token) { tests[“Body has token”] = true; var tokenArray = data.result.access_token.split(" “); postman.setEnvironmentVariable(“access_token”, tokenArray[1]); } else { tests[“Body has token”] = false; } 这段js代码就是获取请求成功后返回的access_token值,将其赋值给postman的环境变量,我们看到请求成功后我们后台返回了一个json,其中就有我们需要的access_token,我们可以再去环境变量那里看看这时候的变量有什么变化可以看到这里的变量access_token已经有值了,就是我们后台返回来的access_token字符串,说明赋值成功接着我们到另一个需要认证的接口测试我们在Authorization类型type选择Bearer Token,在后面Token表单那里打一个’{‘就会自动提示我们设置过的变量{{access_token}}发送请求测试下已经成功了。无痛刷新token那如果token刷新了,经过后台中间件无痛刷新后,会在响应头返回一个新的token(这一次请求用的是旧的token,默认认证通过)现在我们需要在这个接口上直接更新我们的变量access_token(如下图),而不需要去再请求一遍登录接口var authHeader = postman.getResponseHeader(‘Authorization’);if (authHeader){ var tokenArray = authHeader.split(” “); postman.setEnvironmentVariable(“access_token”, tokenArray[1]); tests[“Body has refreshtoken”] = true; } else { tests[“Body has no refreshtoken”] = false; }这段js代码就是将响应头中的Authorization赋值给我们的access_token这是响应头的Authorization,截取了最后面的字符串刷新时间过了后,我们试着再发一次请求,我们可以看到,还是可以访问的,而且请求头里的Authorization已经自动更新过来了用一句话整理大概就是,你需要在哪个接口响应后更新变量,就去这个这个口的Test下写js赋值代码postman.setEnvironmentVariable(“access_token”, token);,只要没错误你就可以在别的地方使用{{access_token}}更新替换了。 ...

November 9, 2018 · 1 min · jiezi

开发一款即时通讯App,从这几步开始

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦本文由腾讯云视频发表于云+社区专栏关注公众号“腾讯云视频”,一键获取 技术干货 | 优惠活动 | 视频方案“晚上去哪吃饭啊?”桌面上来自一条晚上约饭的对话框——QQ。突然灵光一现,新出了优化的IM SDK,可以尝试着搭建一个类似QQ的即时通讯软件01注册账号腾讯云官网注册腾讯云账号,也可以使用QQ或者微信直接登陆02创建应用选择【产品】→【云通信】→【立即使用】→【创建应用接入】03SDK接入1、集成SDK【下载云通信SDK】包括IMSDK(云通信SDK)、TUIKit(基础界面库)2、生成UserSig在【基础配置】下载公私钥,使用【开发辅助工具】生成测试用户的UserSig3、初始化SDKAndroid代码 //应用启动时(一般为Application的onCreate)配置UIKit的基本配置,具体参数说明参考API BaseUIKitConfigs uiKitConfigs = new BaseUIKitConfigs(); uiKitConfigs.appCacheDir(Constants.APP_DIR_CACHE).audioRecordMaxTime(120) .disableAudioPlayedStatusIcon(true).disableAutoPlayNextAudio(true) .ChatProcessor(new PojoChatProcessor()); ILiveUIKit.init(this, uiKitConfigs);4、创建登陆界面Android代码 public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //实例化登录面板 mLoginPanel = new LoginView(this); //将登录组件设置为登录Activity的基本布局,也可在布局文件xml中引用LoginView setContentView((View) mLoginPanel); //添加登录组件的动作事件,登录点击与注册点击 mLoginPanel.setLoginEvent(new ILoginEvent() { @Override public void onLoginClick(View view, String userName, String password) { //点击登录时业务自己的登录逻辑 } @Override public void onRegisterClick(View view, String userName, String password) { //点击注册时业务自己的注册逻辑 } }); }界面实图 5、创建会话列表布局文件 <!–在会话列表布局文件中引用会话列表组件,也可参考登录面板在代码中设置–><?xml version=“1.0” encoding=“utf-8”?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:app=“http://schemas.android.com/apk/res-auto" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“match_parent” android:orientation=“vertical” tools:context=".main.MainActivity”> <com.tencent.qcloud.uikit.business.session.view.SessionPanel android:id=”@+id/session_panel” android:layout_width=“match_parent” android:layout_height=“match_parent” /></LinearLayout>Android代码 /*** 获取会话列表组件,初始化默认设置* 会话组件的默认设置已经实现了会话数据的拉取与处理(与IMSDK关联完成相关逻辑)* 开发者如为特殊要求直接初始化默认设置即可* 另会话组件提供的可扩展的事件和UI处理,具体可参考API文档*/sessionPanel = baseView.findViewById(R.id.session_panel);sessionPanel.initDefault();界面实图6、创建聊天界面Android代码//从布局文件中获取聊天面板组件chatPanel = mBaseView.findViewById(R.id.chat_panel);/** 会话组件的默认设置已经实现了会话数据的拉取与处理(与IMSDK关联完成相关逻辑)* 开发者如无特殊要求直接初始化默认设置即可* 另聊天面板组件提供的可扩展的事件和UI处理,具体可参考API文档*/chatPanel.initDefault();//生成聊天基本信息,如聊天对象的昵称,头像,最后一页聊天信息等BaseChatInfo info = getChatInfo();//设置基本信息,以便用户进入该页面时能即时展示相关信息chatPanel.setBaseChatInfo(info);界面实图7、群管理Android代码//从布局文件中获群管理面板组件GroupManagerPanel groupManagerPanel = mBaseView.findViewById(R.id.group_manager_panel);/** 群管理组件的默认设置已经实现了群管理相关的逻辑也操作(与IMSDK关联完成相关逻辑)* 开发者如无特殊要求直接初始化默认设置即可* 另聊群管理组件提供的可扩展的事件和UI处理,具体可参考API文档*/groupManagerPanel.initDefault();界面实图 通过以上几个步骤,一个拥有单聊、群聊的即时通讯App就这样完成了再次崇拜自己的动手能力,一天就能搭建完成一个APP,啦啦啦啦啦啦最后,了解一下经过优化后的新版本IM SDKIM SDK–体积优化1.android so体积<1M2.ios体积增量<2M–性能优化1.线程裁剪(单线程架构,减少线程切换和线程同步开支)2.cpu占用优化(线程裁剪、代码逻辑优化)–消息到达率深度优化业务逻辑层、会话策略层和网络层,实现四个九(99.99%)的消息到达率–数据监控用户级数据监控,实时跟踪和统计用户行为dau/mau功能统计用户分布–海外布点增加富媒体消息海外布点,为图片视频语音文件的上传下载提速–版本兼容与老版本兼容,实现无缝切换–UIKit插件一套多功能自定义界面库,实现会话列表、聊天、联系人、群管理、弹幕等界面,实现客户一天接入问答请问小程序即时通讯如何接入发送消息?相关阅读IM即时通讯实现原理iOS 即时通讯 + 仿微信聊天框架 + 源码开发一款即时通讯App,从这几步开始 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

October 29, 2018 · 1 min · jiezi

8分钟丨教你玩转 API

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由织云平台团队发表于云+社区专栏背景当下,业界越来越多公司在项目架构设计时,会采用微服务架构。微服务架构,可以让我们的产品有更好的扩展性,更好的伸缩性;但同时也会带来微服务的一系列问题,比如微服务接口怎样规范管理?怎样在多团队协作中开放与复用?等等。同时,业界也在逐渐的引入DevOps理念,来实现开发,测试,运维,运营更紧密的高效配合,提升产品迭代的效率,质量。这里,织云API平台将从“部门内微服务API开放复用”,“产品线API DevOps实践”来分享腾讯社交网络运营部踩过的坑和API平台在“开放”,“DevOps”的理解及实践。1API开放与复用部门长期运营,积累了很多优秀的系统/平台,各个系统/平台也很早的开放了自己的Open API 给其它团队做二次开发和使用。 作为开放接口使用方:要集成A平台的B服务时,你可能会遇到:找不到平台开放接口文档;从平台官网下载的接口文档好像未更新;接口文档定义太简单。看不懂;使用前,不清楚接口的质量现状(成功率,耗时等);出异常时,没法快速界定问题的边界。作为开放接口提供方:你在运营上可能会遇到:接入方很多,长久下来,自己都不清楚调用方是哪些?最近我的接口调用量大增,不清楚这些调用是否合理?旧接口要下线,但仍有请求。不方便快速找到调用者。2产品线API那些事儿织云,是腾讯SNG海量业务运维能力经验沉淀出的产品,它采用微服务架构。在微服务的开发,测试,交付,运营中,我们遇到这样子的问题:web工程师:版本迭代很紧,但是后台同学的接口迟迟出不来,我的工作delay很久了后台工程师:版本迭代很紧,写代码的时间都没有。哪来时间写用例。但每次修改代码,人工自测耗费很多时间。两位工程师:上次不是好说接口长xx样子吗?怎样现在变成这样子了?质量工程师:这个迭代,织云性能是否达标呢?看不见,摸不着,快慢主要凭感觉。运维工程师:客户反馈操作有异常。一个问题都转几手开发。我怎样快速定位问题根源客户:你们的XX能力很好。我们想基于它们接口做二次开发。有开放接口吗织云API平台,就是这种大背景下应运而生。API平台简介定义织云API平台,是一个以API服务管理和代理以基础,赋能接口开发,测试,上线运营,下线管理于一体的API管理与开放平台。应用场景功能介绍1、织云API平台,实现了API统一规范管理与开放。 2、以服务代理为基础,实现了安全认证,过载保护。 3、对于服务调用支持日志查询,数据画像,异常告警,链路分析等功能。 4、可以基于API平台实现基于织云所有能力的定制开发的能力。接口规范和接入成本接口规范屏蔽接口URI层级差异:API平台,统一采用三级结构,通过/平台/服务/接口的层次来管理所有接入API,屏蔽实际接口URI的层级差异;屏蔽接口响应结构差异:API平台,自动转换接口响应结构,屏蔽实际接口的结构差异化。大大简化了集成开发,特别是Web前端同学适配后台接口的复杂度。全局业务错误码:确保服务间的每个错误码都是唯一能溯源的。接入成本–零改造API平台在设计之前就考虑到用户接入的成本。以上规范,API平台都能自动屏蔽差异,自动转换,自动生成。用户接入零改造。注册API服务 — 示例现成的API接口现在我有一个容量的分析接口:查询模块容量持续高低负载数据接口。url: http://capacity/load/sustaine… method: get 入参:type=1&m1id=468095&m2id=468095&m3id=468095&m4id=468095 出参: [ { “m1id”: 1256, “m2id”: 1256010, “day_cnt”: 14, “m4id”: 468095, “avg_load”: 0.25, “type”: 1, “model_cnt”: 1, “m3id”: 11120 } ]创建接口对应的平台,服务操作相似。如创建服务:其中的英文缩写,将是最终API url中对应的服务名。注册接口 - 基础信息注册接口 - 定义请求示例自动生成入出参:在入参,出参示例部分,只须贴入:入参:type=1&m1id=468095&m2id=468095&m3id=468095&m4id=468095, 出参: [ { “m1id”: 1256, “m2id”: 1256010, “day_cnt”: 14, “m4id”: 468095, “avg_load”: 0.25, “type”: 1, “model_cnt”: 1, “m3id”: 11120 } ]API平台会自动帮我们解析结构(当前支持key/value, json结构等解析)用户,只须录入字段是否必填,以及中文说明即可。注册接口 - 定义接口返回码接口开放查看开放API接口列表页:在API平台注册接口后,可以在API平台列表中查询每个开放接口:明细面:在明细面,可以查看接口详情。以及自动生成的调用示例。自动生成接口文档 访问权限申请API平台的接口开放模式暂时有两种:全开放,须审核。全开放:用户须在API平台应用组进行登记注册,API平台会分配一个唯一的apikey给到用户。对于全开放的接口,用户访问时,只须header带上apikey即可。须审核:对于一些敏感数据接口,用户访问前,须进行权限申请,将请求所在的业务模块与当前接口进行绑定。API平台只允许目标业务模块下的IP访问目标接口。场景一:接口开发-无中生有应用前-出现的问题:1.开发耦合:项目迭代刚启动,经常会出现后台开发间,前后端开发间接口相互依赖,导致工作相互delay。2.相互“扯皮”:开发间当面对齐接口,经常出现今天说“一套”,实际输出“一套”。没有接口落地及佐证的地方。应用后-规范的工作流程,实现了并行开发:有了API平台,大家的工作流程规范是这样:1. 接口提供方,注册新接口到API平台;2. 提供方与接口使用方通过API平台对齐接口,达成两方最终接口;3. 使用方使用API平台提供的伪接口进行功能开发及联调;(不再阻塞)4. 接口提供方严格按最终接口参数实现真实接口。5. 接口提供方将测试接口录入API平台,模式从“伪接口”切换成“测试”,接口使用方可以“无感知”的切换成真实接口服务中去。不需要额外联调。场景二:接口测试-可视化用例+自动测试“ 写代码的时间都没有。哪来时间写用例。但每次修改代码,人工自测耗费很多时间。” 这种现象其实在开发中很普遍。有没有一种模式,可以让开发不用写代码就能快速实现接口单元测试用例?甚至让不写代码的测试同学来帮开发实现测试用例?API平台,提供了可视化用例在线编辑:用户只须录入预设值,即可生成用例。一键执行用例,查看测试结果。API平台也实现了依赖第三方环境API的接口本地化测试。关于API平台测试能力这一部分,后面我们再专门单独做介绍。场景三:质量运营安全认证分配apikey: 所有API访问,须在API平台注册,由平台分配唯一的apikey。用户每次请求须带上apikey方可访问;限制开放源:对于敏感API接口,接口使用方须在API平台登记请求来源业务模块,经审核后,方可访问。过载保护每个接口可以自定义访问频率。API平台可以对接口进行限频保护。接口巡检API平台可对线上服务接口进行自定义的主动探测与巡检。在用户察觉问题前发现问题与修复问题。异常告警若API服务出现异常,API平台会主动通知接口使用方与提供方。异常告警案例CMDB下发配置(16:30,17:30灰度),未切走流量,导致接口请求小部分异常。 告警短信:查看API日志: 发现:后台spp服务异常跟进原因: 调用链路分析应用场景应用前-问题场景:A业务页面提示xx保存失败–>A业务开发卷入排查(重现问题+分析)–>发现是公共B服务异常–> B开发卷入相似分析–> 发现是平台服务C异常–> 卷入C开发相似分析–> 确认是redis服务异常。这种问题,如果发生在客户环境,会有ABC三个开发同学要:申请登录客户环境(有时很繁琐很费时)–> 排查–> 内部反馈,流转问题单。有时排查分析时间还没有前后协调时间耗费得多。应用后-链路分析场景:API平台调用链路分析能力,方便不懂业务的运维同学,一键在线查看整个调用链,直达问题根节点:1.获取异常请求ID:前端页面或后台服务出现异常时,定位者可以从页面或日志中获取调用请求的ID,2.还原问题现场:根据请求ID,在API平台获取调用链,快速全方位的还原现场数据:链路中每个请求的入参,出参,耗时,返回码,异常日志等。告别登机子查日志-重试重现问题-大量开发介入-问题修复效率低慢的问题。API调用链路分析API平台根据起始请求,将接口间调用关系生成一棵调用树.我们可以一目了然的看到:1.请求的调用链路;2.每一层调用现场:服务调用方,服务提供方,接口返回码,耗时, 入参,出参, 异常日志(若有异常)利用API平台的调用树能力,我们可以:1、快速了解服务的调用关系,发现不合理调用;2、帮助售后快速定位问题;减少开发介入频率;3、现场复原:不须再重现;避免重现不了问题的定位4、web可视化分析:减少上机子查日志的次数。提升定位效率。案例一:发现不合理调用(1)问题现场devtest环境,执行工具市场工具异常.(2)获取requestId获取id, 输入查询(3)重现调用树+问题现场(4)发现原因/问题结论一(问题原因):命令通道接口-判断设备连通性:发现设备不可通。结论二:通过调用链,发现工具市场存在重复调用cmdb接口问题。工具市场下个迭代修复。案例二:CMDB异常(1)问题现场:执行工具市场时,只提示CMDB异常。但不知道原因。(2)查看API平台调用树:不需求上机子查日志啦。可见原因是DB连接异常。场景四:数据画像API平台有实时日志查询、实时数据画像、性能分析等数据画像能力。这里,针对成功率,耗时做下介绍: 对于运营者来说,我们很关心线上接口的成功率,耗时,这样将直接影响服务质量,用户体验。横向分析查看接口成功率分布及趋势 & 查看接口耗时分布及趋势。平均成功率,平均耗时,会在“平均数据”下掩盖了一些细微的问题。API平台画像,会采用分段模式,下钻一层看问题。纵向分析以“天+接口”纬度查看明细数据:性能优化案例刚接入API平台时,织云自动化服务,共有39个接口有调用记录。其中29个接口(66.7%)不达标(接口耗时超过500ms)。经开发性能后,慢接口大幅减少。小结织云API平台,是结合我们部门“接口开放”,“接口生产”需求、痛点和DevOps理念的一次新探索,新实践。在传统API网关的能力基础上,拓展到更多API周期阶段,实现API的DevOps赋能与管理。以上是API平台简单的介绍和分享,抛砖引玉,希望大家都能打造好自己的微服务管理与开放平台。共勉!· 分 · 割 · 线 · 啦 ·织云企业版预约体验织云社区版 V1.2下载问答无法从API获取数据?相关阅读模型剖析 | 如何解决业务运维的四大难题?混合云管理问题,你解决了么?Pick一下,工具上线前运维必备原则 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

September 30, 2018 · 1 min · jiezi

张腾:腾讯云融合通信应用场景及案例分享

张腾,腾讯通信云高级产品经理,先后负责过手机、智能硬件等终端产品,对运营商、即时通信、音视频产品均有了解,负责产品场景话包装,对融合通信的应用场景具有较深了解。如何帮助这些很大的企业,基于我们融合通信的方案帮助他们去实现他们想要的,提高效率这个核心的诉求。我们也整理出来了一些核心的挑战。主要我们分成四个部分,一个部分是关于企业内部沟通的一些挑战的方面,以及他们针对客户做营销一些挑战,同时还有包括像不同企业之间希望提高企业之间的效率的挑战,最后我们也是基于他们企业一些诉求提供了我们对于他们的整套集成方案。这些是企业内沟通的挑战,首先大家可能都会遇到过,企业内部可能很多APP,尤其通信类的,包括像微信、QQ、钉钉,但是你会发现每一个自己员工在使用APP的时候都有自己爱好,有些人就用QQ,有些人就用微信,大家沟通起来会降低很多效率。还有另外一块是因为大家使用基于微信、QQ的联系方案,所以大家在找人的时候没有办法去很好很快速的找到自己要对应的人,没有自己的架构体系支撑他们寻找到快速对接的人。同时还有像包括企业内部多系统,每一套系统流程全部是独立的,所以说可能某一个单子下来,需要在几个系统里面同时组单,导致整个业务流程相当的耗时。另外就是销售,大家没办法掌握。可能很多销售说,我现在出去见客户了,其实大家并不知道他在干什么。还有移动安全,因为移动安全大家都做公网,很多人都会担心这种信息泄露,外部沟通不便,受到人工影响很大,有些人在离职之后,他所有记录全部就消失掉了,可能跟着这个人走了,没有办法进行有效的留存。这是我们跟客户进行沟通了之后筛选出来几个比较重要的挑战,对于企业内部沟通的。对于我们客户跟企业之间,企业跟我们消费者之间沟通,企业更需要什么,首先他们需要是精准匹配精准营销的能力,他们希望有人可以告诉他们,他们目标客户是谁,谁能买他们产品,他们也需要数据支撑他们如何有效进行市场运作,以及品牌的运作,实现他们品牌的优势。另外他们销售转化,通过什么样的方案,可以实现更多消费者购买他们的产品。最后就是他们希望有更多宣传渠道,大家经常见到是系统推送,外部中心,短信等等多通道的营销手段使企业可以触达更多客户,实现他们整个的营销的闭环。首先对于很多企业来说,缺乏有效的沟通手段,大家全部都是通过刚才说的微信、QQ,只能去找到独立的一些人,很难去看到对方的整个部门是什么情况,是不了解的,包括他们部门在对方体系里面是一个什么样的地位,或者他们的从属是什么样子。每个部门是不一样的,所以会导致我们在沟通的时候缺乏效率。另外一个就是刚才说到寻找对方企业人员难如登天,在沟通的时候没有办法找到很多的资源,另外就是可能销售对接上之后发现采购去对接了,或者其他部门同时也对接的时候,很难找到有效的对接人,浪费大量时间也没有找到核心的人员,进行有效的沟通。另外就是采购销售离职之后企业之间会失联,销售在走了之后所有关于这个企业之间的对接就全部停掉了,后续的人可能会很难接起来,不知道找谁,或者之前的客群关系做成什么样子会没有人知道。另外就是缺乏上下游有效管理,对于我们供应链,对于我们的渠道,没有办法很好实现管控,就导致信息孤岛。同时整个数据和信息安全无法得到保证,大家很多时候都是在微信群、QQ群里沟通,可能这种对于信息的保护,包括像沟通出现问题的时候,进行问题信息的回溯的时候出现很多问题,没有办法找到当时可能责任人是谁,很难实现这种安全,包括信息的留存。所以说很多企业其实从他们的底层来看,他们为了高效引入了像OA系统,视频会议系统,呼叫中心包括CRM、HRM,然而通过这种手段反而导致他们一些业务分散,和一些紧耦合,不得不依赖所有系统打通整套流程,反而拉低他们效率,从我们这边来看,融合的核心就是帮助整个企业形成一种高级程、松耦合的状态,提高他们的效率。所以说针对我们一些企业,我们也提出了我们会融合独立系统组件,提高他们核心效率,可以看到在我们底层,会基于整个方案将OA、呼叫中心、视频组件、包括RM系统在实现打通,通过一些API调用,实现OA、HRM、CRM系统对接。同时在应用层,我们会去帮客户实现他们移动化办公,比如说他们现在需要多终端办公需求,还有像协同的终端系统去应用于客服与知乎之间对接,还有基于视频会议,员工数据同步,他们会把他们接触到的客户全部同步到自己公司的系统里面,保证员工系统留存。同时还有像专属客服座席,智能客服等等。我们像文字、语音、视频多通道的融合,帮助客户从上到下建立一个基于内部的多端在线的移动办公,另外基于对外整套客服系统,同时基于企业外部可以实现企业外部跟上下游之间的沟通和合作。其实从整个需求来看,我们最终的目标是为了提高整个公司的效率,我们从接触到的很多需求来看,大家提出都是基础需求,但是如果说我们从基础需求上筛选,我们发现大家的需求无外乎就是这几点,一个是员工办公移动化,另外就是我们客户的多渠道对接服务,另外就是我们业务的无缝对接。其实最终也就是为了提高效率和降低成本,这个就是我们通过跟很多客户了解以后,我们去考虑到融合通信到底给客户带来什么。我们也是提出了一些自己的看法,核心就是融合通信从底层来看,其实就是打通我们传统IP与传播的电信通信的壁垒,另外打通企业内部沟通壁垒,同时也打破了企业间沟通的壁垒,同时也是有多种通讯方案相互独立的壁垒,最后打破人与机器沟通的壁垒。可以看到从我们对外打破IP与传统电信通信壁垒来看,其实苹果的Imessage,实现了IP跟我们传统电信的打通。例如收发表情,在苹果用户之间是通过他们的系统进行传输,如果给安卓用户发,安卓用户收到是彩信,其实就是说把相关的IP和传统电信实现了打通。另外整个打通企业内部沟通壁垒,之前会把整个企业通信部包括PSTN进行导入,用户之间可以通过公司总机实现快捷拨号找到对应的同事,实现快速的沟通,同时也会基于我们企业微信整套系统,把公司所有同事放在里面,避免了再找不到人的时候随便去问,或者说通过关系,很难去实现有效沟通的场景。另外我们说打破了企业间沟通的壁垒,通过我们这种融合通信方案,可以把自己上下游相关企业内部通信录,权限的选择开放给本公司,尤其是重要部门,像销售、采购重要的部门可以通过这种企业间的通信录实现快速的对接,同时基于我们企业微信会进行消息留存,保证在对接过程中如果出现问题我们是可回溯可以找到当时责任方是谁。我们打破了多种通讯方案的壁垒,很多指挥中心的场景,不仅仅需要我们的IM,还有像音视频,同时还有像对讲机等等一系列通信手段实现了打通,把全部的通信方案进行融合,帮助客户在不同场景之下信息的沟通。同时我们现在也打破了人与机器相沟通的壁垒,之前我们一个很明显的例子,我们之前所有客服都是人工客服,然而现在基本上都已经全部是机器人客服为主,客户通过我们融合通信方案,实现了人和机器之间的沟通。问答云通信有哪些功能?相关阅读破局人工智能:构建AI,与腾讯云一起探索语音应用场景“融而开放、合以创新”——与腾讯云一起探索融合通信小白也能玩转Kubernetes 你与大神只差这几步 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识此文已由作者授权腾讯云+社区发布,更多原文请点击搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!海量技术实践经验,尽在云加社区!

September 25, 2018 · 1 min · jiezi

读完这篇文章,就能拥有炫同事一脸的超能力:JavaScript 魔幻代理

前言什么是代理?上小学的时候,李小红来你家叫你出去玩,第一个回应的不是你自己,是你妈:“王小明在家写作业,今天不出去!”上中学的时候,赵二虎带着小弟们放学在校门口等着揍你,走在前面的不是你自己,是二虎他爸:“考试没及格还学会装黑社会了!”拎起二虎就是一顿胖揍。上了大学,躺在宿舍里的床上,好饿。出门买饭并交代好不要葱蒜多放辣最后还直接端到床上的不是你自己,是快递小哥。这些都是代理。什么是 JavaScript 代理?用官方的洋文来说,是 Proxy:The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).通过 Proxy 我们可以拦截并改变一个对象的几乎所有的根本操作,包括但不限于属性查找、赋值、枚举、函数调用等等。在生活中,通过代理我们可以自动屏蔽小红的邀请、自动赶走二虎的威胁、自动买好干净的饭端到床上。在 JavaScript 世界里,代理也可以帮你做类似的事情,接下来让我们一起琢磨一番。初识代理:Hello World以小学经历为例子,心里是喜欢小红的,于是我们定义:const me = { name: ‘小明’, like: ‘小红’ }这个时候如果调用 console.log(me.like),结果必然是 小红。然而生活并不是这样,作为一个未成年人,总是有各种的代理人围绕在你身边,比如这样:const meWithProxy = new Proxy(me, { get(target, prop) { if (prop === ’like’) { return ‘学习’; } return target[prop]; }});这个时候如果调用 console.log(me.like) 依然是 小红 ,因为真心不会说谎。但当我们调用 console.log(meWithProxy.like) 的时候,就会可耻的输出 学习 ,告诉大家说我们喜欢的是 学习 。小试牛刀:不要停止我的音乐刚才我们简单了解了代理能够拦截对象属性的获取,可以隐藏真实的属性值而返回代理想要返回的结果,那么对于对象属性的赋值呢?让我们一起来看看。假设你正在听音乐:const me = { name: ‘小明’, musicPlaying: true }此时如果我们执行 me.musicPlaying = false 这样就轻而易举地停止了你的音乐,那么如果我们挂上代理人:const meWithProxy = new Proxy(me, { set(target, prop, value) { if (prop === ‘musicPlaying’ && value !== true) { throw Error(‘任何妄图停止音乐的行为都是耍流氓!’); } target[prop] = value; }});这时候如果我们执行 me.musicPlaying = false,就会被毫不留情地掀了桌子:> meWithProxy.musicPlaying = falseError: 任何妄图停止音乐的行为都是耍流氓! at Object.set (repl:4:13)>释放魔法:封装全宇宙所有 RESTful API现在我们已经知道通过 Proxy 可以拦截属性的读写操作,那然后呢?没什么用?仅仅是拦截属性的读写操作,的确没有太大的发挥空间,或许可以方便的做一些属性赋值校验工作等等。但是,或许你还没有意识到一个惊人的秘密:Proxy 在拦截属性读写操作时,并不在乎属性是否真的存在!那么,也就是说:利用 Proxy,我们可以拦截并不存在的属性的读取。再进一步思考:利用 Proxy,我们可以在属性读取的那一瞬间,动态构造返回结果。然而,属性并不局限于字符串、布尔值,属性可以是对象、函数、任何东西。至此,你想到了什么?没想到?不要紧!根据刚才的分析,让我们一起通过下面 17 行代码,来封装全宇宙所有的 RESTful API !import axios from ‘axios’;const api = new Proxy({}, { get(target, prop) { const method = /^[a-z]+/.exec(prop)[0]; const path = ‘/’ + prop .substring(method.length) .replace(/([a-z])([A-Z])/g, ‘$1/$2’) .replace(/$/g, ‘/$/’) .toLowerCase(); return (…args) => { // <—— 返回动态构造的函数! const url = path.replace(/$/g, () => args.shift()); const options = args.shift() || {}; console.log(‘Requesting: ‘, method, url, options); return axios({ method, url, …options }); } }});定义了 api 这个代理之后,我们就可以像下面这样调用:api.get()// GET /api.getUsers()// 获取所有用户// GET /usersapi.getUsers$Books(42)// 获取 ID 为 42 的用户的所有书籍// GET /users/42/booksapi.getUsers$Books(42, { params: { page: 2 } })// 获取 ID 为 42 的用户的所有书籍的第二页// GET /users/42/books?page=2api.postUsers({ data: { name: ‘小明’ } })// 创建名字为 小明 的用户// POST /users Payload { name: ‘小明’ }以上所有的函数都在你调用的那一瞬间,通过代理人的魔法之手动态生成,供我们随意取用。简洁、优雅,哇~ 真是太棒啦!终极魔幻:通读代理人的魔法秘笈到此,我们仅仅使用 Proxy 改造了对象的属性获取、赋值操作,而对于 Proxy 来说,只是冰山一角。Proxy 的基本语法如下:new Proxy(target, handler)其中 target 是即将被代理的对象(比如:想要出门找小红玩耍的 me),handler 就是代理的魔法之手,用来拦截、改造 target 的行为。对于 handler 对象,我们刚才仅仅用到了 get、set 函数,而实际上一共有 13 种可代理的操作:handler.getPrototypeOf()在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。handler.setPrototypeOf()在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。handler.isExtensible()在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。handler.preventExtensions()在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。handler.getOwnPropertyDescriptor()在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, “foo”) 时。handler.defineProperty()在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, “foo”, {}) 时。handler.has()在判断代理对象是否拥有某个属性时触发该操作,比如在执行 “foo” in proxy 时。handler.get()在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。handler.set()在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。handler.deleteProperty()在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。handler.ownKeys()在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。handler.apply()在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。handler.construct()在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。对于以上 13 种可代理的操作,还需要读者自行研究并实践方可踏上终极魔幻之旅。同学,我看好你。参考链接:Proxy - JavaScript | MDNHow to use JavaScript Proxies for Fun and Profit – DailyJS – Medium文 / 王小明本文已由作者授权发布,版权属于创宇前端。欢迎注明出处转载本文。本文链接:https://knownsec-fed.com/2018…想要订阅更多来自知道创宇开发一线的分享,请搜索关注我们的微信公众号:乐趣区。欢迎留言讨论,我们会尽可能回复。欢迎点赞、收藏、留言评论、转发分享和打赏支持我们。打赏将被完全转交给文章作者。感谢您的阅读。 ...

September 20, 2018 · 2 min · jiezi

一文拆解Faas的真实案例

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文来自腾讯云技术沙龙,本次沙龙主题为Serverless架构开发与SCF部署实践 刘敏洁:具有多年云计算行业经验,曾任职于华为、UCloud等企业担任产品开发、产品经理。目前负责腾讯云API生态的推广,帮助开发者进行API网关与无服务器函数、容器、微服务等产品的结合使用,提供完整解决方案。这次我们主要介绍面向于API网关和SCF深度结合应用,API网关与SCF结合可以形成比较完整的Serverless方案。今天的内容分为四部分:第一,API网关这个产品本身的简单介绍和一些能力。第二,API网关和SCF也就是是我们无服务器函数结合使用的时候提供了哪些能力跟它结合方便大家使用。第三,在比较常用的场景中我们整体的使用架构以及现在客户真正使用的案例。最后是大家关心的费用问题。我们做API网关这个产品的初衷不单单是一定说跟SCF强绑定的,是在用户使用中,遇到了很多问题。那么最开始我们为什么会做这个产品?在场大家多数都是做开发的应该很清楚了,比如前端的调用方式越来越多了,后端的部署方式越来越多了,可以用自己的物理机,可以买一个云主机,可以搞一个容器,可以像使用现在更新的微服务,无服务器计算。那么在这种复杂的情况下前端和后端的耦合就会造成业务上在扩容,在做一些新的业务增加的时候变得麻烦。那么此时,解耦就变得异常重要。市面上也有一些API网关这样的开源产品,但是运维的成本,费用问题也会分摊到每个开发同学身上,既然如此我们就在想何不做一个以服务的形式提供API网关能力的产品给客户呢。所以有了今天给大家介绍的API网关这样一款产品。API网关在一般场景下怎么使用的?有一个很简单的电商例子,多数情况下客户后端有很多的业务模块,有商品、用户这些常用的业务模块。如果在以前,可能是前端的APP,小程序,直接调用这些模块,后端也是以API形式提供给前端调用的。但是前端需要理解每个模块的API。现在我们提供API网关服务,客户在使用API网关后,我们建议客户把他们的每个业务模块跟我们API网关进行对接。由API网关统一输出一个API提供给前端调用。包括前面说的小程序,外部API,都是这样的一种可以在前端调用的方式。其实用一句话来总结一下,我们API网关这个服务是什么呢?就是API的一个托管服务,可以对多种后端能力进行统一的管理,输出无状态服务的API给到前端调用。用户可以在API网关上创建API,发布API,上线、下线等。包括建成限流,监控这样的一些能力,这些能力在解耦过程中都是非常需要的。这里总结了几个比较大的能力点:首先,统一鉴权认证,这是非常重要安全防护的能力。API转化和隐藏,像参数,位置,名称都可以做转化和映射,经过映射后真正的调用者不一定拿到这些后端的真实数据。流控和配额也是常见的能力,通常后端业务资源其实是有限的,所以前端需要将API的QPS和配额做限制。另外还有输出API能力,现在腾讯云提供了API市场,如果用户后台有一些业务,有一些能力,有一些数据,想通过API提供出去给第三方伙伴直接进行售卖,都可以通过这个网关输出到API网关进行直接售卖。自动化文档和SDK这个能力呢,其实是针对方便调用者进行调用的,如果我们有了业务后再手写文档,这个工作量也很大,所以API网关可以自动帮助客户生成文档,还有SDK供客户调用。强负载能力,腾讯云API网关依赖于腾讯大的负载平台,可以应对大的突发请求不惧怕大的负载,性能方面是很有保证的。最后,API网关可以对API进行安全防护,对调用API原IP做白名单。这里其实是比较完整的流转方式。API网关分两种角色:一个是发布API的人,一个是调用API的人,他们可能是同一些人,也可能是分别两拨人。发布者先把API发布到API网关中,参数,认证,鉴权,映射等等都需要进行配置,发布者配置后可以直接在API网关控制台上进行调试,在控制台上看看这个通不通,后台响应正不正常,Ok不Ok。如果调试都成功了进行API的发布。发布后就可以把API提供给调用者了。那么API网关怎么提供API给调用者呢?我们的服务其实是以域名的形式提供给调用者的。我们会提供默认的二级域名。当然客户有自己的域名,也可以把这个域名绑到我们二级域名上。另外API网关可以生成文档SDK,提供给调用者。调用者直接使用文档、SDK可以方便的调用。调用后,前端请求到API网关后,API网关会根据之前发布的配置做一些认证,鉴权这样一些基础的校验。到后端我们会做参数的映射,最终把这个请求发到后端业务。其实后端可以对接很多云服务,像SCF,像其他云服务统统都是可以对接的。当然今天主角是无服务器函数,所以我们主要讲一下对接到SCF。对接后它会拉起它的服务响应,根据响应看是不是做一些映射,最后把结果丢会调用者。在这个过程中发布者可以看到监控的信息是怎样,也可以查询到调用。如果调用失败,或者有什么错误,是前端的还是后端的,整个日志都是可以查询到的。这是一个比较完整的流程。我们现在可以看一下API怎么跟SCF结合使用。其实很简单,前面是API网关做一个触发器。用触发器的形式来触发SCF后端的函数。然后SCF做一些计算处理,结束后它可能落到后边的像数据库这些服务,在图中的没有画出来。这种方式现在APP,小程序都是很常用的,包括给第三方合作伙伴,就是它们直接提供API给第三方合作伙伴,不走任何的平台,这种都是非常常用的一种方式。安全与限流,刚刚在我们整个功能里其实有做了一个简单的说明。这里分几块说:第一,我们提供认证能力。认证的能力像密钥对是现在常用的认证方式,我们在网关上生成密钥对,把这个提供给调用者。这个密钥对常用是服务器端对服务器端。也有像用OAuth这种单点登录,客户有自己认证服务器的,我们也是支持的,可以去用API网关对接认证服务器。认证服务器去对调用做认证鉴权,然后我们在做一些校验。这是对整个认证管理做一个安全性的保障。其实还有刚才说的源IP黑白名单。这个常见于大家内部用的,比如我是一个大的公司,内部几个部门之间用。另外限流的部分,当后端业务能承载的QPS有限的时候,在API这边做一个限流控制,如果超过这个QPS,就把这个调用丢掉,后端不会过载。CORS,现在跨域的调用非常多的,比如电商,还有一些WEB页,像CSS、JS静态调用,访问时需要浏览器跨域调用。当请求是跨域请求时,API网关会根据发布者的配置做一些处理,比如配置支持跨域,API网关会把这个CORS头去。直接响应回来后我们再把CORS头放回响应中。这完成的是跨域调用。这样的话其实对SCF开发者会比较方便使用,就不用再操心跨域的问题。响应集成这种也是常见的使用,用户开发小程序时的场景也是响应集成可能比较常用到。比如在对接前面的接口时,需要把SCF抽取成真正的HTTP格式请求返回给前端。API网关与SCF之间同样为HTTP请求,SCF函数返回的响应在响应透传模式时,会被全部放进API网关的响应body中,返回给调用者。这样的话可以直接对接很多已经有一些规范,已经有一些接口的,像小程序这种,会不适用这种方式。那么就需要API网关把SCF的响应抽取为一个标准HTTP的respond返回个标准接口。Websocket能力算是在函数计算里的一个难点,因为本身函数不是常驻的,比如说这个函数跑一跑,在没有触发的时候,可能有一个时间段过去后,目前的资源就释放掉了,下次再触发的时候再拉起来,这很难跟前端保持长连接。那我们怎么帮SCF处理长连接问题的呢?API网关会帮每一个前端的调用者生成一个唯一的ID,这个ID就注册到后面客户的业务函数里。当客户业务函数需要推送的时候,他只要带到这个唯一ID给API网关,API网关就会将这个唯一ID的消息丢给前端,这样我们唯一ID和前面的连接保持了长连接,也保证了后端推送的正确性。举个例子,一个聊天室,有三个人聊天,范冰冰,李晨和黄晓明。他们三个都有唯一ID。黄晓明发出来一句话,后面的函数收到这句话,并且要把他说的话推送给范冰冰和李晨,那么通过唯一ID,可以识别到三个人,后端业务将黄晓明ID的聊天语句推送到API网关,告诉API网关要将此信息推送给范冰冰和李晨ID的前端,API网关收到信息后,则将此信息推送到与范冰冰李晨的客户端保持的长连接中,他们就看到了这条信息。这就是处理Websocket长连接的方式。开放到API市场上,刚才我们也简单说了一下,其实很多能力,我们自己一些能力和数据想直接开放出去给别人,但是并不想做一个页面,计费流程,那么可以放到API市场来进行售卖。其实后端业务放到SCF上是提供服务的一种方式,这样有些用户业务是放在API市场上来进行售卖的。这是把我们技术能力变现的一种方式。文档与SDK刚才也说了很多,这里再来介绍一下。如果用户配了10个API,API网关会帮用户生成这10个API的文档和SDK。swagger文档有点像代码式的文档另外还有普通文字描述的API文档,这两种我们都能生成。生成后可以直接下载下来提供给别人。SDK中有鉴权相关的代码,调用者使用时把自己的参数填进去,对调用者来说更方便。高并发,这里我们还是想强调一下,API网关可以扛住非常非常大的并发请求。当用户请求的并发量极大时,并且有大量HTTPS时,大量请求十分消耗CPU。ABI网关的高性能,及时HTTPS请求的异步处理,可以应对高并发场景,保证服务可用。场景方面,刚才说的是一些具体功能,我们能帮SCF做哪些具体的能力。在整体结构的使用场景这里做一些介绍,这里是小程序公众号,电商这种业务场景现在使用的一些客户的架构,客户的很多业务模块放到SCF上面,然后用API网关作为API服务提供出去。前端无论是小程序还是APP只要有鉴权,有密钥对都可以来进行调用。后端根据自己的业务去跟MySQL等能力来进行对接。刚刚这种方式,像乐凯撒是一个很典型的例子。他们把他们的菜单系统,还有微信支付都放在SCF上面。前端会对接一些固有的HTTP接口,所以用了我们的响应集成的能力,就是API网关帮他抽取成一个标准的HTTP响应返回给前面的调用者。另外因为有很多图片静态资源,他们也使用了跨域能力,这样后端上线的时候非常快。乐凯撒现在有一个小程序用户可以直接在上面下单买披萨,这个小程序中的很多能力是使用上述架构完成的。他们的ERP也有一些后台的系统,也是直接在SCF上进行后台计算,用API网关直接出接口。其实很多能力可以复用,那么API本身就可以进行复用。这样的开发流程,会让整个上线过程非常的迅速。现在用的多的还有AI推理和翻译。用户将自身的计算模型,翻译模型等放在SCF上,每次通过API网关触发来触发计算。API网关将请求带来的数据给到后端,并对每个请求做鉴权认证或ACL管理保障使用的安全性。比较典型的就是搜狗,搜狗在这里其实有一个翻译推理的模型,放在SCF上, SCF跑在GPU上,然后通过API网关做触发。这里用API网关主要是想做前后的解耦。因为搜狗的业务本身解耦是C++写的,所有的请求都是用http的方式传递,请求端和后台解耦合后,很多后端业务不再需要用python重写。荔枝微课是一个在线教育课程。它的情况是,后端SCF跑了一个模型计算。每次当客户在他的页面上搜某个课程。它的前端会把这个课程,用户感兴趣的课程信息,包括客户的ID,之前的一些信息带到后端带给SCF。SCF模型拿到了客户信息,它本身也会有一些自己的原有的信息,这些信息结合起来,再根据其算法进行计算。最后得出的结论,这个客户喜欢某些课程,再把这个课程推送到前端来,最终展示在WEB页上。在这个中间,其实API网关和SCF解耦的时候,对API网关的模型调用,需要有安全保证的,就是有不同IP的时候有源IP调用,还有密钥对的鉴权对后端进行安全保障。另外如果说这个SCF或者调用有任何问题的话,监控报警也是可以快速让我感知到这个模型的状态。最后看一下费用,我们费用也是有很大力度减免的,每月有一百万次的免费调用。网络费用跟公网的的价格是一样的,调用次数0.04元/万次。目前没有收费,预计到年底收费。但是整个资源费用是非常便宜的,所以如果有想试用的同学不用很犹豫。 Q:主要想请问涉及到有时候有自己的线上环境和我们的测试环境,我想知道我们现在如果使用你们的API网关还有SCF,我们怎么去部署这样两套环境?怎么样去完成像我们现在使用的这些持续集成,怎么结合到我现有的产品?因为我目前看到的展示都是基于控制台,没有看到说基于CLI的操作,所以想了解一下。A:其实我们API网关有环境管理和版本管理。版本管理也分了环境,测试环境这些标准环境。后续还会开放这些环境让用户自定义这些环境。CLI部分我理解需要在公台操作,还是命令行操作,本身跟我提不提供环境管理,版本能力应该是两个事情。所以CLI后续会提供。但是本身需要的开发和测试环境我们已经分环境提供。Q:新的代码更新后要上线到SCF上怎么部署?A:后面我们会做一个工具,在这个工具上,比如CLI,可以一键上传,并且配一些API网关。目前这个都在开发中。Q:怎么样买服务器便宜,怎么样买按调用次数的偏移?A:这个还是需要你本身业务来计算一下。比如说我这个业务就是一个触发型的业务,我如果用一个CPM或者物理机部署的话,如果常驻型的话需要占到多少钱,这个钱其实是固定的,每个月需要多少钱。但是如果我知道这个业务是触发型,假如每天就调用一万次,这个也是能算出来的钱。这个钱一比较就很简单就出来了。但是这个都得基于你本身业务的调用量和你本身业务后端消耗资源的情况来进行计算的。所以我没法儿给你一个直接的值。如果真的需要一个常驻的业务,一天二十四小时一直在跑的话,其实这个也是要看,这二十四小时在跑的,我对我本身的服务器我的消耗情况,比如说我买完了一台服务器,市面上的服务器可能就那么多,或者说CPM就那么多,即便买了最小的4核服务器都没跑满,如果计算很轻量没法儿跑满的话,用SCF也会很划算。但是需要大量的,可能用本身的传统的方式会比较简单。Q:在调用的稳定性,延时方面Serverless会有优势吗?A:其实调用的稳定性是我们服务稳定性的保障,这个不用担心,因为我们有承诺。至于延时,是有很多因素影响的,如果你是公网调用,这基于网络的一些情况。如果说API网关到SCF结合的话,本身API网关和SCF可以走内网和公网。API网关本身的延时是在毫秒级别,这个我们测试过的,还是很小的。如果部署一个服务器或者资源,你到那个网络之间的延时,你自己本身原来延时是多少还是要算一下。我可以告诉你我们的延时本身是多少,在可测的环境下我们的延时是多少。但是到你们具体的网络环境下还是要具体测量的。本文PPT附件请点击原文下载。问答Serverless:如何删除一个函数? 相关阅读多个场景中的AI落地实践低于0.01%的极致Crash率是怎么做到的? 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识

September 7, 2018 · 1 min · jiezi