我们好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于自己水平,假如文章和代码有表述不当之处,还请不吝赐教。

以下是正文!

文章背景

咱们最近做了很多项目,有些咱们是总担任的,有些是协作的。这些项目涉及的体系各种各样,但基本上没有一家公司会自动去做『敞开渠道』。这是因为投入产出比较低,项目一旦完成果完毕了,而且标书里也没有要求做敞开渠道。尽管这些项目都是业务体系,没有通用才干好敞开的,但在同一个项目中,总是有些东西需求打通,仍是需求一种轻量、安全的交互办法。

多方合作时,系统间的交互是怎么做的?

场景分类

(一)单点登录

单点登录是一种便利的登录办法,它能够运用在各种场景中,比方门户网站和小程序跳转。用户只需求在登录门户网站时输入用户名和密码,就能够轻松拜访其他相关子体系,无需重复输入登录信息。这不仅便利了用户,还帮助了IT办理人员更好地办理体系。

以百度为例:

多方合作时,系统间的交互是怎么做的?

这便是一个典型的单点登录案例,那么咱们怎样完成单点登录功能呢?

思路剖析

从『体系A门户页』点击导航进入『体系B』,用户信息是怎样同步的呢?把信息放在跳转链接上传给体系A肯定不合适,这相当于走漏了用户信息,计划不可行。咱们的做法是:

  • 用户输入账号密码进入体系A的门户页;
  • 用户点击跳转体系B导航后,体系A会生成一个当前用户唯一标识,一般是一串唯一的字符串,咱们将它成为临时授权码,取名为userToken;
  • 这个标识会当成一个参数拼接在体系B的跳转链接上,比方:systemB.com/index?userT…;
  • 体系A供给一个依据userToken查询当前用户的接口,比方:systemA.com/queryUserBy…; 进入体系B的主页之后,体系B调用体系A的queryUserByToken接口获取信息。
  • 为了安全起见,这个userToken一般都是有时限性的,过了1个小时就不能用了,而且只能用一次,用完就废弃掉。

我画个时序图解释一下这个逻辑

多方合作时,系统间的交互是怎么做的?

(二)接口调用

接口调用办法一般有两种:http接口和rpc接口。

1. http接口

咱们都知道http接口是什么,也能够轻易地运用Java调用Get、Post恳求。但是,咱们需求考虑http接口的数据安全问题。当咱们在浏览器或许postman工具中调用接口时,数据会以明文形式回来,不需求认证也不需求解密,这显然是不太安全的。我在开发过程中,经常会遇到协作方供给的接口直接以明文回来数据,甚至包括灵敏信息如手机号码等。尽管这种办法便利快捷,但整体来说并不太安全和可靠。

想要完成一个相对安全的http接口一般有两种办法:

  • 第一种,调用方需求进行认证并获取token,调用接口时需将token放置于恳求头或Cookie中。处理方经过过滤器查看token的合法性;

多方合作时,系统间的交互是怎么做的?

  • 第二种,处理方应生成并供给给调用方一个唯一的appId和对应的appSecret。调用方运用这个appId去调用接口,处理方运用appId和appSecret对数据进行加密。调用方获取到数据后,运用相同的appId和appSecret进行解密。

多方合作时,系统间的交互是怎么做的?

运用恳求头或Cookie的办法将token放置于恳求中的长处是安全性高,因为token不易被盗取或篡改。而运用appId和appSecret进行加密和解密的办法的长处是便利性高,因为appId和appSecret能够在接口文档或其他途径中公开,调用方只需求运用这些信息即可进行加解密操作,无需每次都进行认证获取token。

两种办法的挑选应依据具体状况而定,通常安全性较为重要的场景能够挑选运用token办法,而便利性较为重要的场景能够挑选运用appId和appSecret办法。

这儿我给我们供给一份可用的代码工具类,亲测可用。 需求的依赖

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.83</version>
</dependency>
<dependency>
   <groupId>org.bouncycastle</groupId>
   <artifactId>bcprov-jdk15on</artifactId>
   <version>1.56</version>
</dependency>
<dependency>
   <groupId>commons-codec</groupId>
   <artifactId>commons-codec</artifactId>
   <version>1.14</version>
