Java 对第三方提供通用接口设计

一. 前言

    在软件开发中,往往需要给第三方提供接口服务,一般通过SOAP协议或者HTTP协议来传输数据,本文不对SOAP协议进行研究,针对HTTP协议进行对外接口通过设计,不过设计思想可以通用。

二. 设计

    1.  首先系统会创建一个账号:密钥id,密钥secret,有效结束时间,状态(0:正常,1:停用),访问方法集合(空即可访问全部接口),签名sign则是通过一定的规则产生。

    2. 先设计一个通用接收字段

字段 类型 说明 备注
accessKeyId String 密钥ID  
sign String 签名  
accessDate String 访问时间 yyyy-MM-dd HH:mm:ss(访问时间不能与服务器时间相差太多,具体差值系统设置)

    3. 签名加密算法定义(可以自定义调节)

        accessDateStr为字符类型格式化

        sign = md5(accessKeySecret + accessKeyId + accessKeySecret + accessDateStr)

    4. 账号授权,系统可以设置每个方法的权限,如果该账号没有被赋予接口访问权限,则不允许访问。

    5. 核验数据有效性,对每条数据都必须进行有效性核验,具体验证流程:

        1)验证账号是否存在

        2)验证账号是否有效

        3)验证账号是否到期

        5)验证是否有接口访问权限

        6)验证访问时间是否有效

        7)验证签名是否有效

    6. 接口访问数据记录,对每次接口访问的数据单独进行日志记录。

三. 测试

    1. 正常访问

{
  
  "accessKeyId": "a123456",
  "sign": "f9595449a3799d938b8d255cde3d6b9c",
  "accessDate": "2020-03-01 10:30:00",
  "nm": "测试数据名称"
}

{
  "code": 0,
  "data": {
    "sign": "f9595449a3799d938b8d255cde3d6b9c",
    "accessKeyId": "a123456",
    "accessDate": "2020-03-01 10:30:00",
    "nm": "测试数据名称"
  }
}

      2. 用户密钥不存在

{
  
  "accessKeyId": "a1234569999999",
  "sign": "f9595449a3799d938b8d255cde3d6b9c",
  "accessDate": "2020-03-01 10:30:00",
  "nm": "测试数据名称"
}

{
  "code": 400,
  "msg": "用户密钥不存在"
}

     3. 签名不正常

{
  
  "accessKeyId": "a123456",
  "sign": "f9595449a3799d938b8d255cde3d6b9c1",
  "accessDate": "2020-03-01 10:30:00",
  "nm": "测试数据名称"
}

{
  "code": 400,
  "msg": "签名不正确"
}

     4. 访问时间错误,现在时间为2020-03-01 10:30:00

{
  
  "accessKeyId": "a123456",
  "sign": "f9595449a3799d938b8d255cde3d6b9c",
  "accessDate": "2020-03-01 10:10:00",
  "nm": "测试数据名称"
}

{
  "code": 400,
  "msg": "请求时间过于提前"
}

{
  
  "accessKeyId": "a123456",
  "sign": "f9595449a3799d938b8d255cde3d6b9c",
  "accessDate": "2020-03-01 10:50:00",
  "nm": "测试数据名称"
}

{
  "code": 400,
  "msg": "请求时间过于延后"
}

    还有其他错误返回就不一一列举了

四. 实现

    1. 签名基础类

