精华帖 (7) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-06-12
最后修改:2012-06-15
首先,非常感谢众多网友对Rop所提出的修改建议,你们的建议是我完善Rop框架的源动力!由于这次更改很大,所以我还是在这边发,后面请大家到Rop群组http://rop.group.iteye.com/中讨论,谢谢合作! 大家在使用中如有什么不满足的,或有什么好点子都欢迎给我提出(http://rop.group.iteye.com/),我会尽快调整。 这段时间,对Rop进行了较大的调整和优化,这边出一个较完整的更改日志,说明如下(近期我着手撰写Rop的文件,到时也会在群组中发布): 1.新增了Rop的Spring Schema命令空间 可以通过rop命名空间配置Rop,如指定应用错误国际化资源地址、设置自定义的RopValidator,配置FormattingConversionService并添加自定义的转换器、注册拦截器,注册监听器,设置系统级参数的名称等,如下所示: <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:rop="http://www.rop.com/schema/rop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.rop.com/schema/rop http://www.rop.com/schema/rop/rop-1.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <!--① 扫描Web类包--> <context:component-scan base-package="com.rop.sample"> <!-- 假设所有的服务方法都放在rop包下--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 配置一个Rop框架 --> <rop:annotation-driven rop-validator="ropValidator" formatting-conversion-service="conversionService" ext-error-base-name="i18n/rop/sampleRopError" sign-enable="true"/> <!--定义自定义的RopValiator,如appKey及密钥的管理器、会话管理器、服务安全访问管理器等--> <bean id="ropValidator" class="com.rop.validation.DefaultRopValidator"> <property name="appSecretManager"> <bean class="com.rop.sample.SampleAppSecretManager"/> </property> <property name="securityManager"> <bean class="com.rop.sample.SampleSecurityManager"/> </property> <property name="sessionChecker"> <bean class="com.rop.sample.SampleSessionChecker"/> </property> </bean> <!--设置自定义的类型转换服务,注册自定义的类型转换器--> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <!--将xxxx-yyyyyyy格式的电话转换为内部的Telephone对象--> <bean class="com.rop.sample.request.TelephoneConverter"/> </set> </property> </bean> <!--注册拦截器,可配置多个--> <rop:interceptors> <bean class="com.rop.sample.ReservedUserNameInterceptor"/> </rop:interceptors> <!--注册监听器,可配置多具--> <rop:listeners> <bean class="com.rop.sample.SamplePostInitializeEventListener"/> <bean class="com.rop.sample.SampleAfterDoServiceEventListener"/> </rop:listeners> <!--自定义method,appKey等这些系统级参数的参数名--> <rop:sysparams format-param-name="messageFormat"/> </beans> 这个RopServlet就变化很简单,自定义配置性的工作转换到Spring的rop schema中,RopServlet的配置简化为: <servlet> <servlet-name>rop</servlet-name> <servlet-class> com.rop.RopServlet </servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>rop</servlet-name> <url-pattern>/router</url-pattern> </servlet-mapping> 原来配置Rop的文件配置也移除了,统一在Spring的rop空间中来做。 2.支持异步调用 ApiMethob易名为ServiceMethod,该注解添加了多个属性,其中有一个timeout属性,当服务端的服务超过后,会返回相应的错误信息,如下所示: @ServiceMethod(value = "user.timeout", version = "1.0", timeout = 1) public RopResponse timeoutService(CreateUserRequest request) throws Throwable { Thread.sleep(2000); CreateUserResponse response = new CreateUserResponse(); //add creaet new user here... response.setCreateTime("20120101010102"); response.setUserId("2"); return response; } 这儿,我们设定timeoutService()服务方法的过期时间为1,如果过滤,将返回如下的错误报文: response:<?xml version="1.0" encoding="utf-8" standalone="yes"?> <error solution="Service Currently Unavailable" message="Service Currently Unavailable" code="1"> <subErrors> <subError message="connect to a remote service timeout Remote connection error" code="isp.remote-service-timeout"/> </subErrors> </error> 3.支持服务的多版本 @ServiceMethod添加了一个version属性,Rop将自动将method和version都匹配的请求路由到相应的服务方法中,如果找不到对应的版本,会报相应的错误,如下所示: @ServiceMethodBean(value = "group1", title = "组1") public class UserRestService { private static final String USER_NAME_RESERVED = "USER_NAME_RESERVED"; private List reservesUserNames = Arrays.asList(new String[]{"tom", "jhon"}); @ServiceMethod(value = "user.add", version = "1.0")//② Let this method service the sample.user.add method public RopResponse addUser(CreateUserRequest request) { if (reservesUserNames.contains(request.getUserName())) { //如果注册的用户是预留的帐号,则返回错误的报文 return new ServiceErrorResponse( request.getRequestContext().getMethod(), USER_NAME_RESERVED, request.getRequestContext().getLocale(), request.getUserName()); } else { CreateUserResponse response = new CreateUserResponse(); //add creaet new user here... response.setCreateTime("20120101010101"); response.setUserId("1"); return response; } } //版本为2.0的user.add @ServiceMethod(value = "user.add", version = "2.0") public RopResponse addUser2(CreateUserRequest request) { if (reservesUserNames.contains(request.getUserName())) { //如果注册的用户是预留的帐号,则返回错误的报文 return new ServiceErrorResponse( request.getRequestContext().getMethod(), USER_NAME_RESERVED, request.getRequestContext().getLocale(), request.getUserName()); } else { CreateUserResponse response = new CreateUserResponse(); //add creaet new user here... response.setCreateTime("20120101010102"); response.setUserId("2"); return response; } } } 以上服务为user.add定义了两个版本的方法。 4.支持HTTP请求方法限制(目前仅支持GET和POST) @ServiceMethod添加了一个httpAction的属性,默认情况下不限定,如果设置后,只有HTTP请求是相应的方法时,才会路由到相应的服务方法中,返回直接驳回请求。如下所示: @ServiceMethod(value = "user.get", version = "1.0", httpAction = HttpAction.GET) public RopResponse getUser(CreateUserRequest request) throws Throwable { String userId = request.getRequestContext().getParamValue("userId"); CreateUserResponse response = new CreateUserResponse(); //add creaet new user here... response.setCreateTime("20120101010102"); response.setUserId(userId); response.setFeedback("user.get"); return response; } 假设使用POST HTTP 方法访问: @Test public void testGetUserWithPOST() { RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); form.add("method", "user.get");//<--指定方法名称 form.add("appKey", "00001"); form.add("v", "1.0"); form.add("sessionId", "mockSessionId1"); form.add("locale", "en"); form.add("userName", "tomson"); form.add("salary", "2,500.00"); //对请求参数列表进行签名 String sign = RopUtils.sign(new ArrayList<String>( form.keySet()), form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde"); form.add("sign", sign); String response = restTemplate.postForObject( "http://localhost:8088/router", form, String.class); System.out.println("response:\n" + response); assertTrue(response.indexOf("code=\"5\"") > -1); } 将返回如下的错误信息: <?xml version="1.0" encoding="utf-8" standalone="yes"?> <error solution="Use the correct HTTP method(GET or POST)" message="Http Action Not Allowed" code="5"/> 5.添加事件体系 支持Rop框架启动后,关闭前的事件,每个服务处理前处理后的事件; 以下是一个监听Rop框架初始化后事件的监听器: public class SamplePostInitializeEventListener implements RopEventListener<AfterStartedRopEvent> { @Override public void onRopEvent(AfterStartedRopEvent ropRopEvent) { System.out.println("execute SamplePostInitializeEventListener!"); } @Override public int getOrder() { return 0; //To change body of implemented methods use File | Settings | File Templates. } } 要使事件启用,必须在Spring中配置好: <rop:listeners> <bean class="com.rop.sample.SamplePostInitializeEventListener"/> </rop:listeners> 6.添加拦截器支持 可以对每个服务调用进行拦截,如果在拦截器中往RequestContext中设置RopResponse了,将阻止业务执行,直接返回,否则继续执行服务: 下面的拦截器,对user.add的方法进行拦截,并发现userName为jhonson(相当于说这是预留的用户名,不能添加这个用户)时,驳回请求,返回相应的错误报文: package com.rop.sample; import com.rop.AbstractInterceptor; import com.rop.RequestContext; import com.rop.sample.response.InterceptorResponse; import org.springframework.stereotype.Component; /** * <pre> * 该拦截器仅对method为“user.add”进行拦截,你可以在{@link #isMatch(com.rop.RequestContext)}方法中定义拦截器的匹配规则。 * 你可以通过{@link com.rop.RequestContext#getServiceMethodDefinition()}获取服务方法的注解信息,通过这些信息进行拦截匹配规则 * 定义。 * </pre> * * @version 1.0 */ @Component public class ReservedUserNameInterceptor extends AbstractInterceptor { /** * 在数据绑定后,服务方法调用前执行该拦截方法 * @param methodContext */ @Override public void beforeService(RequestContext methodContext) { System.out.println("beforeService ..."); if ("jhonson".equals(methodContext.getParamValue("userName"))) { InterceptorResponse response = new InterceptorResponse(); response.setTestField("the userName can't be jhonson!"); //设置了RopResponse后,后续的服务将不执行,直接返回这个RopResponse响应 methodContext.setRopResponse(response); } } /** * 在服务执行完成后,响应返回前执行该拦截方法 * @param methodContext */ @Override public void beforeResponse(RequestContext methodContext) { System.out.println("beforeResponse ..."); } /** * 对method为user.add的方法进行拦截,你可以通过methodContext中的信息制定拦截方案 * @param methodContext * @return */ @Override public boolean isMatch(RequestContext methodContext) { return "user.add".equals(methodContext.getMethod()); } } 这样,将执行下面的测试时将返回由拦截器生产的响应报文: /** * 测试被拦截器阻截服务的情况 (user.add + 1.0) */ @Test public void testAddUserWithInterceptorViolidateReservedUserName() { RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); form.add("method", "user.add");//<--指定方法名称 form.add("appKey", "00001"); form.add("v", "2.0"); form.add("sessionId", "mockSessionId1"); form.add("locale", "en"); form.add("userName", "jhonson");//这个用户名会被ReservedUserNameInterceptor拦截器驳回 form.add("salary", "2,500.00"); //对请求参数列表进行签名 String sign = RopUtils.sign(new ArrayList<String>( form.keySet()), form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde"); form.add("sign", sign); String response = restTemplate.postForObject( "http://localhost:8088/router", form, String.class); System.out.println("response:\n" + response); assertTrue(response.indexOf("the userName can't be jhonson!") > -1); } 返回如下报文: <?xml version="1.0" encoding="utf-8" standalone="yes"?> <createUserResponse testField="the userName can't be jhonson!"/> 7.可以将XML及JSON的请求数据绑定到请求对象中 如果需要使用XML请求数据,则format必须设置成xml,如果需要使用json,则format必须设置成json. 以下是一个带XML的请求: @Test public void testServiceXmlRequestAttr() { RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); form.add("method", "user.add");//<--指定方法名称 form.add("appKey", "00001"); form.add("v", "1.0"); form.add("sessionId", "mockSessionId1"); form.add("userName", "tomson"); form.add("salary", "2,500.00"); //address会正确绑定 form.add("address", "<address zoneCode=\"0001\" doorCode=\"002\">\n" + " <streets>\n" + " <street no=\"001\" name=\"street1\"/>\n" + " <street no=\"002\" name=\"street2\"/>\n" + " </streets>\n" + "</address>"); //对请求参数列表进行签名 String sign = RopUtils.sign(new ArrayList<String>( form.keySet()), form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde"); form.add("sign", sign); String response = restTemplate.postForObject( "http://localhost:8088/router", form, String.class); System.out.println("response:\n" + response); assertTrue(response.indexOf("<createUserResponse createTime=\"20120101010101\" userId=\"1\">") > -1); } 来看一下服务端的 public class CreateUserRequest extends AbstractRopRequest { @Pattern(regexp = "\\w{4,30}") private String userName; @IgnoreSign @Pattern(regexp = "\\w{6,30}") private String password; @DecimalMin("1000.00") @DecimalMax("100000.00") @NumberFormat(pattern = "#,###.##") private long salary; @Valid private Address address;//这个属性对象用于接受address请求参数的XML内容 ... } 来看一下Address的代码: @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "address") public class Address { @XmlAttribute @Pattern(regexp = "\\w{4,30}") private String zoneCode; @XmlAttribute private String doorCode; @XmlElementWrapper(name = "streets") @XmlElement(name = "street") private List<Street> streets; ... } 即也是采用和RopResponse一样的JAXB注解就可以了。 8.RopRequest和RopResponse都接口化 1)原来的RopRequest是一个类,系统级参数放在这个类中,现在移到RequestContext接口中,可以通过RopRequest#getRequestContext()访问这些系统级参数; 2)RequestContext对应每一个请求,它还包括以下接口方法: //获取本次服务对应服务方法处的ServiceMethod注解的信息; ServiceMethodDefinition getServiceMethodDefinition(); //对应本次请求服务客户端传过来的所有参数列表 Map<String, String> getAllParams(); //可以获取客户端传过来的某个参数值 String getParamValue(String paramName); //获取原始的请求对象(目前都为HttpServletRequest对象) Object getRawRequestObject(); 有了这个抽象,如果你的业务请求参数不需要合法性校验且数量很少,那么你就不要再为请求数据定义一个专门的请求类了,直接使用RopRequest作为服务方法的入参就可以了,如下所示: //直接使用RopRequest对象作为入参 @ServiceMethod(value = "user.query", version = "1.0", httpAction = HttpAction.GET) public RopResponse queryUsers(RopRequest request) throws Throwable { //直接从参数列表中获取参数值 String userId = request.getRequestContext().getParamValue("userId"); CreateUserResponse response = new CreateUserResponse(); response.setCreateTime("20120101010102"); response.setUserId(userId); response.setFeedback("user.query"); return response; } 9.ServiceMethodBean 可以在服务类上打上这个注解就可以将其变成一个Bean了,此外它拥有和@Servicemethod类似的属性,如果一个服务类有拥有多个服务方法,这些服务方法都拥有一些相同的属性(如组名,TAG名等),那么可以在ServiceMethodBean中定义,方法处的@Servicemethod 将覆盖类处ServiceMethodBean的定义。 @ServiceMethodBean(value = "group1", title = "组1") public class UserRestService { @ServiceMethod(value = "user.add", version = "1.0") public RopResponse addUser(CreateUserRequest request) { ... } //版本为2.0的user.add @ServiceMethod(value = "user.add", version = "2.0") public RopResponse addUser2(CreateUserRequest request) { ... } } ServiceMethod比ServiceMethodBean多了方法名的属性,其它的属性ServiceMethodBean都有:
组、tags有什么用途呢? 它们可以访问你定义拦截器的拦截过滤规则,如果你的一个拦截器仅希望拦截每些服务方法,那么在制定拦截器的isMatch方法时,使用这些信息将非常方便。如下所示: public boolean isMatch(RequestContext methodContext) { if("group1".equals(methodContext.getServiceMethodDefinition().getMethodGroup())){ //do sth } } 10.请求数据签名的校验的启用和关闭 有些服务方法不需要签名校验,则可以方便地通过 @ServiceMethod(value = "service.method1", ignoreSign = IgnoreSignType.NO) public RopResponse service1() { return new RopResponse() { }; } 你也可以使整个Rop框架不启动签名校验(一般用于测试时使用): <rop:annotation-driven sign-enable="false"/> 这样它就会忽略服务方法处的设置,整体不启用签名校验了。 此外,如果你希望对某个服务方法的某些请求数据不签名(如图片数据这种),那么可以在请求对象类定义处使用@IgnoreSign,如下所示: public class CreateUserRequest extends AbstractRopRequest { @Pattern(regexp = "\\w{4,30}") private String userName; @IgnoreSign @Pattern(regexp = "\\w{6,30}") private String password; @DecimalMin("1000.00") @DecimalMax("100000.00") @NumberFormat(pattern = "#,###.##") private long salary; @Valid private Address address; private String format; private Telephone telephone; } 这样,password的请求参数就会忽略掉签名而仅对其它请求参数实施签名校验了(也即客户端在签名时不要将password添加到SHA1(<secret><paramName><paramValue><secret>)的参数列表中进行签名了),客户端的调用如下所示: @Test public void testIngoreSignParamOfPassword() { RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); form.add("method", "user.add");//<--指定方法名称 form.add("appKey", "00001"); form.add("v", "1.0"); form.add("sessionId", "mockSessionId1"); form.add("userName", "tomsony"); form.add("salary", "2,500.00"); String sign = RopUtils.sign(new ArrayList<String>( form.keySet()), form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde"); form.add("sign", sign); form.add("password", "123456"); //password无需签名,所以放在最后 String response = restTemplate.postForObject( "http://localhost:8088/router", form, String.class); System.out.println("response:\n" + response); assertTrue(response.indexOf("<createUserResponse createTime=\"20120101010101\" userId=\"1\">") > -1); } 11.服务方法不工作在会话环境下 有些方法在不需要会话的时候也能访问,如下载一张图片,检查软件的最新版本等服务,这时可以通过@ServiceMethod的needInSession来设置: //版本为4.0的user.add:不需要会话 @ServiceMethod(value = "user.add", version = "4.0",needInSession = NeedInSessionType.NO) public RopResponse addUser4(CreateUserRequest request) { CreateUserResponse response = new CreateUserResponse(); //add creaet new user here... response.setCreateTime("20120101010102"); response.setUserId("4"); return response; } 以下测试方法在不提供sessionId系统级参数的情况下调用该服务方法: @Test public void testIngoreSession() { RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); form.add("appKey", "00001"); form.add("method", "user.add");//<--指定方法名称 form.add("v", "4.0"); form.add("userName", "tomsony"); form.add("salary", "2,500.00"); String sign = RopUtils.sign(new ArrayList<String>( form.keySet()), form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde"); form.add("sign", sign); String response = restTemplate.postForObject( "http://localhost:8088/router", form, String.class); System.out.println("response:\n" + response); assertTrue(response.indexOf("userId=\"4\"") > -1); } 12.可自定义类型转换器 <rop:annotation-driven formatting-conversion-service="conversionService"/> <!--设置自定义的类型转换服务,注册自定义的类型转换器--> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <!--将xxxx-yyyyyyy格式的电话转换为内部的Telephone对象--> <bean class="com.rop.sample.request.TelephoneConverter"/> </set> </property> </bean> TelephoneConverter负责将010-010121233这种类型的字符串转换为一个内部的 Telephone对象: public class TelephoneConverter implements Converter<String, Telephone> { @Override public Telephone convert(String source) { if (StringUtils.hasText(source)) { String zoneCode = source.substring(0, source.indexOf("-")); String telephoneCode = source.substring(source.indexOf("-") + 1); Telephone telephone = new Telephone(); telephone.setZoneCode(zoneCode); telephone.setTelephoneCode(telephoneCode); return telephone; } else { return null; } } } Telephone类如下: public class Telephone { private String zoneCode; private String telephoneCode; //... } public class CreateUserRequest extends AbstractRopRequest { @Pattern(regexp = "\\w{4,30}") private String userName; @IgnoreSign @Pattern(regexp = "\\w{6,30}") private String password; @DecimalMin("1000.00") @DecimalMax("100000.00") @NumberFormat(pattern = "#,###.##") private long salary; @Valid private Address address; private String format; private Telephone telephone;//这个属性将自动使用TelephoneConverter进行转换 } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-06-12
ROP越来越好了。
|
|
返回顶楼 | |
发表时间:2012-06-13
Rop确实很给力 开发开放平台容易多了!
|
|
返回顶楼 | |
发表时间:2012-06-13
功能很多,代码也很优雅,扩展起来方便,支持~!
新的功能我还没有看,有空研究一下。 再给点建议: 因为是开放api,真正应用起来会要考虑调用的频率、时限等等因素,希望后面可以加入进来 。 |
|
返回顶楼 | |
发表时间:2012-06-13
EricLiang 写道 功能很多,代码也很优雅,扩展起来方便,支持~!
新的功能我还没有看,有空研究一下。 再给点建议: 因为是开放api,真正应用起来会要考虑调用的频率、时限等等因素,希望后面可以加入进来 。 时限已经有了 可以全局设置,也可以单个服务方法设置 频率这个建议非常好!现在的思路是开出一个接口,让用户自己实现,因为不用服务的调用者频率可以不一样(如高级用户不设限,普通用户 一分钟最多100次等,这些规则是业务化的),不能设死,这个我后面考虑下。 不知EricLiang有没有具体实现上的建议呢? |
|
返回顶楼 | |
发表时间:2012-06-13
ptma 写道 ROP越来越好了。
呵呵,非常大家具体用时碰到问题及时给我提意见,这样才能不断改进,谢谢! |
|
返回顶楼 | |
发表时间:2012-06-13
代码优雅 大师风范 看源码学习了不少东西!
|
|
返回顶楼 | |
发表时间:2012-06-13
stamen 写道 ...... 时限已经有了 可以全局设置,也可以单个服务方法设置 频率这个建议非常好!现在的思路是开出一个接口,让用户自己实现,因为不用服务的调用者频率可以不一样(如高级用户不设限,普通用户 一分钟最多100次等,这些规则是业务化的),不能设死,这个我后面考虑下。 不知EricLiang有没有具体实现上的建议呢? 对API的限制我想的比较复杂。 限制策略我分两种:次数限制和频率限制 限制都是针对具体客户端调用具体API行为。 次数限制是指对客户端对具体API总共调用的次数,已达到次数后无法调用,或者重新申请。 频率限制是指某客户端在指定时间段内的调用次数。 所以限制策略涉及具体客户端、具体API、具体的时间段,还是比较复杂的。 要定一策略规则,还要有记录调用日志。 仅供参考 |
|
返回顶楼 | |
发表时间:2012-06-13
EricLiang 写道 stamen 写道 ...... 时限已经有了 可以全局设置,也可以单个服务方法设置 频率这个建议非常好!现在的思路是开出一个接口,让用户自己实现,因为不用服务的调用者频率可以不一样(如高级用户不设限,普通用户 一分钟最多100次等,这些规则是业务化的),不能设死,这个我后面考虑下。 不知EricLiang有没有具体实现上的建议呢? 对API的限制我想的比较复杂。 限制策略我分两种:次数限制和频率限制 限制都是针对具体客户端调用具体API行为。 次数限制是指对客户端对具体API总共调用的次数,已达到次数后无法调用,或者重新申请。 频率限制是指某客户端在指定时间段内的调用次数。 所以限制策略涉及具体客户端、具体API、具体的时间段,还是比较复杂的。 要定一策略规则,还要有记录调用日志。 仅供参考 非常好!我后面会安排实现一下。 |
|
返回顶楼 | |
发表时间:2012-06-13
最后修改:2012-06-13
ServletRequestContextBuilder 中获取IP方法 request.getRemoteAddr();
在有代理的情况下,获取不到真实的IP。 public static String getRemoteIP(HttpServletRequest request) { String remoteIP = request.getHeader("X-Real-IP"); if (!StringUtils.hasText(remoteIP)) { remoteIP = request.getRemoteAddr(); } return remoteIP; } |
|
返回顶楼 | |