</dependency>
加解密工具类
    import java.io.IOException;
  import java.security.Security;
  import java.text.ParseException;
  import javax.crypto.Cipher;
  import javax.crypto.spec.IvParameterSpec;
  import javax.crypto.spec.SecretKeySpec;
  import com.alibaba.fastjson.JSONObject;
  import lombok.extern.slf4j.Slf4j;
  import org.apache.commons.codec.binary.Base64;
  import org.apache.http.HttpEntity;
  import org.apache.http.client.methods.CloseableHttpResponse;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.entity.StringEntity;
  import org.apache.http.impl.client.CloseableHttpClient;
  import org.apache.http.impl.client.HttpClients;
  import org.apache.http.message.BasicHeader;
  import org.apache.http.protocol.HTTP;
  import org.apache.http.util.EntityUtils;
  import org.bouncycastle.jce.provider.BouncyCastleProvider;
  @Slf4j
  public class EncryptUtil {
    static {
      Security.addProvider(new BouncyCastleProvider());
    }
    private static final String CipherMode = "AES/CBC/PKCS7Padding";
    private static final String EncryptAlg = "AES";
    private static final String Encode = "UTF-8";
    /**
    * 加密随机盐
    */
    private static final String AESIV = "ff465fdecc764337";
    /**
    * 加密:有向量16位,成果转base64
    *
    * @param context
    * @return
    */
    public static String encrypt(String context, String sk) {
      try {
        // 下面这行在进行PKCS7Padding加密时有必要加上,不然报错
        Security.addProvider(new BouncyCastleProvider());
        byte[] content = context.getBytes(Encode);
        Cipher cipher = Cipher.getInstance(CipherMode);
        cipher.init(
          Cipher.ENCRYPT_MODE,
          new SecretKeySpec(sk.getBytes(Encode), EncryptAlg),
          new IvParameterSpec(AESIV.getBytes(Encode)));
        byte[] data = cipher.doFinal(content);
        String result = Base64.encodeBase64String(data);
        return result;
      } catch (Exception e) {
        e.printStackTrace();
      }
      return null;
    }
    /**
    * 解密
    *
    * @param context
    * @return
    */
    public static String decrypt(String context, String sk) {
      try {
        byte[] data = Base64.decodeBase64(context);
        Cipher cipher = Cipher.getInstance(CipherMode);
        cipher.init(
          Cipher.DECRYPT_MODE,
          new SecretKeySpec(sk.getBytes(Encode), EncryptAlg),
          new IvParameterSpec(AESIV.getBytes(Encode)));
        byte[] content = cipher.doFinal(data);
        String result = new String(content, Encode);
        return result;
      } catch (Exception e) {
        e.printStackTrace();
      }
      return null;
    }
    public static String sendPost(String url, JSONObject jsonObject, String encoding)
      throws ParseException, IOException {
      String body = "";
      //创立httpclient目标
      CloseableHttpClient client = HttpClients.createDefault();
      //创立post办法恳求目标
      HttpPost httpPost = new HttpPost(url);
      //装填参数
      StringEntity s = new StringEntity(jsonObject.toString(), "utf-8");
      s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
        "application/json"));
      //设置参数到恳求目标中
      httpPost.setEntity(s);
      log.info("恳求地址:" + url);
      //    System.out.println("恳求参数:"+nvps.toString());
      //设置header信息
      //指定报文头【Content-type】、【User-Agent】
      //    httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
      httpPost.setHeader("Content-type", "application/json");
      httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
      //履行恳求操作,并拿到成果(同步阻塞)
      CloseableHttpResponse response = client.execute(httpPost);
      //获取成果实体
      HttpEntity entity = response.getEntity();
      if (entity != null) {
        //按指定编码转换成果实体为String类型
        body = EntityUtils.toString(entity, encoding);
      }
      EntityUtils.consume(entity);
      //释放链接
      response.close();
      return body;
    }
    public static void main(String[] args) {
      String appId = "appId";
      //AES算法支撑的密钥长度有128位、192位和256位,其中128位密钥是最常用的。
      //因此,假如运用AES算法进行加密和解密,有必要确保密钥长度是128位、192位或256位。
      //假如运用的是AES-128算法,则密钥长度应该是128位,也便是16个字节;
      //假如运用的是AES-192算法,则密钥长度应该是192位,也便是24个字节;
      //假如运用的是AES-256算法,则密钥长度应该是256位,也便是32个字节
      String appKey = UUIDUtil.generateString(32);
      //参数加密
      JSONObject jsonObject = new JSONObject();
      jsonObject.put("appId", appId);
      jsonObject.put("appKey", appKey);
      jsonObject.put("data", "我是内容");
      String encrypt = EncryptUtil.encrypt(jsonObject.toJSONString(), appKey);
      System.out.println("加密后内容=" + encrypt);
      //参数界面
      System.out.println("解密后内容=" + EncryptUtil.decrypt(encrypt, appKey));
    }
  }
