1. 程式人生 > >單點登入效能測試方案

單點登入效能測試方案

專案登入系統升級,改為單點登入:英文全稱Single Sign On。SSO是在多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統。 之前有的統一登入方式被廢棄,由於單點登入比較之前的登入系統複雜很多。之前的方案請求一個介面即可獲得使用者校驗令牌。 先分享一下單點登入的技術方案的時序圖:

單點登入時序圖

然後發一下我梳理的前端呼叫介面的時序圖:

單點登入時序圖

效能測試分成了兩個場景: 效能壓測場景分析: 跳過不必要的302響應狀態請求,只測試業務邏輯相關介面,不處理頁面相關介面(資原始檔等),登入完成請求額外介面完成登入驗證。

  • 場景一:單個使用者登入單個系統。 第一步:請求cas服務login頁面,解析頁面獲取祕鑰串(lt/execution) 第二步:請求cas服務登入介面,獲取TGC令牌和ST令牌 第三步:請求svr服務校驗ST令牌,獲取admin_jsessionid資訊 第四步:請求額外介面完成登入狀態驗證

  • 場景二:單個使用者登入兩個系統 第一步:請求cas服務login頁面,解析頁面獲取祕鑰串(lt/execution) 第二步:請求cas服務登入介面,獲取TGC令牌和ST1令牌 第三步:請求svr1服務校驗ST1令牌,獲取admin_jsessionid資訊 第四步:請求額外介面完成登svr1錄狀態驗證 第五步:請求cas服務登入介面(攜帶TGC令牌),獲取svr2對應的ST2令牌 第六步:請求svr2服務校驗校驗ST2令牌,獲取admin_jsessionid資訊 第七步:請求額外介面完成svr2登入狀態校驗

針對這兩個場景,測試指令碼如下:

import com.fun.base.constaint.ThreadBase
import com.fun.config.SqlConstant
import com.fun.frame.excute.Concurrent
import com.fun.utils.Time
import com.okayqa.teacherweb.base.OkayBase
import org.slf4j.Logger
import org.slf4j.LoggerFactory

class Tss extends OkayBase {
    private static Logger logger = LoggerFactory.getLogger(Tss.class)


    public static void main(String[] args) {
        def threadNum = changeStringToInt(args[0])
        def times = changeStringToInt(args[1])
        SqlConstant.flag = false


//        def threadNum = 3
//        def times = 2
        def arrayList = new ArrayList<ThreadBase>()
        for (int i = 0; i < threadNum; i++) {
            def thread = new ThreadBase<Integer>(new Integer(i)) {
                @Override
                protected void before() {

                }

                @Override
                protected void doing() throws Exception {
                    def mark = Time.getTimeStamp()
                    def base = getBase(changeStringToInt(getT()))
//                    def cookies = base.getCookies()
//                    def base1 = new com.okayqa.publicweb.base.OkayBase() //建立public-web專案的使用者物件
//                    base1.init(cookies)//初始化使用者物件
//                    def common = new SchoolCommon(base1)//建立學校公共介面請求物件
//                    def years = common.getYears()//請求學校學年介面
                    def mark0 = Time.getTimeStamp()
                    def i1 = mark0 - mark
                    logger.error("----------------" + i1 + EMPTY)
                }

                @Override
                protected void after() {

                }
            }

            thread.setTimes(times)
            arrayList << thread
        }
        new Concurrent(arrayList).start()
//
        allOver()

    }
}

首先各個專案使用者物件程式碼如下:

package com.okayqa.teacherweb.base;

import com.fun.base.bean.BeanUtil;
import com.fun.base.bean.RequestInfo;
import com.fun.base.interfaces.IBase;
import com.fun.config.HttpClientConstant;
import com.fun.config.SqlConstant;
import com.fun.config.SysInit;
import com.fun.frame.SourceCode;
import com.fun.frame.httpclient.FanLibrary;
import com.okayqa.common.CasCredential;
import com.okayqa.common.Common;
import com.okayqa.common.Users;
import com.okayqa.teacherweb.bean.UserInfoBean;
import com.okayqa.teacherweb.function.UserCenter;
import com.okayqa.teacherweb.profile.Profile;
import com.okayqa.teacherweb.profile.UserApi;
import net.sf.json.JSONObject;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

/**
 * 教師空間專案
 * qa專案base類
 */
public class OkayBase extends SourceCode implements IBase {

    private static Logger logger = LoggerFactory.getLogger(OkayBase.class);

    private static OkayBase base;

    static {
        SqlConstant.REQUEST_TABLE = Common.SQL_REQUEST;
        SqlConstant.flag = Common.SQL_KEY;
        SqlConstant.PERFORMANCE_TABLE = Common.SQL_PERFORMANCE;
        if (FanLibrary.getiBase() == null) FanLibrary.setiBase(new OkayBase());
    }

    public final static String HOST = Profile.HOST;


