2026年7月9日 周四晚上19:30,报名腾讯会议了解“如何构建自进化的动态知识库(Brain)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

站外商详的重构与优化|得物技术

发布日期:2025-01-06 18:38:01 浏览次数: 2332
作者:得物技术

微信搜一搜,关注“得物技术”

目录

一、背景

二、技术方案

    1. 源码搭建

    2. 首屏性能保障方案

    3. 同构与多环境运行

    4. 风险控制/止损策略

三、一些针对性重构

    1. 请求拦截器的重构

    2. 埋点Hook的重构

四、重构后的数据回收

五、总结

六、参考链接


背景

站外商详,功能较为单一

客户端商详,有丰富的类目差异化配置,功能众多


站外商详(H5/小程序)一直以来采用detailV3老接口数据,在样式和功能上,不能与最新版的客户端同步对齐,各个端之间的使用体验之间存在差异。


从唤端数据可以看出来,App商品详情页分享后的唤端成功率非常高,能够达到75%以上,代表着这些用户都是带有明确目标和意愿来App内部进入购买商品,ROI好的高净值用户:


而对于日均pv占据站外流量的TOP3的站外商详来说,唤端价值相对比较高的同时,uni-app多端同构方案的SPA架构限制了H5与小程序页面性能体验天花板,长期以来站外商详的性能指标在前端平台的性能统计大盘下比较靠后:



从上面的性能数据监控并对比源码搭建页面的性能数据可以看出来,旧版的商详性能数据并不理想,对用户在站外商详页的转化率有一定影响:

  • 平均fmp:2.75s;

  • 75分位平均fmp:2.74s——对比源码搭建大盘1.29s多了1.45s;

  • 平均lcp:3.29s;

  • 75分位平均lcp:4.06s——对比源码搭建大盘1.46s多了2.6s;


综上背景,我们决定重构站外商详,一方面可以接入得物后台最新版本的商详数据API,便于后续需求迭代,避免站外商详和App商详体验的持续割裂现象;另一方面可以同时提高站外商详的前端性能,带给用户更好的使用体验。


技术方案

我们本次站外商详升级到创新商详版本,放弃了原项目的uni-app多端同构方案,同时采用营销侧的技术基建——源码搭建;提高了站外秒开性能和用户体验,同时又保证了代码层面的同构开发,本文将详细介绍本次站外商详的重构与优化。


源码搭建

源码搭建是得物前端平台基于SSR架构的C端基建,本次商详重构采用源码搭建来完成重构任务,以下是源码搭建的简要介绍:


源码搭建介绍:

源码搭建是利用页面搭建器现有开发组件能力快速生产页面的开发方式,业务开发不需要关心公用组件、体验、性能和稳定性基础建设,只需要在建立好的页面仓库中开发业务代码即可,集成了流水线构建会自动帮助开发构建上传。


首屏性能保障方案

  • 本次重构有一个核心诉求:提高前端页面加载性能,而提高秒开体验的核心即SSR:在Node端请求服务端数据并渲染出HTML结构直出给到浏览器;

  • 但同时商详数据是电商平台的核心数据,尤其是得物出价相关数据一直都被各种黑产爬虫关注,所以风控侧要求商详接口数据需要做加密处理。


此时就有用户体验 & 数据安全的矛盾存在:

  • 数据加密的情况下,Node端无法解密数据,此时便无法直出HTML结构,相当于降级为SPA,用户体验相较于SSR大打折扣;

  • 如果数据不加密,Node端可以解析数据做到HTML直出,但是数据安全又无法得到保障。


