小說系統原始碼開發,如何優雅的實現對外介面?

雲豹科技程式設計師發表於2021-11-30

在小說系統原始碼的開發中,我們需要實現的對外介面還是很多的,像支付介面、單方登入介面等,而如何優雅的實現這些對外介面呢,需要我們考慮的問題還是有很多的,接下來我們一起了解一下。

做介面需要考慮的問題

什麼是介面

介面無非就是小說系統原始碼客戶端請求你的介面地址,並傳入一堆該介面定義好的引數,通過介面自身的邏輯處理,返回介面約定好的資料以及相應的資料格式。

介面怎麼開發

介面由於本身的性質,由於和合作方對接資料,所以有以下幾點需要在開發的時候注意:

1、定義介面入參:寫好介面文件
2、定義介面返回資料型別:一般都需要封裝成一定格式,確定返回json還是xml報文等
在這裡插入圖片描述

見如下返回資料定義格式:

package com.caiex.vb.model;
 import java.io.Serializable;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlType;
 @XmlAccessorType(XmlAccessType.FIELD)@XmlType(name = "Result", propOrder = { "resultCode", "resultMsg" })public class Result implements Serializable {
	private static final long serialVersionUID = 10L;
	protected int resultCode;
	protected String resultMsg;
 
	public int getResultCode() {
		return this.resultCode;
	}
 
	public void setResultCode(int value) {
		this.resultCode = value;
	}
 
	public String getResultMsg() {
		return this.resultMsg;
	}
 
	public void setResultMsg(String value) {
		this.resultMsg = value;
	}}
package com.caiex.vb.model;
 import java.io.Serializable;
 public class Response implements Serializable {
 
	private static final long serialVersionUID = 2360867989280235575L;
 
	private Result result;
	
	private Object data;
 
	public Result getResult() {
		if (this.result == null) {
			this.result = new Result();
		}
		return result;
	}
 
	public void setResult(Result result) {
		this.result = result;
	}
 
	public Object getData() {
		return data;
	}
 
	public void setData(Object data) {
		this.data = data;
	}
 }

3、確定小說系統原始碼訪問介面的方式,get or post等等,可以根據restful介面定義規則RESTful API:RESTful API

4、定義一套小說系統原始碼全域性統一併通用的返回碼,以幫助排查問題;

 reponse code	public static int NO_AGENT_RATE = 1119;  //未找到兌換率
	
	public static int SCHEME_COMMIT_FAIL = 4000;  //方案提交失敗
	
	public static int SCHEME_CONFIRMATION = 4001;  //方案確認中
	
	public static int SCHEME_NOT_EXIST = 4002;  //方案不存在
	
	public static int SCHEME_CANCEL= 4005;  //方案不存在
	
	//。。。。

5、統一的異常處理:應該每個小說系統原始碼都需要一套統一的異常處理

package com.caiex.vb.interceptor;
 import javax.servlet.http.HttpServletRequest;
 import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;
 import com.caiex.vb.model.Response;
 @ControllerAdvice
@ResponseBodypublic class GlobalExceptionHandler {
	
	private  Logger  logger = LoggerFactory.getLogger(this.getClass()); 
 
