商家营销工具架构升级总结

news2024/9/28 21:56:37

ae9bc046e3fb92ed40c920c561e1d6ab.gif

今年以来,商家营销工具业务需求井喷,需求数量多且耗时都比较长,技术侧面临很大的压力。因此这篇文章主要讨论营销工具前端要如何应对这样大规模的业务需求。

cbd3f65ce5860773c71fc2f50a26209b.png

问题拆解

我们核心面对的问题主要如下:

1. 人力有限

  • 我们除了要支撑存量页面的日常迭代,还需要完成大量页面的新增,虽然有短期的人力支援,但总体还是捉襟见肘。

2. 如何保障交付质量和体验?

  • 商家营销工具核心的业务目标之一是体验优化,因此对前端交付页面的质量和体验,都有一定要求。而我们有大部分人力是新人,如何保证交付质量和体验?

3. 支援撤出后,长尾需求如何应对?

  • 新增页面的持续优化迭代会带来大量长尾需求。支援撤出后如何应对?

问题总结一句话其实就是:如何高效高质量地支撑相比以往更多的需求

最初的思路其实很简单:提高代码复用率

这样:

1. 一方面提高了研发效率,以应对人力瓶颈。

2. 另一方面能通过沉淀下来的标准代码,拔高整体交付产物的质量和体验下限。

9ea99f9cb356477e30acffb50dd9f598.png

统一产品形态与设计规范

前端提高代码复用率的方式,最常用的就是组件沉淀(基础组件基本配套齐全,这里主要指业务组件)。但组件沉淀的实际效果,会受很多因素的影响:

1. 业务形态是否稳定?

  • 针对一个业务场景沉淀的组件,会因该场景的业务调整而面临改造,不稳定的业务形态,会降低组件沉淀的收益。“三天两变”的业务形态下沉淀组件,可能会带来负收益。

2. UI是否稳定?

  • 道理和业务形态相同。

我们当然有一些方法能够尽可能降低这些因素带来的影响。比如可以将 业务逻辑 与 UI 分离,让他们可以独立变更,互不影响;也可以对组件做更多层次更抽象的封装,来让组件能“灵活”地适应不同场景。

但在螺丝壳里做道场,不如考虑从源头解决问题。下边是几个存量工具的PC端页面:

1552cd31b56c170b9f40fad5f50278f5.png

营销工具的产品形态特点鲜明:

1. 大部分工具都有 管理、创建、编辑、查看 四种能力,主要通过 列表页 和 表单页 两个页面来承载。

2. 列表页、表单页各模块的样式和交互模式非常相近。比如列表页顶部的Banner区,创建页的图片预览区。

如果能统一部分模块甚至页面框架的交互模式和样式,那么根据这些场景沉淀的组件,可复用性是很高的。

因此我们对营销工具域内,可规范化的场景做了枚举。去推动产品和设计统一产品形态和设计规范。出乎意料的顺利,产品、设计、前端 三方很快达成了一致,最终我们确定了营销工具域产品&设计规范。

规范的种子其实早已种下,只是缺了一个浇水的人。

  思考

当统一产品形态与设计规范这件事确定以后,技术上我们有了一些新的思路和要面对的问题:

  1. 接口的数据模型是否也能形成规范,我们可以将接口调用与数据处理也内置在组件内,做到更极致的提效。

  2. 不止于组件,我们是否可以将大的业务区块或者整个页面模版都沉淀下来?

  3. 一些数据源会频繁变更,业务逻辑又非常简单的模块,是否可以完全交由PD自行配置,免去迭代的开发工作量?

  4. 过去其实也有约定过一些产品&设计的规范,但都由于 规范粒度不够细、实践过程中产品&设计没有严格遵守规范、一遇到特殊定制逻辑就选择跳出规范等问题,导致最终各工具之间又逐渐趋于差异化,如何避免这种问题?

1cda2da7daf900cd50ce006b48bb9a5c.png

架构设计

