纽交所技术问题致伯克希尔 (BRK.A) 显示跌近 100%
周一,纽交所的一个技术问题导致“股神”巴菲特旗下伯克希尔公司(BRK.A)股价显示下跌近100%。 6月3日,多家行情软件显示,数只美股出现行情异常,伯克希尔、蒙特利尔银行、巴里克黄金等股票跌幅在98%以上,且行情静止不动。 纽交所方面表示,正在调查一个与涨跌幅限制有关的技术问题,涨跌幅限制是因过度波动而停止股票交易的机制。
搭建一个落地页需要涉及到多方合作,需要不断地进行沟通协调。繁杂的流程需要耗费很多的时间,因此我们推动产品重新搭建了一个专门服务于软广投放流程的编辑器——星创,完成广告搭建在投放业务各系统中的闭环。
一、落地页技术架构
名词解释
模板:就是投放业务人员配置一个完整的落地页。
站外落地页:投放在媒体,站外的用户看到的落地页。
站内落地页:用户点击站外落地页,进入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: {
},
}
这里这么设计的考虑有下面👇🏻几点:
新增模板类型
关于为什么新增模板类型,我给出了下面关于投放业务的思考。
因为我们需要支持多种渲染模式,所以在一开始做SSR技术选型的时候,优先选择了next 14版本,可以后面更好地支持业务。
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种方案的优缺点。
动态远程组件
整体的流程大概分这几步:
伪代码如下:
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之中的内容,最后会使用加载成功的组件进行替换。
优点:
劣势:
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作为画布预览方案。
画布通信
说画布通信前,我先说下编辑器全局数据流的技术选型。Hooks时代的react状态管理库,已经不是臃肿的redux,我们应该全面拥抱hooks,所以也就考虑了两种比较有代表性的状态管理库,Zustand和Valito。
Valtio是围绕ES6的Proxy特性来进行设计的,它有以下几点核心的特性:
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配置。
五、收益
六、总结 & 规划
编辑器作为广告投放十分关键的一环,自研编辑器,离不开产品、团队同学、合作方的支持,才能推动落地。
关于编辑器后续的规划主要聚焦以下几个方面:
写到这里如果觉得写的不错,不要吝啬你的赞和转发。
如果你对文中有任何不理解的地方,可以在文后留言,我们可以一起技术讨论。
*文/Fly
本文属得物技术原创,更多精彩文章请看:得物技术官网
未经得物技术许可严禁转载,否则依法追究法律责任!
微信关注我们
转载内容版权归作者及来源网站所有!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。
马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。
为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。
Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。