那么这之间的矛盾和冲突我们是如何解决的呢?

  • 将首屏数据(即对用户体验影响最大的fmp、lcp元素渲染涉及到的数据)从完整接口中拆分出来,这一部分数据跟需要加密的敏感数据无关,所以不需要加密处理;

  • 拆分出首屏数据接口,然后在SSR阶段只请求首屏数据接口,并渲染HTML结构返回到浏览器;

  • 浏览器端运行时(即用户已经在浏览器端打开了页面的时机),再通过风控请求完整的加密数据接口,并渲染到页面;

  • 通过拆分首屏接口和分离首屏数据渲染和完整数据渲染,这样就能同时保障首屏渲染速度与风控侧的加密需求。


简要流程如下图所示:


同构与多环境运行

我们重构的主要目的是为了提高性能以及对接最新版服务端接口,但是又不能因为重构而放弃了以往uni-app架构下的多端同构优势,所以需要设计一套新的运行流程来适配SSR下的新商详。


H5环境下我们可以直接访问SSR架构下的新商详,但是小程序运行环境该如何运行呢?

  • 小程序的开放组件包含了webview组件,所以我们可以依靠小程序环境中的webview组件来替换原本的小程序原生页面,以下设计图大致描述了我们是如何保障一份代码,如何多环境运行的:


风险控制/止损策略

对于pv较高且包含完整交易链路的站外商详来说,冒烟点和阻塞线上购买流程的故障是不可接受的,因此我们设计了相对来说比较完备的止损策略。


故障降级页面——旧版商详

新版商详上线后,旧版页面暂时不会下线,路径和代码依旧保持不变;因此可以作为降级页面,能够保障在新版商详出问题后无缝将流量切回旧版商详。


SSR故障降级

如果SSR侧的请求出现了不可用现象,只会影响简版数据接口的渲染,因此即便失败了也只是影响秒开性能,而不会中断正常业务流程。


灰度策略

结合前端配置中心,我们可以通过逐步灰度放量方式对命中灰度的用户采取跳转新版商详策略,同时灰度配置也可以作为紧急的回滚手段,在遇到故障时及时将灰度放量关闭,引导所有用户跳转旧版商详。


一些针对性重构

在商详页面的整体重构过程中,我们识别出了一些关键模块需要进行针对性的重构。这些模块的重构目标是确保它们能够有效地适配商详页面的整体架构变化,同时提升可拓展性。这些针对性重构帮助我们解决了现有迭代中的瓶颈,并在保证系统稳定性的同时,加速开发的迭代过程。


接下来我们详细介绍其中请求拦截器与业务埋点Hook的重构设计。


请求拦截器的重构

因为新版商详需要在多种场景(Node.js / 微信小程序 / 移动端浏览器)运行代码,同时可以预见的是后续会有更多场景(如:支付宝小程序等)加入运行环境。


为了保障后续更多运行环境拓展性和可维护性,我们重构了请求拦截器模块:


1、RequestInceptor类型定义:

通过从定义层面区分不同环境,可以有效保障拦截器运行在有效环境,也从逻辑底层避免了一些可以前置避免的类型错误(比如在node环境下访问window等):