基于上述的思考,我们针对商家营销工具场景,设计了一套技术架构,划分为以下几层:

  • 页面容器层:这一层,我们用来收敛不同工具之间的共性部分。

    • 如:页面UI框架、可配置化的模块(如列表页顶部Banner区)、请求方法、工具方法、公共依赖等。

  • 场景模版层:这一层,我们用来收敛一些 共性 > 差异 的业务模块中的共性部分。它是可选的,当页面中没有 共性 > 差异 的模块时,那也就无需拆分出这一层。

    • 如:首页的列表区块(包含:Tab区、筛选表单、列表、分页器),其中Tab切换、分页、筛选、样式布局等在不同工具下都是一致的,将这些逻辑固定下来,同时在模块中预留一些拓展点,支持不同工具定制,它就成为了列表场景的模版。

  • 业务定制层:这一层,用来实现不同工具之间的差异部分。

    • 如:在列表页,基于列表模版预留的拓展点进行定制;在表单页,实现单工具创建、编辑、查看态表单。

b716ef669ceb9664df13c650ab91c3bd.png

这样的架构设计:

1. 实现了我们最初的提高代码复用率的目标,提升开发效率,保障质量和体验下限。

2. 实现了一些共性需求或规范的更新,一次开发,整个业务域生效。

3. 会导致代码变更的影响范围被放大,页面容器层、场景模版层的变更,会一次影响到多个工具。但这是一把双刃剑:

  • 对开发在变更代码时提出了更高的要求。

  • 也让产品设计变得更加谨慎,因为越规范的部分越底层,越底层的部分影响范围越大,增强了规范的约束力。

4bba4da1dec7eae2d80de3e7a71765f9.jpeg

架构串联

我们首先要面对的问题,是如何串联实现上述的三层架构。

参考微前端架构,我们可以先简单地将工具的共性部分放到一起作为主应用;而每个工具的定制部分作为微模块。

在主应用中,我们通过路由区分不同工具,以加载不同工具的微模块。同时我们会维护一个页面上下文,在微模块加载后注入进去,用来实现模块与主应用之间的数据通信。

ef5d088968654f8b0609a0081409fc38.png

我们可以快速的搭建出一个示例:

主应用:

import { MicroModule } from '@ice/stark-module';


// 路由与为模块的映射
const MPathToModuleBaseInfo = {
  '/工具A/home': { name: '工具A-home-module', url: 'https://xxx.xxx.xxx' },
  '/工具A/create': { name: '工具A-create-module', url: 'https://xxx.xxx.xxx' },
  ...
}


// 页面主应用页面
export default function App() {
  const [state, setState] = useState(1);
  // 页面上下文
  const pageContext = useMemo(() => ({ setState }), []);


  return (
    ...
    // 渲染微模块
    <MicroModule
      moduleInfo={MPathToModuleBaseInfo[location.pathname]}
      // 将上下文作为 prop 注入进微模块
      pageContext={pageContext}
    />;
    ... 
  )
}

微模块:

export default function Module(props) {
  // 获取上下文
  const { pageContext } = props || {};


  const handleClick = () => {
    // 与页面容器做数据通信
    pageContext.setState((preState: number) => preState + 1);
  }


  // 渲染业务定制模块
  return (<button onClick={handleClick}>state + 1</button>);
}

到这里,我们实现了页面容器层和业务定制层的串联。但场景模版层,有些无处安放。

  • 将它放进主应用中?不太合适,因为场景模版的迭代频率是高于页面容器的,将这两层耦合在一起,会扩大场景模版的影响范围,增大维护压力。

  • 将它放进微模块中?也不合适,这样每个工具都会单独维护一份场景模版,失去了这一层抽象的意义。

有个简单的方式能解决上述问题,将场景模版封装成一个 npm 包,在每个微模块中引入。但这个方案的缺陷在于,每次场景模版迭代,需要到各个工具中去升级npm版本。发布工作量让人头疼;同时还很难管控版本统一,很容易出现多个版本同时在线上运行的情况。

我们在微内核架构中,找到了灵感。这个名字可能会有点陌生,但如果叫它插件系统,大家应该就很熟悉了。像我们工作中经常接触的 Chrome、VS Code,就拥有强大的插件系统。

55e3c6c0c2384a661805ee1c1de88251.png

在微内核架构下,应用被分割为独立的插件模块和核心系统。在我们的设计中,场景模版就可以被看作是一个业务场景下的核心系统;而每个工具对场景模版的定制拓展,就是一个个插件。下边是一段示例:

插件基座:

// 路由与为插件资源的映射
const MPathToPluginInfo = {
  '/工具A/home': { url: 'https://xxx.xxx.xxx' },
  ...
}
// 核心系统
export default function CoreApp() {
  const [tabList, setTabList] = useState([]);


  // 插件API生成
  const pluginApi = useMemo(() => {
    return {
      tabs: {
        add: setTabList;
      },
    }
  }, []);


  // 加载并运行插件
  const pluginResult = useMemo(() => {
    const plugin = registerPlugin(MPathToPluginInfo[location.pathname]);
    plugin(pluginApi);
  }, []);


  // 核心系统内部渲染 Tab List 的逻辑
  return (
    <Tab>
      {
        tabList.map((tab) => (
          <Tab.Item key={tab.key} title={tab.title}>{tab.content}</Tab.Item>
        ))
      }
    </Tab>
  )
}

插件模块:

function Content() {
  return <div>content</div>
}


export default function plugin(api) {
  // 使用 api 对核心系统进行定制
  api.tabs.add((preState) => (
    [...preState, { key: 'demo', title: '标题', content: <Content /> }]
  ))
}

为了和上述微前端的实现结合,我们可以将核心系统作为一个常驻微模块由主应用加载。而加载插件的逻辑可以放在核心系统内部。

  总结

到这里,架构的实现思路基本清晰了。

在表单页,我们可以直接通过 主应用 加载 微模块 的方式就能实现。

在列表页,我们针对列表场景,多做了一层抽象,将列表场景的共性逻辑封装成一个核心系统,各工具的差异逻辑则作为插件,对核心系统进行定制。通过 主应用 加载 核心系统,核心系统 加载 插件 的方式实现。

思路到最终实现,还有很多很多问题要解决。工程链路,渲染链路,发布链路,性能优化 等等。这也非一人之功,其中饱含了团队成员们的巧思,内容足以单开数篇文章,在本篇中就不做展开了。

630890f89bd18141dfe29cd9515c68bf.jpeg

分层实现

在上述架构下,开发业务定制层跟我们平常开发业务代码没什么区别,因此下边主要介绍 页面容器 和 场景模版 两层的一些实现思路。

  页面容器层
  • UI框架

通常最底层的容器,是不耦合UI的。相比于页面逻辑,UI 的变化频率要高得多。耦合UI后,会导致容器要频繁迭代,影响健壮性。

但在统一了产品形态和设计规范以后,有很多页面框架层面的 UI 被固化下来,因此我们决定把整个页面框架的逻辑和UI都放进容器里。里边包含了:页面布局、骨架屏、Error兜底、权限校验 等等。

4afd740b5f46fa6bb6ade841277a6bf7.png

  • 配置化

页面中同样存在一些UI一致,但是内容会频繁变化的模块,比如列表页顶部的Banner、公告、工具介绍、新人引导弹窗 等等。这些模块的变更,一般都是数据源的更新,因此我们决定把这些模块配置化,支持数据源远程下发;同时将配置产品化,将数据模型以表单形式表达,让PD能够自行修改,独立发布。

68d746632a16268524fe30a64b94277a.png

因已有配置产品化平台,接下来的工作就是对数据模型进行设计,并在项目中完成接入。

  • 模块加载

在产品&设计规范统一后,我们可以,也应该把页面的UI框架固定下来。但出于对未来不确定性的担忧,我们在容器的实现上,给自己留了一些后路。

参考了一些C端搭建场景的容器设计,我们将页面按照垂直方向划分为一个个区块,通过一份配置进行渲染。区块与区块通常彼此独立,但也可以通过页面上下文或自建事件通信来实现交互。

0b5ec7c84fdf5dc6a3a2c6e4c3463160.png

import Entry from './components/Entry';
import Header from './components/Header';


// 预设的本地区块
const MPresetNameToComponent = {
  'home-entry': Entry,
  'home-header': Header,
}


const pageConfigs = [
  { name: 'home-header', type: 'preSetComponent' },
  { name: 'home-entry', type: 'preSetComponent' },
  {
    name: 'home-table-layout',
    type: 'microModule',
    moduleInfo: {
      ...
    },
  },
]


export default function App() {
  const pageContext = useMemo(() => {}, []);
  
  // 解析协议,渲染模块
  const renderModule = (config) => {
    const { name, type, moduleInfo } = config || {};
  
    if (type === 'preSetComponent') {
      const Component = MPresetNameToComponent[name];
      if (Component) {
        return <Component pageContext={pageContext} />
      }
    }
  
    if (type === 'microModule') {
      return (
        <MicroModule {...moduleInfo} pageContext={pageContext} />
      )
    }


    return null;
  }
  
  return pageConfigs.map((config) => renderModule(config));
}

这份配置目前直接写死在容器中,但在需要时,我们可以将其改造为远程下发。

这样的渲染模式给页面容器带来了一定的灵活性,它支持了一个工具对页面框架层面的定制诉求。比如工具A想在页面Banner区上方,添加策略推荐模块。按照固定框架的方式实现容器,我们想要支持这样的诉求则需要迭代容器,同时对工具A做特判,来渲染策略推荐模块,这会使得容器越来越臃肿。但通过协议渲染,则不会有这样的问题。

  场景模版层

目前,我们仅在列表页做了这一层抽象。在规范中,列表区被固定为四个部分:Tab 区、筛选表单、列表、分页器。

0faf41696995e537fdfd49afed81601a.png

  • 基础逻辑

Tab区包含的逻辑很少,我们在核心系统中定义好Tab区的数据源,并在插件中通过API对数据源进行定制即可。

  • 最终方案中,在插件里定制数据源:

// 添加Tab1
api.appendItem({ extensionId: EExtensionId.xx, title: 'xxx', type: 'table' });
// 添加Tab2
api.appendItem({ extensionId: EExtensionId.xx, title: 'xxx', type: 'table' });

筛选表单本质是列表接口入参的表达,因此逻辑上相对简单,很少有联动,且表单项的类型都很固定,最常用的就是 Input、Select、DatePicker、Radio 这几种,很适合通过 JSON Schema 的方式渲染,而这种渲染方式,已经有很成熟的解决方案了,比如 formily。我们也是基于它实现的。在核心系统中,我们完成表单实例的创建和提交的监听,插件只需要专注于声明表单项。

  • 最终方案中,在插件里声明筛选表单项:

ExtensibleTable.modifyFilter(() => {
  return (form) => {
    // form 为核心系统中注入的表单实例
    
    return {
      // 表单项1
      A: {
        type: 'string',
        'x-decorator': 'FormItem',
        'x-component': 'Input',
        'x-component-props': {
          placeholder: 'A',
        }
      },
      // 表单项2
      B: {
        type: 'number',
        'x-decorator': 'FormItem',
        'x-component': 'Select',
        enum: [
          { label: 'xxxx', value: 0 },
        ],
        'x-component-props': {
          placeholder: 'B',
          hasClear: true,
        }
      },
    }
  }
})

列表最核心的逻辑其实就是分页、筛选。我们已经完成了筛选表单的定制并能获取到表单值,剩余只需要串联搜索、分页的交互,管理列表、分页器的状态即可。ahooks 的 useFusionTable 已经为我们封装的很完整了,我们在核心系统中将他集成了进来,而在开发插件时,我们只用声明列表接口。

  • 最终方案中,在插件里声明请求方法:

const listXXX = () => {
  return fetch('https://xxx.xxx.xxx')
}


