关于系统接入第三方应用之《顶层设计》

什么情况下会需要这样的代码: 传统的写法有什么优势: 这样的写法有什么优势: 实际案例:

接入第三方应用,多数是通过接口实现的,还有一部份是配合使用消息通知实现的。但,大多数还是API实现的。所以,接下来介绍的就是我的设计,两者都存在的方式。

首先就是调用接口,通过网关的时候都会有AK,SK,所以就会有对应的签名算法:

复制代码
 1 package com.rauto.sales.saicorder.outerapp;
 2 
 3 import java.util.Map;
 4 
 5 /**
 6  * 请求签名算法
 7  *
 8  * @author Euler
 9  * @date 2022 2022/5/23
10  */
11 public interface Sign<T> {
12 
13     /**
14      * 使用的签名算法
15      * 注:默认使用md5
16      *
17      * @return
18      */
19     SignMethodEnum getSignMethod();
20 
21     /**
22      * 计算参数签名
23      *
24      * @param params
25      * @return
26      */
27     String doSign(Map<String, String> params);
28 
29 
30 }
View Code
复制代码
复制代码
 1 package com.rauto.sales.saicorder.outerapp;
 2 
 3 /**
 4  * 签名算法
 5  *
 6  * @author Euler
 7  * @date 2022 2022/5/23
 8  */
 9 public enum SignMethodEnum {
10     MD5("md5", "md5"),
11     HMAC("hmac", "hmac"),
12     HMAC_SHA256("hmac-sha256", "hmac-sha256"),
13     ;
14 
15     private String value;
16     private String description;
17 
18     SignMethodEnum(String value, String description) {
19         this.value = value;
20         this.description = description;
21     }
22 
23 }
View Code
复制代码

 

有了签名算法,有的系统调用接口用的是带有时效性的是accesstoken,并且这个accesstoken是全局通用的,这个时候你就得把这个accesstoken缓存并且全局共享的这种:

复制代码
 1 package com.rauto.sales.saicorder.outerapp;
 2 
 3 /**
 4  * accessToken 是最接近访问接口的动态令牌,拿到此令牌即可调用需要授权的接口
 5  * 注意:在标准的授权登录中,是要先拿到用户授权的code,然后携code取accessToken信息
 6  * T : 表示获取code的参数,通常是app_key和secret
 7  * V : 表示用户授权,开放平台返回的结构
 8  * Z : 表示获取令牌,开放平台返回的结构
 9  *
10  * @author Euler
11  * @date 2022 2022/5/23
12  */
13 public interface AccessToken<T, V, Z> {
14 
15 
16     /**
17      * 获取授权码
18      * <p>
19      * 用户授权后,开放平台返回的code。一般通过redirect方式返回
20      *
21      * @param params
22      * @return
23      */
24     V getCode(T params);
25 
26 
27     /**
28      * 获取访问令牌
29      * <p>
30      * 通过code获取对应的accessToken
31      *
32      * @param codeResponse 授权码响应结果
33      * @return
34      */
35     Z getAccessToken(V codeResponse);
36 
37 
38 }
View Code
复制代码
复制代码
 1 package com.rauto.sales.saicorder.outerapp.tmall.accesstoken;
 2 
 3 
 4 import com.rauto.sales.saicorder.outerapp.AccessToken;
 5 import com.rauto.sales.saicorder.outerapp.tmall.req.CodeRequest;
 6 import com.rauto.sales.saicorder.outerapp.tmall.resp.AccessTokenResponse;
 7 import com.rauto.sales.saicorder.outerapp.tmall.resp.CodeResponse;
 8 
 9 /**
10  * 通过阿里开放平台的接口,获取数据
11  *
12  * @author Euler
13  * @date 2022 2022/5/23
14  */
15 public class BaseAccessToken implements AccessToken<CodeRequest, CodeResponse, AccessTokenResponse> {
16     /**
17      * 获取授权码
18      * <p>
19      * 用户授权后,开放平台返回的code。一般通过redirect方式返回
20      *
21      * @param params
22      * @return
23      */
24     @Override
25     public CodeResponse getCode(CodeRequest params) {
26         return null;
27     }
28 
29     /**
30      * 获取访问令牌
31      * <p>
32      * 通过code获取对应的accessToken
33      *
34      * @param codeResponse 授权码响应结果
35      * @return
36      */
37     @Override
38     public AccessTokenResponse getAccessToken(CodeResponse codeResponse) {
39         return null;
40     }
41 }
View Code
复制代码
复制代码
 1 package com.rauto.sales.saicorder.outerapp.tmall.accesstoken;
 2 
 3 
 4 import com.rauto.sales.saicorder.outerapp.AccessToken;
 5 import com.rauto.sales.saicorder.outerapp.tmall.req.CodeRequest;
 6 import com.rauto.sales.saicorder.outerapp.tmall.resp.AccessTokenResponse;
 7 import com.rauto.sales.saicorder.outerapp.tmall.resp.CodeResponse;
 8 
 9 /**
10  * 基于缓存的accessToken的实现
11  * <p>
12  * 如果多个系统共同使用一个accessToken,可以将其通过redis共享。
13  * 默认实现是基于进程内的内存共享
14  *
15  * @author Euler
16  * @date 2022 2022/5/23
17  */
18 public class CacheWrapperAccessToken implements AccessToken<CodeRequest, CodeResponse, AccessTokenResponse> {
19 
20     /**
21      * 基于请求天猫接口的实现
22      */
23     private AccessToken accessToken;
24 
25     public CacheWrapperAccessToken(AccessToken accessToken) {
26         this.accessToken = accessToken;
27     }
28 
29     /**
30      * 获取授权码
31      * <p>
32      * 用户授权后,开放平台返回的code。一般通过redirect方式返回
33      *
34      * @param params
35      * @return
36      */
37     @Override
38     public CodeResponse getCode(CodeRequest params) {
39         return null;
40     }
41 
42     /**
43      * 获取访问令牌
44      * <p>
45      * 通过code获取对应的accessToken
46      *
47      * @param codeResponse 授权码响应结果
48      * @return
49      */
50     @Override
51     public AccessTokenResponse getAccessToken(CodeResponse codeResponse) {
52         return null;
53     }
54 }
View Code
复制代码

 

