星创编辑器在投放业务中的落地|得物技术

搭建一个落地页需要涉及到多方合作,需要不断地进行沟通协调。繁杂的流程需要耗费很多的时间,因此我们推动产品重新搭建了一个专门服务于软广投放流程的编辑器——星创,完成广告搭建在投放业务各系统中的闭环。

一、落地页技术架构

名词解释

模板:就是投放业务人员配置一个完整的落地页。

站外落地页:投放在媒体,站外的用户看到的落地页。

站内落地页:用户点击站外落地页,进入App站内看到的H5页面。

编辑器:是一个提供投放业务人员的搭建平台。由(画布+设置器)+生成器组成,搭建的数据源是运营常用落地页模板,模板的组成又分为基础模板、玩法模板、定制化模板,实体间遵循的通信协议是DSL。

技术架构图

落地页的大体功能可以参考下面整体的架构图。

整体的架构包含应用层、B端编辑器SDK、C端Node渲染层、以及基于nest.js数据服务存储层。

图片

下面我将从基础框架、模版类型配置、模版渲染3个方面阐述落地页编辑器的技术选型思路。

基础框架搭建

我们是基于pnpm+monorepo+turbo重新搭建的一个新项目。因为我们涉及到3个端的应用,有SSR、nest.js后端服务、前端H5 editor编辑器应用。整体之前肯定会存在一些代码共用,通用的逻辑、通用的utils。如下面图所示。

图片

再说这个架构的好处,就是在开发环境下,如果需要新增一个外投组件,如果不是这种架构,是普通的多项目架构的话,本地调试需要启动两个项目,这是bad case。再monorepo的架构下,只需要在compoents新增一个物料,然后node和B端H5复用这个物料代码,从components去引用这个组件就可以了。在开发环境调试的时候,只需要启动editor这个H5项目,而不是需要启动,H5项目和SSR项目,提高了开发体验。还有就是复用公共的基建,公共的配置、公共的打包。

二、模板JSON设计

这里说的模板,就是投放业务人员配置一个完整的落地页。

编辑器的低代码协议还是以JSON为主,主要设计了模板类型,模板基础配置,模板可变配置、模板通用能力配置。

如下面所示

{
   // 模板对应的类型
   templateType: string, 
   // 模板变化的配置
   defaultConfig: {
   }, 
   // 模板可变配置
   variableConfig:{}

   // 模板不变的配置
   globalConfig: {

   },   
}

这里这么设计的考虑有下面👇🏻几点:

新增模板类型

关于为什么新增模板类型,我给出了下面关于投放业务的思考。

  • 是为了渲染层对于不同模板类型走不同的渲染模式。比如静态化的广告落地页,不涉及请求接口的。统一走SSG或者ISR的渲染模式。如果是依赖接口请求的落地页,走SSR或者是react 18的流式渲染RSC。

因为我们需要支持多种渲染模式,所以在一开始做SSR技术选型的时候,优先选择了next 14版本,可以后面更好地支持业务。

  • 不同的渲染模式,打包出来的产物不同,性能优化的策略侧重点也是不一样的。

  • 之前投放地落地页链接都是xx-plus/xxx/xx,没法一眼看出当前落地页属于哪个类型,由于我们需要做精细化的落地页数据归因,所以我们存储了落地页的模板类型,所以最后投放链接都是这样。

https://cdn-xx.xx.com/xx-plus/{{模板类型}}/{{模板ID}}
  • 批量修改场景:过去投放落地页会有在节假日进行批量换图、批量修改配置的需求。过去在哪吒中中需要投入大量的维护成本,我们将落地页的类型维度单独抽象出来,通过支持对类型的批量修改来进一步提效。

抽象模板配置

globalConfig:主要是落地页的通用配置,是一个保留字段,主要是对落地页某个类型,同样添加某些功能,比如自动换端、自动全屏点击、一些投放策略的优化、 页面配置信息……

