这次重构早在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原则,也就是一个类对于扩展是开放的,但对于修改是关闭的,合理使用设计模式,代码可读性一定要高,必要的注释必须要有。
本次分享的代码不可直接使用,只是单纯觉得这种思想还不错,所以用伪代码方式分享给大家