您的位置:首页 > 其它

让你在 API 设计中少踩坑的实战分享

2017-12-14 00:00 281 查看

本文来自作者 奔跑吧架构师 在 GitChat 上分享 「让你在 API 设计中少踩坑的实战分享」,「阅读原文」查看交流实录。「文末高能」编辑 | 哈比在项目开发中,实际的编码只占用了整个项目不到 30% 的时间,更多的时间都耗在了需求分析与接口设计上。每一次的需求变更就可能涉及到多个 API 的修改,那么 API 设计书作为项目开发的核心文档。如何才能让前端和移动端开发人员更快的理解参数的意义和作用、适应版本更新,以及保障数据的安全性呢?本场 Chat 将根据之前项目中踩坑和总结对 API 设计进行分析。通常来说,软件的生命周期可以归纳为:可行性分析
需求分析
概要设计
详细设计
编码开发
测试
交付客户
维护
在整个项目周期中,需求分析和设计(概要设计和详细设计)占据了 60% 甚至更多的时间,需求总是在不断变化的,客户有的时候都不知道自己想要什么。很多时候客户只是说了一个模糊的概念,更多的时候还需要我们根据客户一个模糊的概念进行拓展说明。基本上在客户确认好 70% 左右的需求的时候,这个时候设计就开始了。

概要设计

最先进行的是概要设计,根据需求可以整理出以下内容:项目模块划分最核心的需求确认好就可以开始划分项目的模块了,根据项目的特点进行模块划分。比如常用的用户模块,企业模块等,这个是根据具体项目来的,不过通常来说,用户体系是最基本的。DB 表设计根据划分出的模块,可以进行对应的模型设计了。可能有哪些表,表中有哪些字段。这个时候字段都只是大概定义,不会进行实际的字段类型定义,而是先设置好逻辑名称。版本控制这个应该是项目确认要开始做的时候就已经开始弄了,比如在 SVN 上建立工程目录,存放客户的需求和整理的文档。新建 git 工程用于代码库的管理。模块分配如果是小项目的话,可能只需要 1-2 个人就可以完成了,那么就不需要特别划分模块给设计人员。如果是大项目的话,模块可能就会比较多。比如最近做的电商系统,就划分了企业模块,企业用户模块,会员模块,支付模块,内容模块,商品等几大模块。这个时候就需要分工处理了,每个人负责对应的一个或几个模块,专人专职,对模块根据用户需求进行分析设计以及后面的详细设计。页面设计设计人员根据客户的需求,进行简单的线图设计,可以让客户可以更直观的看到页面上的元素信息

详细设计

根据概要设计整理出的东西,就可以对系统进行详细设计了。这里的详细设计包括但不限于 API 接口的设计,比如还有系统之间的设计,系统交互设计,UI 设计,对 DB 中字段进行类型设置,索引配置和物理名称设定,API 设计书编写等。作为开发人员来说,最关注的莫过于 API 设计和 DB 设计了,首先看下 API 设计书。

API 设计书

作为开发人员来说,只需要粗略的了解业务,根据设计书进行编码即可,不需要对整个系统非常了解,重要的是编码。而对于 API 设计人员来说,如何设计出直观方便的 API 设计书还是需要根据项目的需要和特性好好构思的。进行 API 设计时,对整个系统已经有考究了,比如共通参数,API 版本设定,API 参数设定等。本文这里选用的是 excel 作为设计书工具,当然也可以使用其他工具,例如 markdown。如果不对外提供 API 的话,直接可以使用 API 设计书。如果需要对外提供 API 的话,那么需要将请求和返回需要单独抽取出来,对外提供只有参数和返回说明的文档,而不会将详细设计的内容对外提供的。先说说设计书的要点:设计书目录规范设计书根据模块划分目录,每个设计书根据每个模块所在的位置划分到不同的文件目录下,结构为:{moduleName}。比如用户相关的就存放到 user 目录下。则 user 目录为 ${projectName}/user。设计书命名规范为了方便设计书的查找,同一个模块下的设计书编号递增,且以固定模块名开头,比如用户模块编号为 user,假设用户登录为第一个 API,那么用户登录的文件名为:user-001-用户登录 (user_login).xls。这里的编号可以根据项目大小设置位数,三位数的话从 000-999 可以有 1000 个 API。共通设置对于 API 来说,很多地方是共通的,比如共通的请求参数和返回。这时候需要对共通进行抽取,使用时只需要引用即可,否则如果共通的地方有修改的话,那么每个 API 设计书都需要修改,想想就可怕。比如:用户 token 校验的设计,只要涉及到用户权限相关的 API 都需要校验,这时候在每个 API 设计书中只需要引用下共通 API 设计书中 token 校验文档。而不是在每个 API 设计书中都写 token 校验的逻辑。简要流程图在每个 API 设计书中都要有对应的流程图,这样审查的时候可以非常直观的看出这个设计书涉及到了哪些业务,而不需要重头到尾再看一次设计。更新履历每个 API 设计书在开始开发后,所有的修改都必须要记录履历,标明修改的原因和修改的地方。这样开发人员只需要根据变动点进行对应的代码修改。作者每本设计书都需要标注作者,这样开发人员有问题时,可以直接询问作者而不是啥事都需要找项目经理或技术经理。请求方式如果整个系统的请求方式的都统一为 POST 的话,可以不需要这个说明,但是如果有不同的请求方式,那么 API 设计书中就需要标注出这个 API 接受哪些请求方式。并且在 API 一览中添加请求方式说明。API 一览所有的 API 都应该统计到一个文档中,根据这个文档就可以看出系统中有多少接口,每个接口是做什么使用的。同时也可以在这个文档中对每个 API 添加访问需要的权限,这样就可以非常直观的看出哪些接口对应的权限了。

