审核中台服务的一次重构(抽象模版设计模式+抽象策略设计模式+工厂设计模式)

这次重构早在2021年就已经重构结束了,但是今天还是想分享一下,不拖拉,直奔主题

角色说明

甲方:业务层开发同事 乙方:审核中台(我方) 丙方:第三方服务平台

重构前实现逻辑

重构前的实现逻辑很简单,甲方提出一个审核需求,我方就实现一个接口来应对,代码严重冗余,对于甲方来说要频繁进行接口联调,开发周期会大大增加,一个小的变动都需要更改多处代码,简直就是屎山级别的代码。(据小道消息透露,开发这个中台的同事是个实习生,开发期间差点把带他的那个同事气吐血,做完这个项目没多久就走了)我接手代码后也被这臭气熏天的代码恶心的够呛,叔能忍,婶不能忍,屎山必须推平,于是撸起袖子开始搞!

我的方案

接口只允许有一个,减少与甲方的频繁接口联调,用ChannelEnum枚举类来区分各个甲方提出的审核需求,用抽象模版将审核的全部流程串联起来,用抽象策略设计模式实现各个ChannelEnum中涉及的业务逻辑,用策略工厂设计模式存储各个策略的实现,下面上代码

审核的抽象模版代码 (代码仅供参考)

typescript 复制代码
/**
 * @Author: pp
 * @DateTime: 2021/11/27 11:28
 * @Description: 抽象审核模版
 */
public abstract class AuditTemplateAbstract<P, A> {

    //第三方视频审核接口
    @Value("${video.audit.url}")
    private String videoAuditUrl;
    //第三方网页审核接口
    @Value("${webpage.audit.url}")
    private String webpageAuditUrl;
    //第三方文本审核接口
    @Value("${text.audit.url}")
    private String textAuditUrl;
    //第三方图片审核接口
    @Value("${img.audit.url}")
    private String imgAuditUrl;
    //审核失败是否发送钉钉
    @Value("${disable.dingding}")
    private String disableDingDing;

    //审核前置策略工厂
    @Autowired
    private AuditBeforeStrategyFactory auditBeforeStrategyFactory;
    //审核后置策略工厂
    @Autowired
    private AuditAfterStrategyFactory auditAfterStrategyFactory;


    //审核参数校验
    protected abstract void checkAuditParams(P auditParam, boolean isBatch);

    //构建调用丙方审核的参数
    protected abstract Map<String, Object> buildAuditParams(P auditParam);

    //解析审核结果,构建入库对象,写入数据库
    protected abstract A resolveAuditBuildInsertAndWriteDB(JSONObject auditResult, P auditParam);

    /**
     * @Description: 审核
     * @DateTime: 2021/8/11 18:24
     * @Params: auditParam 审核参数
     * @params: auditTypeEnum 审核类型
     * @params: errorRetryNum 错误重试次数
     * @params: isBatch 是否批量审核
     * @Return Object
     */
    public Map<String, Object> audit(P auditParam, AuditTypeEnum auditTypeEnum, int errorRetryNum, ChannelEnum channelEnum, boolean isBatch) {
        //返回结果对象
        Map<String, Object> result = new HashMap<>();
        //审核参数校验
        checkAuditParams(auditParam, isBatch);
        
        //从策略工厂中获取前置策略,判断是否存在审核前置处理,如果存在就执行处理逻辑,返回false表示不执行下面操作,直接返回result
        AuditBeforeStrategy<P> auditBeforeStrategy = auditBeforeStrategyFactory.getPostProcess(channelEnum);
        if (auditBeforeStrategy != null) {
            boolean beforeResult = auditBeforeStrategy.beforeProcess(auditParam, auditTypeEnum, result);
            if (!beforeResult) {
                return result;
            }
        }

        //构建审核参数
        Map<String, Object> auditParams = buildAuditParams(auditParam);

        //请求丙方审核接口
        JSONObject auditResult = sendAudit(auditParams, auditTypeEnum);
       
        //判断请求是否成功,失败重试
        if (errorRetryNum > 0 && !isAuditSuccess(auditResult)) {
            //失败重试
            auditResult = retry(auditParams, errorRetryNum, auditTypeEnum);
        }

        boolean isSuccess = true;
        //处理失败流程
        if (!isAuditSuccess(auditResult)) {
            if (disableDingDing.equals("0")) {
            //发送钉钉通知
                sendDingDing();
            }
            isSuccess = false;
        }

        //解析返回值
        A insertDBData = resolveAuditBuildInsertAndWriteDB(JSONObject.fromObject(auditResult), auditParam);

        //检查是否存在审核结果后置处理策略
        AuditAfterStrategy<P, A> auditAfterStrategy = auditAfterStrategyFactory.getPostProcess(channelEnum);
        if (auditAfterStrategy == null) {
            //没有策略执行默认返回结果构造器
            result = buildDefaultResultMap(isSuccess ? 200 : 300, insertDBData, auditTypeEnum);
        } else {
            //执行策略
            result = auditAfterStrategy.afterProcess(auditParam, insertDBData);
        }

        return result;
    }