export interface RequestInceptor<T = InternalAxiosRequestConfig<unknown>> {(): {// node环境的请求拦截nodeEnv: (config: T, runtimeConfig?: RunTimeConfig) => Promise<T> | T;// 浏览器环境的请求拦截clientEnv: (config: T, runtimeConfig?: Pick<RunTimeConfig, 'query') => Promise<T> | T;};}


2、RequestInceptor的具体实现:

每个RequestInceptor都是一个函数,根据环境返回不同的处理逻辑,示例代码:

const h5CommonHeaders: RequestInceptor = () => ({// 不同环境下需要携带一些不同的request headernodeEnv: config => {config.headers['reqEnv'] = 'node';return config;},clientEnv: async config => {config.headers['appid_org'] = 'wxapp';return config;},});
const yunDunSDK: RequestInceptor = () => ({nodeEnv: config => config,clientEnv: async config => {// 只需要在浏览器环境加载的sdkawait yunDunLoad;return config;},});


3、inceptorsLoader和requestInceptorsCreator共同实现了请求拦截器的处理流程:

inceptorsLoader:

const inceptorsLoader = async (initialConfig: InitialConfig, inceptors: RequestInceptor[]) => {const promiseList = map(inceptors, interceptor => {return async (config: InitialConfig) => {const { nodeEnv, clientEnv } = interceptor();if (isInWindow) {return clientEnv(config, config?.runTimeConfig);} else {return nodeEnv(config, config?.runTimeConfig);}};});const promiseListResult = await promiseList.reduce((promise, fn) =>promise.then(config => {return fn(config);}),Promise.resolve(initialConfig),);return promiseListResult;};


  • 这个函数接收两个参数:initialConfig和interceptors;

  • initialConfig是初始的请求配置,包含请求方法、URL、参数等;

  • interceptors是一个请求拦截器的数组,每个拦截器都是一个对象,包含nodeEnv和clientEnv属性,分别表示在Node环境和浏览器环境下的处理逻辑;

  • 使用map函数将interceptors数组转换为处理函数的数组,并按顺序执行这些处理函数;

  • reduce方法用于串联这些处理函数,形成一个Promise链。每个处理函数(fn)接收当前的配置(config)作为参数,然后根据环境执行相应的处理(nodeEnv或clientEnv)。


requestInceptorsCreator:

export const requestInceptorsCreator = (config: InitialConfig) =>inceptorsLoader(config, [// 通用的headersh5CommonHeaders,// 风控yunDunSDK]);


这个函数是一个工厂函数,它接收一个config对象作为参数,用于创建并返回一个处理后的配置对象。


4、通过RequestInceptor的设计,结合工厂函数requestInceptorsCreator,可以灵活地添加、删除或修改请求拦截器,同时保证拦截器按照特定的顺序执行。这种方式使得请求处理逻辑更加模块化、可测试和易于维护。在实际应用中,只需要调用requestInceptorsCreator函数,传入初始配置,即可得到一个完整的、优化过的请求配置,然后可以将其传给HTTP客户端(如axios)来发起请求。


埋点Hook的重构

一直以来,埋点开发深受前端同学吐槽和困扰,因为大量的埋点逻辑都跟业务逻辑/视图渲染有着强绑定的关系,同时又不得不写大量的“模版式代码”,费心又费力。


本次重构基于React Hook重构了埋点上报的应用层逻辑,可以在组件内引入Hook进行自定义上报/曝光上报;能更加高效的基于不同平台的运行环境去上报指定的埋点参数。


埋点Hook实现层:


1、generateTrackConfig 函数核心代码:

const generateTrackConfig = (trackSend: Readonly<Array<{ name: string }>>) => {return function createTrackConfig() {const names = trackSend.map(item => item.name);
/** * 埋点名由三部分组成: * 例:trackEventName_1234_3210 * event: 'trackEventName' * current_page: '1234' * block_type: '3210' 可能不存在 */// 这里将埋点平台的函数名拆分出具体的上报入参const extractEventData = (current: string) => {const nameSplit = current.split('_');const [page, block] = nameSplit.slice(-2);const isBlockTypePresent = /\d+/.test(page);const event = nameSplit.slice(0, isBlockTypePresent ? -2 : -1).join('_');
return {event,current_page: isBlockTypePresent ? page : block,block_type: isBlockTypePresent ? block : '',};};// ...// 这里统一通过reduce组装成埋点sdk所需要的trackConfig配置return names.reduce((total, current) => {const eventData = extractEventData(current);total[current] = (platform: string, { transParams }: any) =>createEventObject(eventData, platform, transParams);return total;}, {});};};


2、useProTrack 函数核心代码:

export const useProTrack = <T extends string>({ props, functionalRef }: { props: any; functionalRef: any },trackSendProps: Readonly<TrackSend<T>>,) => {// 这里注入核心的埋点sdk配置useWithReactFunctionalTrack({functionalRef: functionalRef,functionalProps: functionalPropsRef.current,useEffect,createTrackConfig: generateTrackConfig(trackSendRef.current),}); // 这里通过useEffect接入曝光埋点的监听useEffect(() => {ObserveTrackRef.current = new IntersectionObserver(handleIntersection);return () => {ObserveTrackRef.current?.disconnect();};}, []);// 这里针对具体埋点函数做统一封装,保证TS类型完整const trackFuncMemo = useMemo(() => {return trackSendRef.current.reduce((result, item) => {result[item.name] = (trackParams?: Record<string, unknown>,options?: { ele?: HTMLElement | null },) => {// 这里针对曝光埋点和主动埋点做区分,统一收敛调用方式,通过入参区分if (!options?.ele) {track(item.name)(trackParams);return;}const { ele } = options;startToObserveMap.current[item.name]();};return result;}, {} as { [key in T]: trackFunc });}, []);
return trackFuncMemo;};


埋点Hook应用层:


1、获取埋点名,从埋点任务中复制,不要手动拼写:


 2、组件内引用useProTrack,并注册埋点名:

const proTrack = useProTrack({ props, functionalRef }, [{ name: 'trackEvent_1234' },{ name: 'trackEvent_2345' },]);


3、主动上报类型例子:

proTrack.trackEvent_1234({button_title: '我知道了',});


这里会有埋点名的类型提示,注意选择正确的埋点名:


4、曝光上报例子:

 <divclassName={bindClass(styles.button)}onClick={handleGoBuy}ref={ele =>trackFn?.trackEvent_1234_3210({trade_type_list: tradeTypeListStr,},{ ele },)}></div>


在HTML元素(不要写在react组件只能是HTML元素)中的ref钩子注册,语法相比主动曝光多了一个配置(见上图)


埋点Hook的重构收益

  • 节省前端工时,减少心智负担:

    前端无需手动配置trackConfig文件;

    无需感知/调用埋点SDK内部。


  • 减少流程,避免埋点验证时返工:

    简单一步,复制埋点系统的埋点名即可创建埋点函数;

    再也不用担心埋点event、current_page、block_type写错了;

    完备的TS类型提示。


重构后的数据回收

  • 非指标向收益:

    通过本次重构,我们接入了得物后台最新版本的商详数据API,在数据层面有了完整性和最新版的保障,后续对于App商详的规划理论上站外商详也都可以参与进来。


    本次重构在样式上基于最新的得物App设计语言,保持和App商详一致,避免了用户在多端切换之间的割裂感;在功能上虽然暂时还没有完全对齐App能力,但是重点模块都已经具备,进一步还有更多App商详模块会逐渐迁移到站外商详。


  • 前端性能指标收益:

  • 平均fmp:1.06s,相比旧版2.75s,提升61.4%;

  • 75分位平均fmp:1.13s相比旧版2.74s,提升58.75%;

  • 平均lcp:1.20s,相较旧版3.29s,提升63.5%;

  • 75分位平均lcp:1.37s,相较旧版4.06s,提升66.25%;


可以看出来重构后的性能收益非常明显,对用户体验提升有比较大的帮助。


业务指标收益:

  • H5平台

    立即购买点击(唤起浮层)触达率相较于旧版,约提升2.96个百分点;

    购买按钮点击(跳转确认订单)触达率相较于旧版,约提升0.92个百分点。


  • 小程序平台

    立即购买点击(唤起浮层)触达率相较于旧版,约提升0.72个百分点;

    购买按钮点击(跳转确认订单)触达率相较于旧版,约提升0.08个百分点。


总结

本次针对站外商详页面的重构取得了显著的性能提升,给用户带来了更好的体验。


站外商详,作为得物站外链路的主要商品信息承载页,在本次重构之后也接入了最新的商详数据API,为后续功能扩展和后续的多环境集成也打下了良好的技术基础。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