Rop开发手册(5):最简单的服务开放平台框架
传统的Web Service框架帮助你建造房子,而Rop框架帮助您建造城市。
目录
1.快速了解Rop
2.请求服务模型
3.应用授权及验证
4.服务会话管理
5.错误处理模型
6.响应报文控制
7.文件上传
8.服务安全控制
9.拦截器及事件体系
10.性能调优
11.开发客户端SDK
12.参考资料
错误模型概述
对于服务开放平台来说,不管发生了什么错误,都必须返回相应的错误报文,以便客户端应用能够根据错误报文做出相应的响应。每个服务都可能存在各种错误,如服务参数不合法、访问权限不足、版本不正确、访问超限等等。如果需要开发者自行设计这套错误模型并处理所有这些错误,那将是一项艰巨的工程。
Rop参考TOP建立了一个完整的错误模型,该错误模型拥有强大的表达能力和扩展性,很多错误自动于Rop负责处理,对于业务性的错误,开发者按错误模型构造即可。Rop将开发者从服务错误处理的荆棘丛中解放出来,从而可以将精力集中于具体的业务逻辑的处理上。
Rop的错误模型分为主错误和子错误两个层级,每个错误报文都会对应一个主错误和若干个子错误,主错误描述错误的类型,子错误说明错误的原因。子错误根据责任归属可以划分为ISP(Internet Service Provider)和ISV(Independent Software Vendors)两种类型的错误。如下图所示:
ISP代表平台服务提供商一方,如服务内部异常、服务端数据库资源不可用等错误,其责任归属于ISP,这类错误称为ISP错误。ISP错误的错误编码以“isp.”为前缀,如isp.xxx-service-unavailable、isp.xxx-service-timeout等。
ISV代表基于平台服务开发应用的开发者一方,这类错误产生的原因是由于开发者造成的。ISV错误的错误编码以“isv.”为前缀,如isv.invalid-permission、isv.missing-parameter:xxx等。
当服务发生错误时,Rop将返回统一格式的错误报文,它由一个主错误和若干个子错误组成。下面即是一个完整的错误报文:
- <?xml version="1.0" encoding="utf-8" standalone="yes"?>
- <error code="33">
- <message>非法的参数</message>
- <solution>请查看根据服务接口对参数格式的要求</solution>
- <subErrors>
- <subError code="isv.invalid-paramete:salary">
- <message>参数salary无效,格式不对、非法值、越界等</message>
- </subError>
- <subError code="isv.invalid-paramete:userName">
- <message>参数userName无效,格式不对、非法值、越界等</message>
- </subError>
- </subErrors>
- </error>
系统级主错误编码
Rop内置定义了一套和业务无关的错误代码体系,我们称这些错误为系统级错误。Rop在i18n/rop/error的国际化资源文件(目前提供了zh_CN和en两个国际化资源文件)中定义了系统级主错误及子错误。
在Rop中,主错误都对应一个唯一的数字编码,目前共包括27个主错误,通过下表进行说明:
表1 系统级主错误编码
错误编码 | 错误说明 | 错误编码 | 错误说明 | 错误编码 | 错误说明 |
1 | 服务不可用 | 20 | 缺少sessionId参数 | 29 | 非法的版本参数 |
2 | 开发者权限不足 | 21 | 无效的sessionId参数 | 30 | 不支持的版本号 |
3 | 用户权限不足 | 22 | 缺少appKey参数 | 31 | 无效报文格式类型 |
4 | 图片上传失败 | 23 | 无效的appKey参数 | 32 | 缺少必选参数 |
5 | HTTP方法被禁止 | 24 | 缺少签名参数 | 33 | 非法的参数 |
6 | 编码错误 | 25 | 无效签名 | 34 | 用户调用服务的次数超限 |
7 | 请求被禁止 | 26 | 缺少方法名参数 | 35 | 会话调用服务的次数超限 |
8 | 服务已经作废 | 27 | 不存在的方法名 | 36 | 应用调用服务的次数超限 |
9 | 业务逻辑出错 | 28 | 缺少版本参数 | 37 | 应用调用服务的频率超限 |
27个系统级错误可以划分为以下几类:
- 系统级参数错误:这类错误是由于系统级请求参数缺失或不合法引起的,20~31都是这一类型的错误;
- 业务级参数错误:业务级参数缺失或不合法而引起的错误,如32和33;
- 服务访问超限错误:客户端的服务调用超过配额,34~37都是这一类型的错误;
- 权限不足错误:如2和3的错误都是开发者或应用用户的权限不足,造成服务无法访问的错误;
- 其它错误:以上类型之外的错误。
子错误的编码是一个格式化的层级编码串,如isp.xxx-service-unavailable、isv.xxx-not-exist:invalid-yyy等,其中xxx和yyy都是变量占位符,会根据具体的错误填写相应的值。
每个子错误都对应一个主错误,而一个主错误可以对应多个子错误。子错误和主错误的映射关系在Rop的com.rop.validation.SubErrors中定义,SubErrors类拥有一个获取子错误对应主错误的静态方法:
- public static MainError getMainError(SubErrorType subErrorType, Locale locale)
ISP子错误编码
Rop目前仅拥有两个ISP的子错误,它们对应的主错误编码为1,即Service Currently Unavailable主错误。这两个ISP子错误分别是:
- isp.xxx-service-unavailable=调用后端服务{0}抛异常:{1},服务不可用。\n异常信息:\n{2}
- isp.xxx-service-timeout = 调用{0}服务超时,该服务的超时限制为{1}秒,请和服务平台提供商联系
isp.xxx-service-unavailable的错误消息是带参数的格式化串,Rop会将引起错误的异常信息格式化到消息内容中。假设我们调用user.getSession服务方法,其内部抛出了IllegalArgumentException,则客户端将得到一个如下的错误报文:
- <?xml version="1.0" encoding="utf-8" standalone="yes"?>
- <error code="1">
- <message>Service Currently Unavailable</message>
- <solution>Service Currently Unavailable</solution>
- <subErrors>
- <subError code="isp.user-getSession-service-unavailable">
- <message>
- call the back-end service user.getSession thrown
- exception:java.lang.IllegalArgumentException, the service is unavailable.
- exception info is :
- …
- </message>
- </subError>
- </subErrors>
- </error>
Rop允许设置服务执行的最大过期时间,即一个服务的执行超过指定限时后,直接返回错误报文,释放服务端资源,以平衡服务平台资源的利用。Rop默认不设置服务过期时间,可通过<rop:annotation-driven/>的service-timeout-seconds属性指定服务执行最大过期时间,单位为秒:
- <rop:annotation-driven service-timeout-seconds="10"/>
UserService.java:指定服务过期时间
- @ServiceMethod(method = "user.timeout", version = "1.0", timeout = 1) ①
- public RopResponse timeoutService(CreateUserRequest request) throws Throwable {
- Thread.sleep(2000); ②
- CreateUserResponse response = new CreateUserResponse();
- response.setCreateTime("20120101010102");
- response.setUserId("2");
- return response;
- }
- <?xml version="1.0" encoding="utf-8" standalone="yes"?>
- <error code="1">
- <message>服务不可用</message>
- <solution>服务目前无法使用</solution>
- <subErrors>
- <subError code="isp.user-timeout-service-timeout">
- <message>
- 调用user.timeout服务超时,该服务的超时限制为1秒,请和服务平台提供商联系。
- </message>
- </subError>
- </subErrors>
- </error>
在实际应用中,开发者遇到更多的都会是ISV的错误。Rop定义了5个ISV子错误,说明如下:
- isv.missing-parameter:xxx=缺少必要的参数{0}
- isv.parameters-mismatch:xxx-and-yyy=传入的参数{0}和{1}不匹配;
- isv.invalid-paramete:xxx=参数{0}无效,格式不对、非法值、越界等
- isv.invalid-permission=权限不够、非法访问
- isv.xxx-not-exist:invalid-yyy=根据{0}查询不到{1}
Rop将请求参数绑定到请求参数对象属性时,如果发生类型不匹配的错误(如将aaa赋给一个整型的属性),将返回isv.parameters-mismatch:xxx-and-yyy的子错误码,其中xxx为参数名,而yyy为请求参数的值。违反其它校验规则的统一返回isv.invalid-paramete:xxx的子错误,其中xxx为未通过校验的参数名。
可以通过SecurityManager实现类开发服务权限管理逻辑,如果SecurityManager驳回服务的访问,则说明应用或用户的权限不足,不能访问某个受限平台服务,Rop将返回isv.invalid-permission的子错误,
isv.xxx-not-exist:invalid-yyy子错误表示不存在对应某个ID的对象,如根据userId获取用户对象时,如果查不到对应的对象,则返回该子错误。以上我们介绍的4个ISV子错误,ROP都会自动产生,无需服务开发者关注。
而isv.xxx-not-exist:invalid-yyy则需要服务开发者自己负责创建,可以通过Rop的NotExistErrorResponse对象产生该错误。来看一个rop-sample中的例子:
代码清单 UserService.java:模拟返回CreateUserResponse响应
- @ServiceMethod(method = "user.get", version = "1.0", httpAction = HttpAction.GET)
- public RopResponse getUser(RopRequest request) throws Throwable {
- String userId = request.getRopRequestContext().getParamValue("userId");
- if("9999".equals(userId)){ ①
- return new NotExistErrorResponse("user","userId","9999",
- request.getRopRequestContext().getLocale());
- }else{
- CreateUserResponse response = new CreateUserResponse();
- //add creaet new user here...
- response.setCreateTime("20120101010102");
- response.setUserId(userId);
- response.setFeedback("user.get");
- return response;
- }
- }
- <?xml version="1.0" encoding="utf-8" standalone="yes"?>
- <error code="33">
- <message>Invalid Arguments</message>
- <solution>
- check the Required application parameter is valid(refer the subError message)
- </solution>
- <subErrors>
- <subError code="isv.user-not-exist:invalid-userId">
- <message>cant find userId by 9999</message>
- </subError>
- </subErrors>
- </error>
业务级子错误编码
系统级子错误模型是业务无关的通用性错误,在使用Rop开发您的服务平台时,一定会有很多业务相关的错误。如删除一个不允许删除的业务单据,在单据未通过审核时直接审批等等,这些业务相关的错误,称之为业务级子错误。
Rop设计了一个通用的业务级子错误编码: isv.xxx-service-error:yyy,其中xxx为服务方法名(服务方法名的“.”替换成“-”),而yyy为业务错误代码,一般由大写英文字符组成,来看几个具体的业务级子错误编码的示例:
- isv.order-delete-service-error:ORDER_NOT_ALLOW_DELETE;
- isv.order-approve-service-error:ORDER_IS_NOT_AUDIT;
- isv.user-password-change-service-error:PASSWORD_TOO_SIMPLE;
在属性资源文件中,属性键名如果包含“:”或“=”的字符,必须使用转义符,即在其前面添加“\”字符。如果属性值一行编写不下,可以通过“\”进行分行。具体参见java.util.Properties类的JavaDoc的说明。
必须在<rop:annotation-driven/>中指定业务级子错误资源文件的地址,以便Rop将其加载到框架中:
- <rop:annotation-driven ext-error-base-name="i18n/rop/sampleRopError"/>
- public BusinessServiceErrorResponse(String serviceName, String errorCode, Locale locale,
- Object... params)
我们通过rop-sample的一个例子了解业务级子错误的具体使用方法:
代码清单UserService.java:模拟返回CreateUserResponse响应
- @ServiceMethod(method = "user.add", version = "1.0")
- public RopResponse addUser(CreateUserRequest request) {
- if (reservesUserNames.contains(request.getUserName())) {
- return new BusinessServiceErrorResponse(
- request.getRopRequestContext().getMethod(), "USER_NAME_RESERVED",
- request.getRopRequestContext().getLocale(), request.getUserName()); ①
- } else {
- CreateUserResponse response = new CreateUserResponse();
- response.setCreateTime("20120101010101");
- response.setUserId("1");
- return response;
- }
- }
假设“jhon”是预留的用户名,如果调用user.add服务时,上传的userName参数是“jhon”,将返回如下的错误响应报文:
- <?xml version="1.0" encoding="utf-8" standalone="yes"?>
- <error code="9">
- <message>业务逻辑出错</message>
- <solution>请了解服务调用的前置条件,检查是否满足业务逻辑</solution>
- <subErrors>
- <subError code="isv.user-add-service-error:USER_NAME_RESERVED">
- <message>jhon 是预留的用户,请选择其它的用户名.</message>
- </subError>
- </subErrors>
- </error>
转自:http://stamen.iteye.com/blog/1654615