defaultConfig:主要是模板的基础配置,就是运营新建一个模板,这里配置已经不需要填了,已经由框架侧内置,进一步节省运营的配置时间。

variableConfig:主要是模板的可变配置,也就是运营经常会配置的。

我这里上一张图方便大家理解下:

图片

如上图所示:

之前落地页都通过内部的A搭建器进行搭建。A搭建器为了保持通用性,提供大量配置,从实践上看,配置的数量和复杂性,对投放运营来说是非常大的心智负担。因此在设计星创编辑器时,我们将符合业务特点的经验配置直接做了内置,并且简化了大量配置。投放业务人员只需要做一些简单的配置,就可以完成落地页的搭建。

图片

我举其中一个例子我们来看下对比,配置商品流落地页简化到了只需要选择自己的商品ID就已经完成了站内站外落地页搭建。

图片

我们已经内置了站内落地页,创建一个站外落地页,会自动把站内落地页同时创建,然后塞到站外落地页的还原链接里。

图片

模板渲染

实际渲染的数据是defaultConfig和variableConfig进行对象深度合并,形成最终落地页所SSR渲染需要的数据。对于落地页模板的通用能力,比如星创落地页需要支持自动换端,那么这个配置显然这是落地页的通用能力,对于这个配置我们是放在globalConfig中,开启了表示落地页支持自动换端。

我这里对容器的定义,就是根据模板类型,选择不同的渲染容器。目前支持的容器,有静态容器,和动态容器。

静态容器,顾名思义也就是在构建的时候,或者是根据接口数据能够知道当然渲染的模板数据。但是动态容器也就是没法在SSR侧知道渲染哪一个组件,比如我们的AB外投测试落地页,至于最终渲染哪个落地页需要在客户端才知道当前用户命中的是哪一个组件。

我整理了一些流程图方便大家理解:

图片

三、后台权限设计

编辑器的node服务主要有4大块,投放业务人员搭建、内部接口http调用,SSR渲染调用,对应不同的网关。

如下图所示我在nest基础上了,用了gurad路由守卫,自定义user装饰器,为了方便获取用户信息,和打关键用户行为日志。

图片

四、B端编辑器的选型思考

关于B端编辑器主要有2块,第一个是B端的画布、第二个是BC端组件配置如何映射,第三个编辑器的工具栏。

画布配置预览

这里由于我们的业务是外投的落地页C端页面,主要是移动端H5。所以整体的画布功能和PC端画布功能是取舍的。H5不需要特别复杂的功能,比如拖拽、样式、resize……唯一的要求做到所见就所得的就好了。

当时做画布配置预览的时候主要考虑了两种技术方案,一种是ifrmae去做整个编辑器,配置预览。第二种是组件、配置支持动态化。以远程组件的方式进行加载。我下面分别分析一下2种方案的优缺点。

动态远程组件

整体的流程大概分这几步:

  1. 先有一个组件。

  2. 将组件打包成UMD格式,可供浏览器使用(后面会介绍UMD),或者使用System.js去打包。

  3. 将其上传到CDN,然后还需要一个物料管理平台,主要管理组件的版本、支持注册、可回滚。

  4. 编辑器沙箱能力的考虑,防止污染全局。

  5. SSR根据组件类型进行动态渲染。

伪代码如下

const DynamicComponent = ({name, children, ...props}) => {
  const Component = useMemo(() => {
    return React.lazy(async () => fetchComponent(name))
  }, [name])

  return (
    <Suspense
      fallback={
        <div style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
          <span style={{fontSize: 50}}>Loading...</span>
        </div>
      }>
      <Component {...props}>{children}</Component>
    </Suspense>
  )
}

export default React.memo(DynamicComponent)

这里使用到了React中的Suspense组件和React.lazy方法,关于他们的用法这里不做过多解释,整个DynamicComponent组件的含义是远程加载目标组件,这个过程该组件会渲染传入Suspense参数fallback之中的内容,最后会使用加载成功的组件进行替换。