    /**
     * 登入響應
     */
    JSONObject loginResponse;

    private UserInfoBean userInfoBean = new UserInfoBean();

    /**
     * 獲取物件方法
     * <p>
     * 暫未進行使用者管理,同意使用單例
     * </p>
     *
     * @return
     */
    public static OkayBase getBase() {
        if (base == null) base = new OkayBase(0);
        return base;
    }

    public static OkayBase getBase(int i) {
        return new OkayBase(i);
    }

    public static OkayBase getBase(String name) {
        return new OkayBase(name);
    }

    long uid;

    String token;

    String username;

    public JSONObject getCookies() {
        return cookies;
    }

    public void setCookies(JSONObject cookies) {
        this.cookies = cookies;
    }

    public void addCookie(JSONObject cookies) {
        this.cookies.putAll(cookies);
    }

    JSONObject cookies = new JSONObject();


    @Override
    public void login() {
//        /**
//         * 單點登入方式
        String url = UserApi.LOGIN;
        JSONObject params = new JSONObject();
        params.put("loginType", "1");
        params.put("platformType", "teacher");
        params.put("username", username);
        params.put("password", getPassword());
        params.put("pictureVerifyCode", "");
        params.put("phone", "");
        params.put("traceno", "");
        params.put("phoneVerifyCode", "");
        JSONObject tgc = CasCredential.getTGC(HOST, params);
        this.cookies = tgc.getJSONObject("cookie");
        String location = tgc.containsKey("location") ? tgc.getString("location") : EMPTY;
        if (!location.contains("ticket=ST-")) logger.error("登入失敗!");
        JSONObject getResponse = this.getGetResponse(location.replace(HOST, EMPTY));
        UserCenter userCenter = new UserCenter(this.cookies);
        userInfoBean = userCenter.getUserinfo();
        logger.info("賬號:{},暱稱:{},學科名稱:{},登入成功!", username,userInfoBean.getName(),userInfoBean.getSubjectName());
    }

    /**
     * 獲取到明文的預設密碼
     *
     * @return
     */
    public String getPassword() {
        return Profile.PWD;
    }

    public OkayBase(String username) {
        this.username = username;
        login();
    }

    public OkayBase(int i) {
        this.username = Users.getTeaUser(i);
        login();
    }

    protected OkayBase() {
    }

    public OkayBase(OkayBase okayBase) {
        this.uid = okayBase.uid;
        this.username = okayBase.username;
        this.token = okayBase.token;
        this.userInfoBean = okayBase.userInfoBean;
        this.cookies = okayBase.cookies;
    }

    public JSONObject getParams() {
        return getJson("_=" + getMark());
    }


    @Override
    public void init(JSONObject jsonObject) {
        addCookie(jsonObject);
        HttpGet get = FanLibrary.getHttpGet(Profile.LOGIN_REDIRECT);
        get.addHeader(FanLibrary.getCookies(jsonObject));
        JSONObject response = FanLibrary.getHttpResponse(get);
        JSONObject credential = CasCredential.verifyST(response.getString("location"));
        addCookie(credential);
    }

    public JSONObject getLoginResponse() {
        return loginResponse;
    }

    public long getUid() {
        return uid;
    }

    public String getToken() {
        return token;
    }

    public String getUname() {
        return username;
    }

    public UserInfoBean getUserInfoBean() {
        return userInfoBean;
    }

    @Override
    public HttpGet getGet(String s) {
        return FanLibrary.getHttpGet(HOST + s);
    }

    @Override
    public HttpGet getGet(String s, JSONObject jsonObject) {
        return FanLibrary.getHttpGet(HOST + s, jsonObject);
    }

    @Override
    public HttpPost getPost(String s) {
        return FanLibrary.getHttpPost(HOST + s);
    }

    @Override
    public HttpPost getPost(String s, JSONObject jsonObject) {
        return FanLibrary.getHttpPost(HOST + s, jsonObject);
    }

    @Override
    public HttpPost getPost(String s, JSONObject jsonObject, File file) {
        return FanLibrary.getHttpPost(HOST + s, jsonObject, file);
    }

    @Override
    public JSONObject getResponse(HttpRequestBase httpRequestBase) {
        setHeaders(httpRequestBase);
        JSONObject response = FanLibrary.getHttpResponse(httpRequestBase);
        handleResponseHeader(response);
        return response;
    }

    @Override
    public void setHeaders(HttpRequestBase httpRequestBase) {
        httpRequestBase.addHeader(Common.REQUEST_ID);
        this.addCookie(getJson("user_phone_check_" + this.username + "=true"));
        if (!cookies.isEmpty()) httpRequestBase.addHeader(FanLibrary.getCookies(cookies));
    }

    @Override
    public void handleResponseHeader(JSONObject response) {
        if (!response.containsKey(HttpClientConstant.COOKIE)) return;
        cookies.putAll(response.getJSONObject(HttpClientConstant.COOKIE));
        response.remove(HttpClientConstant.COOKIE);
    }

