拒绝 If-Else 屎山:利用适配器模式(Adapter)构建第三方登录的“防腐层”实战

前言

做过第三方对接的兄弟都知道,接一个是享受,接三个是工作,接十个那就是灾难。

最近在负责公司核心的多渠道第三方登录与用户信息同步模块。我们面临的现状是:业务方要求接入七八个外部渠道(包括某头部电商、某保险平台、某社交巨头等)。

起初我觉得没啥,不就是调个 API 拿个 JSON 吗?结果拿到接口文档我就裂开了:

  • A 渠道:姓名分了"汉字"和"拼音/片假名"两个字段返回;性别用 1/2 表示。

  • B 渠道:姓名是一个合并的长字符串;性别直接给的是 male/female。

  • C 渠道:日期格式甚至是 YYYY/MM/DD,跟我们库里的 Date 完全不兼容。

如果直接在业务核心代码里写 if ("ChannelA".equals(type)) 然后去解析数据,那这块代码不出一个月就会变成无人敢动的"屎山"。

所以这里使用了适配器模式(Adapter Pattern)来构建一层防腐层(ACL, Anti-Corruption Layer)。

什么是防腐层?

我们就是DDD架构,DDD架构中的防腐层。就是防止外部的其他逻辑,污染内部的核心逻辑。你要学很多语言,不能学一个语言就写到家规里,你得有一个翻译官,翻译官就是防腐层。

其实说人话就是:别让外面的脏数据,污染了我们内部的代码。

外部渠道的数据结构是他们定的,千奇百怪;但我们系统内部的 User 模型是标准的。我需要一个中间人,把那些乱七八糟的数据,"清洗"成我们看着舒服的标准模样,然后再放进业务逻辑里。

这个"中间人",就是适配器。 适配器 + 转换数据的逻辑就是防腐层。

具体落地

第一步:定义通用的"转换器"标准

首先,利用 Java 的泛型,定义一个所有适配器都必须遵守的接口。 S 代表 Source(外部的脏数据),T 代表 Target(内部的标准数据)。

java 复制代码
/**
 * 外部用户信息适配器接口
 * @param <S> Source: 外部渠道返回的原始对象
 * @param <T> Target: 内部标准用户模型
 */
public interface ExternalUserInfoAdapter<S, T> {
    /**
     * 核心转换方法:把脏数据变成标准数据
     */
    T adapt(S source);
}

第二步:编写具体的渠道适配器

以那个"姓名分段、性别用英文"的某电商渠道为例。我写了一个专门的 Adapter 类,把脏活累活全封装在这里。

注意这里加了 @Component,交给 Spring 管理。

java 复制代码
@Component
public class ChannelAUserInfoAdapter implements ExternalUserInfoAdapter<ChannelAUserResponse, StandardUserDTO> {

    @Override
    public StandardUserDTO adapt(ChannelAUserResponse source) {
        if (source == null) return null;

        StandardUserDTO target = new StandardUserDTO();

        // 1. 处理姓名:该渠道把名字拆成了两部分,我们需要分别映射
        // 比如 source.getKanjiName() -> 映射到内部的 givenName
        // source.getKanaName() -> 映射到内部的 localGivenName
        target.setGivenName(source.getKanjiName());
        target.setLocalGivenName(source.getKanaName());

        // 2. 处理性别:把字符串转成内部枚举
        // 外部是 "male"/"female",内部是 GenderEnum
        if ("male".equalsIgnoreCase(source.getGenderStr())) {
            target.setGender(GenderEnum.MALE);
        } else if ("female".equalsIgnoreCase(source.getGenderStr())) {
            target.setGender(GenderEnum.FEMALE);
        } else {
            target.setGender(GenderEnum.UNKNOWN);
        }

        // 3. 处理日期:字符串转 Date 对象
        // 这里省略具体的 DateUtil 解析代码
        target.setBirthday(DateUtil.parse(source.getBirthStr()));

        return target;
    }
}

