猫耳大型活动提效——组件低代码化
1. 引言
猫耳前端在开发活动的过程中,经历过传统的 pro code 阶段,即活动页面完全由前端开发编码实现,直到 2020 年接入公司内部的低代码活动平台,满足了大部分日常活动的需求,运营可自主配置活动并上线,释放了相当一部分的开发人力。不过,此时的方案仍然无法很好地服务大型直播活动场景,比如年度 S 级的直播活动,这类活动赛程多且持续时间长(可长达两周),页面和组件都包含多种状态,运营难以配置出大型直播活动的所有需求,故此阶段的大型活动仍然完全由开发编码实现,需要占用较多人力,但此类活动从数量上来看连 1% 都不到。鉴于我们已在日常活动中积累维护了相当多功能的低代码组件,如何复用已有的低代码组件来更快实现更多活动玩法就成了一个值得研究的问题。此外,大型活动从筹备到结束,时间长达数月,而密集开发阶段可能只占其中的一个月,密集开发结束后的长尾需求又该如何优化提效?接下来本文将介绍猫耳前端在活动场景的低代码探索经验,以及最终稳定的开发模式。
2. 场景分析与问题识别
2.1 不同场景的开发方案
把时间先拨回 2022 年,当时我们已有一定的平台使用经验,从技术角度将活动划分为两大类型,根据业务需求采取不同的方案:
2.1.1 配置类页面(Low Code + No Code)
此类活动经产运及技术负责人评估后,若无需开发参与则由产运同事自助配置并上线。若需要开发参与,则视情况修改组件或开发 JS 或 CSS 补丁代码来实现,沉淀下来的代码也可用于后续的类似活动。
2.1.2 开发类页面(Pro Code)
在一些玩法较为复杂、活动状态多的场景,配置页面的难度可能会呈指数级上升,当时平台也并不具备完善的预览能力,所以此类活动几乎完全由开发负责实现(部分页面可能会跳转到配置类页面)。虽然工作量较大,但简单来说就是根据设计稿、产品需求文档、接口文档这几个关键输入来实现活动页面,在流程上并没有太多可说的。
2.2 问题与尝试
回到引言的问题,在讨论更具体的解决方案之前,我们先看看在经过几次大型活动的 pro code 开发后发现了哪些问题,在前期又做了哪些尝试:
2.2.1 活动规则变更效率低
由于活动页为了保证用户体验会集成较多的玩法规则,由于运营无法在活动的密集开发阶段一次性给到准确无误或完全不变的规则说明,在活动上线前,前端需要持续配合更新页面内的活动规则,有时候是换图,有时候是调整文字描述,改动的发布流程较长且影响开发测试效率。
前期尝试:比较泛的解决方法是将前端写死的切图改为从静态资源服务中获取,我们也确实在个别活动中尝试约定过资源路径,产运将资源上传到特定路径后,开发使用约定好的路径来加载资源,但此方案并没有很好地利用现有的低代码平台(如:配置间距、管理资源版本),也存在额外的上手成本和维护成本。
2.2.2 部分常见的需求处理效率低
相似需求总是需要走开发上线的流程,导致开发人力总是紧张,且效率难提高,如:
-
每次活动都会设计新的榜单样式,这类换皮工作对开发来说耗时且枯燥,影响其他需求的开发进度
-
直播活动结束后,开发需要按业务流程固化榜单数据,每次都需要将服务端导出的数据替换到代码中,然后再走一遍上线流程
该问题在前期并没有想到很好的处理方法,直到后续复用低代码组件才得以解决。
2.2.3 重复开发组件
前端在大型活动中存在重复开发组件的情况(如:直播榜单组件)。榜单在直播活动中是标配组件,但大型活动页面状态较为复杂,无法直接通过低代码平台配置,导致这类场景我们一直没能复用运营配置的组件,于是又额外开发维护了一套 pro code 场景的榜单组件。
前期尝试:将配置了单个 low code 组件的页面通过 iframe 的形式载入页面框架内,也确实满足了一些活动需求,但同时也发现了其局限性:
-
组件若涉及弹窗和遮罩,由于弹窗和遮罩都是基于 iframe 页面插入的,难以直接基于宿主页面居中摆放弹窗,遮罩的处理也比较复杂
-
页面之间可能存在跨域的情况,个别需求得花心思解决
-
iframe 页面完整加载较慢,会重复请求宿主页面加载过的资源,部分请求也并非必要
以上提到的几个痛点,不仅影响我们每次活动的交付效率,也使得前端团队整体的资源利用率较低,前端团队总是处理类似需求得不到成长,也无法推进其它业务需求。
3. 解决方案与实施
秉持着不写新代码就没有新 bugDRY 的开发原则,作者在经过几次 pro code 活动后开始思考:是否有方案可以解决以上问题,让我们可以有精力去做其它更加价值的工作?
3.1 远程组件
2022 年 10 月,首个接入B站远程组件能力(内部名:片段加载器)的猫耳直播活动上线,本次活动在 pro code 页面框架的基础上加载了运营配置的低代码榜单组件、弹窗、活动规则等内容,解决了第一部分提及的所有问题。
3.1.1 原理
顾名思义,远程组件依赖于服务端的组件数据,其包含三大数据:组件 JS 资源、组件 CSS 资源、组件 JSON 参数(运营配置)。组件数据的拉取、组件的渲染等工作,则由客户端的加载器提供支持。
前端研发人员在 pro code 页面框架内集成加载器后,只需传入约定好的页面片段 ID,加载器即可完成页面片段的拉取工作,并复用 low code 页面的渲染器完成渲染工作。
3.1.2 项目工作流
上述编码细节转变的背后,更深层次的转变其实是「开发类页面工作流程」的转变,以及项目人员的工作职责变化。
从新的流程中不难观察到,前端能够在设计稿尚未交付的情况下就提前介入,设计一些适合低代码化的功能模块,然后转交给运营配置使用。前端不仅能够在更专注业务逻辑的情况下提前交付,运营也在复杂活动中获得了调整和发布的能力(仅限于部分功能模块),一些运营侧的调整也不必再走前端的发布流程,减少各端沟通返工的情况,项目的整体效率更高。
回到第一部分的具体问题来说,技术侧从以往活动中识别出后期需求、维护需求、效率问题后,可以借助低代码平台将流程功能化,从而实现更顺滑的交付效果。
3.1.3 接入前后的代码对比
旧的代码实现
为了更好地理解效率提升的效果,让我们先看一个典型的旧代码实现示例。这段代码展示了如何编写一个直播榜单组件,一个需要定制的 pro code 榜单通常需要 100+ 行的 CSS 代码,这也意味着前端要等设计资源完全 ready 后才能进开发(给设计也带来了压力),而在活动中需要定制的榜单往往不止一个,且每次活动都需要开发。在活动 DDL 明确的情况下,很容易给前端造成任务堆积,人力不足的问题。
新的代码实现
现在,通过采用远程加载低代码组件的方法,我们大大简化了这个过程。下面是一个使用远程加载低代码组件的示例代码。前端只需一行代码即可加载运营配置的 low code 榜单组件,无需编写繁琐的 CSS 代码,设计同事也可以调低榜单模块的优先级,优先完成其它高优的设计任务。
以下是直播榜单组件在后台系统中的界面截图,展示了运营人员如何通过界面配置而不是编写代码来完成同样的任务。
3.2 接入方案
以下是猫耳前端在接入公司内部低代码平台过程中积累的接入方案。
3.2.1 Pro Code 页面能力对齐 Low Code 页面
为确保 low code 组件能够在 pro code 页面框架内正常运行,我们将一些页面的基础能力封装到了单独的包内,使得不同环境都能够获得一致的效果。
基于 RxJS 实现组件间的数据共享与事件通信
在 low code 页面中,我们基于 RxJS 的 BehaviorSubject 实现了页面级别的数据共享方案,各组件可通过订阅 window 上挂载的 BehaviorSubject 数据流,及时获取全局数据(如:活动状态)。
而在 pro code 页面框架内,在综合考虑开发成本、性能、隔离性(不考虑沙盒环境)这几点后,所以我们直接在页面框架内复用了 low code 页面的初始化流程,确保各组件能够直接接入页面。
内置 Low Code 环境组件,优化加载速度
在 low code 页面中,我们用于初始化页面环境的组件本身也是个 low code 组件,按流程来说,在 pro code 页面内使用时也需要等待加载器的拉取渲染时间,而其余组件则要等初始化完成后才能正常渲染,所以存在一定的阻塞情况。
优化前的默认实现
以下是优化前的页面初始化时序图。
优化后的实现
优化后的时序图已经在远程组件的 3.1.1 原理小节贴过了,可以返回查看。
简而言之,我们将 low code 页面的环境组件直接集成到了 pro code 页面框架内,跟随页面一起构建发布,并且屏蔽了加载器对于环境组件的拉取和渲染逻辑,使得页面能够更快完成初始化。
Pro Code 页面维护所有 Low Code 组件配置数据,支持「合并同类组件的请求」
对于传统的 pro code 页面来说,如果出现需要请求同一接口的同类组件(如:多个音频、多个主播的信息),我们可能只需要在请求时带上 ID 数组即可,但对于 low code 页面来说,为了保证最灵活的制作能力,我们不一定会封装固定布局的组件,更倾向于封装原子组件或者较为基础的业务组件,所以运营拖入页面的可能是一个个独立的组件(同类组件,但配置了多个),每个组件都单独配置了 ID,如果不做处理的话可能会并发出非常多的请求。
对于这种场景,我们以前采取的方案是:在该 low code 组件的 JS 资源被插入执行时(此时渲染器还没有渲染组件),读取页面内配置的所有同类组件,合并配置后统一发出请求,然后借助 rxjs 缓存接口响应数据。等到组件被渲染器实际渲染出来后,再订阅该 rxjs 数据源,实现合并批量请求的目的。
如果现在需要在 pro code 页面框架内实现该场景,对比 low code 页面我们缺少了直接可用的完整组件配置,需要等所有片段都加载完才能拿到完整组件数据。不过这也不难解决,基于已有的 rxjs 数据共享流程,我们只需要对这类组件稍加改造,将直接获取所有组件的配置数据改为订阅所有组件的配置数据。当页面框架逐个获取到组件信息后,逐个塞入 rxjs subject 的组件数据列表,然后组件侧结合 rxjs debounce 操作符(避免频繁请求接口),按一样的流程请求接口获取数据即可。
以下是简化后的请求远程组件并渲染的核心流程。
fetchSegment(pageId)
.then(async (res: any) => {
if (!res || !containerRef.current) {
console.error('segment load failure')
return
}
const { info, render } = res
if (!loadedPageIds.includes(pageId)) {
loadedPageIds.push(pageId)
const newComponentsMap = makeComponentsMap(info.configure?.pageData)
window.MissEvanEvents.next((prev: any) => {
const mergedComponentsMap = Object.entries(newComponentsMap).reduce((acc, [key, value]) => {
acc[key] = (acc[key] ?? []).concat(value)
return acc
}, prev.componentsMap ?? {})
return {
...prev,
componentsMap: mergedComponentsMap,
}
})
}
render({ container: containerRef.current, needRenderEnvComponent: false })
})
.catch((e: any) => {
console.error('segment load failure', e)
})
3.2.2 规范代码块开发模式
上文在配置类页面已经提到过代码块,代码块由 JS 和 CSS 文件构成,在特定时机插入页面执行,主要执行一些 dom 操作或添加样式。在 pro code 页面框架内集成代码块时我们也做了一些优化工作。
由于代码块的效果依赖 JS 的插入执行,在按需渲染的场景会存在重复插入执行的情况,若忘记清理副作用也更容易出现问题(如:事件泄漏、定时器无法终止)。为优化该问题,我们在 JS 中临时加入了一些代码来检查逻辑是否执行过,以及定时任务是否需要执行等操作,避免出现不必要的问题,并在后续基于 document.currentScript 设计了一套代码块的生命周期函数,用于规范常用代码块的参数、执行、清理。
// 代码块被插入时的定制逻辑
function mount(dependency) {}// 代码块被销毁时的清理逻辑
function unmount(dependency) {}ContextManager.initLifecycle({ mount, unmount })
我们在 24 年 3 月份将该方案提交给活动中台并进行了比较密切的几次讨论,平台综合各个业务的需求在 24 年 9 月份上线了更加通用的平台级别的代码块开发工具,后续也仍在持续迭代。
4. 成果与效益
4.1 释放前端开发人力
鉴于活动玩法并非一成不变,不同开发者的能力也有区别,单纯的代码量或是工时人天数据并不能准确体现出低代码带来的提升,顶多用于内部预估未来活动的工作量,故以下统计的是各个具有代表性的活动低代码程度,以及组件和代码块的复用情况。由于部分需求比较零散,实际成果可能会比下表更多一些:
从表中可以发现前端自主开发了非常多的代码块,尤其是某些乍一看随便写写也挺快而且没啥复用价值的功能模块,我们也选择将其低代码化交由运营配置。这样做的好处是:不仅大部分内容可以实现脱离设计稿提前开发,还可以摆脱许多后期需求和维护需求,对前端开发来说是一个非常有利的提效模式。
从岗位职责的角度来看,也可以认为代码块开发模式(高频)是在产品正式介入组件设计前(较低频),前端提前将功能模块进行了一部分的抽象设计,便于后续将该功能开发成低代码组件。在代码块开发模式下,运营侧的体验可能弱于组件模式(平台层面已在优化这个问题,如:降低配置难度,提高开发效率),但是仍能够保持一定的交付效率,以及用户侧一致的体验。
4.2 版本管理颗粒度进一步细化
pro code 页面接入 low code 组件后,也带来了一个副产品——页面被打散,版本管理的颗粒度更细了。我们也亲身经历过线上活动出现紧急问题,产运直接调整发布,快速缩小影响范围然后开发再排查修复的情况。远程组件让我们在应急处理线上问题时又多了一种手段。
5. 结语
以上是猫耳前端对于活动低代码场景的探索和经验总结,欢迎在评论区继续讨论,一起挖掘业务提效的方法。在此感谢猫耳前端组以及 EVA 平台组一起参与建设的同事们,特别感谢 EVA 平台组的璇儿、琥珀草、谷风长道、小白白川、V、Fryderyk 等同事对我们提供的大力支持。对 bilibili 活动中台系统设计感兴趣的小伙伴可以继续移步查看这篇新活动平台建设历程与架构演进。
-End-
作者丨Helson、Rui
相关文章:

猫耳大型活动提效——组件低代码化
1. 引言 猫耳前端在开发活动的过程中,经历过传统的 pro code 阶段,即活动页面完全由前端开发编码实现,直到 2020 年接入公司内部的低代码活动平台,满足了大部分日常活动的需求,运营可自主配置活动并上线,释…...

亿级分布式系统架构演进实战(二)- 横向扩展(服务无状态化)
亿级分布式系统架构演进实战(一)- 总体概要 服务无状态化详细设计 目标:确保服务实例完全无状态,可任意扩缩容 1. 会话存储改造(Session Management) 核心问题:传统单体应用中,用…...

零成本短视频爆款制造手册
——Q版+情感+互动的流量密码拆解 适用平台:抖音/快手/视频号 核心指标:点赞率>10% | 完播率>40% | 涨粉成本<0.3元 一、底层逻辑框架 1. 爆款元素融合公式 [ 3秒钩子 ] + [ 7秒沉浸 ] + [ 5秒引爆 ] = 15秒黄金结构 │ │ └─▶ 互动指令+情感…...

红队思想:Live off the Land - 靠山吃山,靠水吃水
在网络安全领域,尤其是红队(Red Team)渗透测试中,“Live off the Land”(简称 LotL,中文可译为“靠山吃山,靠水吃水”)是一种极具隐秘性和实用性的攻击策略。这一理念源于现实生活中…...

C语言八股---预处理,编译,汇编与链接篇
前言 从多个.c文件到达一个可执行文件的四步: 预处理–>编译–>汇编–>链接 预处理 预处理过程就是预处理器处理这些预处理指令(要不然编译器完全不认识),最终会生成 main.i的文件 主要做的事情有如下几点: 展开头文件展开宏条件编译删除注释添加行号等信息保留…...

平衡二叉树(AVL树)
平衡二叉树是啥我就不多说了,本篇博客只讲原理与方法。 首先引入平衡因子的概念。平衡因子(Balance Factor),以下简称bf。 bf 右子树深度 - 左子树深度。平衡结点的平衡因子可为:-1,0,1。除此…...

SpringBoot(一)--搭建架构5种方法
目录 一、⭐Idea从spring官网下载打开 2021版本idea 1.打开创建项目 2.修改pom.xml文件里的版本号 2017版本idea 二、从spring官网下载再用idea打开 三、Idea从阿里云的官网下载打开 编辑 四、Maven项目改造成springboot项目 五、从阿里云官网下载再用idea打开 Spri…...

RabbitMQ使用延迟消息
RabbitMQ使用延迟消息 1.什么情况下使用延迟消息 延迟消息适用于需要在一段时间后执行某些操作的场景,常见的有以下几类: 1.1. 订单超时取消(未支付自动取消) 场景: 用户下单后,如果 30 分钟内未付款&a…...

MyBatis-Plus 分页查询接口返回值问题剖析
在使用 MyBatis-Plus 进行分页查询时,很多开发者会遇到一个常见的问题:当分页查询接口返回值定义为 Page<T> 时,执行查询会抛出异常;而将返回值修改为 IPage<T> 时,分页查询却能正常工作。本文将从 MyBatis-Plus 的分页机制入手,详细分析这一问题的根源,并提…...

DeepLabv3+改进7:在主干网络中添加SegNext_Attention|助力涨点
🔥【DeepLabv3+改进专栏!探索语义分割新高度】 🌟 你是否在为图像分割的精度与效率发愁? 📢 本专栏重磅推出: ✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化 ✅ 即插即用模块:ASPP+升级、解码器 PS:订阅专栏提供完整代码 论文简介 近期有关移动网络设计…...

c语言笔记 内存管理之栈内存
物理内存和虚拟内存 在c语言的程序需要内存资源,用来存放变量,常量,函数代码等,不同的内容存放在不同的内存区域,不同的内存区域有着不同的特征。 c语言的每一个进程都有着一片结构相同的 虚拟内存,虚拟内…...

分布式事务的原理
文章目录 基于 XA 协议的两阶段提交(2PC)三阶段提交(3PC)TCC(Try-Confirm-Cancel)Saga 模式消息队列(可靠消息最终一致性) 分布式事务是指在分布式系统中,涉及多个节点或…...

鸿基智启:东土科技为具身智能时代构建确定性底座
人类文明的每一次跨越都伴随着工具的革新。从蒸汽机的齿轮到计算机的代码,生产力的进化始终与技术的“具身化”紧密相连。当大语言模型掀起认知革命,具身智能正以“物理实体自主决策”的双重属性重新定义工业、医疗、服务等领域的运行逻辑。在这场革命中…...

SQL29 计算用户的平均次日留存率
SQL29 计算用户的平均次日留存率 计算用户的平均次日留存率_牛客题霸_牛客网 题目:现在运营想要查看用户在某天刷题后第二天还会再来刷题的留存率。 示例:question_practice_detail -- 输入: DROP TABLE IF EXISTS question_practice_detai…...

MWC 2025 | 移远通信推出AI智能无人零售解决方案,以“动态视觉+边缘计算”引领智能零售新潮流
在无人零售市场蓬勃发展的浪潮中,自动售货机正经历着从传统机械式操作向AI视觉技术的重大跨越。 移远通信作为全球领先的物联网整体解决方案供应商,精准把握行业趋势,在2025世界移动通信大会(MWC)上宣布推出全新AI智能…...

sparkTTS window 安装
下载 Spark-TTS Go to Spark-TTS GitHubClick "Code" > "Download ZIP", then extract it. 2. 建立 Conda 环境 conda create -n sparktts python3.12 -y conda activate sparktts 3. Install Dependencies pip install -r requirements.txt In…...

数据库原理6
1.数据是信息的载体 2.数据库应用程序人员的主要职责:编写应用系统的程序模块 3.关系规范化理论主要属于数据库理论的研究范畴 4.数据库主要有检索和修改(包括插入,删除,更新)两大操作 5.概念模型又称为语义模型。…...

接口自动化入门 —— Http的请求头,请求体,响应码解析!
在接口自动化测试中,HTTP请求头、请求体和响应码是核心组成部分。理解它们的作用、格式和解析方法对于进行有效的接口测试至关重要。以下是详细解析: 1. HTTP 请求头(Request Header) 1.1 作用 请求头是客户端向服务器发送的附加…...

tcc编译器教程6 进一步学习编译gmake源代码
本文以编译gmake为例讲解如何使用tcc进行复杂一点的c代码的编译 1 简介 前面主要讲解了如何编译lua解释器,lua解释器的编译很简单也很容易理解.当然大部分c语言程序编译没那么简单,下面对前面的gmake程序进行编译. 2 gmake源码结构 首先打开之前tcc-busybox-for-win32\gmak…...

公司共享网盘怎么建立
公司共享网盘的建立,关键在于明确使用需求、选择合适的网盘服务、搭建统一的文件管理规范、做好权限分级与安全防护。尤其要强调选择合适的网盘服务这一点,如果企业规模较大,且对协同办公的需求强烈,就需要考虑支持多人实时协作、…...

【高分论文密码】AI大模型和R语言的全类型科研图形绘制,从画图、标注、改图、美化、组合、排序分解科研绘图每个步骤
在科研成果竞争日益激烈的当下,「一图胜千言」已成为高水平SCI期刊的硬性门槛——数据显示很多情况的拒稿与图表质量直接相关。科研人员普遍面临的工具效率低、设计规范缺失、多维数据呈现难等痛点,因此科研绘图已成为成果撰写中的至关重要的一个环节&am…...

深入理解Java中的static关键字及其内存原理
static是Java中实现类级共享资源的核心修饰符,它突破了对象实例化的限制,使得变量和方法能够直接与类本身绑定。这种特性让static成为构建工具类、全局配置等场景的利器,但同时也带来独特的内存管理机制需要开发者关注。 static修饰成员变量…...

linux 系统 之centos安装 docker
对于 CentOS 安装 Docker 的前置条件 首先,需要安装一些必要的软件包, 对于 CentOS 7,可以使用以下命令: sudo yum install -y yum-utils device-mapper-persistent-data lvm2添加 Docker 仓库 设置 Docker 的官方仓库。对于 …...

Python语法核心架构与核心知识点:从理论到实践
一、Python的核心设计哲学 Python以“简洁优雅”为核心理念,遵循以下原则: # Zen of Python(输入 import this 可查看) >>> import this The Zen of Python, by Tim Peters ... Simple is better than complex. Readab…...

FreeRTOS(5)内核控制函数及其他函数
FreeRTOS 提供了一些用于控制内核的 API 函数,这些 API 函数主要包含了进出临界区、开关中断、启停任务调度器等一系列用于控制内核的 API 函数。本章就来学习 FreeRTOS 的内 核控制函数。 内核控制函数 1. 函数 taskYIELD() 此函数用于请求切换任务, …...

网络DNS怎么更改?
访问速度慢或某些网站无法打开?改变网络DNS设置可能会帮助解决这些问题。本文将详细介绍如何更改网络DNS,包括更改的原因、具体步骤。 一、为什么要更改DNS? 更改DNS的原因有很多,以下是一些主要的考虑因素:某些公共DNS服务器的响应速度比…...

VIC模型有哪些优势?适用哪些范围?基于QGIS的VIC模型建模;未来气候变化模型预测;基于R语言VIC参数率定和优化
VIC模型是一个大尺度的半分布式水文模型,其设计之初就是为了模拟大流域的水文过程;它能够计算陆地-大气的能量通量,考虑土壤性质和土地利用的影响,自带有简化的湖泊/湿地模块,也能够将植被状况,…...

脏读、不可重复读,幻读的区别 mvcc及四种隔离级别
脏读:事务a还未提交更新事务b就可以看见 不可重复读:强调修改和删除,一个事务多次查询同一个表结果不同 幻读:强调新增,也是一个事务多次查询同一个表结果不同 mvcc是用来解决读写冲突的无锁并发控制 三个实现基础&…...

SpringAI介绍及本地模型使用方法
博客原文地址 前言 Spring在Java语言中一直稳居高位,与AI的洪流碰撞后也产生了一些有趣的”化学反应“,当然你要非要说碰撞属于物理反应也可以, 在经历了一系列复杂的反应方程后,Spring家族的新成员——SpringAI,就…...

numpy广播性质
一、核心规则 一维数组本质 shape (n,)的数组是无方向向量,既非严格行向量也非列向量 自动广播机制 在矩阵乘法(或np.dot())中,一维数组会自动调整维度: 前乘时视为行向量 shape (1,n)后乘时视为列向量 shape (n,1) 二、运算类型对比 假…...