用产品思维设计API(一)——RESTful就是个骗局

来源:互联网 发布:如何登陆香港的淘宝网 编辑:程序博客网 时间:2024/06/03 22:44

用产品思维设计API(一)——RESTful就是个骗局

前言

最近公司内部在重构项目代码,包括API方向的重构,期间遇到了很多的问题,不由得让我重新思考了下。
- 一个优雅的API该如何设计?
- 前后端分离之后,API真的解耦分离了吗?
- 不断的版本迭代,API的兼容性该如何做?

年前,我司内部的接口已经进入了一个完全的重构阶段,参考了市面上各大平台的API和文档,自己也总结出了很多的心得。这里向大家分享一下,接下来一个月,我们向从下面几个方面向大家介绍一个优雅的API(至少我认为挺优雅)该如何设计。

  1. RESTful就是个骗局 (http://blog.csdn.net/yzzst/article/details/53775319)
  2. 数据解耦,才是前后分离的本质(http://blog.csdn.net/yzzst/article/details/53844590)
  3. 版本控制,没有你想的这么简单(http://blog.csdn.net/yzzst/article/details/54755077)
  4. 随意定义错误码,你还在这样干?(http://blog.csdn.net/yzzst/article/details/54799971)
  5. 安全,就只能用HTTPS?(http://blog.csdn.net/yzzst/article/details/54882346)

ps. 打一个广告,公司内部现在在招聘各种技术岗位,Java、Android、前端等,待遇保证能让你涨30%,有兴趣的朋友可以加我微信,二维码在文章最后。

Ok,今天是第一篇文章——再看RESTful。

回顾一下HTTP协议

基于HTTP协议的API使我们在开发APP、网站中最常见的形式,为了更好的了解如何设计一个良好的API,我们这里先简单的回顾一个HTTP协议。

先抓包看一个请求demo

我们用Fiddler抓了一下360浏览器的任务中心的API接口信息,如下是它的请求信息:

POST http://task.browser.360.cn/online/setpoint HTTP/1.1Accept: */*User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)Pragma: no-cacheContent-Type: application/x-www-form-urlencodedHost: task.browser.360.cnContent-Length: 449Cookie: T=s%3Ddaf1a2e6347e01ebccc72d639441f9ef%26t%3D1456561881%26lm%3D%26lf%3D1%26sk%3D03714c868adc12684c89a65c05bc7709%26mt%3D1466821360%26rc%3D4%26v%3D2.0%26a%3D1; zsmodel=MI%20NOTE%20LTE; zsosv=4.4.2; __guid=243694361.1257306006931263000.1482113838601.9001stamp=1482151473&qt=Q%3Du%3Dfgpubh%26n%3D%25PO%25QR%25P9%25R1%25P0%25RS%25O5%25P4%25PO%25N7%25O9%25S8%26le%3Dp3EwnT91WGDjZGLmYzAioD%3D%3D%26m%3DZGZlWGWOWGWOWGWOWGWOWGWOZwDl%26qid%3D29340825%26im%3D1%5Ft013ba372ccf308e7b6%26src%3D360se%26t%3D1%0D%0AT%3Ds%3D76f8c61854f171f63f66a8f552962e8e%26t%3D1456561881%26lm%3D%26lf%3D1%26sk%3De79c0ecb263d447d96e85f23825c3924%26mt%3D1466821360%26rc%3D9%26v%3D2%2E0%26a%3D1&verify=6fce1222e64cefa2b5b3d24d65fa9eb1

得到的服务器响应结果,如下所示:

HTTP/1.1 200 OKServer: nginx/1.6.3Date: Mon, 19 Dec 2016 12:42:58 GMTContent-Type: text/html; charset=utf-8Transfer-Encoding: chunkedConnection: close{"errno":"0","errmsg":"success","lastpoint":"2016-12-19-20-42"}

这里,没有任何文档我们也能够直接的看出来。大概步骤是:
1. 浏览器向 http://task.browser.360.cn/online/setpoint 发起了一个 POST请求
2. 该请求中附带了一些cookie信息,也带了一些自定义的消息体
3. 响应结果是正确的,返回给浏览器一个JSON格式的数据。

Http协议的构成

对于一个完整的请求(Request)、响应(Response)来说,还是有一定的套路的,这里我们看一下HTTP请求和响应的规范格式。

对于上面360浏览器的demo,结合两个协议图文,我们能够看到更多的信息:

  • 请求Request
    • 请求的方式 POST
    • 请求的地址 URL
    • 版本号
    • 请求的头部信息,headers(cookie\UA等在于此处存着)
    • 附属体信息 (通常为自定义上传的信息)
  • 响应Response
    • 版本号
    • 状态码 (http协议的状态码)
    • 相应头部信息headers (时间、数据格式、编码等信息)
    • 附属体信息(通常为相应的自定义数据体)

OK,既然我们已经了解了HTTP协议的请求与响应的构成,理论上我们已经可以利用上述的逻辑加入完成自己的API设计了。
设计之前,我们需要思考一下,我们需要设计吗?目前市面上最流行的RESTful API协议为什么我们不用?

再看RESTful

在API设计上,如果有一样东西获得广泛认可的话,那就是 RESTful 原则。

REST的关键原则与将你的API分割成逻辑资源紧密相关,并采用所用并理解的原理。使用HTTP请求控制这些资源,其中,这些方法(GET, POST, PUT, PATCH, DELETE)具有特殊含义。

举例来说,有一个API提供公司内部(company)的信息,还包括各种部门(departments
)和雇员(employees)的信息,则它的路径应该设计成下面这样。

https://api.example.com/companyhttps://api.example.com/departmentshttps://api.example.com/employees

基于请求的方式和路径来作为常见的CURD(增删改查)

GET /company:列出所有公司POST /company:新建一个公司GET /company/ID:获取某个指定公司的信息PUT /company/ID:更新某个指定公司的信息(提供该公司的全部信息)PATCH /company/ID:更新某个指定公司的信息(提供该公司的部分信息)DELETE /company/ID:删除某个公司GET /company/ID/employees:列出某个指定公司的所有雇员DELETE /company/ID/employees/ID:删除某个指定公司的指定雇员

对于请求后的响应结果,RESTful也做了一个很好得定义:

GET /collection:返回资源对象的列表(数组)GET /collection/resource:返回单个资源对象POST /collection:返回新生成的资源对象PUT /collection/resource:返回完整的资源对象PATCH /collection/resource:返回完整的资源对象DELETE /collection/resource:返回一个空文档

RESTful API设计确实比较不多,对于一些简单的APP来说,能够快速的开发,并满足他们的绝大部分需求。

但是,请来一个神,就必须将神供着!

我们在使用之中也出现了很多头痛的问题,如:

问题1:多表多条件联查接口,如何设计?

很多情况下,我们的API并不是简简单单的查询(select)数据库中的数据,直接返回给调用者。我们可能会涉及到多表联查(left join , inner join)、排序(order by)、条件判断(where)、合并(union)等等。

如:找一个在我公司中找32~40岁,月收入在8000元,且在IT部门的人,属于北京事业群,将他们的工作KPI按逆序排列输出。

N多条件在一起的时候,API请求路径就会变得很难设计。

当然,网上有采用如下方式

/api/company?field1=abc&field2__like=%abc%&field3__gt=100&field4__not=999

但是,调用者调用API的时候,就感觉像是写SQL,API本身亦变得不可维护。

问题2:多逻辑请求接口,很难命名

不要小看命名的问题,我们要从名字上便于理解,又不要太长,又不能随意的缩写,其实很难。

如针对上述我们要求查询的逻辑来说,整个结构请求的路径就会变为如下所示:

GET /company/:ID/departments/:ID/employees/:ID/AREA/:ID/KPI/:ID?agemin=32&agemax=40&income=8000&order=api&sort=desc

虽然能够请求,但是已经很难从请求路径上看出我们的请求目的是什么,背离了所见即所需求的目的。

问题3:完整资源对象的返回,并不是调用者所需要的

如果一个数据库的字段很多,如我们的产品表,将近40~50个字段,每一个字段的类型、输出格式化都不一样,这个时候,如果直接将bean打印出来,如下所示:

出现几个问题

  • 太多冗余字段:APP需要的PC不需要,PC需要的,H5不需要
  • 每个字段都很难理解:调用API的人,估计看文档要看疯了,需求稍微一遍,前端样式也要跟着改,再看一次文档(韬哥认为这个是最重要的)
  • 整个库设计完全暴露:安全安全!
{    "totalCount": 15,    "data": [        {            "id": 7956,            "sTitle": "信证-通汇安盈一号",            "productTypeId": 3,            "zdPrice2": 2,            "nianHuaShouYiStart": 8.5,            "nianHuaShouYiEnd": 9.5,            "jdt": 100,            "touZiLingYu": "基础设施",            "level": 99,            "saleStatus": 121,            "category": 30,            "jdTime": "2016-12-20 09:48:13",            "raiseProgress": "【2016年12月20日9时更新】本期为第六期,本期规模不限,本期已封帐,目前年前总剩余额度2520万,需要资产证明,有下期,下期无缝对接中;",            "touZiMenkan": 100,            "collectCount": 1,            "hasCollect": false,            "redPack": "",            "fundType": "基础设施",            "visitCount": 24631,            "docPreviewCount": 11,            "producttagids_intarray": "\t13\t11",            "productTags": "11,二年期;13,固定收益类;",            "title": "信证-通汇安盈一号(第6期)",            "bid": 3,            "statusId": 40,            "qiXian": 24,            "peibi": "",            "pmName": "王佳慧",            "pmUserName": "王佳慧",            "daXiao": "小额畅打",            "lingYu": null,            "shouYiType": "固定收益类",            "payStatus": "半年付息",            "downLoad": "Upload/ProductPDF/20161202/信证-通汇安盈_473.zip",            "groupName": null,            "zdPrice": 1,            "addr": null,            "companyId": 272,            "adminId": 473,            "groupMaxPrice": 0,            "nianHuaShouYiExt": "",            "companyName": "汇蕴",            "fxList": [                {                    "title": "100万≤X<300万",                    "price": "2.0%",                    "isFloat": false,                    "earningRate": "8.5%",                    "packingRate": 8.5                },                {                    "title": "300万≤X<1000万",                    "price": "1.5%",                    "isFloat": false,                    "earningRate": "9%",                    "packingRate": 9                },                {                    "title": "1000万≤X",                    "price": "1.0%",                    "isFloat": false,                    "earningRate": "9.5%",                    "packingRate": 9.5                }            ],            "sourceRepayment": "1、项目公司运营收入\n2、项目公司股东自有资金\n3、银行贷款资金置换",            "fundInvest": "用于佛(山)清(远)从(化)高速公路北段工程建设项目的建设,以期获得投资收益",            "windControl": "1、央企中电建路桥出具完工承诺,保证项目按期运营\n2、投资人成为融资主体股东",            "highlights": "❶【省级重点】标的是广东省基建-高速公路建设,是从“十一五”就提出的建设规划,此项目为广东省重点工程,由政府牵头并给予大力支持,项目公司也与清远市交通运输局签订《特许经营协议》,项目违约成本极高; \n❷【项目把控实力】中证基金是项目公司第一大股东,对整体项目有掌控权,有助于资金安全及还款的及时有效;同时一同参与的有中电建、龙浩集团;\n❸【管理人背景】管理人由中证基金99%控股,中证基金股东为中信证券、华夏资本、中诚信托;\n❹【央企增信】央企中电建路桥出具完工承诺,保证项目能够按期运营;\n❺【银行资金】项目已有意向银行资金83.36亿贷款划拨计划,安全边际较高;",            "productOrganizationId": 318,            "productOrganizationPic": "Upload/productOrganization/20161012/logo_473.png",            "bqqsr": "2016-12-12 00:00:00",            "province": 440000,            "proviceName": "广东省",            "cityName": "佛山市",            "dyl": -1,            "addrId": null,            "updateTime": "2016-12-20 09:48:13",            "listingTime": "2016-12-12 11:41:00",            "parentId": 7300,            "phase": 6,            "issuerCompanyId": 0,            "issuerCompanyName": "",            "issuerId": 0,            "issuerPhone": "",            "issuerName": "",            "project": 3,            "attr": 7,            "jianBan": "",            "appointmentCount": 53,            "downloadCount": 385,            "sendEmailCount": 21,            "cashback": "",            "tagsArray": [                {                    "id": "13",                    "name": "固定收益类"                },                {                    "id": "11",                    "name": "二年期"                }            ],            "bestEarningRate": "9.5%",            "bestEarningRate_fore": 9,            "bestEarningRate_back": "5",            "bestPrice": "2.0%",            "bestPrice_fore": 2,            "bestPrice_back": "0",            "bestGroupPrice": "待定",            "bestGroupPrice_fore": "待定",            "bestGroupPrice_back": 0,            "bestEmployeePrice": "待定",            "bestEmployeePrice_fore": "待定",            "bestEmployeePrice_back": 0,            "saleStatusName": "募集中",            "isHot": false,            "isHotSale": true,            "isRecommend": false,            "packingRate": 0,            "returnCash": 0        }    ],    "isSuccess": true,    "code": 0,    "runSpanTime": 97}

像上面这个demo数据一样。泥他妈这么多字段,让前端、APP都去理解?是否前端也要参与数据库设计?前后端分离的意义在哪?
sorry,忍不住吐槽。

问题4:安全,还是安全

安全这个方向的问题太多,很难一一排查,如见到此类问题就想把协议换为HTTPS的人来说,你基本是还不了解安全。对ROOT后的Android系统来说,HTTPS并没有这么神秘。API上必须考虑安全性。
- 查询ID是不是该用主键?
- 所有下发的字段显示是否应该直接下发?
- 登录限制,频率限制
- 加密措施,如何做?
- 请求路径将表内关系完全暴露,响应结果将表结构暴露,SQL注入\数据爬虫\Replay攻击 防范要求太高。

当然,RESTful API规则上还有很多关于过滤、错误码(我最不能接受的就是状态码,很多运营商直接都劫持了,把你坑死)的说明,这里我们就不一一列举了。

上述一系列的问题,让我们在整个系统开发的过程中,使用倍感困惑。也许是自己对RESTful的了解不够到位,或者使用上无法掌握其精髓。所以,我们最后在设计自己API的时候,采取的是类RESTful协议,用其思想,并在RESTful的基础是做了很多的自定义操作功能。

request设计

整个设计上借鉴了RESTful的思想,将操作同样分为CURD(增删改查),如对用户(User)进行操作
- sheme命名

user/create   # 创建user/delete   # 删除user/update   # 更新user/login    # 登录user/info     # 用户信息user/list     # 列表# 多表联查getUserKpi?agemin=30&agemax=45 #kpi表和user表,根据需求来取名
  • 参数过滤
user/list?query=周%&pageSize=10&start=0  #分页参数过滤
  • 通用请求头部,自定义header

当然,最重要的是,我们的API都需要监控,根据版本号做兼容等,我们需要在request的header之中自定义一些信息,这里我们定义为json格式的信息。如下:

{    "userId": "1000",    "platform": "android",    "imei": "xxxxx",    "appVersion": "1.0",    "cityId": "0",    "platformVersion": "4.2",    "deviceId": "xxxx",    "channel": "xxx",    "protoVersion": 1}
参数名称 类型 说明 userId int 用户的id platform string 请求类型平台。目前有:iOS、Android、pcweb、h5 imei string 设备imei appVersion string 请求来源的version,这里通常指的是app的版本号 cityId int 定位到的城市id platformVersion string 改设备的版本号,如:Android4.4.4,、iOS10.0.2 deviceId string channel string 分发渠道标识 protoVersion int 协议版本号

response设计

相比request,response的设计规范相对简单很多(返回内容设计还是比较重要的,下一篇向大家介绍)。

还是根据RESTful的方式,我们将返回结果分为两部分,错误码和实际结果,如下所示:

{  "head": {    "ret": 0,    "msg": "ok",    "cmd": "user/info"  },  "body": {    "list": [      {      }    ]  }}
参数名称 类型 说明 head object 通用信息 msg string 错误信息 cmd string 请求action body object 返回结果

如何设计body里面的结果才是response的关键,整个API数据解耦的难题。这里不做详细介绍了,下一篇给大家一一分享下。

有问题,或者想吐槽的,请加韬哥微信:

/*
* @author zhoushengtao(周圣韬)
* @since 2016年12月21日 凌晨0:53:13
* @weixin stchou_zst
* @blog http://blog.csdn.net/yzzst
/

5 0
原创粉丝点击