    /**
     * 所有異常報錯
     * @param request
     * @param exception
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value=Exception.class)  
    public Response allExceptionHandler(HttpServletRequest request,  
            Exception exception) throws Exception  
    {  
    	logger.error("攔截到異常:", exception);
        Response response = new Response();
        response.setData(null);
        response.getResult().setResultCode(9999);
        response.getResult().setResultMsg("系統繁忙");
        return response;  
    }  
 }

6、攔截器鏈設定:合作方訪問介面的時候,會根據小說系統原始碼介面定義好的傳參訪問你的介面伺服器,但是會存在介面引數型別錯誤或者格式不對,必傳引數沒傳的問題,甚至一些惡意請求,都可以通過攔截器鏈進行前期攔截,避免造成介面服務的壓力。還有很重要的一點,加簽驗籤也可以在攔截器設定。繼承WebMvcConfigurerAdapter實現springboot的攔截器鏈。實現HandlerInterceptor方法編寫業務攔截器。

package com.caiex.vb.interceptor;
 
 import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.lang3.StringUtils;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;
 import com.alibaba.fastjson.JSON;import com.caiex.redis.service.api.RedisApi;import com.caiex.vb.model.Response;import com.caiex.vb.utils.CaiexCheckUtils;
 @Componentpublic class SignInterceptor extends BaseValidator implements HandlerInterceptor{
	
	private Logger logger = LogManager.getLogger(this.getClass());
	
	@Resource	private RedisApi redisApi;
	
 
	public void afterCompletion(HttpServletRequest arg0,
			HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		// TODO Auto-generated method stub
		
	}
 
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
			Object arg2, ModelAndView arg3) throws Exception {
		// TODO Auto-generated method stub
		
	}
 
	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
			Object arg2) throws Exception {
		if(isTestIpAddr(arg0)){
			return true;
		}
		String securityKey = redisApi.hGet("securityKey", arg0.getParameter("agentid"));
		if(StringUtils.isEmpty(securityKey)){
			Response response = new Response();
			response.setData(null);
			response.getResult().setResultCode(8001);
			response.getResult().setResultMsg("缺少私鑰, 渠道號:" + arg0.getParameter("agentid"));
			logger.error("缺少私鑰, 渠道號:" + arg0.getParameter("agentid"));
			InterceptorResp.printJson(arg1, response);
			return false;
		}
		
		if(StringUtils.isEmpty(arg0.getParameter("sign")) || !arg0.getParameter("sign").equals(CaiexCheckUtils.getSign(arg0.getParameterMap(), securityKey))){
			Response response = new Response();
			response.setData(null);
			response.getResult().setResultCode(3203);
			response.getResult().setResultMsg("引數簽名認證失敗");
			logger.error("引數簽名認證失敗:" + JSON.toJSONString(arg0.getParameterMap()) + " securityKey = " + securityKey);
			InterceptorResp.printJson(arg1, response);
			return false;
		}else{
			return true;
		}
		
	}
 }
package com.caiex.oltp.config;
 import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 import com.caiex.oltp.interceptor.APILimitRateValidator;import com.caiex.oltp.interceptor.CommonValidator;import com.caiex.oltp.interceptor.DDSAuthValidator;import com.caiex.oltp.interceptor.QueryPriceParamsValidator;import com.caiex.oltp.interceptor.TradeParamsValidator;
 
 @EnableWebMvc
@Configuration
@ComponentScanpublic class WebAppConfigurer extends WebMvcConfigurerAdapter {
 
	 	@Bean
	 	CommonValidator commonInterceptor() {
	        return new CommonValidator();
	    }
 
	 	@Bean
	 	DDSAuthValidator ddsAuthInterceptor() {
	        return new DDSAuthValidator();
	    }
 
	 	@Bean
	 	QueryPriceParamsValidator queryPriceParamsInterceptor() {
	        return new QueryPriceParamsValidator();
	    }
 
	 	@Bean
	 	TradeParamsValidator tradeParamsInterceptor() {
	        return new TradeParamsValidator();
	    }
	 	
		@Bean
	 	APILimitRateValidator aPILimitRateInterceptor() {
	        return new APILimitRateValidator();
	    }
 
 
	    @Override	    public void addInterceptors(InterceptorRegistry registry) {
	    	
	    	//訪問速率限制
	    	registry.addInterceptor(aPILimitRateInterceptor())
	    	.addPathPatterns("/*/*");
	    	//.addPathPatterns("/price/getPriceParam");
 
	    	//引數簽名認證
	        registry.addInterceptor(ddsAuthInterceptor())
	        .addPathPatterns("/tradeState/*")
	        .addPathPatterns("/recycle/*")
	        .addPathPatterns("/matchInfo/*")
	        .addPathPatterns("/price/tradeTicketParam");
	        
	        //公共引數檢查
	        registry.addInterceptor(commonInterceptor())
	        .addPathPatterns("/price/tradeTicketParam")
	        .addPathPatterns("/tradeState/*")
	        .addPathPatterns("/recycle/*");
	        
	        //詢價引數校驗
	        registry.addInterceptor(queryPriceParamsInterceptor())
	        .addPathPatterns("/price/getPriceParam");
	        
	        //交易引數檢查
	        registry.addInterceptor(tradeParamsInterceptor())
	        .addPathPatterns("/price/tradeTicketParam");
	        
	        super.addInterceptors(registry);
	    }}

7、token令牌和sign數字簽名實現資料保密性。

建立令牌(Token)

為保證請求的合法性,我們提供第三方建立令牌介面,某些介面需要通過token驗證訊息的合法性,以免小說系統原始碼遭受非法攻擊。

token過期時間目前暫時定為1天,由於考慮到合作方往往是分散式環境,多臺機器都有可能申請token,為了降低合作方保證token一致性的難度,呼叫介面建立token成功以後一分鐘以內,再次請求token返回的資料是一樣的。

獲取私鑰

獲取用於數字簽名的私鑰,第三方獲取的私鑰需妥善儲存,並定期更新,私鑰只參與數字簽名,不作為引數傳輸。

數字簽名方式:

引數簽名;簽名方式:所有值不為null的引數(不包括本引數)均參與數字簽名,按照“引數名+引數值+私鑰”的格式得到一個字串,再將這個字串MD5一次就是這個引數的值。(示例:h15adc39y9ba59abbe56e057e60f883g),所以需要先獲取私鑰。

驗籤方式:

將小說系統原始碼使用者的所有非null引數放入定義好排序規則的TreeSet中進行排序,再用StringBuilder按照按照“引數名+引數值+私鑰”的格式得到一個字串(私鑰從redis拿),再將這個字串MD5一次就是這個引數的值。將這個值與使用者傳來的sign簽名對比,相同則通過,否則不通過。

private String createToken(){
		String utk = "Msk!D*"+System.currentTimeMillis()+"UBR&FLP";
		logger.info("create token   --- "+Md5Util.md5(utk));
		return Md5Util.md5(utk);
	}

8、介面限流
有時候小說系統原始碼伺服器壓力真的太大,以防交易介面被擠死,就可以對一些其他不影響主要業務功能並且計算量大的介面做限流處理。RateLimit–使用guava來做介面限流,當介面超過指定的流量時,就不處理該介面的請求。

9、協議加密,http升級成https;

為什麼要升級呢,為了保證小說系統原始碼資料的安全性。當使用https訪問時,資料從客戶端到服務斷,服務端到客戶端都加密,即使黑客抓包也看不到傳輸內容。當然還有其他好處,這裡不多講。但這也是開發介面專案需要注意的一個問題。

如何提高介面的高併發和高可用

小說系統原始碼介面開發好了,接下來就討論介面的可用性問題。首先我們要將高併發和高可用區分一下,畢竟高可用是在可用的情況,只是很慢或者效率不高。其實也可以歸為一類問題,但是不重要啦,重要的是怎麼提高你寫的介面的訪問速度和效能。

介面的高併發解決方案(其實沒有唯一答案,業界針對不同業務也有很多不同的方法)

當訪問一個小說系統原始碼介面獲取資料時,發現返回很慢,或者總是超時,如果排除網路的原因,那就是介面伺服器壓力太大,處理不過來了。在世界盃期間,我們檢視後臺日誌總是connection by reset和borker pipe和一些超時問題。這時候,你可能遇到了高併發和高可用問題。但是,不管遇到什麼問題,都不能臆斷和亂改,你得需要找到慢的原因,才能對症下藥,亂改可能會導致其他問題的出現。首先,解決高併發問題的三個方向是負載均衡,快取和叢集。

介面高可用問題

高可用問題應該上升到小說系統原始碼整個服務的架構問題上,就是說在搭建整體系統是就應該考慮到。高可用問題是以單點故障,訪問速度慢的問題為主導。

  • redis主從分散式(redis的單點故障和訪問速度的提高和主從備份)
  • 分散式dubbo服務的zookeeper主從叢集
  • strom的主從叢集
  • …等

總結

下面對介面開發服務做一些總結:

1.是拉還是推:

當小說系統原始碼介面作為資料來源時,還要考慮資料是讓合作方主動過來拉還是資料有變化就推送呢,當然是推的效果更好,但是如何有效的推資料,不推重複資料等都是需要根據實際業務考慮的問題。

2.多臺分散式伺服器上,怎麼保證交易的冪等和訂單的唯一性

當小說系統原始碼介面服務和合作方都處於分散式情況下,就很容易出現一個訂單號申請多次交易請求,但是根據冪等性,只能交易一次,並且每次不管何時請求,結果都應該一樣不會改變。這種情況下,我們怎麼保證唯一性呢,我們需要把該訂單和訂單狀態存redis,每次請求時去看是否訂單已存在。但可能這次交易不成功,下次這張票還可以繼續交易,可以生成新的訂單號啊。redis的setNX是一個很好的解決方案,意思是當存在該key時,返回false,當沒有時,該key和value插入成功。用作檢查訂單是否正在提交,如果是,則阻塞本次請求,避免重複提交 ,可以設定過期時間3s。提交之前鎖定訂單,防止重複提交。

3.處理時間超過10s,自動返回該訂單交易失敗

總之,在高併發場景下,導致小說系統原始碼服務崩潰的原因還是redis和資料庫,可能是redis讀寫太慢,或者資料庫的一些sql使用不當,或者沒建索引導致讀寫很慢。總之,這是一條很漫長的路,我們都需要慢慢積累經驗和學習前人更優秀的解決辦法。

本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理
原文連結:https://blog.csdn.net/xiaolizh/article/details/83011031


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2844942/,如需轉載,請註明出處,否則將追究法律責任。

相關文章