DB 设计

在详细设计过程中,表中的字段需要根据概要设计中定义的逻辑名称来定义对应的物理名称。ER 图设计工具也有很多,我们使用的是 ermaster。表设计应该遵从第三范式规范(参考 https://www.cnblogs.com/0515offer/articles/4132171.html 说明),这是最基础的要求,其他根据项目需要添加。共通字段抽取每张表中应该都有共通字段的,如创建日期,更新日期,创建者,更新者,删除标志等。这些共通的需要抽取出来,每张表中引用即可。对于部分模块来说,项目初期就能确定是需要进行并发处理的,这里就需要添加乐观锁进行处理,通常使用 UUID。在表中添加 version 字段,每次更新的时候将查询到的 version 和 db 中的 version 进行对比。如果相同则更新成新生成的 version,此时更新成功。如果 version 不一致,则更新失败,进行事务回滚或者进行重试。sql 生成表设计完成后,需要导出对应数据库的 DDL,导出时注意不要将外键约束导出。实际情况中很少会对表数据进行物理删除,基本上是逻辑删除的。为了搜索更加快速,可以在对应的外键上加上索引。逻辑删除标志每张表都应该有个逻辑删除标志位 (delete_flg),表示该数据已经在逻辑上删除。进行业务操作时,如果已删除的数据就不需要抽取出来了。

API 请求设计

版本设置对于 APP 和对外提供接口来说,api 的版本还是比较需要的。版本信息可以定义到 url 中,格式可以为
/${version}/module/operateName(/v1/user/login)
,也可将版本信息放到 Header 中。但是不推荐放到请求参数中。因为如果放在参数中,使用 rest 方式请求时,参数是以 json 格式存放在输入流中,此时如果要获取对应的用户凭证,则需要将流中的信息读取出来,而且得要重置流下标,否则后面的处理将获取不到参数。共通参数抽取API 中需要将所有共通参数抽取出来,比如用户凭证。建议将用户信息存放到 Header 中,而不是放在参数中。原因同上。请求方式标明此 API 是需要哪种方式请求,POST 还是 GET,又或者是其他 HTTP
规范中的方式。参数定义参数定义指的是在 API 中请求和返回的字段,必须要标注出字段的类型和是否必须等。比如用户登录时用户名和密码是必须的,那么设计书中就必须要体现出来。而且返回的数据类型必须是 json 支持的数据类型。(Number,String,Boolean,Array,Object)共通返回定义所有的返回都应该按照一定的模板来,定义必定会返回的信息,比如请求的状态码,错误信息等。这里就可以根据实际设计来了。比如返回不同 status 表示不同的错误信息,那么 errMsg 就不需要了,这种设计比较适合用于需要处理国际化信息上。缩进所有的参数中,如果是对象类型数据,那么对象中的数据定义都应该基于此对象的定义进行一个 tab 的缩进,表示这些定义都是属于对象属性。比如下面设计书中的 data 字段。返回示例:为了方便开发人员理解,还需要根据返回的字段信息,添加对应的返回示例。例如:status 表示返回的状态码 ,200 表示处理成功,400 表示请求失败 …
errMsg 返回失败的原因
data 返回对应的数据,可以是对象,可以是数组
{
"status": 200,
"errMsg": "",
"data": {}
}根据以上的说明,整理出用户登录的设计书:







用户凭证涉及到权限相关的请求都需要对用户身份进行认证,只有已授权的用户才能访问指定的 API。这里只讨论前后端分离的情况,非前后端分离的项目直接在 session 中就可以存储用户权限了,这里不加讨论。前后端分离时在请求头中添加标识用户身份信息的字段 token。在用户登录时在后端生成一个全局唯一的 token 返回。在每次 API 请求时将 token 放入 Header 中,如果此 API 需要特定的权限, 那么需要根据 token 从数据库或者缓存中获取用户的权限。普通网站直接在 session 存储用户相关的权限信息。

用户凭证生成

用户凭证 token,代表着一个用户。所以这个 token 需要是全局唯一的,且不可伪造的。token 生成方式可以分为两种UUIDUUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,能够确保在系统中是唯一存在的。用户每次访问时通过 token 到 DB(缓存)中查询此用户的权限信息,根据用户的权限进行访问控制。缺点也很明显了,判断是否有权限访问和 token 是否合法都需要从 DB(缓存)中获取。JWTJWT 由三部分组成,第一部分我们称它为头部(header), 第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature). 这里可以参考此博客:http://www.jianshu.com/p/576dbf44b2aeJWT 适合存储用户非敏感信息,因为第二部分 payload 存放的数据是原始数据通过 base64 编码得到的数据,这里可以直接获取到信息。比如存放用户的名称,昵称,id 等信息。使用 JWT 的优点是可以在 token 中设置超时时间,可以防止 token 被伪造和修改,直接通过验证 JWT 信息的完整性即可以判断用户的权限,比使用 uuid 的方式可以存储更多的信息,而且可以减少用户查询用户信息的次数。

