• 1. ROP开源项目指南陈雄华
  • 2. 目录REST服务框架的问题域1TOP简介2ROP快速入门3ROP框架概述4错误处理5响应流化输出6
  • 3. Web Service实现方式代码优先 代码优先是传统Web Services开发世界中最常见、最容易上手的一种开发模式,也是大多数开发人员喜欢的一种开发模式。其开发过程相对比较简单,之所以简单,是因为它迎合了开发人员的惰性。代码驱动Web服务构建,是通过相关工具类库(如CXF等)直接导出服务类的Web Service调用接口,绕过编写数据契约(XSD)及SOAP的烦琐过程。 契约优先(文档优先) 契约优先的开发模式,是目前Web Services开发领域中的最佳实践,也是Spring 极力倡导的。所谓的契约优先,先要定义Web服务的数据契约,也就是服务交互过程中的数据类型及请求响应消息的数据契约,然后再编写相应服务端点的业务处理逻辑。
  • 4. 契约优先的难点 请求报文也许容易制定,但是由于数据校验错误、服务错误、系统错误的众多错误,使得定义一个服务的响应报文变得很困难。 因此,对于契约优先的Web Service服务来说,重点就在如何建立好一个即在结构上相对稳定,而在内容上又易于扩展的错误描述体系。
  • 5. Rest服务的问题域通讯报文协议:一般需要支持XML和JSON格式; 异常描述体系:由于Rest调用只能通过报文进行交互,因此不管是正常还是异常情况,通信报文都必须描述出来,建立异常报文体系; 请求数据校验:由于Rest服务的独立性,可能有多个客户端,因此必须对请求数据做合法性校验,而不能寄希望于客户端的数据校验; 发展性问题:必须考虑API的变更问题,支持多版本; 安全性问题:如何对服务请求的安全进行控制,阻止非法的客户端; 日志和审计:方便调试和问题的追踪和审计,方便开发期和运行期的问题跟踪。
  • 6. 目录REST服框架的问题域1TOP简介2ROP快速入门3ROP框架概述4错误处理5响应流化输出6
  • 7. TOP框架简介 淘宝开放平台(Taobao Open Platform,简称 TOP)是大淘宝电子商务基础服务的重要开放途径。TOP 自2009年6月22日发布以来,以其明晰的优势和特点吸引了大批开发者的关注和加入。 淘宝开放平台为外部合作伙伴提供了极大的商业想象空间——截至2011年6月,对外开放的 API近300个,涵盖了淘宝核心交易和各项垂直业务的主要流程,API日均调用量超过10亿次/天。无论是从业务开放的广度和深度,淘宝开放平台都是国内开放业务规模最大的开放平台。 同时,淘宝开放平台形式自由——不限语言、不限平台(支持浏览器、桌面)、不限使用场所(除了淘宝自身网站外,鼓励和支持外部所有网站使用淘宝开放平台服务)。
  • 8. TOP的特点统一URL:http://gw.api.tbsandbox.com/router/rest,; 通过method参数指定具体API服务:如taobao.user.get、taobao.users.get等。相比于http://host/user/tom,TOP使用http://gw.api.tbsandbox.com/router/rest?method=taobao.user.get&userName=tom,乍看起来纯REST简单,但是如API数量大后,TOP的风格更有优势了,因为每个TOP API用户只有参数上的差异。 API的调用参数化:请求参数分为系统级和应用级两类,系统级参数每个API都相同,如method,appKey等,而应用级参数由每个API指定; 正确报文和错误报文分开:正确时返回正确的业务报文,而错误时返回统一的错误报文。这种将正确和错误的报文解耦的方式有利于简化报文的设计。
  • 9. TOP的系统级参数名称类型是否必须描述methodString是API接口名称sessionString否TOP分配给用户的SessionKey,自用型应用不需要传入该参数的;非自用型的应用对于需要用户授权的API必须要传入该参数,对于不需要用户授权的API则不必传入该参数。timestampString是时间戳,格式为yyyy-MM-dd HH:mm:ss,例如:2008-01-25 20:23:30。淘宝API服务端允许客户端请求时间误差为10分钟。formatString否可选,指定响应格式。默认xml,目前支持格式为xml,jsonapp_keyString是TOP分配给应用的AppKeyvString是API协议版本,可选值:2.0。signString是API输入参数签名结果sign_methodString是参数的加密方法选择,可选值是:md5,hmac。这个参数只存在于2.0中。
  • 10. TOP的应用级参数名称类型必须示例值默认值描述fieldsField List必须user_id,nick,seller_credit需返回的字段列表。可选值:User结构体中的所有字段;以半角逗号(,)分隔。不支持:地址,真实姓名,身份证,手机,电话nickString可选hz0799用户昵称,如果昵称为中文,请使用UTF-8字符集对昵称进行URL编码。  注:在传入session的情况下,可以不传nick,表示取当前用户信息;否则nick必须传. 自用型应用不需要传入nick 应用级参数由API自己定义,下面是taobao.user.get (获取单个用户信息)API的应用级参数。
  • 11. TOP的错误处理API平台错误API业务错误 TOP拥有一个结构稳定,表达能力强的错误体系,这是TOP框架最值得称赞的一个设计。API平台错误和业务错误都包含主错误和子错误,主错误采用数字编码(如31,32),子错误采用文本层级串编码(如isp.remote-service-error、isv.invalid-permission),其中平台的子错误码以isp.开头,而业务子错误码以isv.开头。 而容器类错误只有主错误,没有子错误,主错误也采用数字编码。参见:http://open.taobao.com/doc/detail.htm?id=114容器类错误
  • 12. TOP平台主错误错误码错误描述-英文错误描述-中文解决方案3Upload Fail图片上传失败将传入的图片格式改为正确的格式、适当的大小的图片放进消息体里面传输过来,如果传输仍然失败需要减小图片大小或者增加网络带宽进行尝试7App Call Limited应用调用次数超限,包含调用频率超限调整程序合理调用API,等限频时间过了再调用,淘客的调用频率是系统按照上个月交易额自动修改的,修改后的频率会在官方论坛首页以公告形式通知,开发者可自行查看9Http Action Not AllowedHTTP方法被禁止请用大写的POST或GET,如果有图片等信息传入则一定要用POST才可以...............
  • 13. TOP平台子错误子错误码格式错误信息归属方可重试否isp.***-service-unavailable调用后端服务***抛异常,服务不可用ISP是isp.remote-service-error连接远程服务错误ISP是isp.remote-service-timeout连接远程服务超时ISP是isp.remote-connection-error远程连接错误ISP是isp.null-pointer-exception空指针异常错误ISP否isp.top-parse-errorapi解析错误(出现了未被明确控制的异常信息)ISP否isp.top-remote-connection-timeouttop平台连接后端服务超时ISP是isp.top-remote-connection-errortop平台连接后端服务错误,找不到服务ISP是isp.top-mapping-parse-errortop-mapping转换出错,主要是由于传入参数格式不对ISP否isp.unknown-errortop平台连接后端服务抛未知异常信息ISP是
  • 14. TOP业务主错误错误码错误描述-英文错误描述-中文解决方案40Missing Required Arguments缺少必选参数API文档中设置为必选的参数是必传的,请仔细核对文档41Invalid Arguments非法的参数参数类型不对,例如:需要传入的是数字类型的,却传入了字符类型的参数
  • 15. TOP业务子错误子错误码格式错误信息归属方可重试否isv.###-not-exist:***根据***查询不到###ISV否isv.missing-parameter:***缺少必要的参数***ISV否isv.invalid-paramete:***参数***无效,格式不对、非法值、越界等ISV否isv.invalid-permission权限不够、非法访问ISV否isv.parameters-mismatch:***-and-###传入的参数***和###不匹配,两者有一定的对应关系ISV否isv.***-service-error:###调用***服务返回false,业务逻辑错误,###表示具体的错误信息ISV否 TOP的业务子错误的结构是固定的,但其扩展性很强,如isv.missing-parameter:***的错误可以表示所有必须输入的参数的错误,如isv.missing-parameter:userName、isv.missing-parameter:password等。 这样,你无需为设计各种错误代码而发愁,根据结构扩展之即可。
  • 16. TOP的安全 TOP为了防止伪造客户端的情况,对请求参数列表进行签名,每个接入应用在开发时必须通过授权获得一个appKey和secret,appKey是公开的,而secret是保密的,只有应用开发者知道。 在发送请求时,客户端需要使用appKey对应的secret对参数列表进行签名,如:
  • 17. TOP交互流程
  • 18. 目录REST服务框架的问题域1TOP简介2ROP快速入门3ROP框架概述4错误处理5响应流化输出6
  • 19. ROP概述 ROP是采用Spring MVC 3.0框架,实现的模拟TOP的轻量级Web Service框架,完全开源,使用ROP可以非常快速地构建您自己的开放平台。 ROP采用如下实现技术: Spring MVC 3.0:整个ROP构建于Spring MVC 3.0基础上; JSR 303:采用JSR 303校验注解对请求参数进行合法性校验,ROP会自动将校验结果转换成错误报文输出; JAXB:虽然Spring MVC 3.0可以支持将POJO流化成XML及JSON输出,不过控制上不太便利,因此ROP采用JAXB对响应的对象进行注解,并通过JAXB+Jackson将响应对象流化等价XML或JSON; 国际化支持:错误信息支持国际化。
  • 20. 5分钟快速入门 例子说明: 通过ROP开发一个创建新用户的服务,提供的信息包括userName、password及salary的信息。如果用户已经存在返回错误响应报文,否则返回创建成功的响应报文。
  • 21. Step 1:创建请求对象package com.stamen.sample.rop.request; import com.stamen.rop.RopRequest; import org.springframework.format.annotation.NumberFormat; import javax.validation.constraints.* public class CreateUserRequest extends RopRequest { @Pattern(regexp = "\\w{4,30}") private String userName; @Pattern(regexp = "\\w{6,30}") private String password; @DecimalMin("1000.00") @DecimalMax("100000.00") @NumberFormat(pattern = "#,###.##") private long salary; //getter and setter... }
  • 22. Step 2:创建正确响应的对象package com.stamen.sample.rop; import com.stamen.rop.RopResponse; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "sampleRopResponse1") public class CreateUserResponse implements RopResponse{ @XmlAttribute private String userId; @XmlAttribute private String createTime; //getter and setter... }
  • 23. Step 3:创建服务对象package com.stamen.sample.rop; import com.stamen.rop.ApiMethod; import com.stamen.sample.rop.request.CreateUserRequest; import com.stamen.sample.rop.response.CreateUserResponse; import org.springframework.stereotype.Service; @Service public class UserRestService { @ApiMethod("sample.user.add") public RopResponse addUser(CreateUserRequest request) { CreateUserResponse response = new CreateUserResponse(); //add creaet new user here... response.setCreateTime("20120101010101"); response.setUserId("1"); return response; } }
  • 24. Step 3:创建服务对象package com.stamen.sample.rop; import com.stamen.rop.ApiMethod; import com.stamen.sample.rop.request.CreateUserRequest; import com.stamen.sample.rop.response.CreateUserResponse; import org.springframework.stereotype.Service; @Service public class UserRestService { @ApiMethod("sample.user.add") public RopResponse addUser(CreateUserRequest request) { CreateUserResponse response = new CreateUserResponse(); //add creaet new user here... response.setCreateTime("20120101010101"); response.setUserId("1"); return response; } }
  • 25. Step 4:编写测试用例package com.stamen.sample.rop; import com.stamen.rop.validation.DefaultRopValidator; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.util.ArrayList; public class UserRestServiceClient { public static void main(String[] args) { RestTemplate restTemplate = new RestTemplate(); MultiValueMap form = new LinkedMultiValueMap(); form.add("method", "sample.user.add"); form.add("appKey", "00001"); form.add("v", "1.0"); form.add("sessionId", "test");//must be the valid sessionId form.add("format", "xml"); form.add("locale", "en"); form.add("userName", "tomson"); form.add("password", "123456"); form.add("salary", "2,500.00"); String sign = DefaultRopValidator.sign(new ArrayList( form.keySet()), form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde"); form.add("sign", sign); String response = restTemplate.postForObject( "http://localhost:9080/router", form, String.class); System.out.println("response:\n" + response); } }
  • 26. Step 5:运行测试启动Web服务器,运行测试用例,则返回如下的报文: 将format参数改为json,则返回:{"sampleRopResponse1":{"userId":"1","createTime":"20120101010101"}}假设将salary参数设置为错误的值,如“aaa”,则返回错误报文: isv.parameters-mismatch:salary-and-aaa incoming parameter salary and aaa does not match, both have a certain correspondence between
  • 27. 目录REST服务框架的问题域1TOP简介2ROP快速入门3ROP框架概述4错误处理5响应流化输出6
  • 28. ROP框架结构RopServiceRouterRopServiceHandlerRegistry RopServiceMethod AdapterUserRestServiceRopResponseMarshaller23RopResponse6RopResponse71@ApiMethodRopValidator458XML/JSON
  • 29. 扫描注册处理方法 DefaultRopServiceMethodHandlerRegistry是RopServiceHandlerRegistry接口的实现类,它使用Spring的提供的ReflectionUtils工具类,对Spring容器中所有的Bean进行扫描,将发现方法有标注@ApiMethod注解时,将方法的反射对象注册到DefaultRopServiceMethodHandlerRegistry的注册表中。Bean1Bean2Bean3Bean4Bean5BeanBeanRopServiceHandlerRegistry Bean1Bean4标注了@ApiMethodSpring容器ROP处理器注册表
  • 30. 请求对象转换 RopServiceRouter的接口方法被AnnotationRopServiceRouter实现,其实现方法如下: @RequestMapping("/router") public void service(WebRequest webRequest, HttpServletResponse httpServletResponse) {...} 通过Spring MVC,所有/router的URI,由该方法进行处理,它负责将请求派发给不同的处理方法。Spring MVC将HttpServletRequest绑定到WebRequest中,而ROP利用Spring的DataBinder,则webRequest对象绑定到目标方法的入参对象中,该对象继承于RopRequest。HttpServletRequestWebRequestSpring MVCROP
  • 31. 目录REST服务框架的问题域1TOP简介2ROP快速入门3ROP框架概述4错误处理5响应流化输出6
  • 32. ROP的响应 ROP框架自动 处理的错误 (如系统参数缺失、业务请求数据校验,抛出异常等)ErrorResponseRopResponse开发者处理 的错误 (如执行的业务不满足前提条件,如创建新用户时,用户名已经存在等)XxxResponse
  • 33. ROP框架自动处理的错误 在将请求转交给处理方法之前,ROP会进行系统参数、业务参数的合法性校验,如果发现错误,即驳回请求,直接返回错误的报文,这部分工作是ROP框架完成的,服务开发者无须关注,包括以下三类: 系统参数的合法性校验:如method、sessionId、v是否缺失,签名是否合法,会话是否存在等; 业务参数的合法性校验:开发者在创建服务请求对象(继承于RopRequest)时,在请求对象属性上通过JSR303指定校验规则,ROP框架会自动利用这些校验规则并将校验错误转换为相应的响应错误码; 在执行服务操作时抛出了未知异常,这些ROP会捕获异常,并将其转换为错误响应。 第1和第2类错误都是在调用具体的请求服务前产生了,而第3类错误是在请求服务执行时产生的。第1类错误只有主错误码,而第2和第3类错误都有主错误和子错误码。
  • 34. 数据校验 由于Spring的DataBinder可以设置一个自定义的validator,ROP直接使用Spring的LocalValidatorFactoryBean构造一个validator,在数据绑定后,对标注了JSR303注解的RopRequest进行数据校验。 而ROP的RopValidator只是简单地将Spring 的校验错误(BindingResult.getAllErrors())转换为ROP的错误,如果发生错误,将直接返回ErrorResponse的响应(RopResponse的子类)。WebRequestBindingResultMainError《Spring》DataBinderFieldErrorFieldErrorFieldError《Spring》 LocalValidatorFactoryBeanSubErrorSubErrorSubError《ROP》 RopValidatorErrorResponse《ROP》 RopServiceMethodAdapter
  • 35. 错误码体系 主错误预定义子错误自定义子错误
  • 36. 错误信息国际化处理 MainErrorMainErrorsSubErrorsgetError(MainErrorType mainErrorType, Locale locale)MainErrorgetSubError(String subErrorCode, String subErrorKey, Locale locale, Object... params) SubErrorSubError/resource/i18n/rop/error错误码错误信息ERROR_1Service Currently Unavailableisv.xxx-not-exist\:invalid-yyycan't find '{0}' by '{1}'isv.missing-parameter\:xxxthe lack of the necessary parameters {0}isp.null-pointer-exceptionnull pointer exception error国际化的错误信息,可带参
  • 37. 错误码体系 ROP错误体系完全参考TOP的错误体系,其改进在于: 对于数据校验的错误完全由ROP自动完成校验错误到响应报文错误码的转换,非常方便; 响应报文的错误信息支持国际化,可以在resource/i18n/rop的error_xx_xx.properties中定义国际化信息; 采用带参的错误键和带参的,如: isv.missing-parameter:xxx = the lack of the necessary parameters {0} 键名的xxx即为变更,当然属性值也可以带参,只须一个国际化消息属性就可以变换出各种错误键,如:isv.missing-parameter:userName isv.missing-parameter:password 这样既可以做到ROP响应错误码应用相关,又可复用的错误码国际化信息,大大简化了错误码国际化信息的定义。
  • 38. 业务错误响应:ServiceErrorResponse 如果服务发生错误,必须返回继承于ErrorResponse的响应对象,除ROP框架自动处理的错误外,业务错误必须于服务开发者自动处理,ROP提供了3个业务级的错误响应类: ServiceErrorResponse:服务错误,一般是由于业务条件不满足造成的:如在还未支付的情况下执行退款服务。其错误码格式形如: isv.yyy-service-error:XXX,如:isv.fellow-add-service-error:INVALID_INVITE_CODE isv.fellow-add-service-error:EXPIRED_INVITE_CODE 其中fellow-add自动对应fellow.add的请求方法,则INVALID_INVITE_CODE由开发者指定。必须在错误国际化信息中定义对应的错误消息,如: isv.fellow-add-service-error\:INVALID_INVITE_CODE={0}不是合法的邀请码 通过如下代码创建返回对象:return new ServiceErrorResponse(request.getMethod(), INVALID_INVITE_CODE, request.getLocale(), request.getInviteCode());
  • 39. 业务错误响应:NotExistErrorResponse 如果是根据id获取某个对象,如果id对应的对象不存在,这时将返回类似: isv.xxx-not-exist:invalid-yyy 的错误。 该错误的国际化消息属性可复用,不用分别定义,通过如下构造函数创建该类错误错误响应: NotExistErrorResponse(String objectName, String queryFieldName, Object queryFieldValue, Locale locale) 如: new NotExistErrorResponse(“user”,”userId”, “001, locale) 则其产生的错误键为: isv.user-not-exist:invalid-userId
  • 40. 目录REST服务框架的问题域1TOP简介2ROP快速入门3ROP框架概述4错误处理5响应流化输出6
  • 41. ROP的响应 JaxbXmlRopResponseMarshallerRopResponseMarshallerJacksonJsonRopResponseMarshaller@XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "createUserResponse") public class CreateUserResponse implements RopResponse{ @XmlAttribute private String userId; @XmlAttribute private String createTime; ... }
  • 42. ROP的响应:XML @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "createUserResponse") public class CreateUserResponse implements RopResponse{ @XmlAttribute private String userId; @XmlAttribute private String createTime; ... }JaxbXmlRopResponseMarshaller format=xml
  • 43. ROP的响应:JSON @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "createUserResponse") public class CreateUserResponse implements RopResponse{ @XmlAttribute private String userId; @XmlAttribute private String createTime; ... }JacksonJsonRopResponseMarshaller{"sampleRopResponse1":{"userId":"1","createTime":"20120101010101"}}format=json
  • 44. 参考资源 项目地址:https://github.com/itstamen/rop; 快速入门:https://github.com/itstamen/rop/wiki/Five-minutes-quick-start 我的微博:http://t.qq.com/hopeahead
  • 45. 谢 谢 !www.bookegou.com