这段时间做新的Android项目的client和和REST API通讯框架架构设计。使用了非常多新技术,终于的方案也相当简洁优雅。client仅仅须要传Java对象,server端返回json字符串,自己主动解析成Java对象, 无状态安全验证基于JWT实现,JWT规范的细节能够參考我前面的文章。
JWT的token和数据防篡改签名统一放在HTTP Header中。这样就实现了对请求内容和返回结果的无侵入性,server端也能够在全局过滤器中统一处理安全验证。
Androidclient使用了Volley网络请求框架和Gson解析库。基于这2个Goolge的框架封装了自己的GsonRequest网络请求基类。 网络请求类没有做特别处理,网络response返回消息,做了个带泛型的基类。统一处理错误码,消息和返回结果,Android端的代码例如以下:
package com.zxt.network; /** * HTTP返回基类。返回正确结果status>0,错误结果status<0, * message为server端返回消息,client直接显示 * T data为返回的json对象或数组,自己主动解析为Java对象 * errorCodes为server端返回的Dubbo接口错误码。逗号分隔,仅做调试用途 * Created by zhangxitao on 15/8/17. */ public class BaseResponse<T> { public int status; public String message; public String errorCodes; public T data ; public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getErrorCodes() { return errorCodes; } public void setErrorCodes(String errorCodes) { this.errorCodes = errorCodes; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
package com.zxt.network; import android.text.TextUtils; import android.util.Log; import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.toolbox.HttpHeaderParser; import com.google.gson.Gson; import com.qianmi.qmpos.AppConfig; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; /** * Gson请求类,传入请求对象和返回类名,返回解析好的对象,封装JWT安全验证,数据签名 * @param <T> */ public class GsonRequest<T> extends Request<T> { private static final String TAG = "GsonRequest"; private final Listener<T> mListener; private Gson mGson; private Class<T> mClass; private Object mRequest; private String mBody; public GsonRequest(int method, String url, Object request, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) { super(method, url, errorListener); mGson = new Gson(); mClass = clazz; mListener = listener; mRequest = request; if (null != request) { mBody = mGson.toJson(mRequest); } } public GsonRequest(String url, Object request, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) { this(Method.POST, url, request, clazz, listener, errorListener); Log.d(TAG, "-----url is:" + url); } @Override public byte[] getBody() throws AuthFailureError { if (null == mRequest) { return null; } Log.d(TAG, "-----request json: " + mBody); return mBody.getBytes(); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(mGson.fromJson(jsonString, mClass), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(T response) { mListener.onResponse(response); Log.i(TAG, "-----response:" + new Gson().toJson(response)); } @Override public Map<String, String> getHeaders() throws AuthFailureError { HashMap<String, String> extraHeaders = new HashMap<>(); extraHeaders.put("Content-Type", "application/json"); extraHeaders.put("Accept", "application/json"); //Data Sign if (!TextUtils.isEmpty(mBody)) { extraHeaders.put("Sign", MD5Util.stringToMD5(AppConfig.SIGN_SECRET + mBody + <span style="font-family: Arial, Helvetica, sans-serif;">AppConfig.SIGN_SECRET </span><span style="font-family: Arial, Helvetica, sans-serif;">));</span> } //JWT token if (!TextUtils.isEmpty(AppConfig.TOKEN)) { extraHeaders.put("Authorization", "Bearer " + AppConfig.TOKEN); } Log.d(TAG, "-----extra headers: " + extraHeaders.toString()); return extraHeaders; } }
一个简单的调用样例
LoginRequest loginRequest = new LoginRequest(); loginRequest.setNickName(username); loginRequest.setPassword(RSAUtils.getEncryptPwd(password)); Request request = new GsonRequest<LoginResponse>( AppConfig.SERVER_URL + "/login",loginRequest, LoginResponse.class, new Response.Listener<LoginResponse>() { @Override public void onResponse(LoginResponse resp) { L.d("TAG", "user is " + resp.data); //TODO } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("TAG", error.getMessage(), error); } }); mVolleyQueve.add(request)
server端使用Spring Boot,Spring MVC。 JPA。JWT等技术构建。相同的简洁优雅, Spring Boot微服务技术确实简化了接口的开发,能够做到零XML配置文件。因为临时权限这边不够复杂。临时没有引入Spring Security框架,安全验证通过一个全局的过滤器完毕。API接口通过使用@RestController注解实现。详细的參考代码例如以下:
import com.zxt.domain.BaseResponse; import com.zxt.domain.pos.Device; import com.zxt.service.DeviceService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/device") public class DeviceController { @Autowired DeviceService deviceService; @RequestMapping("activate") @ResponseBody public BaseResponse activate(@RequestBody @Validated Device device) { try { this.deviceService.activateDevice(device); } catch (Exception e) { return new BaseResponse(-1,"activate failed"); } return new BaseResponse(1,"device activated"); } }
Google Volley这个框架除了攻克了频繁网络请求情景的性能问题之外。可定制性也是非常强大的,通过定制我们自己的GsonRequest类,实现了对数据传输无侵入性的JWT安全验证和数据防篡改, 双向自己主动化的JSON解析能力,极大的简化了Android网络App的开发工作。