/**
 * 

* 签名基础类 *

* * @author yuyi ([email protected]) */ @SuppressWarnings("deprecation") @Data public class BaseSignRo implements Serializable { private static final long serialVersionUID = 8126572563688838556L; @ApiModelProperty(value = "签名") @NotEmpty(message = "签名不能为空", groups = {AddGrp.class, UpdGrp.class}) private String sign; @ApiModelProperty(value = "密钥ID") @NotEmpty(message = "密钥ID不能为空", groups = {AddGrp.class, UpdGrp.class}) private String accessKeyId; @ApiModelProperty(value = "访问时间") // @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // @JsonDeserialize(using = DateJsonDeserializer.class) @NotEmpty(message = "访问时间不能为空", groups = {AddGrp.class, UpdGrp.class}) private Date accessDate; }

    2. 测试业务类

/**
 * 

* 测试签名 *

* * @author yuyi ([email protected]) */ @Data @EqualsAndHashCode(callSuper = true) public class TestSignRo extends BaseSignRo { private static final long serialVersionUID = 5811444046840617970L; @ApiModelProperty(value = "测试参数") private String nm; }

    3. 签名验证类

package yui.comn.web.utils;

import java.text.ParseException;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;

import lombok.extern.slf4j.Slf4j;
import yui.bss.sys.en.SysApiEn;
import yui.comn.api.co.SysApiCo;
import yui.comn.api.ro.BaseSignRo;
import yui.comn.utils.BssExpUtils;
import yui.comn.utils.DateUtils;
import yui.comn.utils.HttpRequestUtils;
import yui.comn.utils.MD5Util;

/**
 * 

* 签名验证工具类 *

* * @author yuyi */ @Slf4j public class SignUtils { public static String prodSign(BaseSignRo signRo, String accessKeySecret) { // String accessDateStr = DateUtils.format(signRo.getAccessDate(), DateUtils.FULL_ST_FORMAT); return MD5Util.encode(String.format("%s%s%s%s", accessKeySecret, signRo.getAccessKeyId(), accessKeySecret, signRo.getAccessDate())); } public static void checkSign(BaseSignRo signRo, SysApiCo apiCo) { // 验证账号是否存在 checkAccessKey(apiCo); // 验证账号是否有效 checkStatus(apiCo); // 验证账号是否到期 checkVldToTm(apiCo); // 验证是否有接口访问权限 checkMethod(apiCo); // 验证访问时间是否有效 checkAccessDate(signRo); // 验证签名是否有效 checkSign(signRo, apiCo.getAkSecret()); } private static void checkSign(BaseSignRo signRo, String accessKeySecret) { String sign = prodSign(signRo, accessKeySecret); if (!StringUtils.equals(sign, signRo.getSign())) { BssExpUtils.error("签名不正确", log); } } private static void checkAccessKey(SysApiCo apiCo) { if (null == apiCo) { BssExpUtils.error("用户密钥不存在", log); } } private static void checkStatus(SysApiCo apiCo) { if (apiCo.getStatus() == SysApiEn.Status.DISABLE.cd()) { BssExpUtils.error("用户密钥停用", log); } } @SuppressWarnings("deprecation") private static void checkMethod(SysApiCo apiCo) { String methodStr = apiCo.getMethod(); if (StringUtils.isNotBlank(methodStr)) { HttpServletRequest request = HttpRequestUtils.getHttpServletRequest(); String reqtMethod = StringUtils.replaceAll(StringUtils.substring(request.getRequestURI(), 1), "/", "."); methodStr = StringUtils.replaceAll(methodStr, ",", ","); String[] methods = StringUtils.split(methodStr, ","); boolean authz = false; for (String method : methods) { if (StringUtils.equals(StringUtils.trim(method), reqtMethod)) { authz = true; break; } } if (!authz) { BssExpUtils.error("没有访问该方法权限", log); } } } private static void checkAccessDate(BaseSignRo signRo) { Date accessDate = DateUtils.formatDate(signRo.getAccessDate(), DateUtils.FULL_ST_FORMAT); Date ftDateBeg = DateUtils.getDate(accessDate, 0, 0, 0, 0, -10, 0); //减去X分钟 Date ftDateEnd = DateUtils.getDate(accessDate, 0, 0, 0, 0, 10, 0); //增加X分钟 if (DateUtils.compareMill(ftDateBeg, DateUtils.getCurrentTime()) < 0) { BssExpUtils.error("请求时间过于延后", log); } if (DateUtils.compareMill(ftDateEnd, DateUtils.getCurrentTime()) > 0) { BssExpUtils.error("请求时间过于提前", log); } } private static void checkVldToTm(SysApiCo apiCo) { Date vldToTm = apiCo.getVldToTm(); if (null != vldToTm && DateUtils.compareMill(vldToTm, DateUtils.getCurrentTime()) > 0) { BssExpUtils.error("账号到期", log); } } }

    4. 接口实现

/**
 * 

* 系统测试接口 *

* * @author yuyi ([email protected]) */ @Api(value="系统测试") @RestController @RequestMapping("sys/test") public class SysTestController extends BaseController { @Reference private SysApiMgr sysApiMgr; @Log(type = LogType.SYS_API_LOG) @ApiOperation(value = "测试签名请求") @PostMapping("api") public Object api(@RequestBody TestSignRo signRo) { SysApiCo sysApiCo = sysApiMgr.getSysApiCo(signRo.getAccessKeyId()); SignUtils.checkSign(signRo, sysApiCo); return build(signRo); } }

        5.管理后台

Java 对第三方提供通用接口设计_第1张图片

Java 对第三方提供通用接口设计_第2张图片

 

 

 

 

 

 

 

你可能感兴趣的:(第三方接口,对外接口,通用接口)