前后端接口之争的解决原则
前言
前后端在接口上永远在争斗,各种论坛总少不了这种吐槽,但总要有什么办法去解决这个问题。
首先保持积极的平和的心态最重要,提高个人水平也迫在眉睫,在这个前提下,依然会存在一些争端,这些争端分两种:
-
第一种:一些人既傻逼,又懒逼,对付这种人,就特么扣钱就老实了。
-
第二种:一些事真的有争议。
这两种我都说一下,我希望用下面这些原则来解决:
原则一、坚定的遵守团队制定的API规范
说话算数,这是正常人起码的行为准则。前后端之前约定好了RESTful API的规范,但是后端不严格执行,说好新增和修改是2个接口,结果有时候就合并成了一个接口,有时候用DELETE方法,有时候就成了POST方法,你永远不知道对方什么套路,这绝对不行。
原则二、不要给别人找麻烦
例1,后端写完接口不测试
个别后端认为:反正前端跟我联调的时候会测试,我Postman跑一次能跑通就得了。
实践证明,个别后端所谓的“能跑通”,是在理想化参数、理想化权限、理想化数据内容的前提下能跑通,甚至在修改了一些代码之后并没有做再次测试。
一个团队往往后端的人数是前端的2倍以上,而且倾向于压缩前端人数,这个前提下,不要给前端找麻烦。
例2,后端对响应体过度包裹
比如前端要的结构是:
{
"msg": "操作成功",
"code": 200,
"data": {
"a": [1,2,3,4,5,6],
"b": "haha"
}
}
某后端给的结构是(情节有所夸张,但也有代表性):
{
"msg": "操作成功",
"code": 200,
"data": [
{
"resultVo": {
"a": [
{"value": 1},
{"value": 2},
{"value": 3},
{"value": 4},
{"value": 5},
{"value": 6}
],
"b": "haha"
}
}
]
}
简直就是屎山!各种无用的嵌套!最牛逼的是那一坨value
是什么鬼?!
还有个resultVo
,前端问:Vo
是什么?后端说:“你不用管”。。
那我不管,你来写前端,好么?
不要给别人找麻烦!
例3,响应体加了莫名其妙的Java术语
专门说一下这个可笑的问题吧,比如:
{
"msg": "操作成功",
"code": 200,
"data": {
"userListPojo": []
}
}
请问List
的Pojo
是什么Pojo
?这群user都姓Pojo?
不要给别人找麻烦!
例4,后端自己能取到的数据,却找前端要
- 某后端要求前端传
userId
,前端说传的Token里包含了userId
,后端说懒得从Token取,嫌麻烦。
WTF!!我传了别人的userId,而你根本不打算做验证(取都懒得取,当然不会做验证),那我是不是可以攻击别人的账号了?
- 某后端要求前端传当前用户的所在部门ID。
大哥,当前用户的部门ID是可以数据库自查的,就像你能查当前用户的姓名性别出生年月一样,让前端给你传部门ID做什么??如果我传了一个虚假的部门ID,你做不做验证?不做验证的话,我做越权攻击你怎么防?
不要给别人找麻烦!
例5,前端传无用字段,后端返无用字段
后端需要10个字段,前端给了18个,有8个无用字段,却美其名曰“用不到可以无视”,神他妈的“无视”!结果后端DEBUG的时候眼花缭乱。
同样的,前端需要10个字段,前端也给了18个,也美其名曰“用不到可以无视”,结果前端调试也眼花缭乱。
最后,用户说:“老子流量不要钱的?浪费老子流量!”
如果程序员连过滤自己发出的字段都做不到,请辞职。
不要给别人找麻烦!
例6,后端要求传重复的字段
比如,某后端要求前端传:
"userId": 111,
"personId": 111,
前端问:这俩字段有什么区别?后端说:没区别,因为要存到两个表,所以传2遍。前端问:那么这两个字段的值永远是一样的?后端说:是的。前端问:那我也得传2个字段是吗?后端说:是的。
是你大爷,你怎么不去死呢?
不要给别人找麻烦!
例7,字段语意不明
一个接口,后端返回:
"nickName": "张三",
"realName": "李四"
某后端说:nickName
是顾客名字,realName
是服务员名字。
我特么砍死你信不信?
不要给别人找麻烦!
例8,字段名不统一
有一个接口,某后端返回:
"roomId": 111
他说这是房间ID。
OK。
后来又有一个强相关的接口,同样的数据,某后端返回:
"houseId": 111
他说这是房间ID。
一会room
一会house
,砍死你信不信?
他说好吧,改一改。然后改成了:
"room_id": 111
特么驼峰又去哪了?咋下划线了?
不要给别人找麻烦!
原则三、二次加工数据由前端加工还是由后端加工?视情况而定
例1,递归加工数据
比如,我希望数据库保存某人在公司的所属部门,比如他属于:
某公司->销售大部->市场部->市场1部->华北片区组
假如华北片区组的部门ID是1324
,前面几级的ID分别是1, 4, 118, 567
,那么,他的部门应该在服务器应该保存为1324
?还是保存为1, 4, 118, 567, 1324
呢?显然应该保存为1324
,因为部门树状结构是可能会变的,把1, 4, 118, 567, 1324
写死到数据库是绝对不行的。
那么问题来了,前端要显示面包屑导航,也就是显示成某公司->销售大部->市场部->市场1部->华北片区组
这样子,这个字符串由前端负责还是后端负责呢?视情况而定。
- 如果后端给出了一个全公司部门的树状JSON,那么,面包屑应该由前端负责,因为由服务器做递归计算是很不划算的,收益小成本高,而由前端递归计算面包屑,收益高成本低,毕竟本地JS运算不消耗服务器资源。
- 如果后端给不出全公司部门的树状JSON,那么,面包屑只能是由后端负责迭代计算。
例2,传数组还是分别传?
比如一个时间选择器,有开始时间和结束时间,这种组件通常会生成数组:
['2020-02-02','2020-12-12']
那么前端应该给后端传数组还是拆分成2个字段呢?
我认为这个应尊重后端的意见,因为这2个字符串通常在数据库存储为2个字段,所以后端通常会要求前端按2个字段来传,前端没必要因为此事争执,我建议前端先拆分为2个字段,然后再传,除非后端说,前端随意传,后端都能处理。
那么数据库可否将2020-02-02,2020-12-12
存入一个字段里呢?虽然也可以,但是修改其中一个值就比较麻烦,需要先取出再存入,计算的时候也需要先打散成数组再去计算。服务器运算收益低。
那么数据库这两个字段的值,返回前端,是按数组传还是分别传呢?
依然应尊重后端的意见,如果后端说分开返,那么就分开返。况且,如果前端有一天因为某某原因不再用Date-Range组件,而是分成2个独立输入框,那么返回数组反而麻烦了。
例3,时间数据怎么传?怎么返?
时间一般说有3种格式:
-
标准dateString,例如:
Thu Aug 20 2020 10:08:20 GMT+0800 (中国标准时间)
-
高可读dateString,例如:
2020-08-11 00:00:00
-
毫秒级时间戳,例如:
1597889706250
怎么传,怎么返,也应尊重后端要求,因为无论哪一种时间格式,前端都能用转换器轻松转换。
转换 | 方法 |
---|---|
标准dateString -> 高可读dateString | 需写工具函数,例如new Date('Thu Aug 20 2020 10:08:20 GMT+0800 (中国标准时间)').getFullYear() + ... ,或工具库处理 |
毫秒级时间戳 -> 高可读dateString | 需写工具函数,例如:new Date(1597889706250).getFullYear() + ... ,或开源工具库处理 |
标准dateString -> 毫秒级时间戳 | new Date('Thu Aug 20 2020 10:08:20 GMT+0800 (中国标准时间)').getTime() |
高可读dateString -> 毫秒级时间戳 |
new Date('2020-08-13 11:11'.replace(/-/g,'/')).getTime() 或工具库处理 |
高可读dateString -> 标准dateString |
new Date('2020-08-13 11:11'.replace(/-/g,'/')) 或工具库处理 |
毫秒级时间戳 -> 标准dateString | new Date(1597889706250) |
注意:高可读格式转其他格式时,某些浏览器的new Date()
对格式的要求会比较苛刻,比如不允许短横线、只允许斜线分隔等等。因此,要么上传2020-12-22
,由后端处理,要么由.replace(/-/g,'/')
替换再转换,要么由专业工具库处理成标准dateString或毫秒级时间戳。
例4
比如,前端为了适应前端框架、为了业务,需要把一个一维数组变成二维数组,那么请前端自己完成变换计算,不要让后端计算好了再传过来,除非后端返的数据不够,不足以让前端完成变换计算,或者前端必须递归推测到底需要get多少组数据,这种情况为了严谨性,最好由后端计算。
又比如,前端想给数组内的对象增加属性,比如增加个isEditing: false
(正在编辑)属性,这种属性不能让后端返回,应该前端自己遍历加上属性。
原则四、优先照顾封装
前端打算封装一个《性别下拉菜单》组件,要求后端提供一个性别字典接口,后端利索的提供了,见下。那么之后,整个项目必须用sex
表示性别,不要时不时来一个gender
字段,这样不仅仅造成混乱,而且封装组件就很繁复,随时要防备你来个新名词。虽然A页面永远用sex
,B页面永远用gender
,分别处理,并不会引起bug,但是这绝对会增加维护难度。组件必须又能接收sex
,又能接收gender
,烦不烦?!
同样的,请永远尊重字典接口里的dictValue
,既然规定好了0
永远代表男性,1
就永远代表女性,那么,就别自作主张又增加一个字典表,去让1
代表男性,2
代表女性。虽然只要2个字典表坚持不再变,确实不会引起bug,但是会增加维护难度。
{
"sex": [
{
"dictLabel": "男",
"dictValue": "0"
},
{
"dictLabel": "女",
"dictValue": "1"
}
]
}
还有,如果定了"dictValue"值为字符串,那么业务接口返回数据就不要给数字型。后端自己跟自己的接口都矛盾,还让前端去擦屁股?
不要给别人找麻烦!
原则五、写代码前,先定名词字典
这里说的“名词字典”不是上面说的“数据字典”,比如:
房间,有人叫house
,有人叫room
。
充值,有人叫recharge
,有的人叫invest
。
小区,有人叫estate
,有的人叫residentialarea
。
姓名,有人叫nickName
,有的叫nickname
,有的叫realName
,有的叫realname
,有人叫fullName
,有人叫fullname
,还特么有人叫name
!
凡是不确定的字段,别自己自作主张,团队商量一下,再落笔不迟。
一旦确定一个译法,就项目内永远用这一种译法!