随机字符串生成类
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
public class UUIDUtil {
  public static final String allChar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  public static final String letterChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  public static final String numberChar = "0123456789";
  public static String[] chars =
      new String[]{
          "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
          "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
      };
  /**
  * 用于生成8位唯一标识字符串
  */
  public static String generateShortUuid() {
    StringBuffer shortBuffer = new StringBuffer();
    String uuid = UUID.randomUUID().toString().replace("-", "");
    for (int i = 0; i < 8; i++) {
      String str = uuid.substring(i * 4, i * 4 + 4);
      int x = Integer.parseInt(str, 16);
      shortBuffer.append(chars[x % 36]);
    }
    return shortBuffer.toString();
  }
  /**
  * 生成指定长度纯数字唯一标识字符串
  *
  * @param length
  * @return
  */
  public static String generatePureNumberUuid(int length) {
    StringBuffer shortBuffer = new StringBuffer();
    Random random = new Random();
    for (int i = 0; i < length; i++) {
      shortBuffer.append(numberChar.charAt(random.nextInt(10)));
    }
    return shortBuffer.toString();
  }
  /**
  * 由大小写字母、数字组成的随机字符串
  *
  * @param length
  * @return
  */
  public static String generateString(int length) {
    StringBuffer sb = new StringBuffer();
    Random random = new Random();
    for (int i = 0; i < length; i++) {
      sb.append(allChar.charAt(random.nextInt(allChar.length())));
    }
    return sb.toString();
  }
  /**
  * 由大小写字母组成的随机字符串
  *
  * @param length
  * @return
  */
  public static String generateMixString(int length) {
    StringBuffer sb = new StringBuffer();
    Random random = new Random();
    for (int i = 0; i < length; i++) {
      sb.append(letterChar.charAt(random.nextInt(letterChar.length())));
    }
    return sb.toString();
  }
  /**
  * 由小字字母组成的随机字符串
  *
  * @param length
  * @return
  */
  public static String generateLowerString(int length) {
    return generateMixString(length).toLowerCase();
  }
  /**
  * 由大写字母组成的随机字符串
  *
  * @param length
  * @return
  */
  public static String generateUpperString(int length) {
    return generateMixString(length).toUpperCase();
  }
  /**
  * 产生指字个数的0组成的字符串
  *
  * @param length
  * @return
  */
  public static String generateZeroString(int length) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < length; i++) {
      sb.append('0');
    }
    return sb.toString();
  }
  /**
  * 将数字转化成指字长度的字符串
  *
  * @param num
  * @param fixdlenth
  * @return
  */
  public static String toFixdLengthString(long num, int fixdlenth) {
    StringBuffer sb = new StringBuffer();
    String strNum = String.valueOf(num);
    if (fixdlenth - strNum.length() >= 0) {
      sb.append(generateZeroString(fixdlenth - strNum.length()));
    } else {
      throw new RuntimeException("将数字" + num + "转化为长度为" + fixdlenth + "的字符串产生异常!");
    }
    sb.append(strNum);
    return sb.toString();
  }
  /**
  * 将数字转化成指字长度的字符串
  *
  * @param num
  * @param fixdlenth
  * @return
  */
  public static String toFixdLengthString(int num, int fixdlenth) {
    StringBuffer sb = new StringBuffer();
    String strNum = String.valueOf(num);
    if (fixdlenth - strNum.length() >= 0) {
      sb.append(generateZeroString(fixdlenth - strNum.length()));
    } else {
      throw new RuntimeException("将数字" + num + "转化为长度为" + fixdlenth + "的字符串产生异常!");
    }
    sb.append(strNum);
    return sb.toString();
  }
  /**
  * 生成订单编号,时刻戳+后8位随机字符串
  *
  * @return
  */
  public static String getOrderNo() {
    String orderNo = "";
    String sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
    orderNo = sdf + generateShortUuid();
    return orderNo;
  }
  /**
  * 这个办法只支撑最大长度为32的随机字符串,如要支撑更大长度的,能够恰当修改此办法,如前面补、后面补,或许多个uuid相连接
  *
  * @param length
  * @return
  */
  private static String toFixedLengthStringByUUID(int length) {
    // 也能够经过UUID来随机生成
    UUID uuid = UUID.randomUUID();
    return uuid.toString().replace("-", "").substring(0, length);
  }
  /**
  * 生成订单编号,时刻戳+后8位随机字符串
  *
  * @return
  */
  public static String getBarCode() {
    String barCode = "";
    String sdf = new SimpleDateFormat("yyyyMMdd").format(new Date());
    barCode = sdf + generatePureNumberUuid(4);
    return barCode;
  }
}