    //发送审核
    private JSONObject sendAudit(Map<String, Object> paramMap, AuditTypeEnum auditTypeEnum) {
        
        switch (auditTypeEnum){
             case TEXT:
                 return HttpRequestUtils.httpPost(textAuditUrl, JSONObject.fromObject(paramMap));
             case IMAGE:
                 return HttpRequestUtils.httpPost(imgAuditUrl, JSONObject.fromObject(paramMap));
             case WEB_PAGE:
                 return HttpRequestUtils.httpPost(webpageAuditUrl, JSONObject.fromObject(paramMap));
             case VIDEO:
                 return HttpRequestUtils.httpPost(videoAuditUrl, JSONObject.fromObject(paramMap));
             default:
                 return null;
         }

    }

    /*
     * 构建统一返回值
     * */
    private Map<String, Object> buildDefaultResultMap(int code, A auditResult, AuditTypeEnum auditTypeEnum) {
         Map<String, Object> result = new HashMap<>();
         result.put("code", code);
         switch (auditTypeEnum){
             case TEXT:
                 result.put("auditText", auditResult);
                 break;
             case IMAGE:
                 result.put("auditImg", auditResult);
                 break;
             case WEB_PAGE:
                 result.put("auditWebpage", auditResult);
                 break;
             case VIDEO:
                 result.put("auditVideo", auditResult);
                 break;
             default:
                 break;
         }
         return result;
    }

    /*
     * 请求是否成功
     * */
    private boolean isAuditSuccess(JSONObject auditResult) {
        return auditResult != null && auditResult.getInt("code") == SUCCESS_CODE;
    }

    /**
     * 重试
     */
    private JSONObject retry(Map<String, Object> object, int errorRetryNum, AuditTypeEnum auditTypeEnum) {
        JSONObject jsonObject;
        int currentRetryNum = 1;
        while (true) {
            //再次审核
            jsonObject = sendAudit(object, auditTypeEnum);
            if (isAuditSuccess(jsonObject)) {
                //审核成功
                return jsonObject;
            } else if (currentRetryNum == errorRetryNum) {
                return jsonObject;
            }
            currentRetryNum++;
        }
    }

    /**
     * 发送钉钉报警群
     */
    private void sendDingDing() {
        dingNotify.send();
    }

}

前置和后置抽象策略类代码 (代码仅供参考)

typescript 复制代码
/**
 * @Author: pp
 * @DateTime: 2021/11/25 16:36
 * @Description: 后置策略处理审核方法
 */
public interface AuditAfterStrategy<P, A> {
    Map<String, Object> afterProcess(P p, A a);
}

/**
 * @Author: pp
 * @DateTime: 2021/11/25 16:36
 * @Description: 前置策略处理审核方法
 */
public interface AuditBeforeStrategy<P> {
    Boolean beforeProcess(P p, AuditTypeEnum auditTypeEnum, Map<String, Object> result);
}

前置抽象策略和后置抽象策略实现代码 (代码仅供参考)

typescript 复制代码
@Service
public class PrivateLetterImgBeforeStrategy implements AuditBeforeStrategy<AuditReq> {
    @Override
    public Boolean beforeProcess(AuditReq req, AuditTypeEnum auditTypeEnum, Map<String, Object> result) {
        //私信前置策略业务代码,如果前置策略通过,返回true,不通过返回false,然后封装好result进行返回
        return true;
    }
}

@Service
public class PrivateLetterImgAfterStrategy implements AuditAfterStrategy<AuditReq, AuditImg>{
    @Override
    public Map<String, Object> afterProcess(AuditReq req, AuditImg result) {
        //私信后置策略业务代码,result为审核结果,req为审核参数
         Map<String, Object> map = new HashMap<>();
         map.put("code", 200);
         map.put("auditImg", 审核结果);
         return map;
    }
}

策略工厂实现代码 (代码仅供参考)

java 复制代码
/**
 * @Author: pp
 * @DateTime: 2021/11/25 16:47
 * @Description: 策略工厂
 */
@Service
public class AuditAfterStrategyFactory {


    private final Map<ChannelEnum, AuditAfterStrategy> postProcessFactory;

    @Autowired
    public AuditAfterStrategyFactory(PrivateLetterImgAfterStrategy privateLetterImgAfterStrategy) {
        postProcessFactory = new HashMap<ChannelEnum, AuditAfterStrategy>() {
            {
                put(ChannelEnum.PRIVATE_CHAT, privateLetterImgAfterStrategy);
          
            }
        };
    }


    public AuditAfterStrategy getPostProcess(ChannelEnum channelEnum) {
        AuditAfterStrategy auditAfterStrategy = postProcessFactory.get(channelEnum);
        return auditAfterStrategy;
    }


}

这样一套实现下来,我方只需要提供一个统一接口,甲方每次提出需求,我们只需要给一个指定ChannelEnum值给甲方,然后实现抽象模版中的方法即可,如果审核中夹杂着业务逻辑,我们只需要实现不同的前置后置策略就能满足,代码冗余大大减少,屎山代码就此推平。

作为中台服务,稳定要放到第一位,其次必须遵循OCP原则,也就是一个类对于扩展是开放的,但对于修改是关闭的,合理使用设计模式,代码可读性一定要高,必要的注释必须要有。

本次分享的代码不可直接使用,只是单纯觉得这种思想还不错,所以用伪代码方式分享给大家

相关推荐
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
齐 飞3 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod3 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。4 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man4 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*4 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu4 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s4 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子4 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王5 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构