여러 당사자가 협력할 때 시스템 간의 상호 작용은 어떻게 이루어집니까?

여러분, 안녕하세요! 저는 1차 저레벨 코드 파머 숨모입니다. 평소에 기술적인 문제에 대해 공부하고 생각하고 글로 정리하는 것을 좋아합니다. 제 수준에 제한이 있습니다. 글에 부적절한 표현이 있으면 그리고 코드, 저를 계몽 주시기 바랍니다.

다음은 본문입니다!

기사 배경

우리는 최근에 많은 프로젝트를 진행해 왔으며 그 중 일부는 우리가 전반적으로 책임지고 일부는 공동 작업을 했습니다. 이러한 프로젝트에는 다양한 시스템이 포함되지만 기본적으로 "개방형 플랫폼"을 주도하는 회사는 없습니다. 이는 투입과 산출이 상대적으로 적고 프로젝트가 완료되면 종료되며 입찰 문서에 개방형 플랫폼이 되어야 한다는 요구 사항이 없기 때문입니다. 이러한 프로젝트는 모두 비즈니스 시스템이며 공개할 수 있는 일반적인 기능이 없지만 동일한 프로젝트에는 항상 공개해야 하는 항목이 있으며 가볍고 안전한 상호 작용 방법이 여전히 필요합니다.
여기에 이미지 설명 삽입

장면 분류

(1) 싱글 사인온

Single Sign-On은 편리한 로그인 방식으로 포털 사이트, 애플릿 점프 등 다양한 시나리오에 적용할 수 있습니다. 사용자는 포털 웹사이트에 로그인할 때 사용자 이름과 비밀번호만 입력하면 되며, 로그인 정보를 반복적으로 입력하지 않고도 다른 관련 하위 시스템에 쉽게 접근할 수 있습니다. 이것은 사용자를 용이하게 할 뿐만 아니라 IT 관리자가 시스템을 더 잘 관리하는 데 도움이 됩니다.

Baidu를 예로 들어 보겠습니다.
여기에 이미지 설명 삽입

이것은 일반적인 싱글 사인온 사례인데 어떻게 싱글 사인온 기능을 구현합니까?