2. RPC接口

RPC(Remote Procedure Call)远程过程调用是一种进程间通讯的办法,能够让不同的体系之间经过网络进行通讯和交互。但是,因为RPC接口需求事先定义好接口的参数、回来值、异常等,而且多方协作的开发结构要大致一样,因此其运用场景比较受限制。此外,不同体系之间的RPC接口需求坚持兼容性,不然可能会呈现接口不匹配、数据传输过错等问题。因此,在运用RPC接口时,需求进行充分考虑和规划,以确保接口的正确性和可靠性。尽管RPC接口的运用场景有限,但在特定的场景下,RPC接口能够供给高效、可靠的通讯办法,如分布式架构中体系间的服务调用。

我在工作中只遇到过一次RPC调用的状况。其时,我与公司的不同部门协作,咱们运用了同一套结构,他们供给的是RPC接口,我只需引进他们的jar包就能轻松调用他们的服务。不过,除了公司内部,我很少遇到其他机构或公司运用RPC调用的办法。通常,大多数外部接口服务都是经过HTTP接口完成的。

(三)中间件交互

这儿我引用一下ChatGPT的回答:

多方合作时,系统间的交互是怎么做的?

而我遇到的状况:有一次,A方需求自动将数据推送给B方,所以提出了用音讯行列的计划,一听两方都觉得既解耦又便利,所以开始举动。A方在自己的服务器上部署了音讯行列,但没想到,各方的服务器环境是隔离的,网络不通,B方根本无法连接到A方的音讯行列。他们所以找到了私有云的运维人员,问他们能不能做敞开端口、IP加白等一大堆操作,但不知道啥原因便是不行。最后他们只好改为B方供给一个Http接口,A方自动调用接口把数据送过去才得以处理。。。

总结一下

在多体系协作的场景中,体系间的交互是十分要害的。交互协议的一致性、数据格局的一致性、安全性保证、过错处理机制、交互频率、监控和日志记录等方面,都需求特别注意,以确保体系间的交互安稳和可靠。

  • 交互协议的一致性是体系间进行数据传输的根底,需求明确定义恳求和呼应报文格局、数据类型、处理规矩等。数据格局的一致性也十分重要,需求确定数据交换的格局和编码办法,防止因为格局不一致而导致的数据解析异常。

  • 安全性保证是防止体系中呈现不合法拜访和数据走漏的重要手段,需求选用各种安全措施来保证体系的安全性。

  • 过错处理机制需求考虑体系中可能呈现的各种异常状况,并对不同的异常状况进行分类处理,确保信息及时反馈给用户。

  • 交互频率需求依据实际状况来拟定,防止频繁的调用形成体系压力过大。

  • 监控和日志记录需求对体系进行实时监控,及时发现和处理问题,并记录日志以便进行排查和剖析。

综上所述,在多方协作时,需求全面考虑体系间的交互问题,以确保体系间的交互安稳可靠,保证协作的顺利进行。 最后温馨提醒我们一下,多方协作少不了开会对齐,在沟通的时候仍是要耐性和自动一些,都是干活的应该互帮互助才对,齐心协力才干少加班嘛!