权限校验

通过以上步骤可以确认出使用哪种方式分配用户 token。在每次请求需要权限的 API 时,系统需要根据用户的权限进行访问控制。常用的框架有 SpringSecurity 与 Shiro。这里推荐使用 Shiro,因为 Shiro 相对于 SpringSecurity 来说更加轻便。上面说的将用户的 token 存放在请求 Header 中,是因为在 Filter 层中就可以直接获取出 token 信息,更加方便。使用 Shiro 可以自定义 Filter 进行权限的拦截。这里添加 token 拦截器:       @Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager, ShiroConfigProperties properties,
       JwtManager jwt) {
   ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
   shiroFilter.setSecurityManager(securityManager);
   shiroFilter.setLoginUrl(properties.getLoginUrl());
   shiroFilter.setSuccessUrl(properties.getLoginSuccess());
   shiroFilter.setUnauthorizedUrl(properties.getUnauthorized());
   shiroFilter.setFilterChainDefinitionMap(properties.getFilters());

   TokenFilter tokenFilter = new TokenFilter(jwt);
   Map<String, javax.servlet.Filter> filters = new HashMap<>();
   tokenFilter.setEnabled(true);
   tokenFilter.setUnauthorizedUrl(properties.getUnauthorized());
   filters.put("token", tokenFilter);
   shiroFilter.setFilters(filters);

   return shiroFilter;
}为什么推荐在 Filter 层中做权限校验?权限校验宜早不宜迟,在最外层就做好了拦截了,就不需要在经过其他 Filter 一系列的处理了,可以提高效率。权限拦截策略这里推荐使用白名单规则,默认都是需要权限校验的,不需要权限校验的 API 都需要手动配置。这样可以防止某些重要的 API 忘记加权限而导致系统不安全。

踩坑总结

目前我们的 API 设计书都是按照以上要求进行的,之前的项目因为没有在设计书中添加简要流程图,导致一段时间过后再看设计书时有点晕,要了解一本设计书涉及到的业务还需要从头到尾再看一遍,非常的浪费时间。所以简要的流程图还是非常必要的。由于一开始项目中没有把共通字段抽取出来,导致后面变更字段时需要将所有的设计书都更新一遍这个就非常浪费时间了。以上规范只是项目中总结出来觉得非常有必要的信息,这个读者可以根据自己项目要求进行取舍。如果觉得上文中有什么不对的地方,这里也欢迎各位读者留言指正。至于如何使用 JWT,已经将如何使用 token 的代码提交至:https://github.com/cmlbeliever/ProjectForChat 下的 ChatForApi 工程。



项目中共有登录和获取用户信息两个接口,使用 postman 调用登录接口后获取到用户 token,根据 token 通过 user/getUserInfo 接口获取到用户信息。这里的 token 是作为 Header 传入的。近期热文[b][b][b]《深度学习在摄影技术中的应用与发展[/b][/b][/b][b][b][b]《这样做,你的面试成功率将达到 90%[/b][/b][/b][b][b][b]《如何用 TensorFlow 让一切看起来更美?[/b][/b][/b][b][b][b]《Web 安全:前端攻击 XSS 深入解析[/b][/b][/b][b][b][b]《300万粉丝,全国最大的线上抽奖平台,深度解析[/b][/b][/b][b][b][b][b][b]免费福利[/b][/b][/b][/b][/b]

「阅读原文」看交流实录,你想知道的都在这里
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: