从技术债务到架构升级,滴滴国际化外卖的变革

背 景

商家营销简述

在外卖平台的运营中,我们致力于通过灵活的补贴策略激励商家,与商家共同打造良好的合作关系,也会提供多样化的营销活动,帮助商家吸引更多用户下单。通过这些活动,不仅能够提高商家的销量,还能让用户感受到实际的优惠,从而增强他们对平台的粘性。

前端技术特点
业务特点:营销场景玩法多、活动类型多、活动链路长、活动规则复杂。
中后台技术特点:活动配置表单规则多、联动复杂。
前台技术特点:终端类型多(PC/EXE/PAD/PHONE)、代码重复度高、用户输入校验规则复杂。
业务技术架构

活动链路

商家营销相关项目迭代时间较长,积累的历史技术债务越来越多,结合业务背景和以上的技术特点,之前对历史项目进行过一波代码治理,比如:多端代码复用、后台复杂度治理等。
2023年的治理更多解的是代码重复及腐化相关的问题,而外卖商家营销活动的配置能力还处于非常基础的阶段,2024年,我们结合业务新诉求的契机,从系统架构治理维度,对商家营销前端项目做了一个全面的升级。

现状
外卖商家营销活动,按活动来源区分主要有四大类:平台招商、代运营、品牌代建、商户自营销,按类型区分主要有四大类:特价菜、买赠、免配、满减。前后端底层区分这些活动渠道及类型都是case by case的形式,以招商活动创建为例:
不同活动类型,优惠信息都放在不同的字段里,特价菜是specialItemRule,免配是freeDeliveryRule,满减是reductionRule,买赠是buyGiftsRule,且规则rules字段层级嵌套冗余,字段属性没有规律可循,以下列举了两类活动的部分字段。
特价菜活动规则字段示例
"specialItemRule": [{"rulePurposeType": 0,"rules": [{"type": 3,"content": {"discountValueRange": null,"discountValueList": [10, 11, 20],"discountType": 1}}],"selectItemNumRange": {"min": 1,"max": 10},"itemPromoRangeValue": {"min": 1,"max": 50},"picLimit": 1,"priceLimit": null,"itemType": 0,"checkItemPriceDay": 7}] 满减规则字段示例
"reductionRule": [{"rulePurposeType": 0,"rules": [{"type": 1,"content": {"threshold": 10,"discount": 5}}, {"type": 1,"content": {"threshold": 20,"discount": 8}}]}] 
面临的挑战
2024年随着国际化外卖营销业务需求明显增长,比如:需要从0到1搭建连锁品牌商家自运营能力、拓展新的营销活动类型(商家券),按照现有的架构及配置能力来看,存在以下几个问题:
产品需求迭代支撑效率低:涉及通用字段,需要重复修改,特价菜+免配+满减+买赠,4种活动类型改4次,如果再算上活动渠道修改,需要再翻倍,4种渠道✖️4类活动 = 16 次。
开发遗漏:活动链路长,以当前最为复杂的招商活动为例,从运营后台配置招商计划=>商户前台报名招商活动,是一个较长的链路,由于系统数据模型不够灵活,导致修改字段及UI展示时无规律可循,经常需要梳理遗漏点。
可拓展性差:当前架构下,如果新增活动类型,则涉及全链路所有接口改动,可复用性低。
由于业务发展的契机,国际化外卖商家侧需要新增一个连锁品牌管理端,借助这个项目,我们进行了商家营销架构的升级。

解决方案

问题分析
商家营销配置能力薄弱主要体现在底层数据结构缺乏通用性和扩展性。
从全局配置维度来看:

从数据结构现状来看:

同一类活动,在不同平台(端、后台)数据结构不一致。
同一类活动,在不同活动来源场景下(自营销、招商、代运营、品牌),数据结构不一致。
四类活动,活动规则数据结构不一致,创建需要case by case拼装,详情需要case by case渲染。
差异化分支共有:自营销创建4 + 招商报名4 + 招商计划4 + 代运营1 + 品牌4 = 17。

整体思路
架构治理最重要的一环就是设计出一个统一的活动数据模型,涵盖所有平台和活动来源场景。

抽象活动实体信息
统一差异化配置
按活动信息维度拆分组织字段,而不是按业务维度拆分(收敛类型、来源)。
按通用字段概括优惠类型,而不是按业务概念枚举(收敛规则)。
支持灵活拓展

项目成果
在抽象出活动配置模型后,为保证后续需求或者人员变更能够按照规范持续迭代,通过对应的配置模型的API文档,配套前端JSON Schema校验工具,约束后续拓展。
底层数据结构完成治理后,在新项目中,我们也对配置表单方案进行了优化,使得项目的数据流转更加清晰,确保数据的一致性和可靠性。
而在前后端交互层面,对接口字段进行了运行时校验,做到了接口安全约束,避免因数据缺陷而导致的前端错误。

数据结构对比
新版活动数据结构是一个面向对象的设计架构,采用组合式领域模型设计,通过策略模式实现业务规则的动态装配。
基础活动模型(ActInfoModel)可以被视为一个父类或者超类,定义了通用的属性和行为,而其子类(如自营销活动模型selfOpsModel)继承了基础特性,并可以实现或者重写一些特定的功能,以满足不同渠道的具体需求。
当出现新的渠道或者活动类型时,只需要创建新的子类,遵循现有的父类结构。而基础模型的修改也不会影响所有子类,只需要确保子类能够适应父类的接口变化即可。


配置表单方案优化
在之前的项目里,表单间的组件通信,是传统多层组件的数据传递形式,通过父子组件层层传递。

数据流:自上而下,每个组件都需要通过props接受和传递数据。
缺点:增加了代码复杂性,每个组件都需要显式传递数据,容易出现冗余代码和数据同步问题。
这种形式对于简单的表单场景来说,比较直观,但是对于商家营销活动配置场景来说,在过往需求迭代中出现了维护困难和数据同步异常的问题,在新项目里,我们使用了配置模型+依赖注入的表单方案。

数据流: 数据通过依赖注入在组件树的各层之间传递,子组件直接获取所需数据
优点: 降低了组件之间的耦合性,减少了多层传递的冗余性,数据更加集中且易于管理
数据流转对比

使用配置模型 + 依赖注入的方式不仅可以简化数据流转,还能实现集中管理,减少代码冗余,提高数据一致性,更容易进行维护和调试,特别是在需要动态配置或复杂业务逻辑的场景下表现尤为突出。

接口安全保障
当前数据安全问题
为了避免接口数据异常,导致前端页面白屏,我们通常会在代码中加一些字段兜底逻辑,这样带来的问题:
冗余的兜底逻辑:在组件中,使用“||“操作符、可选链和解构默认值等方式进行兜底处理,导致同样的逻辑在多个地方反复出现。
复杂的数据结构处理:对于复杂的数据结构,通常为了某个字段兜底会出现一大坨繁琐的代码,影响代码可读性与代码效率。
数据类型安全问题:常规兜底形式无法保证数据类型安全,可能造成不符合预期的类型错误,进而引发应用程序中的逻辑错误或页面崩溃。
在抽象出活动配置模型后,活动配置的定义是由标准的JSON Schema描述组成的,在这个基础上,我们定义一些校验及默认填充规则,并引入集中式的兜底机制,在接口数据返回时,调用一个校验工具函数,实现统一的兜底策略。校验工具函数是借助zod这个工具库去实现的。
招商活动配置描述示例
// 招商活动规则
export const SignUpActRuleSchema = ActRuleSchema.extend({selectNumRange: z.object({min: z.number().default(0),max: z.number().default(0),}).default({ min: 0, max: 0 }),actType: z.union([z.number(), z.string()]).default(0),rule: z.array(SignUpRuleSchema).default([]),
})
export type SignUpActRule = z.infer<typeof SignUpActRuleSchema>// 招商活动详情页接口信息
export const SignUpDetailSchema = z.object({actRule: SignUpActRuleSchema.default({}),actInfo: SignUpInfoSchema.default({}),shopJoinInfo: z.array(ShopJoinInfoSchema).default([]),
}) 接口返回处理示例
// 招商活动详情接口
// useApiSchema是统一的返回数据校验工具函数
export async function getSignUpDetail(params: object = {}): Promise<SignUpDetail> {const response = await post(GET_SIGN_UP_DETAIL, params, { returnData: false })return useApiSchema<SignUpDetail>(SignUpDetailSchema, response.data, response.traceId)
} useApiSchema函数功能包含:数据校验、兜底数据填充、埋点上报。
接口返回字段中若出现返回数据类型错误或者未返回的情况,将返回自定义的默认值从而保障页面正常展示,对于错误数据也做了埋点上报,当到达一定阈值时会进行报警。

效率提升
日常迭代
活动配置模型通过字段的抽象和整合,大幅提升了字段扩展的效率。原本因各活动类型和场景的数据结构差异,需要在多处修改数据结构和组件逻辑的场景,现在只需在一处进行修改即可,大大提高了开发效率。
以前台项目活动规则相关迭代为例:

开发实例
以近期需求为例,我们需要新增一种券活动类型,通过采用活动配置模型和集中式状态管理的开发形式,使得开发过程中对于数据相关的处理逻辑与状态管理要比之前简易很多,开发效率提升约40%。

后续规划
以上架构治理都是针对新的项目去做的实践,而对于国际化外卖商家营销前端的其他项目同样需要做架构升级改造,后续我们计划收敛运营后台的活动配置,将最为复杂的招商活动链路进行标准化,后台配置=>前台应用,引用同一套数据模型。
国际化外卖商家营销前端架构预期


往期文章回顾


致谢
核心开发:杜雨轩、闫莉
关键合作方:宋亚阁、陈珏、吴召学
项目指导:董亚杰
整体方案的产出到落地实践离不开以上同学的辛苦付出,借此机会表示由衷感谢!同时,也非常感谢能够耐心阅读到这里的读者,业务技术架构治理是一个持续的过程,需要持续的关注、投入与调整,也需要与业务深度融合,以实现业务与技术目标的双赢。
欢迎感兴趣的同学拍砖指导,一起交流讨论!小编将选取5位同学,送上20W无线充电器!

