基于Volley和Gson的http网络请求设计(客户端和服务端)
2015-01-14 13:35
585 查看
经过对项目中http网络请求的多次修改,也算是一个很好的封装了,这个过程中查找了许多资料,发现网上零零散散的介绍比较多,并没有一个系统的例子讲解,这里给大家分享一下我的设计。
Volley和Gson都是Google为Android平台量身打造的框架,Volley是一个异步访问框架,Gson是对Json数据处理的框架,这里就不介绍了,假设大家已经知道怎么使用这两个框架了(这两个框架的用法都是很简单的)
客户端:Volley、Gson
服务端:SpringMVC
首先介绍数据封装对象ClientResponse<T>,每次我们请求服务端都会返回这么一个对象,它封装了返回的数据。
statusCode:状态码——用来表示服务端返回的请求状态(成功、失败、超时、错误等)
message:描述——对响应的描述
data:返回的数据——返回的数据,它是一个泛型T类型
accessory:附带值——这个值不是必须的,需要传输多个值的时候才用上
下面是一个协议命令字定义类Protocol
--------------------------------------------------------------------客户端--------------------------------------------------------------------------
Volley框架给我们提供了很好的拓展,让我们可以根据自己的想法定义Request请求,这里定义一个GsonRequest
现在对这个类中几个关键的部分进行解析,从上到下:
param是存放请求参数的Map;
mType是Gson中的类,用于解析Json数据时,指定解析类型
接着是几种构造方法的重载
Http是无状态的协议,程序需要在Session保持状态,由于我们后台的使用Java实现的,保持session是根据解析请求头中的SessionId来判断身份,
所以我们每次请求都需要带上这个SessionId,至于SessionId如何获取的等到了相应代码再说明。
这里return的header将会设置成http请求的Header,在这里我们加上之前保存好SessionId的header,看下SessionUtil类
到了最关键的响应解析,当服务端返回响应后,会回调parseNetworkResponse(NetworkResponse response)方法,方法参数中的response中的data就是服务费返回的二进制格式数据
1.首先从response.headers中解析得到SessionId,这就是我们上面提到的;
2.将response.data生成Json字符串;
3.Gson的解析,clientResponse = new Gson().fromJson(parsed, mType);这里的mType是构造方法中传入的,会根据Type类型把json字符串转换成对应对象.
4.对错误情况进行处理;
5.return后会回调对应的成功或者失败的监听器。
以上就是GsonRequest的整个定义,接下来我们看怎么使用它发起并处理请求,以登录为例子
在创建GsonRequest的时候指定泛型对象UserDto,
new TypeToken<ClientResponse<UserDto>>(){}.getType(),指定响应的类型,这样在监听器Listener的onRequest方法中的返回值便是我们指定的这个类型,
参数中的“cmd”是请求的命令字,这是我们业务定义好的,服务端根据这个命令字区分不同的方法请求
由于自己的泛型设计能力不好,这里的GsonRequest<UserDto>和new TypeToken<ClientResponse<UserDto>>(){}.getType()有点重复的意思,暂时找不到更好的方法,先凑合着用,如果有更好的方法请告诉我。
以上就是客户端的完整设计
--------------------------------------------------------------------服务端--------------------------------------------------------------------------
服务端的Controller层是基于SpringMVC实现的,换成其他框架思想也是一样的。
服务端也需要开始的时候提到的两个ClientResponse<T>和Protocol
服务端的方法入口是唯一,根据请求的命令字分发request
首先配置拦截器
接下来看看MobileAccessInterceptor.java
接下来是程序的入口controller
这里在handleRequest方法包含try_catch代码块,当catch到exception后则返回错误信息的ClientResponse,handlerRequest根据CMD命令字对请求进行分发,在这里根据业务调用对应的Service处理方法,
每个service方法返回的时候都必须构造一个ClientResponse对象,并用Gson( ).toJson方法序列化成json 字符串,最终返回到客户端处理。
以上就是整个Http请求的流程,有什么需要改正的地方敬请提出。
Volley和Gson都是Google为Android平台量身打造的框架,Volley是一个异步访问框架,Gson是对Json数据处理的框架,这里就不介绍了,假设大家已经知道怎么使用这两个框架了(这两个框架的用法都是很简单的)
客户端:Volley、Gson
服务端:SpringMVC
首先介绍数据封装对象ClientResponse<T>,每次我们请求服务端都会返回这么一个对象,它封装了返回的数据。
package com.runsdata.tower.android.message; import java.io.Serializable; /** * 服务端返回的Json响应 * * @author leo.lai * */ public class ClientResponse<T> implements Serializable { private static final long serialVersionUID = 1L; /** * 状态码 */ private int statusCode; /** * 描述 */ private String message; /** * 返回的数据 */ private T data; /** * 附带值 */ private Object accessory; }它由四个字段组成
statusCode:状态码——用来表示服务端返回的请求状态(成功、失败、超时、错误等)
message:描述——对响应的描述
data:返回的数据——返回的数据,它是一个泛型T类型
accessory:附带值——这个值不是必须的,需要传输多个值的时候才用上
下面是一个协议命令字定义类Protocol
public class Protocol { // 请求成功状态码 public final static int TOWER_CODE_STATUS_SUCCESS = 0; // 通用的或未知的错误 public final static int TOWER_CODE_STATUS_UNKNOWN_ERROR = 0x8000; // session超时 public final static int TOWER_CODE_STATUS_SESSION_OVERTIME = 0x8001; // 未找到(期望存在的)记录 public final static int TOWER_CODE_STATUS_DATA_NOT_FOUND = 0x8002; // 无数据 public final static int TOWER_CODE_STATUS_NOTHING = 0x8003; // 参数错误 public final static int TOWER_CODE_STATUS_PARAMETER_ERROR = 0x8004; // 数据库操作时发生未知错误 public final static int TOWER_CMD_REQUEST_DATABASE_UNKNOWN_ERROR = 0x81001; // 数据库插入数据时发生错误 public final static int TOWER_CMD_REQUEST_DATABASE_INSERT_ERROR = 0x8101; }
这个类可以不用关注,由不同业务定义的不同状态常量,上面ClientResponse中的statusCode就是这里定义的。
--------------------------------------------------------------------客户端--------------------------------------------------------------------------
Volley框架给我们提供了很好的拓展,让我们可以根据自己的想法定义Request请求,这里定义一个GsonRequest
package com.runsdata.tower.android.volley.gson; import java.io.UnsupportedEncodingException; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import android.util.Log; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.runsdata.tower.android.listener.HttpServiceListener; import com.runsdata.tower.android.message.ClientResponse; import com.runsdata.tower.android.message.Protocol; import com.runsdata.tower.android.utils.SessionUtil; import com.runsdata.tower.android.volley.AuthFailureError; import com.runsdata.tower.android.volley.NetworkResponse; import com.runsdata.tower.android.volley.Request; import com.runsdata.tower.android.volley.Response; import com.runsdata.tower.android.volley.Response.ErrorListener; import com.runsdata.tower.android.volley.Response.Listener; import com.runsdata.tower.android.volley.VolleyError; import com.runsdata.tower.android.volley.toolbox.HttpHeaderParser; /** * 对ClientResponse请求进行了封装, 保持了sessionId,同时对请求错误做了统一处理 */ public class GsonRequest<T> extends Request<ClientResponse<T>> { public static final String TAG = "GsonRequest"; //响应监听器 private final Listener<ClientResponse<T>> mListener; //请求参数Map private final Map<String, String> params = new HashMap<String, String>(); //返回数据的类型 private Type mType; /** * 只处理成功的回调 * @param url * @param type * @param listener */ public GsonRequest(String url, Type type, Listener<ClientResponse<T>> listener) { this(url, listener, null); this.mType = type; } /** * 对成功和失败的回调 * @param url * @param type * @param listener */ public GsonRequest(String url, Type type, CompletedListener<T> listener) { this(url, listener, listener); this.mType = type; } public GsonRequest(String url, Type type, HttpServiceListener<T> listener) { this(url, listener, listener); this.mType = type; } private GsonRequest(int method, String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } private GsonRequest(String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) { this(Method.POST, url, listener, errorListener); } @Override protected void deliverResponse(ClientResponse<T> response) { mListener.onResponse(response); } /** * 解析返回响应 */ @Override protected Response<ClientResponse<T>> parseNetworkResponse(NetworkResponse response) { SessionUtil.parserSessionIdFromCookie(response.headers); String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } ClientResponse<T> clientResponse = null; try { clientResponse = new Gson().fromJson(parsed, mType); } catch (JsonSyntaxException e) { e.printStackTrace(); return Response.error(new VolleyError("数据解析出错")); } /** * 错误处理 */ VolleyError error = processError(clientResponse); if (error != null) { return Response.error(error); } else { return Response.success(clientResponse, HttpHeaderParser.parseCacheHeaders(response)); } } /** * 错误处理 * * @param clientResponse */ private VolleyError processError(ClientResponse<T> clientResponse) { String errorDesc = null; if(clientResponse == null){ return new VolleyError("服务器无响应"); } if (clientResponse.getStatusCode() == Protocol.TOWER_CODE_STATUS_SUCCESS) { // 响应成功 return null; } else { int statusCode = clientResponse.getStatusCode(); switch (statusCode) { case Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR: errorDesc = "服务端异常"; break; case Protocol.TOWER_CODE_STATUS_SESSION_OVERTIME: errorDesc = "登录超时"; UITools.sendSessionOvertimeBroadcast(); break; case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_NOT_FOUND: errorDesc = "用户不存在"; break; case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_DENY: errorDesc = "用户状态异常不允许登录"; break; case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_INVALID_PASSWORD: errorDesc = "密码错误"; break; default: errorDesc = "访问网络发生错误"; break; } } Log.i(TAG, errorDesc); UITools.showTip(errorDesc); return new VolleyError(errorDesc); } /** * 设置请求的Header */ @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = new HashMap<String, String>(); SessionUtil.addSessionCookie(headers); return headers; } /** * 得到请求参数 */ @Override protected Map<String, String> getParams() throws AuthFailureError { return params; } /** * 设置参数 * * @param name * @param value */ public void setParam(String name, String value) { params.put(name, value); } /** * 设置多个参数 * @param paramMap */ public void setParam(Map<String, String> paramMap) { params.putAll(paramMap); } /** * 去除某个参数 * @param key */ public void removeParam(String key){ params.remove(key); } }
现在对这个类中几个关键的部分进行解析,从上到下:
//响应监听器 private final Listener<ClientResponse<T>> mListener; //请求参数Map private final Map<String, String> params = new HashMap<String, String>(); //返回数据的类型 private Type mType;mListener是响应成功的监听器,由构造方法传入或在构造方法中设置;
param是存放请求参数的Map;
mType是Gson中的类,用于解析Json数据时,指定解析类型
接着是几种构造方法的重载
/** * 只处理成功的回调 * @param url * @param type * @param listener */ public GsonRequest(String url, Type type, Listener<ClientResponse<T>> listener) { this(url, listener, null); this.mType = type; } /** * 对成功和失败的回调 * @param url * @param type * @param listener */ public GsonRequest(String url, Type type, CompletedListener<T> listener) { this(url, listener, listener); this.mType = type; } public GsonRequest(String url, Type type, HttpServiceListener<T> listener) { this(url, listener, listener); this.mType = type; } private GsonRequest(int method, String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } private GsonRequest(String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) { this(Method.POST, url, listener, errorListener); }主要是用到前几个带Type参数的。
Http是无状态的协议,程序需要在Session保持状态,由于我们后台的使用Java实现的,保持session是根据解析请求头中的SessionId来判断身份,
所以我们每次请求都需要带上这个SessionId,至于SessionId如何获取的等到了相应代码再说明。
/** * 设置请求的Header */ @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = new HashMap<String, String>(); SessionUtil.addSessionCookie(headers); return headers; }
这里return的header将会设置成http请求的Header,在这里我们加上之前保存好SessionId的header,看下SessionUtil类
/** * Session保持工具类 * @author leo.lai * */ public class SessionUtil { public static final String SET_COOKIE_KEY = "Set-Cookie"; public static final String COOKIE_KEY = "Cookie"; public static final String SESSION_COOKIE = "JSESSIONID"; /** * 当前保持的sessionId */ public static String SESSIONID; /** * 从Cookie中得到SessionId * @param headers */ public static void parserSessionIdFromCookie(Map<String, String> headers) { if (headers.containsKey(SET_COOKIE_KEY) && headers.get(SET_COOKIE_KEY).startsWith(SESSION_COOKIE)) { String cookie = headers.get(SET_COOKIE_KEY); if (cookie.length() > 0) { String[] splitCookie = cookie.split(";"); String[] splitSessionId = splitCookie[0].split("="); SESSIONID = splitSessionId[1]; } } } /** * 给请求带上SessionId * @param headers */ public static void addSessionCookie(Map<String, String> headers) { if(SESSIONID == null) return; if (SESSIONID.length() > 0) { StringBuilder builder = new StringBuilder(); builder.append(SESSION_COOKIE); builder.append("="); builder.append(SESSIONID); if (headers.containsKey(COOKIE_KEY)) { builder.append("; "); builder.append(headers.get(COOKIE_KEY)); } headers.put(COOKIE_KEY, builder.toString()); } } /** * 得到设置好sessionId的header * @return */ public static Header getSessionHeader(){ Header header = new BasicHeader(COOKIE_KEY, "JSESSIONID=" + SESSIONID); return header; } /** * 得到sessionId * @return */ public static String getSessionId(){ return "JSESSIONID=" + SESSIONID; } }这里主要是字符串的解析过程,组装成特定格式的Header
到了最关键的响应解析,当服务端返回响应后,会回调parseNetworkResponse(NetworkResponse response)方法,方法参数中的response中的data就是服务费返回的二进制格式数据
/** * 解析返回响应 */ @Override protected Response<ClientResponse<T>> parseNetworkResponse(NetworkResponse response) { SessionUtil.parserSessionIdFromCookie(response.headers); String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } ClientResponse<T> clientResponse = null; try { clientResponse = new Gson().fromJson(parsed, mType); } catch (JsonSyntaxException e) { e.printStackTrace(); return Response.error(new VolleyError("数据解析出错")); } /** * 错误处理 */ VolleyError error = processError(clientResponse); if (error != null) { return Response.error(error); } else { return Response.success(clientResponse, HttpHeaderParser.parseCacheHeaders(response)); } }
1.首先从response.headers中解析得到SessionId,这就是我们上面提到的;
2.将response.data生成Json字符串;
3.Gson的解析,clientResponse = new Gson().fromJson(parsed, mType);这里的mType是构造方法中传入的,会根据Type类型把json字符串转换成对应对象.
4.对错误情况进行处理;
5.return后会回调对应的成功或者失败的监听器。
/** * 错误处理 * * @param clientResponse */ private VolleyError processError(ClientResponse<T> clientResponse) { String errorDesc = null; if(clientResponse == null){ return new VolleyError("服务器无响应"); } if (clientResponse.getStatusCode() == Protocol.TOWER_CODE_STATUS_SUCCESS) { // 响应成功 return null; } else { int statusCode = clientResponse.getStatusCode(); switch (statusCode) { case Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR: errorDesc = "服务端异常"; break; case Protocol.TOWER_CODE_STATUS_SESSION_OVERTIME: errorDesc = "登录超时"; UITools.sendSessionOvertimeBroadcast(); break; case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_NOT_FOUND: errorDesc = "用户不存在"; break; case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_DENY: errorDesc = "用户状态异常不允许登录"; break; case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_INVALID_PASSWORD: errorDesc = "密码错误"; break; default: errorDesc = "访问网络发生错误"; break; } } Log.i(TAG, errorDesc); UITools.showTip(errorDesc); return new VolleyError(errorDesc); }根据在Protocal中定义的状态对错误状态进行处理。
以上就是GsonRequest的整个定义,接下来我们看怎么使用它发起并处理请求,以登录为例子
GsonRequest<UserDto> loginRequest = new GsonRequest<UserDto>(Configuration.SERVER_ACCESS, new TypeToken<ClientResponse<UserDto>>(){}.getType(), new CompletedListener<UserDto>() { @Override public void onResponse(ClientResponse<UserDto> response) { processAfterLogin(response); } @Override public void onErrorResponse(VolleyError error) { // Toast.makeText(mContext,error.getMessage(),Toast.LENGTH_SHORT).show(); } } ); loginRequest.setParam("username", username); loginRequest.setParam("password", passwd); loginRequest.setParam("cmd", Protocol.TOWER_CMD_REQUEST_USER_LOGIN + ""); executeRequest(loginRequest);
在创建GsonRequest的时候指定泛型对象UserDto,
new TypeToken<ClientResponse<UserDto>>(){}.getType(),指定响应的类型,这样在监听器Listener的onRequest方法中的返回值便是我们指定的这个类型,
参数中的“cmd”是请求的命令字,这是我们业务定义好的,服务端根据这个命令字区分不同的方法请求
由于自己的泛型设计能力不好,这里的GsonRequest<UserDto>和new TypeToken<ClientResponse<UserDto>>(){}.getType()有点重复的意思,暂时找不到更好的方法,先凑合着用,如果有更好的方法请告诉我。
以上就是客户端的完整设计
--------------------------------------------------------------------服务端--------------------------------------------------------------------------
服务端的Controller层是基于SpringMVC实现的,换成其他框架思想也是一样的。
服务端也需要开始的时候提到的两个ClientResponse<T>和Protocol
服务端的方法入口是唯一,根据请求的命令字分发request
首先配置拦截器
<!-- 拦截器 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/platform/tower/client/facade.ht" /> <mvc:mapping path="/platform/tower/client/test.ht" /> <bean class="com.hotent.mobile.interceptor.MobileAccessInterceptor"> <!-- 配置放行的CMD --> <property name="excludeCMD"> <list> <!-- 用户登录 --> <value>0x0101</value> <!-- 版本检测 --> <value>0x0201</value> </list> </property> </bean> </mvc:interceptor> </mvc:interceptors>excludeCMD为放行的命令字(用户登录、版本检查),是不需要检测用户是否在线的。
接下来看看MobileAccessInterceptor.java
/** * Session判断拦截器 * * @author leo.lai * */ public class MobileAccessInterceptor extends HandlerInterceptorAdapter { private static final Logger logger = Logger .getLogger(MobileAccessInterceptor.class); private List<Short> excludeCMD; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String cmd = request.getParameter("cmd"); if (cmd == null) { ClientResponse clientResponse = new ClientResponse(); clientResponse.setStatusCode(Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR); clientResponse.setMessage("请求命令字为空"); writeJson(response,clientResponse); } // 1.判断命令字是否放行 if (excludeCMD.contains(Short.valueOf(cmd))) { return true; } HttpSession session = request.getSession(); // 2.判断Session是否超时 SysUser sysUser = (SysUser) session.getAttribute("currentUser"); if (sysUser != null) { // 设置后续用到的变量 ContextUtil.setCurrentUser(sysUser); return true; } else { // 超时处理 logger.info("session超时,id = " + session.getId()); ClientResponse clientResponse = new ClientResponse(); clientResponse.setStatusCode(Protocol.TOWER_CODE_STATUS_SESSION_OVERTIME); clientResponse.setMessage("Session超时"); writeJson(response,clientResponse); return false; } } /** * 返回json * @param response * @param clientResponse */ public void writeJson(HttpServletResponse response,ClientResponse clientResponse) { response.setContentType("text/json"); response.setCharacterEncoding("UTF-8"); try { response.getWriter().write(new Gson().toJson(clientResponse)); } catch (IOException e) { e.printStackTrace(); } } public List<Short> getExcludeCMD() { return excludeCMD; } public void setExcludeCMD(List<Short> excludeCMD) { this.excludeCMD = excludeCMD; } }先判断命令字是否放行,接着从session中获得user对象判断是否为空(当然可以根据其他超时条件判断),如果超时则直接返回超时的ClientResponse对象;
接下来是程序的入口controller
/** * 手机客户端访问入口 * @author leo.lai * */ @Controller @RequestMapping("/platform/tower/client") public class ClientFacadeController{ private static final Logger logger = Logger.getLogger(ClientFacadeController.class); @Resource private ClientUserService userService; /** * 请求命令字 * @param request * @param session * @param cmd 请求命令字 * @return */ @RequestMapping(value = "facade",produces="application/json;charset=UTF-8") @ResponseBody public String facade(HttpServletRequest request,HttpSession session,int cmd){ String result = null; try{ result = handleReuqest(request, session, cmd); }catch(Exception e){ e.printStackTrace(); ClientResponse response = new ClientResponse(); response.setStatusCode(Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR); response.setMessage(e.getMessage()); result = new Gson().toJson(response); } logger.info("[========Response content========] " + result); return result; } /** * 请求处理 * @param request * @param session * @param cmd * @return * @throws Exception */ public String handleReuqest(HttpServletRequest request,HttpSession session,int cmd) throws Exception{ String result = null; switch (cmd) { case Protocol.TOWER_CMD_REQUEST_USER_LOGIN: result = userService.login(request, session); break; case Protocol.TOWER_CMD_REQUEST_SETTING_LATEST_VERSION: result = versionControlService.getLatestVersion(request); break; <span style="white-space:pre"> </span>//等等。。。。。 case Protocol.TOWER_CMD_REQUEST_GET_PROCESSES: result = myWorkService.getMyProcessCopyList(request); break; default: break; } return result; }
这里在handleRequest方法包含try_catch代码块,当catch到exception后则返回错误信息的ClientResponse,handlerRequest根据CMD命令字对请求进行分发,在这里根据业务调用对应的Service处理方法,
每个service方法返回的时候都必须构造一个ClientResponse对象,并用Gson( ).toJson方法序列化成json 字符串,最终返回到客户端处理。
以上就是整个Http请求的流程,有什么需要改正的地方敬请提出。
相关文章推荐
- [置顶] 优雅设计封装基于Okhttp3的网络框架(完):原生HttpUrlConnction请求、多线程分发 及 数据转换
- 本篇从基于TCP/IP协议出发,探讨现代流行的应对高并发请求网络服务端设计架构
- Volley—轻量级HTTP客户端网络请求框架
- Android---网络交互之客户端请求服务端资源
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- 很好的设计-服务端网络请求处理模型
- Android Asynchronous Http Client-Android异步网络请求客户端接口(转)
- Android-网络交互之客户端请求服务端资源-stoneson的专栏-  -  棒槌网@Android开发论坛 - Powered by phpwind
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- Android Asynchronous Http Client-Android异步网络请求客户端接口 .
- 基于HTTP缓存轻松实现客户端应用的离线支持及网络优化
- 利用android-async-http-1.4.4.jar异步网络请求客户端
- http客户端请求及服务端详解
- 基于Hadoop开发网络云盘系统客户端界面设计初稿
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- Android Asynchronous Http Client-Android异步网络请求客户端接口
- 基于Hadoop开发网络云盘系统客户端界面设计初稿
- 基于Hadoop开发网络云盘系统客户端界面设计初稿
- Android客户端采用Http 协议Post方式请求与服务端进行数据交互
- Spring HttpInvoker 服务端安全验证的和客户端请求配置