小说系统源码开发,如何优雅的实现对外接口?

在小说系统源码的开发中,我们需要实现的对外接口还是很多的,像支付接口、单方登录接口等,而如何优雅的实现这些对外接口呢,需要我们考虑的问题还是有很多的,接下来我们一起了解一下。

做接口需要考虑的问题

什么是接口

接口无非就是小说系统源码客户端请求你的接口地址,并传入一堆该接口定义好的参数,通过接口自身的逻辑处理,返回接口约定好的数据以及相应的数据格式。

接口怎么开发

接口由于本身的性质,由于和合作方对接数据,所以有以下几点需要在开发的时候注意:

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
@ResponseBody
public 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;
 
@Component
public 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
@ComponentScan
public 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使用不当,或者没建索引导致读写很慢。总之,这是一条很漫长的路,我们都需要慢慢积累经验和学习前人更优秀的解决办法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值