昨天奮戰(zhàn)了一天才搞定,記錄一下。
權(quán)限驗證 權(quán)限驗證實現(xiàn)需要截取request參數(shù),這個實現(xiàn)很簡單,springboot中可以使用interceptor,Aspect,filter實現(xiàn).具體實現(xiàn)網(wǎng)上一大把,就懶得寫了,關(guān)鍵字搜就是。 通過request獲取到請求參數(shù)后,按照自己定義的規(guī)則計算出sign值,例如把token+timestamp+邏輯方法參數(shù)字典排序后md5+base64位,然后和客戶端傳過來的sign值對比。 API接口統(tǒng)一返回格式 這個才是折騰人的玩意,在net下實現(xiàn)的時候覺得挺簡單的,在spring boot下做就要了老命了。 統(tǒng)一返回格式的目的是把邏輯方法和系統(tǒng)返回格式解耦合。例如API接口返回格式如下:
//權(quán)限驗證失敗返回 { "data": null, "code": 1, "msg": "服務(wù)器權(quán)限驗證失敗" } //發(fā)生未處理異常事件的返回 { "data": null, "code": -1, "msg": "服務(wù)器異常,請稍后再試" } //請求成功返回 { "data": { "id": 1, "userid": "test1", "name": "孫楊", "phone": "183*******", "sourcetype": 1, "loginname": "test1", "loginpwd": "test1", "powertype": 1, "registertime": "2017-10-19" }, "code": 10000, "msg": "" } 這種格式由2部分組成,第一部分就是最外層的data,code,msg。我稱之為系統(tǒng)級返回參數(shù),data里面的是方法級返回參數(shù)。處理邏輯是捕獲邏輯方法返回值,然后轉(zhuǎn)換為標準格式返回給客戶端。 spring boot的問題是interceptor不能獲取到返回值,restAPI方式的請求,ModelAndView這個參數(shù)返回的是null,也沒法從response參數(shù)里面取出返回值。google百度了一個下午,最后放棄。 然后選擇Aspect方式,利用round注解,很輕松的就拿到了返回值,一切看上去很順利,結(jié)果在修改返回值時,報錯了。。。因為產(chǎn)生響應(yīng)時,Aspect執(zhí)行順序在servlet前面,即邏輯方法返回model,然后Aspect執(zhí)行,然后servlet再執(zhí)行,而servlet默認會根據(jù)邏輯方法的返回值來對返回值進行序列化,這時候因為在Aspect中,我已經(jīng)修改了返回值類型,于是就會出現(xiàn)類型轉(zhuǎn)換錯誤。Aspect達不到目標,也宣告放棄。 最后看到外網(wǎng)一位網(wǎng)友建議使用filter。filter執(zhí)行順序在servlet之后,試了下,ok了,能捕獲,能修改。不過還有個問題,因為是在servlet之后執(zhí)行,而servlet默認是會把邏輯方法的返回值序列化的。。。于是data參數(shù)里面裝的就是一個字符串,然后filter返回的時候再序列化一次,data里面的數(shù)據(jù)格式就慘不忍睹了,客戶端還不罵死啊。夜深人懶,不想再累了,就直接把獲取到的邏輯方法的json字符串反序列化為object,然后裝配到data中,然后再把filter的參數(shù)序列化。。。最后接口返回的效果就是目前看的樣子,效率低,達到最低效果要求,日后有空再優(yōu)化吧。。。。。。下面貼下代碼 過濾器代碼: /** * @author 孫楊 * @date Created in 下午6:01 17/10/19 */ @WebFilter(filterName = "全局過濾器", urlPatterns = "/*") public class GlobalFilter implements Filter { private final String JsonArraySign = "["; private final boolean DEBUG = true; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ResponseModel response = new ResponseModel(); LogUtil logger = new LogUtil(); StringBuilder sb = new StringBuilder(); Gson gson = new GsonBuilder().serializeNulls().create(); //權(quán)限校驗參數(shù) String token = ""; String timestamp = ""; String path = ""; String sign = ""; HttpServletRequest request = (HttpServletRequest) servletRequest; String[] paths = request.getRequestURL().toString().split("/api/sx"); if (paths.length > 1) { path = paths[1]; } long startTime = System.currentTimeMillis(); sb.append("\n路徑: " + request.getRequestURL() + "\n"); sb.append("method: " + request.getMethod() + "\n"); sb.append("QueryString: " + request.getQueryString() + "\n"); sb.append("請求參數(shù):\n"); Enumeration<String> paras = request.getParameterNames(); while (paras.hasMoreElements()) { String name = paras.nextElement(); String value = request.getParameter(name); if ("token".equals(name)) { token = value; } if ("timestamp".equals(name)) { timestamp = value; } if ("sign".equals(name)) { sign = value; } sb.append(name + ":" + value + "\n"); } if (!DEBUG && !sign.equals(AuthVerificationUtil.countSign(token, timestamp, path))) { sb.append("方法耗時: " + (System.currentTimeMillis() - startTime) + "毫秒\n"); response.setCode(CodeTable.ACCESSDENIED); response.setMsg("服務(wù)器權(quán)限驗證失敗"); response.setData(null); sb.append("邏輯錯誤:" + "服務(wù)器權(quán)限驗證失敗: token:" + token + " timestamp:" + timestamp + " path:" + path + " 服務(wù)器端sign: " + AuthVerificationUtil.countSign(token, timestamp, path)); logger.error(sb.toString()); } else { MyResponseWrapper responseWrapper = new MyResponseWrapper((HttpServletResponse) servletResponse); try { filterChain.doFilter(servletRequest, responseWrapper); String responseContent = new String(responseWrapper.getDataStream()); //判斷返回值是jsonObject還是jsonArray if (responseContent.startsWith(JsonArraySign)) { response.setData(new JsonParser().parse(responseContent).getAsJsonArray()); } else { response.setData(new JsonParser().parse(responseContent).getAsJsonObject()); } response.setCode(CodeTable.SUCCESS); response.setMsg(""); sb.append("方法耗時: " + (System.currentTimeMillis() - startTime) + "毫秒\n"); sb.append("響應(yīng)參數(shù): " + responseContent); logger.info(sb.toString()); } catch (Exception e) { sb.append("方法耗時: " + (System.currentTimeMillis() - startTime) + "毫秒\n"); if (e instanceof MyException) { response.setCode(((MyException) e).getCode()); response.setMsg(((MyException) e).getMsg()); response.setData(null); sb.append("邏輯錯誤:" + ((MyException) e).getLog()); logger.error(sb.toString()); } else { response.setCode(CodeTable.UNKNOWERROR); response.setMsg("服務(wù)器異常,請稍后再試"); response.setData(null); sb.append("未捕獲異常:" + e.getMessage()); logger.error(sb.toString()); } } } ((HttpServletResponse) servletResponse).setHeader("Content-type", "application/json;charset=UTF-8"); servletResponse.getOutputStream().write(gson.toJson(response).getBytes()); } @Override public void destroy() { } } 邏輯方法類似如下: @RequestMapping("login") public User login(LoginModel loginModel) { User user = userRepository.findByLoginnameAndLoginpwd(loginModel.getLoginname(), loginModel.getLoginpwd()); if (user == null) { throw new MyException(10001, "用戶名或密碼不對"); } return user; } 折騰了這么多東西,目的就是徹底簡化邏輯方法代碼難度~定義好接口文檔和路徑,邏輯代碼編寫就可以丟給實習(xí)生了,又可以偷懶了~~
還有2個輔助類我也貼一下:
public class MyResponseWrapper extends HttpServletResponseWrapper { ByteArrayOutputStream output; FilterServletOutputStream filterOutput; public MyResponseWrapper(HttpServletResponse response) { super(response); output = new ByteArrayOutputStream(); } @Override public ServletOutputStream getOutputStream() throws IOException { if (filterOutput == null) { filterOutput = new FilterServletOutputStream(output); } return filterOutput; } public byte[] getDataStream() { return output.toByteArray(); } } public class FilterServletOutputStream extends ServletOutputStream { DataOutputStream output; public FilterServletOutputStream(OutputStream output) { this.output = new DataOutputStream(output); } @Override public void write(int arg0) throws IOException { output.write(arg0); } @Override public void write(byte[] arg0, int arg1, int arg2) throws IOException { output.write(arg0, arg1, arg2); } @Override public void write(byte[] arg0) throws IOException { output.write(arg0); } @Override public boolean isReady() { return false; } @Override public void setWriteListener(WriteListener writeListener) { } } |
|