ExtensibleTable.modifyActions((columns, ctx) => {
  return {
    search: {
      // 声明列表接口
      request: ({ current, pageSize, filterValue }) => {
        // filterValue 中每个 key 和声明的表单项一一对应
        return listXXX({ current, pageSize, filterValue });
      }
  }
});
  • 数据模型

上述的基础逻辑内置在核心系统中,已经能相当程度上减少列表区的开发工作量,但我们仍想更进一步。

列表核心是对服务端数据模型的表达,而数据模型又是对业务模型的抽象。在商家营销工具域内,不同工具之间有很多相似的业务模型,比如活动模型,它包含:活动名称、ID、时间、状态、绑定的商品等等字段信息。

在统一了产品形态和设计规范以后,我们顺其自然的想要推动服务端数据字段的统一。因此我们同样对可规范数据字段的场景进行了枚举,并以此去推动服务端统一数据字段与一些常用的功能型接口。最终我们达成了一致。

基于统一的数据,我们将部分列表单元格的渲染也内置到了核心系统中,根据插件中声明的单元格类型,核心系统自动去接口返回中找对应字段,并使用对应组件进行渲染。

  • 最终方案中,在插件里声明列表列:

ExtensibleTable.modifyColumns((columns, ctx) => {
    return [
      // 活动信息模型 AA,对应字段 A、B、C
      { title: '活动', 'x-component': 'AA', width: 180 },
      // 活动状态模型 BB,对应字段 D
      { title: '活动状态', 'x-component': 'BB', width: 90 },
      // 活动时间模型 CC,对应字段 E、F、G
      { title: '活动时间', 'x-component': 'CC', width: 180 },
      {
        title: 'xx',
        dataIndex: 'xx',
        cell: (value, index, record) => {
          // 该工具独有的单元格,自定义渲染
        },
        width: 240,
      },
      // 操作列数据模型 TT,对应字段 H
      {
        title: '操作',
        width: 150,
        'x-component': 'TT',
        'x-component-props': { maxCol: 3, maxRow: 5 },
      },
    ];
  });
  • 交互模型

到这里,列表的主要逻辑中还剩余最后一块拼图,操作列交互。在中后台系统中,列表的操作是逻辑繁重的部分。我们在产品&设计规范中,对操作列的交互类型也做了统一。常见的交互类型有:二次确认、复制链接、导出文件、跳转 等。

我们将一个交互恒定的部分在核心系统中实现,在需要变化的部分预留好拓展点,由插件进行定制。

同时一些功能型接口,比如文件导出等,我们也推动了服务端统一,这样在插件中我们只需声明业务参数,甚至无需封装接口调用方法。

  • 最终方案中,在插件里定义操作交互:

ExtensibleTable.modifyRowActions((columns, ctx) => {
 return {
   // 自定义交互
   custom: (option, rowData, tableContext) => {
     // 操作的业务类型为 A 时,对应的交互
     if (option.type === 'A') {
        // do things
     }


     // 操作类型为 B 时,对应的交互
     if (option.type === 'B') {
        // do things
     }
   },
   // 二次确认交互
   doubleCheck: (option, rowData) => {
     let message = '';
     
     // 不同业务类型对应不同的提示文案
     if (option?.type === 'C') { message = 'CCC'; };
     if (option?.type === 'D') { message = 'DDD'; };
     
     return {
       message,
       onConfirm: () => {
         // 操作类型为 E,点击确认时要调用的接口
         if (option.type === 'E') {
           return fetch(params)
             .then(() => {
               Message.success('成功');
             });
         }
         
         if (option.type === 'F') {
           return fetch(params)
             .then(() => {
               Message.success('成功');
             })
         }
      },
    }
  },
  // 导出文件交互
  export: (option, rowData) => {
    let type = '';
    if (option?.type === 'G') type = 'GGG';
    if (option?.type === 'H') type = 'HHH';
    // 声明导出文件的业务参数,无需封装接口调用方法
    return {
      params: {
        type,
      }
    }
  },
  }
});

5fe3da11a67382ceb5f6d69e966e440d.jpeg

结语

基于上述这套方案开发的增量页面进行了研发效能核算。以使用常规方案开发的排期耗时为基准,最终的提效收益是很明显的,都在 50% 以上。

除了研发提效外,这套方案还带来了一些额外的收益

1. 一些在工具之间有共性的需求,比如资损校验等,在统一了设计规范之后,将其集成进 页面容器层 或 场景模版层 能做到一次开发,所有工具生效;

2. 部分模块可完全交给产品配置,变更无需排期;

3. 拔高了交付页面的质量和体验下限,视觉一致性也得到了保障。

同时也引入了一些问题

1. 架构复杂度提升很多,对稳定性和页面性能是一个考验;

2. 同时也对线上问题的排查、变更影响范围的评估带来了一定影响。

因此,未来这套方案目前能想到的迭代方向有几个:

1. 降低由架构引入的问题带来的影响;

2. 探索表单场景是否也能做场景模版层的抽象;

3. 提升存量页面需求开发的研发效率。

816465139f292ee5551559b88df0dd23.jpeg

团队介绍

我们是淘天集团-营销中后台前端团队,负责核心的淘宝&天猫营销业务,搭建千级运营小二、百万级商家和亿级消费者三方之间的连接通道,在这里将有机会参与到618、双十一等大型营销活动,与上下游伙伴协同作战,参与百万级流量后台场景的前端基础能力建设,通过标准化、归一化、模块化、低代码化的架构设计,保障商家与运营的经营体验和效率;参与面向亿级消费者的万级活动页面快速生产背后的架构设计、交付手段和协作方式。目前团队25届秋招进行中,对我们团队感兴趣的同学可以将简历发送至邮箱:wuzhiwei.wzw@taobao.com,欢迎加入!

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全国省、市、县(区)土地利用类型及面积面板数据(2019-2022年)

土地利用类型是根据土地利用方式和地域差异对土地资源单元进行划分的基本土地地域单元。 2019年-2022年全国省、市、县&#xff08;区&#xff09;土地利用类型及面积面板数据_土地利用类型数据下载资源-CSDN文库https://download.csdn.net/download/2401_84585615/89466102 …

2024版最新Wireshark安装使用教程(非常详细)零基础入门到精通,收藏这一篇就够了_wireshark 4.4.0安装要求

前言 这是大白给粉丝盆友们整理的网络安全渗透测试入门阶段渗透测试工具第9篇。 喜欢的朋友们&#xff0c;记得给大白点赞支持和收藏一下&#xff0c;关注我&#xff0c;学习黑客技术 Wireshark 什么是WireShark&#xff1f;Wireshark 是一个开源抓包工具或者叫网络嗅探器&a…

FPGA-Vivado-IP核-逻辑分析仪(ILA)

ILA IP核 背景介绍 在用FPGA做工程项目时&#xff0c;当Verilog代码写好&#xff0c;我们需要对代码里面的一些关键信号进行上板验证查看。首先&#xff0c;我们可以把需要查看的这些关键信号引出来&#xff0c;接好线通过示波器进行实时监测&#xff0c;但这会用到大量的线材…

ViTamin——视觉-语言时代的可扩展视觉模型设计

人工智能咨询培训老师叶梓 转载标明出处 尽管视觉-语言模型&#xff08;VLMs&#xff09;已经取得了显著的成就&#xff0c;但在图像编码器的选择上&#xff0c;传统的视觉Transformer&#xff08;ViT&#xff09;依然是主流。尽管Transformer在文本编码领域已经证明了其有效性…

无线感知会议系列【5】 无线感知边界-1

前言&#xff1a; 无线感知边界是整个ISAC 里面一个研究的难点和重点。 本篇主要来源于2022 《WiFi感知边界研究-Ubicomp2022论文分享》 感知的相关论文组会 2016年无线感知研究主要是国内高校主导,各种无线感知论坛 2021年无线感知 VIVO,OPPO &#xff0c;华为&#xff0c;国…

LeetCode讲解篇之33. 搜索旋转排序数组

文章目录 题目描述题解思路题解代码 题目描述 题目链接 题解思路 旋转后的数组具备一个特性&#xff0c;如果把数组分割成两部分&#xff0c;必定至少有一部分是递增的&#xff0c;并且其中递增区间可以通过左端点小于右端点这个特征来确定 我们基于这个特性&#xff0c;进…

通信工程学习:什么是MIMO多输入多输出技术

MIMO:多输入多输出技术 MIMO(Multiple-Input Multiple-Output)多输入多输出技术是一种在无线通信中广泛应用的技术,它通过利用多个天线进行数据传输和接收,可以显著提高无线通信系统的性能和容量。以下是对MIMO技术的详细解释: 一、定义与原理 MIMO技术…

XWF使用指南

简介 X-Ways Forensics 是由 Stefan Fleischmann 编写的一个轻量化的应急响应及取证工具&#xff0c;是 WinHex 的法证版本&#xff0c;因此界面逻辑和 WinHex 较为相似。在配置好 mplayer 的情况下&#xff0c;程序总体积在 100MiB 左右&#xff0c;运行时内存占用极低&#…

【数据修复指南】手把手教你使用线性插值填补各类遥感数据缺失——Modis、Landsat和Sentinel

线性插值 1. 写在前面2. MODIS数据插值3. Landsat数据插值3.1 参数修改以适应其他类型的遥感数据3.2 Landsat数据汇总3.3 Sentinel卫星介绍 1. 写在前面 之前我写了使用年内均值或者中值来填补数据控制的方法&#xff0c;这种方法较为简单&#xff0c;不够精确。因此&#xff0…

面向人工智能: 对红酒数据集进行分析 (实验四)

由于直接提供截图是不切实际的&#xff0c;我将详细解释如何使用scikit-learn&#xff08;通常称为sk-learn&#xff09;自带的红酒数据集进行葡萄酒数据的分析与处理。这包括实验要求的分析、数据的初步分析&#xff08;完整性和重复性&#xff09;以及特征之间的关联关系分析…

SAP EWM QM 集成

目录 1 简介 2 业务流程 3 后台配置 4 主数据 5 业务操作 5.1 创建 EWM 交货单 5.2 不同的质检结果导致不同的入库地点 - 质检通过 5.3 不同的质检结果导致不同的入库地点 - 质检失败 1 简介 EWM 与 QM (quality management) 集成,自动 or 手动执行质检流程。质检可以…

现代cpp多线程与并发初探

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 在现代c(c20)中,有了jthread和协程的概念,使得我们编写并发程序更加方便. 这里作简单学习. 前言知识 多线程编程 std::thread 用于创建一个执行的线程实例,所以它是一切并发编程的基础,使用时需要包含 <thread…

XSS(内含DVWA)

目录 一.XSS的攻击方式&#xff1a; 1. 反射型 XSS&#xff08;Reflected XSS&#xff09; 2. 存储型 XSS&#xff08;Stored XSS&#xff09; 3. DOM型 XSS&#xff08;DOM-based XSS&#xff09; 总结 二..XSS的危害 三.常见的XSS方式 1.script标签 四.常见基本过滤方…

假期旅行数仓项目--OLAP

需要这个完整离线数仓项目的源码和流程PPT可以私信我&#xff0c;可以帮助解决项目中遇到的问题&#xff0c;做完项目可以让你对数仓有更加清晰的认识 项目流程&#xff1a; 配置文件 kafka server.properties hive : hvie-site.xml 启动mysql 的binlog日志 修改maxwell配置…

QT:常用类与组件

1.设计QQ的界面 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QLineEdit> #include <QLabel>//自定义类Widget,采用public方式继承QWidget&#xff0c;该类封装了图形化界面的相关操作&#xff…

怎么绕开华为纯净模式安装软件

我是标题 众所周不知&#xff0c;华为鸿蒙系统自带纯净模式&#xff0c;而且 没法关闭 : ) 我反正没找到关闭键 以前或许会有提示&#xff0c;无视风险&#xff0c;“仍要安装”。但我这次遇到的问题是&#xff0c;根本没有这个选项&#xff0c;只有“应用市场”和“取消”&…

动态规划笔记

第一轮面试准备到第26题 一 解题步骤 对于动态规划问题&#xff0c;我将拆解为如下五步曲&#xff0c;这五步都搞清楚了&#xff0c;才能说把动态规划真的掌握了&#xff01; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历…

基于yolov8的海上红外目标系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的海上红外目标系统是一项集成了前沿技术的创新解决方案&#xff0c;专为复杂海洋环境下的目标检测而设计。该系统利用YOLOv8深度学习模型的强大目标检测能力&#xff0c;结合红外成像技术&#xff0c;实现了对海上小型船只、浮标、甚至水下潜器等目标…

Arch - 架构安全性_传输(Transport Security)

文章目录 OverView导图1. 概述2. TLS的基本概念2.1 什么是TLS&#xff1f;2.2 TLS与SSL的关系2.3 TLS的工作原理 3. TLS的核心组件3.1 加密算法3.2 哈希函数3.3 数字证书 4. TLS握手过程4.1 客户端Hello4.2 服务器Hello4.3 证书验证4.4 密钥交换4.5 会话密钥生成4.6 安全连接建…

解锁初中学习新境界 —— 初中通关宝典速记手册

在初中这个学习生涯的关键阶段&#xff0c;掌握扎实的基础知识是取得优异成绩的关键。为此&#xff0c;我们特别推荐《初中通关宝典》——一本专为初中生打造的各科基础知识速记手册&#xff0c;它将成为你学习路上的得力助手。 文章目录 1. 全科覆盖&#xff0c;精准速记2.科学…