有了accesstoken你就可以调用API,API接口设计要具备单一职责、开闭原则,至于实现代码怎么写,只要没有bug都没问题:

View Code
View Code
vvv
View Code

 

 

然后就是有的系统是通过MQ接入进来的:

复制代码
 1 package com.rauto.sales.saicorder.outerapp;
 2 
 3 import com.rauto.sales.saicorder.outerapp.tmall.dto.MessageDTO;
 4 import com.rauto.sales.saicorder.outerapp.tmall.dto.MessageDealResultDTO;
 5 import com.taobao.api.ApiException;
 6 
 7 /**
 8  * 消息处理方案
 9  */
10 abstract public class MessageObserver {
11 
12     /**
13      * 后面紧接着要处理的业务
14      * 这里通常是将一个业务线上的很长的执行步骤,按照某一个维度进行垂直分割以后,形成的一个chain.
15      * 执行的时候是按照业务线依次执行的。如果可以一步完成,就没有next processor
16      */
17     private MessageObserver nextProcessor;
18 
19     public MessageObserver(MessageObserver nextProcessor) {
20         this.nextProcessor = nextProcessor;
21     }
22 
23     /**
24      * 没有next processor的可以直接无参构造
25      */
26     public MessageObserver() {
27     }
28 
29     /**
30      * 处理逻辑
31      *
32      * @param messageData
33      * @param contextObject
34      * @return
35      */
36     public abstract MessageDealResultDTO dealWithMessage(MessageDTO messageData, MessageDealResultDTO contextObject) throws ApiException;
37 
38 
39     public MessageDealResultDTO doProcessor(MessageDTO messageData, MessageDealResultDTO contextObject) throws ApiException {
40         dealWithMessage(messageData, contextObject);
41         if (nextProcessor != null) {
42             nextProcessor.doProcessor(messageData, contextObject);
43         }
44         return contextObject;
45     }
46 
47 
48 }
View Code
复制代码

 

有消息进来,就少不消息过滤:

复制代码
 1 package com.rauto.sales.saicorder.outerapp.tmall.message;
 2 
 3 import com.rauto.sales.saicorder.mybatis.entity.TbSyncThirdOrder;
 4 import com.rauto.sales.saicorder.outerapp.tmall.enums.FilterGroupEnum;
 5 import com.taobao.api.ApiException;
 6 
 7 /**
 8  * 消息过滤
 9  *
10  * @author Euler
11  * @date 2022 2022/6/7
12  */
13 public interface MessageFilter<T> {
14 
15     /**
16      * 过滤执行
17      *
18      * @param tbSyncThirdOrder 入参
19      * @return 如果符合条件,就返回true
20      */
21     boolean doFilter(T tbSyncThirdOrder) throws ApiException;
22 
23     /**
24      * 如果有多个过滤条件,可以指定优先级
25      *
26      * @return
27      */
28     int getSort();
29 
30     /**
31      * 获取当前的filter是属于哪个分组的
32      *
33      * @return
34      */
35     FilterGroupEnum getGroup();
36 
37 }
View Code
复制代码

 

有消息就会有消息状态流转,这里使用访问者模式,配合双分派,完美解决:

复制代码
 1 package com.rauto.sales.saicorder.outerapp.tmall.message.visitor;
 2 
 3 import com.rauto.sales.saicorder.outerapp.tmall.enums.BusinessStatusEnum;
 4 import com.rauto.sales.saicorder.outerapp.tmall.enums.MessageDealWithResultEnum;
 5 
 6 /**
 7  * @author Euler
 8  * @date 2022 2022/11/22
 9  */
10 public abstract class DbStatus {
11     /**
12      * 处理结果
13      */
14     private Integer businessStatus;
15 
16     public DbStatus(Integer businessStatus) {
17         this.businessStatus = businessStatus;
18     }
19 
20     private DbStatus() {
21     }
22 
23     /**
24      * 接收天猫状态的visitor
25      * @param tmallVisitorStatus
26      * @return
27      */
28     public abstract MessageDealWithResultEnum acceptTmallVisitor(TmallVisitorStatus tmallVisitorStatus);
29 
30 
31     /**
32      * 将DB记录的处理结果转成BusinessStatusEnum
33      * @return
34      */
35     protected BusinessStatusEnum getBusinessStatus(){
36         return BusinessStatusEnum.valueOfCode(businessStatus);
37     }
38 }
View Code
复制代码
复制代码
 1 package com.rauto.sales.saicorder.outerapp.tmall.message.visitor;
 2 
 3 import com.rauto.sales.saicorder.outerapp.tmall.enums.BusinessStatusEnum;
 4 import com.rauto.sales.saicorder.outerapp.tmall.enums.MessageDealWithResultEnum;
 5 
 6 /**
 7  * 天猫visitor
 8  *
 9  * @author Euler
10  * @date 2022 2022/11/22
11  */
12 public interface TmallVisitorStatus {
13 
14     /**
15      * 判断数据库记录是否处理成功
16      *
17      * @param statusEnum
18      *
19      * @return
20      */
21     default boolean dbRecordIsFail(BusinessStatusEnum statusEnum) {
22         return BusinessStatusEnum.ABNORMAL.equals(statusEnum) || BusinessStatusEnum.CREATE_ORDER_ABNORMAL.equals(statusEnum);
23     }
24 
25 
26     /**
27      * 访问Db 已支付状态
28      *
29      * @param dbPayedStatus
30      *
31      * @return
32      */
33     MessageDealWithResultEnum visitDbStatus(DbPayedStatus dbPayedStatus);
34 
35 
36     /**
37      * 访问Db已核销状态
38      *
39      * @param dbChargeOffStatus
40      *
41      * @return
42      */
43     MessageDealWithResultEnum visitDbStatus(DbChargeOffStatus dbChargeOffStatus);
44 
45 
46     /**
47      * 访问Db 已退款状态
48      *
49      * @param dbCloseStatus
50      *
51      * @return
52      */
53     MessageDealWithResultEnum visitDbStatus(DbCloseStatus dbCloseStatus);
54 
55 
56 }
View Code
复制代码

 

有了以上的抽象层设计,就可以随意扩展了。同时还要做的一个是限流、日志记录、数据的最终一致性少不了重试。方便排查,方便甩锅。

 

posted @   Eular  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
点击右上角即可分享
微信分享提示