// E N D //
相关文章:
从技术债务到架构升级,滴滴国际化外卖的变革
背 景 商家营销简述 在外卖平台的运营中,我们致力于通过灵活的补贴策略激励商家,与商家共同打造良好的合作关系,也会提供多样化的营销活动,帮助商家吸引更多用户下单。通过这些活动,不仅能够提高商家的销量,…...
DeepSeek教unity------MessagePack-05
动态反序列化 当调用 MessagePackSerializer.Deserialize<object> 或 MessagePackSerializer.Deserialize<dynamic> 时,二进制数据中存在的任何值都将被转换为基本值,即 bool、char、sbyte、byte、short、int、long、ushort、uint、ulong、…...
SQL Query美化
推荐一个可以美化Query的网站! 名称:SQL formatter | Online free SQL Beautifier 地址:https://sqlformatter.org/# 在处理 SQL 查询语句时,可读性是至关重要的。 杂乱无章的 SQL代码不仅难以理解,还会给后续的维…...
探索RDMA技术:从基础到实践
1. 引言 在当今的高性能计算(HPC)和数据中心领域,数据传输的效率和速度至关重要。RDMA(Remote Direct Memory Access,远程直接内存访问)技术作为一种高效的网络通信机制,能够显著减少数据传输的延迟和CPU负载。本文将从基础到实践,详细介绍RDMA技术及其编程模型,帮助…...
2025 docker可视化管理面板DPanel的安装
1.什么是 DPanel ? DPanel 是一款 Docker 可视化管理面板,旨在简化 Docker 容器、镜像和文件的管理。它提供了一系列功能,使用户能够更轻松地管理和部署 Docker 环境。 软件特点: 可视化管理:提供直观的用户界面&#…...
mapbox V3 新特性,添加下雪效果
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象…...
【STM32】H743的以太网MAC控制器的一个特殊功能
调试743的MAC,翻阅手册的时候,发现了一个有意思的功能 混杂模式 H743的MAC控制器,可以设置为混杂模式,这就意味着它可以做一些网络监控的应用,譬如连接具备端口镜像功能的交换机,然后直接代替PC实现网络数据…...
WEB攻防-第60天:PHP反序列化POP链构造魔术方法流程漏洞触发条件属性修改
目录 一、序列化与反序列化基础 1.1 什么是序列化与反序列化 二、魔术方法的生命周期 2.1 常见的魔术方法 2.2 模式方法的生命周期触发调用 2.2.1 __construct() 2.2.2 __destruct() 2.2.3 __sleep() 2.2.4 __wakeup() 2.2.5 __invoke() 2.2.6 __toS…...
STM32硬件SPI函数解析与示例
1. SPI 简介 SPI(Serial Peripheral Interface)即串行外设接口,是一种高速、全双工、同步的通信总线,常用于微控制器与各种外设(如传感器、存储器等)之间的通信。STM32 系列微控制器提供了多个 SPI 接口&a…...
如何设置Python爬虫的User-Agent?
在Python爬虫中设置User-Agent是模拟浏览器行为、避免被目标网站识别为爬虫的重要手段。User-Agent是一个HTTP请求头,用于标识客户端软件(通常是浏览器)的类型和版本信息。通过设置合适的User-Agent,可以提高爬虫的稳定性和成功率…...
二、交换机的vlan子设备接入
一、交换机的vlan设置-CSDN博客 二、交换机的vlan子设备接入-CSDN博客 接上篇的文章,本文接入了子设备 网络结构如下: 用路由器A和POE交换机B代替第一篇中的笔记本电脑,路由器A和交换机B都关闭DHCP服务,并分别接入一个IPC&#…...
Spring IoC的实现机制是什么?
大家好,我是锋哥。今天分享关于【Spring IoC的实现机制是什么?】面试题。希望对大家有帮助; Spring IoC的实现机制是什么? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring IoC(Inversion of Control…...
配置mysql8.0使用PXC实现高可用。
准备好下面三台服务器 cat >> /etc/hosts << EOF 192.168.1.11 pxc1 192.168.1.12 pxc2 192.168.1.13 pxc3 EOF 三台服务器同时进行,下载安装包 [rootlocalhost ~]#yum module disable mysql [rootlocalhost ~]#yum ins…...
对openharmony HDF驱动框架的C/S设计模式和单例类的说明
在分析openharmony的HDF驱动框架时我们会发现用了很多面向对象的思想,例如类继承、接口、单例类等,本来应该是好事情,**但使用时对象之间的关系交错复杂,不太符合linux内核分层分模块的思路,导致整体理解起来比较困难&…...
联合汽车电子嵌入式面试题及参考答案
所使用的板子 Flash 内存是多少,单位 b 指的是 byte 还是 bit? 不同的嵌入式板子具有不同的 Flash 内存容量。常见的有几 KB 到几 MB 甚至更大。比如一些小型的单片机开发板可能只有几 KB 的 Flash,如 AT89C51 单片机的 Flash 一般为 4KB,这里的 KB 是指千字节(kilobyte)…...
Vue 2 路由指南:从基础到高级
注意:对于代码看不清的部分,用鼠标选中就能看到了,背景颜色和字体颜色过于接近,我也不知道怎么调,只能这样子先看着了 一、Vue Router 是什么? Vue Router 是 Vue.js 官方的路由管理器,它允许…...
vue学习10
1.GPT和Copilot Copilot Tab接受 删除键,不接受 ctrlenter更多方案 更适合的是修改方向 const submitForm async () > {//等待校验结果await formRef.value.validate()//提交修改await userUpdateInfoService(form.value)//通知user模块,进行数据更…...
WebSocket 握手过程
文章目录 1. WebSocket 握手过程概述2. 客户端发送握手请求3. 服务器响应握手请求4. 客户端验证握手响应5. 建立 WebSocket 连接6. 安全性与注意事项7. 应用示例 在现代 Web 开发中,WebSocket 协议因其高效的实时通信能力而被广泛应用。WebSocket 允许客户端和服务器…...
如何正确安装Stable Diffusion Web UI以及对应的xFormers
本文是我总结的步骤,验证了几次保证是对的。因为正确的安装 Stable Diffusion Web UI 以及对应的 xFormers 实在是太麻烦了,官方和网上的步骤都是残缺和分散的,加上国内网络速度不理想,所以需要一些额外步骤,之前研究出…...
图形渲染(一)——Skia、OpenGL、Mesa 和 Vulkan简介
1.Skia —— 2D 图形库 Skia 是一个 2D 图形库,它的作用是为开发者提供一个高层次的绘图接口,方便他们进行 2D 图形渲染(比如绘制文本、形状、图像等)。Skia 本身不直接管理 GPU 或进行底层的渲染工作,而是通过 底层图…...
从源代码编译构建vLLM并解决常见编译问题
源代码构建vLLM 前言构建vLLM异常问题异常1异常2异常3 构建成功 前言 在通过创建全新虚拟环境条件下,使用pip install vllmx.x.x.方式安装VLLM后,遇到了VLLM使用方面的异常,经过多种方式尝试解决,最终无果。 仔细查看官方文档后&…...
SQL-leetcode—1683. 无效的推文
1683. 无效的推文 表:Tweets ----------------------- | Column Name | Type | ----------------------- | tweet_id | int | | content | varchar | ----------------------- 在 SQL 中,tweet_id 是这个表的主键。 content 只包含美式键盘上的字符&am…...
轻量级TinyXml2的应用
TinyXml2库基本介绍 TinyXML2 是 simple、small、efficient 的基于DOM (Document Object Model,文档对象模型) 的开源 C XML文件解析库,可以很方便地应用到现有的项目中 。目前,TinyXML1 开发已经停止,所有…...
DeepSeek正重构具身大模型和人形机器人赛道!
中国人工智能公司DeepSeek(深度求索)以“低成本、高效率、强开放”的研发范式横空出世,火遍并震撼全球科技圈;DeepSeek展现出来的核心竞争力,除了低成本及推理能力,更重要的是开源模型能力追赶上了最新的闭…...
centos7 升级openssl并安装python3
参考文章:https://www.cnblogs.com/chuanzhang053/p/17653635.html 卸载已有版本 yum remove -y openssl openssl-devel下载1.1版本 wget https://www.openssl.org/source/openssl-1.1.1v.tar.gztar -zxf openssl-1.1.1v.tar.gz 查看openssl.conf文件的目录 fin…...
Linux库制作与原理:【静态库】【动态库】【目标文件】【ELF文件】【ELF从形成到假造轮廓】【理解链接和加载】
目录 一.什么是库 二.静态库 2.1创建静态库 我们在之前的路径下新建lib使用我们自己的库 2.2 使用makefile生成静态库 三.动态库 3.1动态库生成 3.2动态库使用 3.3库运行搜索路径 四.目标文件 五.ELF文件 六.ELF从形成到加载轮廓 6.1ELF形成可执行 6.2 ELF可执行文…...
2025前端面试题
2025前端面试题 uniappuniapp如何打包发版到线上 vuekeep-alive 有哪几个生命周期vue3构建项目vue如何封装组件vue2的响应式原理vue3的响应式原理vue3和2的区别Vuex中的重要核心属性有哪些?Vue-router有哪几种路由守卫 es6数组去重的方法slice与splice的区别数组有哪…...
Win7本地化部署deepseek-r1等大模型详解
参考链接 在Windows 7操作系统,基于llama.cpp本地化部署 deepseek-r1模型的方法 2025-02-08 2G内存Windows7运行deepseek-r1:1.5b 这两个链接写的可能不够详细,有同学私信问实现过程,这里进一步解释一下。 一、准备 需要准备的大模型、工具…...
【ubuntu24.04】 强制重启导致大模型的磁盘挂载出错
挂载NTFS文件系统出错 各种模型放在了这个机械硬盘上,虽然速度慢,但是好在容量大。大模型在工作,但是程序看起来有问题,导致系统卡死了,然后我重启了,然后报错:wrong fs type bad option &…...
Spring Boot(8)深入理解 @Autowired 注解:使用场景与实战示例
搞个引言 在 Spring 框架的开发中,依赖注入(Dependency Injection,简称 DI)是它的一个核心特性,它能够让代码更加模块化、可测试,并且易于维护。而 Autowired 注解作为 Spring 实现依赖注入的关键工具&…...