    @Override
    public JSONObject getGetResponse(String s) {
        return getResponse(getGet(s));
    }

    @Override
    public JSONObject getGetResponse(String s, JSONObject jsonObject) {
        return getResponse(getGet(s, jsonObject));
    }

    @Override
    public JSONObject getPostResponse(String s) {
        return getResponse(getPost(s));
    }

    @Override
    public JSONObject getPostResponse(String s, JSONObject jsonObject) {
        return getResponse(getPost(s, jsonObject));
    }

    @Override
    public JSONObject getPostResponse(String s, JSONObject jsonObject, File file) {
        return getResponse(getPost(s, jsonObject, file));
    }

    @Override
    public boolean isRight(JSONObject jsonObject) {
        if (jsonObject.containsKey("success")) return jsonObject.getBoolean("success");
        int code = checkCode(jsonObject, new RequestInfo(getGet(HOST)));
        try {
            JSONObject data = jsonObject.getJSONObject("data");
            return code == 0 && !data.isEmpty();
        } catch (Exception e) {
            output(jsonObject);
            return false;
        }

    }

    /**
     * 獲取並檢查code
     *
     * @param jsonObject
     * @return
     */
    public int checkCode(JSONObject jsonObject, RequestInfo requestInfo) {
        int code = TEST_ERROR_CODE;
        if (SysInit.isBlack(requestInfo.getHost())) return code;
        try {
            code = jsonObject.getInt("code");
        } catch (Exception e) {
            logger.warn("非標準響應:{}", jsonObject.toString());
        }
        return code;
    }

    /**
     * 測試結束,資源釋放
     */
    public static void allOver() {
        FanLibrary.testOver();
    }

}

統一驗證類的程式碼如下:

package com.okayqa.common

import com.fun.config.HttpClientConstant
import com.fun.frame.httpclient.FanLibrary
import com.fun.utils.Regex
import net.sf.json.JSONObject
import org.apache.http.client.methods.HttpGet
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
 * cas服務驗證類,主要解決web端登入驗證功能
 */
class CasCredential extends FanLibrary {
    static final String OR="/"
    private static Logger logger = LoggerFactory.getLogger(CasCredential.class)
    /**
     * 校驗值,隨機一次性,從login返回頁面中獲取
     */
    String lt
    /**
     * 校驗值,隨機一次性,從login返回頁面中獲取,正常值長度在4000+,低於4000請檢查請求連線是否傳入了回撥服務的地址
     */
    String execution

/**
 * 從cas服務的login頁面獲取到令牌對,此處正則暫時可用,二期會修改表單提交
 */
    CasCredential(String host) {
        def get = getHttpGet(Common.CAS_LOGIN + (host.endsWith(OR) ? host : host + OR))
        get.addHeader(Common.REQUEST_ID)
        def response = getHttpResponse(get)
        def string = response.getString("content")
        this.lt = Regex.getRegex(string, "<input type=\"hidden\" name=\"lt\" value=\".*?\" />")
        this.execution = Regex.getRegex(string, " <input type=\"hidden\" name=\"execution\" value=\".*?\" />")
//        logger.info("cas服務登入host:{},lt:{},execution:{}", host, lt, execution)
    }

/**
 * 各個服務端引數一致,由各個服務自己把引數拼好之後傳過來,之後在去cas服務拿到令牌對
 * @param host 服務的host地址,回撥由各個服務自己完成,二次驗證也是,此處的host不做相容,有cascredential做處理
 * @param params 拼好的引數
 * @return
 */
    static JSONObject getTGC(String host, JSONObject params) {
        def credential = new CasCredential(host)
        params.put("lt", credential.getLt());
        params.put("execution", credential.getExecution())
        params.put("_eventId", "submit");
        def post = FanLibrary.getHttpPost(Common.CAS_LOGIN + (host.endsWith(OR) ? host : host + OR), params)
        post.addHeader(Common.REQUEST_ID);
        FanLibrary.getHttpResponse(post)
    }

/**
 * 通過使用者
 * @param url
 * @return
 */
    public static JSONObject verifyST(String url) {
        HttpGet location = FanLibrary.getHttpGet(url);
        location.addHeader(Common.REQUEST_ID);
        JSONObject httpResponse = FanLibrary.getHttpResponse(location);
        httpResponse.getJSONObject(HttpClientConstant.COOKIE) as JSONObject
    }
}

然後順利完工。因為之前效能測試方案都是使用jmeter作為解決方案,這次架構變更的測試用例難以實現,故才用了指令碼。效能框架才用了之前發過的效能測試框架有興趣的可以點選檢視一下,語言以Java為主,指令碼使用Groovy寫的。

技術類文章精選

非技術文章精選

大咖風采

點選檢