文章 / 从硬编码到配置化——星辉广告系统的同步引擎 V2 改造实录
案例复盘

从硬编码到配置化——星辉广告系统的同步引擎 V2 改造实录

2026.07.06 · 约 2800 字

2025 年底,星辉广告系统的同步引擎到了必须重构的临界点。原因是每个上游供应商的 API 都需要一份独立的适配代码——新增一个供应商,就要写一遍 HTTP 请求、参数拼装、鉴权、分页、字段映射、写入逻辑。当时系统接了 5 家上游,对应的同步代码已经膨胀到 3000 多行,每接一家新供应商,开发周期至少两到三周。

而业务那边又在催:下个月要再接入两家。

这不是一个技术难题,这是一个架构问题——我们把应该配置化的东西写死在了代码里。

一、V1 的问题

先看清楚 V1 为什么撑不下去了:

硬编码的代价

V1 的同步代码大致长这样——每个供应商一个文件,每个文件里重复着 80% 相似的逻辑:

// 供应商 A 的同步代码
func SyncSupplierA() error {
    url := "https://api.supplier-a.com/v1/report"
    headers := map[string]string{
        "Authorization": "Bearer " + cfg.APIKeyA,
    }
    params := buildParams(date, "daily")
    resp, _ := httpGet(url, headers, params)
    rows := parseResponseA(resp)  // 每个供应商的解析逻辑不同
    for _, row := range rows {
        db.Exec("INSERT INTO stats ...", row)
    }
}

// 供应商 B:同样的结构,不同的 URL、鉴权、解析
func SyncSupplierB() error { ... }

这种模式的问题很明显:

二、V2 的设计——三级配置化

重构的核心思路很简单:把变化的抽成配置,把不变的固化成引擎

经过几轮迭代,最终确定了三级配置结构:

V2 同步引擎架构:

┌─────────────────────────────────────────────┐
│             同步引擎(通用逻辑)              │
│  HTTP 请求 / 鉴权 / 分页 / 重试 / 错误处理   │
│  字段映射 / 数据清洗 / 写入                  │
└──────────────────┬──────────────────────────┘
                   │
        读取配置   │
                   ▼
┌─────────────────────────────────────────────┐
│  一级:供应商配置(supplier_sync_config)      │
│  └─ base_url、鉴权方式、请求频率              │
│                                              │
│  二级:端点配置(sync_endpoint)              │
│  └─ API路径、参数风格、分页方式               │
│                                              │
│  三级:字段映射(sync_field_mapping)         │
│  └─ 上游字段名 → 系统字段名、类型转换         │
└─────────────────────────────────────────────┘

一级:供应商配置

定义"怎么连"——每个上游 API 的基本连接信息:

// 供应商级配置表(简化)
supplier_sync_config {
    id          INT PRIMARY KEY,
    name        VARCHAR(64),        // 供应商名称
    base_url    VARCHAR(255),       // API 基础地址
    auth_type   ENUM('none','header_auth','query_key'),
    auth_key    VARCHAR(128),       // 鉴权字段名
    auth_value  VARCHAR(256),       // 鉴权密钥(加密存储)
    interval_m  INT,                // 同步间隔(分钟)
    enabled     TINYINT(1),
}

二级:端点配置

定义"请求什么"——数据在哪个 API 路径、怎么分页:

// 端点级配置表(简化)
sync_endpoint {
    id              INT PRIMARY KEY,
    supplier_id     INT,            // 关联供应商
    name            VARCHAR(64),    // 端点名称(daily/unit/hourly...)
    path            VARCHAR(255),   // API 路径
    param_style     ENUM('query','json_body'),
    page_param      VARCHAR(64),    // 分页参数名
    page_size       INT,            // 每页大小
    date_param      VARCHAR(64),    // 日期参数名
    date_format     VARCHAR(32),    // 日期格式
}

三级:字段映射

定义"字段怎么对"——上游返回的 JSON 字段名映射到系统字段:

// 字段映射配置表(简化)
sync_field_mapping {
    id          INT PRIMARY KEY,
    endpoint_id INT,                // 关联端点
    source_key  VARCHAR(128),       // 上游 JSON 中的字段名
    target_key  VARCHAR(128),       // 系统数据库字段名
    data_type   ENUM('string','int','float','date'),
    transform   VARCHAR(64),        // 可选:转换函数名
    required    TINYINT(1),         // 是否必填
}

三、引擎的核心逻辑

配置建好了,引擎要做的就是通用的"读取配置 → 组装请求 → 调用 API → 解析响应 → 映射字段 → 写入库":

// V2 引擎核心流程(简化伪代码)
func SyncByHTTPAPIV2(endpointID int) error:
    // 1. 读取三级配置
    supplier  = loadSupplierConfig(endpointID)
    endpoint  = loadEndpointConfig(endpointID)
    mappings  = loadFieldMappings(endpointID)

    // 2. 按日期范围分批执行
    for each day in dateRange:
        // 3. 组装请求参数
        params = buildParams(endpoint, day)

        // 4. 发送 HTTP 请求
        resp = httpGet(supplier.baseURL + endpoint.path,
            auth=supplier.auth,
            params=params)

        // 5. 解析响应(通用 JSON 解析)
        rows = parseResponse(resp)

        // 6. 字段映射
        for each row in rows:
            mapped = applyMappings(row, mappings)

            // 7. 清洗 + 写入
            cleaned = clean(mapped)
            writeToDB(cleaned)

    // 8. 记录同步状态
    saveSyncLog(endpointID, date, status)

这里面每一步都是通用的——API 响应格式不同的场景,通过 动态 JSON 解析器来兼容:引擎自动识别响应是列表还是嵌套结构,根据字段映射配置提取数据。

关键设计: 引擎本身不做任何"供应商特定"的决策。新增一个供应商 = 在配置表里 INSERT 三条记录(供应商 + 端点 + 字段映射)。零代码改动,零部署。

四、改造的效果

V2 上线后的变化是立竿见影的:

指标V1V2
新增供应商周期2-3 周1-2 天
供应商相关代码行数~3000 行(5 家)~200 行引擎 + 配置表数据
新增供应商修改文件数4-6 个0 个(只需 INSERT)
同步失败率~5%(各供应商处理不一致)<1%(统一错误处理+重试)

更重要的是,配置化带来的是能力边界的变化。V1 时代,新增供应商是一个开发任务——要排期、要测试、要部署。V2 时代,新增供应商是一个运营任务——运营同学在管理后台配好表就能上线。这彻底改变了团队的协作模式。

五、踩过的坑

这次改造并非一帆风顺。记录几个关键的教训:

最大教训: 不要追求"一次设计到位"。V2 的第一个版本只覆盖了 80% 的场景,剩下的 20% 通过后续迭代逐步补齐。如果一开始就想覆盖所有场景,V2 可能到现在还没上线。

六、总结

从 V1 到 V2 的核心变化:

   硬编码                   → 配置化
   每个供应商一个文件        → 三级配置表
   新增 = 开发任务           → 新增 = 运营任务
   3000 行供应商代码         → 200 行通用引擎
   2-3 周接入周期            → 1-2 天

三条经验:
1. 把变化的抽成配置,把不变的固化成引擎
2. 先做 80%,剩下的迭代补齐
3. 配置化改变的不只是代码结构,还有协作模式

这次改造最大的收获不是代码量减少了,而是系统能力的边界扩展了。当一个之前需要两到三周才能完成的事情变成一两天就能搞定,你的业务选择空间也随之变大。这大概就是架构设计的价值——它不直接产生功能,但它决定了你能以多快的速度产生功能。

💬 评论

加载中...
请友善发言