第三步:在业务层清爽地调用

有了适配器,Service 层的代码简直干净得令人感动。我完全不需要关心对方是给的 male 还是 1,我只管拿标准对象。

java 复制代码
@Service
public class ChannelAUserHandler {

    @Autowired
    private ChannelAUserInfoAdapter adapter; // 注入上面的适配器

    public void syncUser(String token) {
        // 1. 调用外部 RPC/API 拿到原始数据(脏数据)
        ChannelAUserResponse rawData = thirdPartyClient.getUserInfo(token);

        // 2. 一行代码完成"清洗"
        // 业务层不需要任何 if-else 来判断性别格式,适配器都搞定了
        StandardUserDTO standardUser = adapter.adapt(rawData);

        // 3. 执行核心业务(入库、更新Session等)
        userRepository.saveOrUpdate(standardUser);
    }
}

进阶思考:适配器模式 vs 策略模式

在做代码 Review 的时候,有同事问我:"这玩意儿跟策略模式(Strategy Pattern)看着很像啊,都是接口+实现类,为啥叫适配器?"

这也是面试中常考的点,我是这么区分的:

  1. 意图不同(事后 vs 事前)

适配器(Adapter)是"事后补救":两边的接口(外部API vs 内部模型)已经定死了,谁都改不了,且死活对不上。这时候只能插一个适配器在中间做胶水。

策略(Strategy)是"事前规划":我要做一件事(比如登录),但有多种做法(A渠道登录、B渠道登录)。我定义一个策略接口,可以在运行时灵活切换算法。

  1. 职责不同(数据 vs 行为)

适配器关注"数据流转":看我的 adapt 方法,输入是 A,输出是 B。它只负责类型转换(Type Conversion),不应该包含复杂的业务逻辑(比如查库、发短信),它是无副作用的。

策略关注"业务行为":策略模式的 execute 方法通常包含具体的业务逻辑。

在我的项目中,ChannelAUserInfoAdapter 负责把数据变身(适配器),而 ChannelAUserHandler 负责具体的登录流程控制(这其实更像策略模式的使用场景)。一个负责变身,一个负责打仗。

拓展 - 怎么区别不同的渠道

在API设计层面,把对应的渠道标识放到请求路径里,通过@PathVariable 获取字符串。

每个渠道的策略器中实现接口方法,getChannel。

然后把每个策略放到Factory中,Factory中实际就维护一个Map<String,Adapter> 然后根据getChannel获取String,自动找到对应的适配器。

核心逻辑,Service层只有三行。

  1. 调对面接口;
  2. 适配器转换,转换成我们系统的格式;
  3. 入库等业务操作;

总结

通过引入这层防腐层,我们后续再接入新渠道时,核心业务代码一行都不用改,只需要新增一个 XxxAdapter 类即可。

经过测算,这一波重构将新渠道的接入效率提升了约 40%。更重要的是,它把脏数据隔离在了核心业务之外,这让代码维护起来心里踏实多了。

相关推荐
learning-striving2 小时前
kali默认桌面Xfce切换为GNOME桌面
linux·运维·服务器·kali
源远流长jerry2 小时前
dpdk之kni处理dns案例
linux·网络·网络协议·ubuntu·ip
黄昏晓x2 小时前
Linux----进程控制
android·linux·运维
郝亚军2 小时前
ubutnu 64位系统,需要安装的工具包
linux·运维·ubuntu
EmbedLinX2 小时前
嵌入式Linux C++常用设计模式
linux·c++·设计模式
AI Echoes2 小时前
LangChain Runnable组件重试与回退机制降低程序错误率
人工智能·python·langchain·prompt·agent
AAD555888992 小时前
YOLO11-Seg+ContextGuided:智能交通流量估算与拥堵检测实战指南
python
吕司2 小时前
Linux系统安装MySQL
linux·运维·服务器
rose and war2 小时前
python和jinja版本问题导致的访问报500
python·ios