优点:

  • 动态性(当组件需要更新时,可以直接JS内容,就可以实现动态更新)。

  • 不确定性(对于主应用而言,其不知道用户会拖拽多少个组件以及每个组件长什么样,它只需要将用户拖拽的JSON数组进行循环遍历,并渲染,然后将配置的属性传递过去就可以了,具体到每个组件具体是长什么样其不关心)。

劣势:

  • 我们一个落地页的组件类型的是确定的,在编辑器配置落地页,只有选择模板类型,选择完模板类型,自动会加载模板对应的组件配置。不存在用户拖拽、选择。星创落地页产品的核心逻辑就是轻量。

Iframe

Iframe也就是中间预览的画布是SSR提高一个预览url。

只需要在node层对每个落地页类型,都新增一个预览preview路由和正式投放路由做区分。这么做的好处,预览路由和正式路由,在逻辑上解耦。可以做一些在node做一些定制化的业务预览逻辑。比如飞书审核的时候,设计看到的就是预览路由,不是真正的路由。审核没有通过的落地页,正式线上投放链接,看到是带有水印的。

目录结构如下图所示:

图片

渲染的伪代码如下:

///预览路由
export default HocPreview(({ componentList }) => <Container componentList={componentList} />)

// 正式路由
export default HocApp(({ componentList }: { componentList: any }) => {
  return <Container componentList={componentList} />
})

这里我是通过两个React HOC组件去做业务逻辑分离的处理。

好处:

  • 天然的沙盒化因为iframe的天然隔离性,画布渲染器中的所有逻辑、样式不会影响编辑器本身。

  • 利于多人编辑单人编辑时使用iframe进行通信,而多人编辑时可将iframe通信切换成WebSocket通信,设计时有异曲同工之妙。

  • 编辑器页面打开快,体验好,不需要加载很多物料,预览的加载收敛到SSR去处理。

坏处:

  • 可扩性特别差,新增落地页类型,都需要开发、测试。不支持定制化。

  • 没法做到组件级别的回滚。

最终左右对比,从项目开发周期、上线时间、业务模型的综合考虑下,我还是选择了iframe作为画布预览方案。

画布通信

说画布通信前,我先说下编辑器全局数据流的技术选型。Hooks时代的react状态管理库,已经不是臃肿的redux,我们应该全面拥抱hooks,所以也就考虑了两种比较有代表性的状态管理库,Zustand和Valito。

Valtio是围绕ES6的Proxy特性来进行设计的,它有以下几点核心的特性:

  • 代理状态,基于Proxy。

  • 响应式更新,对状态的操作都会通过代理进行,Valtio内部会跟踪并自动渲染。

  • 细粒度渲染,Valtio的代理可以实现细粒度的依赖跟踪,这样只有组件实际使用的状态变化时才会重新渲染,避免了不必要的渲染,性能方面很不错。

  • 简洁的API,Valtio尽可能地让开发者操作状态就像操作普通对象一样自然。

  • 订阅/通知模式。

  • 状态适用于组件之外,除了组件外,也支持用在某些逻辑上。

Zustand的特性如下:

  • 简洁性:Zustand通过一个干净直观的API简化了状态管理,减少了代码的复杂性。不需要应用外侧包裹一层Provider。

  • 性能:Zustand高度优化,为你的应用提供卓越的性能。

  • 可扩展性:随着你的项目增长,Zustand仍然易于使用并且扩展性好。

  • 不变性:Zustand鼓励不变性,使跟踪状态变化和调试问题变得更容易。

  • 灵活性:它不限于特定的框架。你可以在React、React Native或任何其他JavaScript环境中使用Zustand。

本身这两个状态库,各有千秋,没有谁好谁不好一说,只有更合适的业务的场景。考虑到我们是B端编辑器场景,Valito写法和vue相似,基于Proxy响应式的理念、以及学习成本最终使用了Valito作为我们的状态管理库

我们再聊一下画布通信,由于选用了iframe做了画布渲染的工具,所以我们通信方案也是基于iframe的postMessage。大家可以看一下流程图:

图片

核心代码渲染逻辑,大家可以参考一下。

** 高阶组件 基于iframe 进行封装 */
export const HocPreview = (Template: (componentList: any) => JSX.Element) => {
  return ({ data }: { data: any }) => {
    const [componentList, setComponentList] = useState(data?.componentList ?? [])

    useEffect(() => {
      // 在iframe中且没有hash值的时候,添加hover样式并通知父级
      if (window.self !== window.top && !window.location.hash) {
        const compElemList = document.querySelectorAll('.editor-box>div>*')
        compElemList.forEach((item, index) => {
          item.addEventListener('mouseenter', () => {
            window.parent.postMessage(index, '*')
            compElemList.forEach((citem) => {
              citem.classList.remove('actived')
            })
            item.classList.add('actived')
          })
        })
      }

      window.addEventListener('message', ({ data }: { data: PostComponentMsg[] }) => {
        setComponentList([...data])
      })
      // 去除预览页面的滚动条
      document.querySelector('html')?.classList.add('no-scroll-bar')
    }, [])

    const handleClick = (e: SyntheticEvent) => {
      if (window.self !== window.top) {
        e.stopPropagation()
      }
    }

    return (
      <div className="editor-box" onClickCapture={handleClick}>
        {/* <Mask /> */}

        <Template componentList={componentList} />
      </div>
    )
  }
}

B端组件配置

B端的form我们是基于antd的proCompoents我们选用的是SchemaForm。

SchemaForm是根据JSON Schema来生成表单的工具。SchemaForm会根据valueType来映射成不同的表单项。

除了内置valueType如图所示:

图片

https://procomponents.ant.design/components/schema#valuetype-%E5%88%97%E8%A1%A8

我们还会根据业务类型新增一些自定义的valueType。

我们的模板都有一些通用的B端column配置,因此我们将这封装成hook,所以这些通用hook,可以当做我们元数据,任何几个hook组合形成,就能搭建一个B端配置。

所以后续新增任意外投模板,可以大大节约开发的时间,因为外投模板都可以复用之前的column配置。

五、收益

  1. 提升投放业务人员的体验,提供软广服务的多样性。

  2. 积累数据效果,迭代站外落地页的效果。

  3. 潜在钱效收益:过往流程过长会发生影响如期上线的问题,大促节点如期上线抢占高下单率时机对钱效也有正向影响。

  4. 规避人工搭建跳转错误造成损失问题。

六、总结 & 规划

编辑器作为广告投放十分关键的一环,自研编辑器,离不开产品、团队同学、合作方的支持,才能推动落地。

关于编辑器后续的规划主要聚焦以下几个方面:

  1. 丰富外投落地页的通用能力,提高拉新和还原指标。

  2. 落地星创落地页数据洞察,从投放到站内承接,我们需要拿到更多的信息,能够得到更多的投放策略。

  3. 建立一套高效率的机制能快速实验和数据回收,帮助业务达成目标。

写到这里如果觉得写的不错,不要吝啬你的赞和转发。

如果你对文中有任何不理解的地方,可以在文后留言,我们可以一起技术讨论。

*文/Fly

本文属得物技术原创,更多精彩文章请看:得物技术官网

未经得物技术许可严禁转载,否则依法追究法律责任!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/680729.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据新生态:Web3如何重新定义个人数据权利

随着数字化时代的不断深入&#xff0c;个人数据已经成为了现代社会中最宝贵的资源之一。然而&#xff0c;传统互联网时代下&#xff0c;个人数据往往被大型科技公司垄断、滥用&#xff0c;个人数据权利常常受到侵犯。而随着Web3技术的崛起&#xff0c;人们开始期待一种全新的数…

这几年一直有人在问:软件行业现在环境好不好?(俩张图告诉你答案)IT还有机会回暖吗?

这几年一直有人在问:软件行业现在环境好不好?(俩张图告诉你答案)IT还有机会回暖吗? 近几年软件行业确实是不太景气,身边很多朋友都面临找工作难的景象.it培训行业也是对老师进行裁员,砍掉大部分学科, 大家可以在下方评论发表自己的看法,认为IT还有机会回暖吗?

Vue进阶之Vue无代码可视化项目(四)

Vue无代码可视化项目 左侧栏第一步LeftPanel.vueLayoutView.vuebase.css第二步LayoutView.vueLeftPanel.vue编排引擎smooth-dnd安装创建文件SmoothDndContainer.tsutils.tsSmoothDndDraggable.tsLeftPanel.vue左侧栏 第一步 创建LeftPanel LeftPanel.vue <script setup…

第15章 面向服务架构设计理论实践

服务是一个由服务提供者提供的&#xff0c;用于满足使用者请求的业务单元。服务的提供者和使用者都是软件代理为了各自的利益而产生的角色。 在面向服务的体系结构(Service-Oriented Architecture,SOA)中&#xff0c;服务的概念有了延伸&#xff0c;泛指系统对外提供的功能集。…

Python文档生成工具库之alabaster使用详解

概要 在编写文档时,美观和易读性是两个重要的方面。Sphinx 是一个广泛使用的文档生成工具,而 Alabaster 是 Sphinx 默认的主题。alabaster 主题以其简洁优雅的设计和易用的配置选项受到广大用户的欢迎。本文将详细介绍 alabaster 库,包括其安装方法、主要特性、基本和高级功…

php质量工具系列之PHPCPD

PHPCPD 用于检测重复代码&#xff0c;直观的说就是复制粘贴再稍微改改 该工具作者已经 停止维护 安装 composer global require --dev sebastian/phpcpd执行 phpcpd --log-pmd phpcpd_result.xml ./app参数介绍 --log-pmd 将结果保存在phpcpd_result.xml 中 ./app 是phpcpd扫…

【传知代码】偏标记学习+图像分类(论文复现)

前言&#xff1a;偏标记学习&#xff0c;顾名思义&#xff0c;就是在训练数据集中&#xff0c;每个样本的标签不是完全确定的&#xff0c;而是由多个可能的标签组成的集合。这种学习范式更加贴近现实世界的场景&#xff0c;因为在很多情况下&#xff0c;我们无法为图像提供精确…

可的哥(Codigger)推出Monaco编辑器插件,提升编程体验

Monaco编辑器&#xff0c;作为业界领先的代码编辑器&#xff0c;在编程体验中发挥着不可或缺的重要作用&#xff0c;能够在多种编程语言和开发环境中表现出色&#xff0c;为开发者提供高效、便捷的编程环境。可的哥&#xff08;Codigger&#xff09;在应用商店上线Monaco编辑器…

在618集中上新,蕉下、VVC们为何押注拼多多?

编辑&#xff5c;Ray 自前两年崛起的防晒产品&#xff0c;今年依旧热度不减。 头部品牌蕉下&#xff0c;2020年入驻拼多多&#xff0c;如今年销售额已过亿元。而自去年起重点押注拼多多的时尚防晒品牌VVC&#xff0c;很快销量翻番。这两家公司&#xff0c;不约而同在618之前上…

设备巡检系统是如何实现一次操作闭环管理的

设备巡检系统通过一系列功能设计&#xff0c;实现了从任务分配到问题处理的一次操作闭环管理。以下是具体的实现方式&#xff1a; 一、多类型任务无感操作 任务识别与整合&#xff1a;系统能够自动识别各种巡检、重大危险源排查及现场检修等任务的类型和优先级&#xff0c;并…

企业全面管理解决方案:基于Java技术的ERP管理系统源码

功能模块与描述&#xff1a; ERP首页&#xff1a; 销售统计与采购统计&#xff1a;实时展示销售和采购金额的统计数据。折线图统计&#xff1a;通过图表直观展示销售和采购趋势。 采购管理&#xff1a; 采购订单管理&#xff1a;处理采购订单的搜索、新增、导出等。采购入库与退…

进程同步的基本元素

目录 临界资源 临界区 信号量机制 整形信号量 记录型信号量 AND信号量 信号量集 信号量的应用 实现进程互斥 实现前驱关系 管程机制 总结 临界资源 I/O设备属于临界资源。著名的生产者-消费者问题就是关于临界资源的争夺产生的进程同步的问题。 生产者-消费者 描…

产品经理:做好有效的客户需求分析

需求分析是产品开发过程中的重要环节&#xff0c;它直接决定了产品是否能够满足市场需求和用户期望。通过深入了解客户需求&#xff0c;产品经理可以确保产品功能的设计符合用户的实际需求&#xff0c;从而提高产品的用户满意度和市场竞争力。 一、识别用户需求 识别用户需求…

mysql用户管理知识点

1、权限表 1.1、user表 1.1.1、用户列 Host、User、Password分别表示主机名、用户名、密码 1.1.2、权限列 决定了用户的权限&#xff0c;描述了在全局范围内允许对数据和数据库进行操作。 1.1.3、安全列 安全列有6个字段&#xff0c;其中两个是ssl相关的&#xff0c;2个是x509相…

虚拟仿真实训平台如何与不同专业进行融合?

虚拟仿真实训平台根据跨专业实训教学和职业培训的不同特点&#xff0c;兼顾实训课程设计的专业性和兼容性&#xff0c;根据不同专业特性确定虚拟仿真实训教学内容&#xff0c;研发虚拟仿真实训教学资源&#xff0c;优化人才培养方案和职业培训方案&#xff0c;改革实训教学体系…

游戏陪玩系统源码线上陪玩软件开发电竞陪练小程序陪玩APP

思维导图 规则说明 支持陪玩官&#xff0c;一级和二级&#xff0c;系统配置设置的是初始化佣金&#xff0c;可以单个设置某个人的佣金比例&#xff0c;分销模块只涉及到下单交易模块&#xff0c;其他的不参与&#xff0c;邀请的用户被下单&#xff0c;即可获得收益。 理解规则…

【面试笔记】C++ 软件开发工程师,智驾研发方向(非算法)

文章目录 1. 前言2. 基础问题2.1 什么是C++中的类?如何定义和实例化一个类?2.2 请解释C++中的继承和多态性。2.3 什么是虚函数?为什么在基类中使用虚函数?2.4 解释封装、继承和多态的概念,并提供相应的代码示例。2.5 如何处理内存泄漏问题?提供一些常见的内存管理技术。2…

LabVIEW冲击响应谱分析系统

LabVIEW冲击响应谱分析系统 开发了一种基于LabVIEW开发的冲击响应谱分析系统&#xff0c;该系统主要用于分析在短时间内高量级输入力作用下装备的响应。通过改进的递归数字滤波法和样条函数法进行冲击响应谱的计算&#xff0c;实现了冲击有效持续时间的自动提取和响应谱的精准…

13.56MHz电动车NFC刷卡解锁

随着电动车市场的快速发展&#xff0c;车主对车辆的智能化和便捷性的要求也在不断提升。仪表盘作为电动车的重要组成部分&#xff0c;不仅需要提供基本的行驶信息&#xff0c;还需要具备智能交互功能。 基于13.56MHz频率的NFC&#xff08;近场通信&#xff09;技术为电动车仪表…

李国武:六西格玛绿带项目的实施过程中可能遇到哪些问题?

作为六西格玛管理体系中的中坚力量&#xff0c;绿带项目在企业的转型升级中扮演着举足轻重的角色。然而&#xff0c;在实施六西格玛绿带项目的过程中&#xff0c;企业往往会遭遇一系列挑战。具体如深圳天行健企业管理咨询公司下文所述&#xff1a; 首先&#xff0c;人才与知识的…