아이디어 분석

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

  • 사용자는 시스템 A의 포털 페이지에 들어가기 위해 계정 암호를 입력합니다.
  • 사용자가 클릭하여 시스템 B로 이동하면 시스템 A는 현재 사용자에 대한 고유 식별자(보통 고유 문자열 문자열)를 생성합니다. 이를 임시 인증 코드라고 하고 이름을 userToken으로 지정합니다.
  • 이 로고는 매개변수로 시스템 B의 점프 링크에 연결됩니다. 예: https://systemB.com/index?userToken=xxxx;
  • 시스템 A는 userToken을 기반으로 현재 사용자를 쿼리하는 인터페이스를 제공합니다(예: https://systemA.com/queryUserByToken?userToken=xxx).
    시스템 B의 홈페이지에 들어간 후 시스템 B는 시스템 A의 queryUserByToken 인터페이스를 호출하여 정보.
  • 안전을 위해 이 userToken은 일반적으로 시간 제한이 있으며 1시간 후에는 사용할 수 없으며 한 번만 사용할 수 있으며 다 사용하면 폐기됩니다.

논리를 설명하기 위해 시퀀스 다이어그램을 그려 보겠습니다.
여기에 이미지 설명 삽입

(2) 인터페이스 호출

일반적으로 인터페이스를 호출하는 방법에는 http 인터페이스와 rpc 인터페이스의 두 가지가 있습니다.

1. HTTP 인터페이스

우리 모두는 http 인터페이스가 무엇인지 알고 있으며 Java를 사용하여 Get 및 Post 요청을 쉽게 호출할 수 있습니다. 그러나 http 인터페이스의 데이터 보안 문제를 고려해야 합니다. 브라우저나 Postman 도구에서 인터페이스를 호출하면 데이터가 인증이나 암호 해독 없이 일반 텍스트로 반환되므로 분명히 안전하지 않습니다. 개발 과정에서 휴대폰 번호와 같은 민감한 정보를 포함하여 데이터를 일반 텍스트로 직접 반환하기 위해 파트너에서 제공하는 인터페이스를 자주 접합니다. 이 방법은 편리하고 빠르지만 일반적으로 안전하고 신뢰할 수 있는 방법은 아닙니다.

일반적으로 상대적으로 안전한 http 인터페이스를 구현하는 두 가지 방법이 있습니다.

  • 첫 번째 유형에서 호출자는 인증을 수행하고 토큰을 얻어야 하며 토큰은 인터페이스를 호출할 때 요청 헤더 또는 쿠키에 배치해야 합니다. 프로세서는 필터를 통해 토큰의 적법성을 확인합니다.
    여기에 이미지 설명 삽입

  • 둘째, 프로세서는 호출자에게 고유한 appId 및 해당 appSecret을 생성하고 제공해야 합니다. 호출자는 이 appId를 사용하여 인터페이스를 호출하고 프로세서는 appId 및 appSecret을 사용하여 데이터를 암호화합니다. 호출자가 데이터를 얻은 후 동일한 appId 및 appSecret을 사용하여 데이터를 해독합니다.
    여기에 이미지 설명 삽입

使用请求头或Cookie的方式将token放置于请求中的优点是安全性高,因为token不易被窃取或篡改。而使用appId和appSecret进行加密和解密的方式的优点是方便性高,因为appId和appSecret可以在接口文档或其他途径中公开,调用方只需要使用这些信息即可进行加解密操作,无需每次都进行认证获取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(원격 프로시저 호출) 원격 프로시저 호출은 서로 다른 시스템이 네트워크를 통해 통신하고 상호 작용할 수 있도록 하는 프로세스 간 통신 방법입니다. 그러나 RPC 인터페이스는 미리 인터페이스의 매개변수, 반환 값, 예외 등을 정의해야 하고 다자간 협력의 개발 프레임워크가 대략 동일해야 하므로 적용 시나리오가 상대적으로 제한됩니다. 또한 서로 다른 시스템 간의 RPC 인터페이스는 호환성을 유지해야 하며 그렇지 않으면 인터페이스 불일치 및 데이터 전송 오류와 같은 문제가 발생할 수 있습니다. 따라서 RPC 인터페이스를 사용할 때 인터페이스의 정확성과 신뢰성을 보장하기 위해 충분한 고려와 설계가 필요합니다. RPC 인터페이스의 애플리케이션 시나리오는 제한적이지만 특정 시나리오에서 RPC 인터페이스는 분산 아키텍처의 시스템 간 서비스 호출과 같은 효율적이고 안정적인 통신 방법을 제공할 수 있습니다.

직장에서 한 번만 RPC 호출을 받았습니다. 그 당시 저는 회사의 여러 부서와 함께 일했습니다. 우리는 동일한 프레임워크 세트를 사용했습니다. 그들은 RPC 인터페이스를 제공했습니다. 나는 그들의 서비스를 쉽게 호출하기 위해 그들의 jar 패키지를 가져오기만 하면 되었습니다. 그러나 내부적으로 RPC 호출을 사용하는 조직이나 회사를 거의 보지 못했습니다. 일반적으로 대부분의 외부 인터페이스 서비스는 HTTP 인터페이스를 통해 구현됩니다.

(3) 미들웨어 상호 작용

다음은 ChatGPT의 답변을 인용한 것입니다.

여기에 이미지 설명 삽입

그리고 내가 만난 상황: 한번은 A가 B에게 데이터를 적극적으로 푸시해야 했기 때문에 메시지 큐를 사용하는 방안을 제안했고, 그 말을 듣고 양 당사자는 그것이 분리되고 편리하다고 느끼며 행동하기 시작했습니다. Party A는 자신의 서버에 메시지 큐를 배포했지만 예기치 않게 모든 당사자의 서버 환경이 격리되고 네트워크가 차단되어 Party B는 Party A의 메시지 큐에 전혀 연결할 수 없었습니다. 그래서 프라이빗 클라우드의 운영 및 유지보수 담당자를 찾아 포트 개방, IP 화이트닝 등 많은 작업을 할 수 있느냐고 물었지만, 왠지 할 수 없었다. 결국 Http 인터페이스를 제공하기 위해 Party B로 변경해야 했고, Party A는 문제를 해결하기 위해 데이터를 전송하기 위해 인터페이스를 적극적으로 호출했습니다. . .

결론적으로

다중 시스템 협력 시나리오에서 시스템 간의 상호 작용은 매우 중요합니다. 상호 작용 프로토콜의 일관성, 데이터 형식의 일관성, 보안 보장, 오류 처리 메커니즘, 상호 작용 빈도, 모니터링 및 로깅 등 모두 시스템 간의 안정적이고 신뢰할 수 있는 상호 작용을 보장하기 위해 특별한 주의가 필요합니다.

  • 상호작용 프로토콜의 일관성은 시스템 간의 데이터 전송을 위한 기반이며 요청 및 응답 메시지 형식, 데이터 유형, 처리 규칙 등을 명확하게 정의해야 합니다. 데이터 형식의 일관성 또한 매우 중요한데, 일관성 없는 형식으로 인한 데이터 구문 분석 예외를 피하기 위해 데이터 교환의 형식 및 인코딩 방법을 결정해야 합니다.

  • 보안 보증은 시스템의 불법 접근 및 데이터 유출을 방지하기 위한 중요한 수단이며 시스템의 보안을 보장하기 위해 다양한 보안 조치가 필요합니다.

  • 오류 처리 메커니즘은 시스템에서 발생할 수 있는 다양한 비정상 상황을 고려하고 정보가 적시에 사용자에게 피드백되도록 다양한 비정상 상황을 분류하고 처리해야 합니다.

  • 빈번한 호출로 인해 시스템에 과도한 부담이 가지 않도록 실제 상황에 따라 상호 작용 빈도를 공식화해야 합니다.

  • 모니터링 및 로깅에는 시스템의 실시간 모니터링, 적시의 문제 발견 및 처리, 문제 해결 및 분석을 위한 로그 기록이 필요합니다.

요약하면, 다자간 협력에서 시스템 간의 상호 작용이 안정적이고 신뢰할 수 있고 협력의 원활한 진행을 보장하기 위해 시스템 간의 상호 작용을 충분히 고려할 필요가 있습니다.
最后温馨提醒大家一下,多方合作少不了开会对齐,在沟通的时候还是要耐心和主动一些,都是干活的应该互帮互助才对,齐心协力才能少加班嘛!

Supongo que te gusta

Origin blog.csdn.net/weixin_33005117/article/details/131115486
Recomendado
Clasificación