spring boot架構設計——權限驗證及API接口統一返回格式


昨天奮戰了一天才搞定,記錄一下。

 

權限驗證

權限驗證實現需要截取request參數,這個實現很簡單,springboot中可以使用interceptor,Aspect,filter實現.具體實現網上一大把,就懶得寫了,關鍵字搜就是。

通過request獲取到請求參數后,按照自己定義的規則計算出sign值,例如把token+timestamp+邏輯方法參數字典排序后md5+base64位,然后和客戶端傳過來的sign值對比。

API接口統一返回格式

這個才是折騰人的玩意,在net下實現的時候覺得挺簡單的,在spring boot下做就要了老命了。

統一返回格式的目的是把邏輯方法和系統返回格式解耦合。例如API接口返回格式如下:

 

//權限驗證失敗返回
{
    "data": null,
    "code": 1,
    "msg": "服務器權限驗證失敗"
}
//發生未處理異常事件的返回
{
    "data": null,
    "code": -1,
    "msg": "服務器異常,請稍后再試"
}
//請求成功返回
{
    "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。我稱之為系統級返回參數,data里面的是方法級返回參數。處理邏輯是捕獲邏輯方法返回值,然后轉換為標准格式返回給客戶端。

spring boot的問題是interceptor不能獲取到返回值,restAPI方式的請求,ModelAndView這個參數返回的是null,也沒法從response參數里面取出返回值。google百度了一個下午,最后放棄。

然后選擇Aspect方式,利用round注解,很輕松的就拿到了返回值,一切看上去很順利,結果在修改返回值時,報錯了。。。因為產生響應時,Aspect執行順序在servlet前面,即邏輯方法返回model,然后Aspect執行,然后servlet再執行,而servlet默認會根據邏輯方法的返回值來對返回值進行序列化,這時候因為在Aspect中,我已經修改了返回值類型,於是就會出現類型轉換錯誤。Aspect達不到目標,也宣告放棄。

最后看到外網一位網友建議使用filter。filter執行順序在servlet之后,試了下,ok了,能捕獲,能修改。不過還有個問題,因為是在servlet之后執行,而servlet默認是會把邏輯方法的返回值序列化的。。。於是data參數里面裝的就是一個字符串,然后filter返回的時候再序列化一次,data里面的數據格式就慘不忍睹了,客戶端還不罵死啊。夜深人懶,不想再累了,就直接把獲取到的邏輯方法的json字符串反序列化為object,然后裝配到data中,然后再把filter的參數序列化。。。最后接口返回的效果就是目前看的樣子,效率低,達到最低效果要求,日后有空再優化吧。。。。。。下面貼下代碼

過濾器代碼:

/**
 * @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();

        //權限校驗參數
        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("請求參數:\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("服務器權限驗證失敗");
            response.setData(null);
            sb.append("邏輯錯誤:" + "服務器權限驗證失敗: token:" + token +
                    " timestamp:" + timestamp + " path:" + path
                    + " 服務器端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("響應參數: " + 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("服務器異常,請稍后再試");
                    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;
    }

折騰了這么多東西,目的就是徹底簡化邏輯方法代碼難度~定義好接口文檔和路徑,邏輯代碼編寫就可以丟給實習生了,又可以偷懶了~~

 

還有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) {

    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM