第一部分:执行摘要:2025年 Kotlin Multiplatform 最终评估

 

2025年,Kotlin Multiplatform (KMP) 已经毫无疑问地成为一项成熟且可用于生产环境的技术,尤其在Android与iOS应用之间共享业务逻辑方面。其来自JetBrains和Google的双重战略支持 ,已经巩固了它作为该领域一流解决方案的地位。对于在2025年考虑采用KMP的团队而言,最关键且最微妙的决策已不再是“是否能用”,而是“如何架构用户界面(UI)”。

近期,Compose Multiplatform for iOS的稳定版发布 ,为开发者开辟了两条截然不同但均可行的路径:

  1. 共享逻辑,原生UI (The Pragmatic Approach):一种灵活的、原生优先的策略,仅共享非UI部分的业务逻辑,而UI层则由各个平台的原生技术(Android的Jetpack Compose/XML,iOS的SwiftUI/UIKit)独立构建。
  2. 共享逻辑与UI (The Unified Approach):一种追求高效率的统一策略,使用Compose Multiplatform将业务逻辑和UI代码一并共享。

这一选择对团队结构、开发体验、最终产品的用户体验(UX)乃至项目风险管理都具有深远的影响。本报告将深入剖析KMP生态系统的现状,对这两种UI策略进行详尽的比较分析,并提供一个可操作的决策框架,旨在为技术决策者在进行这项重大的技术投资时提供清晰、有据可依的指引。

 

第二部分:平台现状:核心技术与战略支持

 

要评估一项技术的成熟度,首先必须审视其底层基础的稳定性以及来自行业领导者的战略承诺。在2025年,KMP在这两方面都表现出前所未有的强势。

 

2.1 K2编译器与Kotlin 2.2:构建于速度与现代性之上的基础

 

KMP的性能和开发效率在2025年得到了根本性的提升,这主要归功于K2编译器的全面落地和Kotlin语言本身的持续进化。

  • K2编译器成为默认选项:从IntelliJ IDEA 2025.1版本开始,K2编译器已成为默认选项 。这不仅仅是一次版本更新,而是一次对编译器架构的彻底重写。JetBrains内部一个包含超过1200万行Kotlin代码的巨型项目,在切换到K2模式后,构建时间缩短了40% 。这一成就直接解决了跨平台项目长期以来的痛点之一:缓慢的编译和持续集成(CI)流程,极大地提升了开发生产力。
  • Kotlin 2.2语言新特性:随着Kotlin 2.2.0成为最新的稳定版本 ,一系列新的语言特性,如

    when表达式的守卫条件(guard conditions)和上下文参数(context parameters),进一步增强了代码的安全性和表达力 。这意味着开发者可以编写出更健壮、更易于维护的共享业务逻辑。同时,2.2.20的Beta版本已经发布 ,显示出JetBrains正以极快的节奏进行迭代和优化。

 

2.2 2025年路线图:双轨并行的市场策略

 

JetBrains为KMP制定的2025年路线图清晰地展示了一种双轨并行的市场渗透策略,旨在满足不同类型团队的需求,从而最大化其市场接受度。

  • 第一轨道:“全栈Kotlin”路径与Compose Multiplatform:JetBrains的首要任务是将Compose Multiplatform for iOS打造成一流的稳定解决方案。2025年的工作重点包括:实现与Jetpack Compose的功能对等、优化iOS端的渲染性能,以及完善导航、资源管理、无障碍(Accessibility)和国际化等核心功能 。在KotlinConf 2025上正式宣布其稳定,是整个生态系统的一个里程碑事件 。
  • 第二轨道:“务实集成”路径与Swift导出:与此同时,JetBrains正在积极开发一种直接将Kotlin代码导出为Swift模块的机制,目标是在2025年发布首个公开版本 。该工具旨在提供与现有Objective-C桥接相当的用户体验,同时克服后者的种种限制,例如能更好地处理可空类型、泛型和枚举,使Swift调用Kotlin代码时感觉更自然、更符合语言习惯 。

这种双轨策略并非摇摆不定,而是对市场需求的深刻洞察。它允许KMP同时吸引两个截然不同的用户群体:一是追求最大化代码复用和统一开发流程的初创公司或Android背景浓厚的团队(Compose Multiplatform路径);二是拥有庞大且对原生工具有着深厚执念的iOS团队的大型企业(Swift导出路径)。这种灵活性是KMP相较于Flutter等“全有或全无”框架的显著竞争优势。通过提供一个低摩擦的集成选项,KMP有效地降低了在复杂组织中推广的阻力。

 

2.3 Google的官方背书:从实验到生产级支持

 

如果说JetBrains的努力是KMP发展的内生动力,那么Google的官方支持则为其注入了强大的外部可信度。

  • 官方支持与生产应用:在Google I/O大会上,Google正式宣布为KMP提供支持,特别是用于在Android和iOS之间共享业务逻辑 。这并非空头支票,Google已将自家的旗舰应用,如Google Docs,在iOS端投入使用KMP进行生产环境的运行,并报告其性能与之前相比持平甚至更优 。这种规模的应用实践为KMP的稳定性、性能和可扩展性提供了最有力的证明。
  • Jetpack库的多平台化:Google正在投入大量资源,将核心的Jetpack库进行多平台化改造。目前,包括Room(数据库)、DataStore(数据存储)、Paging(分页加载)、ViewModel(视图模型)在内的多个关键库已经发布了支持iOS的稳定版本 。这对开发者而言是一个颠覆性的改变,因为它意味着团队可以在共享代码中直接使用那些他们在Android开发中早已熟悉、功能强大且经过大规模实战检验的架构组件。

下表总结了KMP技术栈中关键组件在2025年的成熟度状态,为技术决策者提供了一个清晰的概览。

表2.1:KMP技术栈成熟度 (2025年)

组件 状态 2025年关键里程碑 相关资料来源
Kotlin语言 (K2) Stable K2编译器成为IDE默认选项,显著提升构建速度
KMP核心框架 Stable 已在众多大型应用中得到生产验证
Compose Multiplatform for iOS Stable API稳定,正式发布,可用于生产环境
Compose Multiplatform for Desktop Stable 已稳定,广泛用于桌面应用开发
Compose for Web (Wasm) Beta 性能和API对等性持续改进,向Beta阶段迈进
Kotlin-to-Swift 导出 Experimental 计划2025年发布首个公开版本
Amper 构建工具 Experimental 2025年重点支持KMP移动应用开发

 

第三部分:核心决策:用户界面(UI)的架构选择

 

在确认KMP核心技术已然成熟之后,团队面临的最关键决策便是如何处理UI。2025年,两条路径都已清晰且可行,但它们各自通往截然不同的开发模式和产品形态。

 

3.1 策略一:共享逻辑,原生UI(务实主义路径)

 

这种模式是KMP最经典、风险最低的应用方式,它将代码共享的边界严格限制在非UI层面。

  • 架构解析:该模型的核心是创建一个共享的Kotlin模块,其中包含所有的业务逻辑、数据层(网络请求、数据库访问)、状态管理等。然后,在此之上构建两个完全独立的、100%原生的UI层:Android端使用Jetpack Compose或传统的XML,iOS端则使用SwiftUI或UIKit 。当共享代码需要调用平台特有的API(如传感器、文件系统、加密算法)时,通过KMP提供的

    expect/actual机制来实现,这允许在共享模块中定义一个期望的(expect)接口,然后在各个平台模块中提供实际的(actual)实现 。

  • 优势分析
    • 极致的原生保真度:UI/UX是完全原生的,可以完美地遵循各平台的设计规范、交互习惯和动画效果,确保用户获得最流畅、最地道的体验,不存在任何“非原生感” 。
    • 发挥团队专长:允许Android和iOS的专家团队继续使用他们最擅长的工具链(Android Studio/Compose, Xcode/SwiftUI),最大程度地减少了学习成本和文化摩擦 。
    • 低风险的增量迁移:对于已有的原生应用,这是理想的迁移路径。团队可以从共享一小部分逻辑开始(例如一个复杂的算法或网络层),然后逐步扩大共享范围,风险完全可控 。
  • 挑战与开发体验
    • “两个团队,一个大脑”的协作难题:此模式的成败高度依赖于团队间的无缝协作。如果Android和iOS团队处于孤岛状态,很容易出现“逻辑分叉”的问题:iOS开发者可能会因为共享模块过于“Android中心化”或难以使用,而选择在Swift中重写逻辑,这便完全违背了KMP的初衷 。
    • 状态管理的互操作性:将Kotlin现代的状态管理模式(如StateFlow)转换为SwiftUI能够方便消费的形式,并非易事。如果没有清晰的架构约定和良好的封装,这里很容易成为产生摩擦和bug的重灾区 。
    • iOS端的调试困境:从Xcode中调试共享的Kotlin代码至今仍是一个挑战,断点功能可能不稳定。这常常迫使iOS开发者将共享模块视为一个“黑盒”,严重影响了生产力,也增加了他们重写逻辑的动机 。
  • 案例深度剖析
    • Netflix:在其内部使用的Prodicle工作室应用中,Netflix利用KMP共享了平台无关的业务逻辑、网络层(使用Ktor)和数据持久化(使用SQLDelight),尤其是在需要强大离线支持的场景下,同时保持UI完全原生 。
    • Forbes:共享了超过80%的业务逻辑,这使得他们能够在两个平台同步推出新功能,同时保留了根据平台特性定制原生UI的灵活性 。
    • McDonald’s:共享了应用内支付等复杂功能的逻辑。在KotlinConf 2025上,他们展示了一个关键的创新:将导航逻辑(即决定在何种条件下从哪个屏幕跳转到哪个屏幕)也放入了KMP共享模块,而UI界面和导航控制器本身仍然是原生的 。

 

3.2 策略二:共享逻辑与UI,采用Compose Multiplatform(统一化路径)

 

随着Compose Multiplatform for iOS的稳定,这条追求极致代码复用的路径变得切实可行。

  • 架构解析:在这种模式下,一个单一的Kotlin代码库不仅包含业务逻辑,还包含了使用Compose Multiplatform编写的声明式UI代码 。这套共享的UI代码在Android上通过Jetpack Compose渲染,在iOS上则通过一个宿主

    UIViewController嵌入,并利用原生的图形库进行绘制 。

  • 优势分析
    • 最大化代码复用:可以实现高达95%甚至更高的代码共享,极大地减少了开发和维护的工作量,避免了“一份需求,两份实现”的资源浪费 。
    • 提升开发速度:由于无需在两个平台上分别编写和同步UI代码,新功能的交付速度得以大幅提升。这对于开发MVP(最小可行产品)、内部工具,或者那些品牌视觉一致性高于平台原生性的应用来说,优势尤为明显 。
    • 统一的团队结构:可以组建一个单一的Kotlin开发者团队来负责两个平台的开发,简化了团队管理和知识传递。例如,Physics Wallah公司通过此举将Android和iOS工程师整合成了一个统一的移动团队 。
  • 挑战与“稳定版”的现实: Compose for iOS的“稳定”主要指API的稳定性,为生产使用提供了保障。然而,开发者在实际项目中仍会遇到一些挑战 :
    • UI/UX的一致性问题:目前Compose Multiplatform缺少一套原生的“Cupertino”风格组件库,导致所有组件默认呈现Material Design风格,这在iOS上可能会显得格格不入。尽管滚动物理效果、文本处理等方面已经对齐了iOS的原生行为 ,但要让所有组件都达到完美的iOS原生质感,仍需大量的自定义工作 。
    • 性能与调试:虽然应用的启动时间与原生应用相当 ,但在复杂的UI界面上,开发者报告了布局卡顿(jank)、CPU占用率高和内存泄漏等问题。此外,由于Xcode的视图调试工具无法检查Compose的视图层级,可视化调试变得非常困难 。
    • 无障碍功能(Accessibility):将Compose视图正确地暴露给iOS的无障碍服务,目前仍存在一些挑战,可能会影响特殊用户群体的使用体验 。
  • 案例深度剖析
    • Bitkey by Block:这家公司最初采用了原生UI的策略,但后来主动迁移到了Compose Multiplatform。其核心原因并非技术瓶颈,而是组织效率:在一个没有严格区分iOS/Android专家的精干团队中,频繁在Jetpack Compose和SwiftUI之间切换上下文,既繁琐又容易出错。统一的UI代码库提升了他们对功能一致性的信心,并简化了开发流程 。
    • Feres & WallHub:这两个应用报告称使用Compose Multiplatform共享了90-100%的UI代码,充分展示了该策略在代码复用方面的巨大潜力 。

技术决策的核心,往往是在不同类型的风险之间进行权衡。在KMP的UI策略选择上,这一点体现得淋漓尽致。选择“原生UI”路径,意味着将赌注押在团队的协作和架构能力上,它最小化了产品/UX风险(保证了最佳用户体验),但最大化了执行/团队风险。一旦团队协作出现问题 ,其所有优势都将荡然无存。反之,选择“共享UI”路径,则最小化了

执行/团队风险(简化了开发流程和团队结构),但引入了产品/UX风险。团队可能会交付一个在iOS上感觉不够“地道”的应用,从而影响用户观感和接受度。

因此,这个决策并非纯技术问题,而是要求技术领导者对团队文化、技能储备和项目目标进行综合评估。

表3.1:UI策略决策矩阵 (KMP原生UI vs. Compose Multiplatform)

决策因素 策略1:原生UI (共享逻辑) 策略2:共享UI (Compose Multiplatform) 推荐指南
用户体验保真度 极高 (100%原生) 中到高 (依赖优化,可能存在非原生感) 如果应用对平台原生体验有极致要求,选策略1。
开发速度 (上市时间) 较慢 (需开发两套UI) 极快 (UI只需开发一次) 对于MVP或追求快速迭代的项目,策略2优势明显。
代码复用率 (%) 30-80% (仅限逻辑) 90-95+% (逻辑+UI) 如果最大化代码复用是首要目标,选策略2。
长期维护成本 较高 (需维护两套UI代码) 较低 (单一代码库) 对于需要长期维护的大型项目,策略2成本更低。
团队结构与技能 需要专业的Android和iOS团队,并强调协作 可由统一的Kotlin团队负责,降低招聘和管理复杂性 策略1适合现有的大型专业团队,策略2适合精干或新组建的团队。
原生API/SDK访问 无缝直接访问 通过expect/actual或平台视图嵌入,可能增加复杂性 如果应用深度依赖复杂的原生SDK(如ARKit),策略1更直接。
生态与库支持 UI层可使用所有原生库 共享UI库正在发展,但不如原生生态成熟 策略1在UI库选择上更丰富。
项目风险画像 执行风险高,产品风险低 产品风险高,执行风险低 需根据公司对不同风险的容忍度进行选择。

 

第四部分:开发者生态:工具、库与社区

 

一项技术能否在实际项目中成功落地,其周边的生态系统——包括开发工具、可用的库和社区支持——起着决定性的作用。

 

4.1 开发环境:IDE与构建工具

 

  • IDE的统一:目前,KMP的开发体验已经全面整合到开发者熟悉的IntelliJ IDEA和Android Studio中 。JetBrains已经调整了策略,不再将实验性的Fleet IDE作为KMP的主要开发环境,而是加倍投入于完善基于IDEA的工具链 。官方的KMP插件提供了项目创建向导和集成化工具,尽管一些开发者仍然认为,与纯原生开发相比,其流程中还存在一些摩擦点 。
  • 构建系统的挑战:Gradle:Gradle是当前官方指定的构建工具。然而,它的复杂性是一个被广泛承认的痛点,特别是对于不熟悉JVM生态的开发者(例如大多数iOS开发者)而言。多平台的Gradle配置可能相当复杂,并且是导致构建失败的常见原因 。
  • 构建系统的未来:Amper的承诺:JetBrains正在开发一个名为Amper的实验性项目。这是一个专为Kotlin和JVM设计的、更为简洁的构建工具。2025年的重点目标就是使其能够完全支持包含共享UI的KMP移动应用开发 。Amper的存在本身就是对Gradle复杂性的承认,它预示着未来KMP项目的配置体验将得到极大的改善。

 

4.2 库生态景观:两大生态的融合

 

KMP的库生态系统在2025年呈现出前所未有的强大,这得益于两个成熟生态的交汇。

  • 成熟的Kotlin原生库生态:由JetBrains和社区主导开发的核心库已经非常成熟且可用于生产。这包括用于并发的kotlinx.coroutines、用于JSON序列化的kotlinx.serialization、用于网络编程的Ktor,以及用于本地数据库的SQLDelight 。这些库构成了绝大多数KMP项目的基石。
  • 颠覆性的Jetpack多平台库生态:Google承诺将Jetpack库多平台化,是近年来KMP生态最重要的发展。像RoomDataStorePagingViewModel这样的库,现在已经为iOS提供了稳定(Tier 1级别)的支持 。这意味着团队可以在共享代码中使用与Android开发完全相同的、功能强大、文档齐全且经过实战检验的架构组件。
  • 分级支持模型:必须清楚地认识到,Jetpack库的支持是分级的。Android、iOS和JVM属于Tier 1级别,意味着得到了全面的测试和支持。macOS和Linux等桌面平台属于Tier 2级别(部分测试)。而Web/Wasm则属于Tier 3级别(实验性) 。这意味着,虽然KMP在移动端的故事非常强大,但如果要扩展到其他平台,就需要仔细核对所需库的支持级别。

KMP生态系统的力量源于两个强大库生态(Kotlin自身和Android Jetpack)的融合,但其当前的短板在于将它们粘合在一起的工具(Gradle)。JetBrains显然已经认识到Gradle的陡峭学习曲线是阻碍KMP被更广泛采用(尤其是在非JVM背景的iOS开发者中)的主要瓶颈。因此,Amper项目是一个旨在降低入门门槛、平滑开发者体验的战略性举措。对于2025年的决策者来说,当前的工具链可以被视为一个“充满挑战但正在改善”的现状,并且已经有了一条官方明确支持的、通往更美好未来的道路。

表4.1:关键Jetpack多平台库对iOS的支持情况 (2025年)

Jetpack库 Maven Group ID 最新稳定版 对iOS的支持级别 在共享代码中的常见用途
Room androidx.room 2.7.2 Tier 1 在共享模块中实现结构化的本地数据库
DataStore androidx.datastore 1.1.7 Tier 1 用于存储键值对或类型化的对象,替代SharedPreferences
Paging androidx.paging 3.3.6 Tier 1 实现高效的、支持网络和数据库的分页加载逻辑
ViewModel androidx.lifecycle 2.9.2 Tier 1 在共享模块中管理UI相关的状态和业务逻辑
Lifecycle androidx.lifecycle 2.9.2 Tier 1 在共享ViewModel中管理协程作用域
Collection androidx.collection 1.5.0 Tier 1 提供优化的集合类,如LruCache
SQLite androidx.sqlite 2.5.2 Tier 1 为Room提供底层的SQLite驱动
Annotation androidx.annotation 1.9.1 Tier 1 在共享代码中使用注解以增强静态检查

注:版本号基于截至2025年8月1日的数据 。

 

第五部分:性能、架构与团队动态

 

一个成功的技术选型不仅要看其功能,还要评估其非功能性表现以及对团队组织结构的影响。本部分将探讨KMP在实际运行中的性能、构建可维护应用的架构模式,以及成功实施所必需的“人”的因素。

 

5.1 性能画像:原生设计

 

KMP的性能优势根植于其核心设计理念:将代码编译为目标平台的原生二进制文件(Android为JVM字节码,iOS为基于LLVM的原生代码) 。这意味着共享的业务逻辑是以原生速度运行的,这与那些依赖于JavaScript桥接的框架形成了鲜明对比,后者在复杂计算或高频交互时可能成为性能瓶颈 。

  • 基准测试数据:与React Native相比,KMP应用在基准测试中展现出更优的性能。其启动时间快约30%,内存消耗低15-20%,电池消耗也减少了10-15% 。
  • UI性能:当采用“原生UI”策略时,UI性能与完全原生的应用完全相同 。当采用“共享UI”(Compose Multiplatform)策略时,对于大多数场景,性能表现良好且与原生应用相当。然而,如前所述,在处理极其复杂的UI或长列表时,可能会遇到需要特别优化的性能问题,如卡顿或CPU占用过高 。

 

5.2 架构最佳实践:构建长青应用

 

社区在长期的实践中,已经沉淀出了一套行之有效的KMP架构模式。

  • 整洁架构 (Clean Architecture):这是KMP社区中最主流的架构模式。它倡导将应用清晰地划分为不同的层次(如领域层、数据层、表现层),这与KMP将共享逻辑与平台特定UI分离的理念完美契合 。
  • 多模块结构:对于大型项目,强烈建议采用多模块的组织方式,而不是将所有代码都放在一个庞大的shared模块中。可以按功能划分模块(如core-network, feature-login, feature-dashboard),这有助于改善编译速度、提高代码的可维护性,并支持团队并行开发 。
  • expect/actual模式的审慎使用:这是KMP实现平台互操作性的关键机制。最佳实践是将其严格限制在架构的边界处(例如,在数据层提供平台特定的数据库驱动或网络客户端实现),以此来封装平台差异,避免平台相关的细节泄漏到核心业务逻辑中 。

 

5.3 人的因素:团队结构与协作

 

KMP项目最常见的失败原因并非技术本身,而是组织和协作上的障碍。

  • 最大的风险:团队孤岛:一种注定会失败的模式是:Android团队负责开发共享模块,然后像“扔过墙”一样将其交给iOS团队使用 。
  • “黑盒”反模式:这种孤岛式的协作方式,会导致iOS团队将KMP模块视为一个不透明、不可信的第三方依赖。当遇到问题时,他们无法或不愿去调试Kotlin代码,最终选择在Swift中重写逻辑,从而使KMP的优势荡然无存 。
  • 成功的模式:统一的移动团队:成功的KMP实践需要文化的转变。像Physics Wallah这样的公司,已经将其Android和iOS工程师整合成一个统一的“移动团队” ,共同对整个代码库负责。
  • 协作最佳实践
    • 共同所有权:Android和iOS开发者都必须对共享模块负责。这可能需要对iOS开发者进行Kotlin/Gradle的培训,或者在处理共享代码相关的任务时采用结对编程 。
    • 清晰的架构契约:共享模块与原生UI之间的接口必须被良好地定义和文档化,并避免做出“Android中心化”的假设 。
    • 同理心与沟通:向团队推广KMP需要充分理解各平台专家的顾虑。JetBrains官方甚至提供了详细的指南,指导如何管理这一过渡过程,包括解释采用KMP的“原因”,以及创建概念验证项目来展示其价值 。

KMP不仅仅是一项技术,它更像是一个组织变革的催化剂。它迫使团队重新审视移动开发的协作模式。一个项目的成功,在很大程度上不取决于代码写得如何,而取决于协作模式是否有效。一个技术领导者在考虑KMP时,不仅要为开发时间做预算,更要为组织变革管理(如培训、工作坊、团队重组)预留资源。对KMP投资的回报率,与跨团队协作的水平成正比。忽视人的因素,是导致失败的最大预兆。

 

第六部分:竞争格局与最终建议

 

为了做出最终决策,我们需要将KMP置于更广阔的跨平台市场中进行比较,并综合所有分析,给出一套明确的建议。

 

6.1 KMP vs. Flutter vs. React Native:2025年战略快照

 

这三个框架并非简单的优劣之分,而是代表了三种不同的开发哲学和应用场景。

  • Kotlin Multiplatform:“务实的”原生派。它最适合那些希望在保留100%原生UI/UX控制权的同时共享业务逻辑的团队,或者是那些寻求使用Compose技术栈实现最大化代码复用的团队。其核心优势在于性能灵活性
  • Flutter:“统一的”UI派。它最适合用单一代码库构建视觉效果丰富、高度定制化的UI。其核心优势在于UI一致性和UI密集型应用的开发速度
  • React Native:“Web到移动”的桥梁。它最适合拥有现有JavaScript/React技术栈的团队。其核心优势在于庞大的生态系统和对于Web开发者极低的入门门槛

表6.1:KMP vs. Flutter vs. React Native:2025年战略快照

属性 Kotlin Multiplatform Flutter React Native
核心哲学 共享代码,而非平台抽象 一套代码库,绘制所有平台的UI 用Web技术构建原生应用
主要用例 共享业务逻辑,原生UI优先;或全栈Kotlin UI驱动,品牌一致性强的应用 Web团队快速开发移动端MVP
性能模型 编译为原生代码,性能极高 编译为原生代码,自带渲染引擎,性能高 通过JS桥接与原生通信,性能良好但有瓶颈
UI策略 灵活可选:原生UI或共享UI (Compose) 统一UI:所有平台使用相同的Widget 使用原生组件,通过JS控制
代码复用 灵活 (30% – 95+%) 极高 (UI+逻辑) 高 (UI+逻辑)
学习曲线 (对Android开发者) 极低 中等 (需学习Dart) 中等 (需熟悉React生态)
学习曲线 (对iOS开发者) 中等 (需学习Kotlin/Gradle) 中等 (需学习Dart) 中等 (需熟悉React生态)
生态成熟度 快速成熟,受益于Kotlin和Android生态 成熟,社区活跃 非常成熟,库最丰富
企业支持 JetBrains + Google Google Meta

 

6.2 最终建议与项目准备清单

 

KMP在2025年的最终价值主张在于其战略灵活性。它是唯一一个不强迫开发者采用“一刀切”模式的主流框架。它允许企业根据自身需求,选择代码共享的程度——从共享10%(一个核心算法)到共享95%(全部逻辑和UI),并允许这种策略随着产品和团队的演进而动态调整。这种适应性降低了被单一技术路线锁定的风险。

在2025年,您的团队应该采纳Kotlin Multiplatform,如果:

  • 您的应用拥有复杂且关键的业务逻辑,必须在各平台间保持高度一致(例如金融、健康、物流领域) 。
  • 您正在计划迁移一个现有的大型原生应用,并需要一条低风险、可增量实施的代码共享路径 。
  • 您的团队已经拥有深厚的Kotlin/Android开发经验,这将极大地降低学习成本 。
  • 性能和真实的原生外观感受是不可妥协的需求(这将引导您选择“原生UI”策略) 。
  • 您是一个初创公司或一个优先考虑开发速度的团队,并且愿意拥抱完整的Compose Multiplatform技术栈(这将引导您选择“共享UI”策略) 。

您应该持谨慎态度或选择其他方案,如果:

  • 您的应用极其简单,主要以UI展示为主,几乎没有复杂的共享逻辑 。
  • 您的首要目标是以最快速度推出一个简单的MVP,且团队拥有强大的Web开发背景(在这种情况下,React Native的初始开发速度可能更快) 。
  • 您的组织内部,移动开发团队之间壁垒森严,存在强烈的协作抵触文化(这是采用KMP的最大危险信号) 。

总而言之,Kotlin Multiplatform在2025年已经发展成为一个强大、灵活且得到行业巨头支持的成熟技术。它为那些希望在代码复用带来的效率和原生应用提供的卓越体验之间找到最佳平衡点的团队,提供了一个无与伦比的解决方案。它并非适用于所有场景的“银弹”,但对于合适的项目和团队而言,它无疑是通往未来跨平台开发的一条坚实路径。


title: 使用N8N工作流自动化解决三方API数据对接

0、n8n是什么?

n8n 是免费的基于节点的工作流自动化工具,可以轻松实现跨不同服务的任务自动化。它可以自托管,易于扩展,因此也可以与内部工具一起使用。

1、安装

参见n8n官方文档^[1],推荐docker安装

2、需求案例

在商业推广中往往有一些API对接的需求,细分下来有这几类
* ① 通用广告投放平台 如果是广点通、巨量引擎、橙子建站线索API对接等
* ② 我方开放API
* ③ 三方API对接
市场推广活动会产生三方API对接的需求。
这样的需求去基于代码开发的方式对接,对于开发者来说价值不高,使用N8N能解决这种对接问题

例如一个推广服务商使用API提供线索,需要实现全自动化同步到我方系统

工作流逻辑上就是: 计划任务周期运行 > 读取数据通过API(三方) > 数据转换处理 > 写入数据通过API(我方)

3、n8n实现

n8n工作流可视化效果图
使用N8N工作流自动化解决三方API数据对接

共需 4个 核心节点(node)(写入excel文件可忽略)
计划任务周期运行Cron CORE NODES > Flow > Cron
读取数据通过API(三方)DEVELOPMENT > HTTP Request
数据转换处理 CORE NODES > Function
写入数据通过API(我方)DEVELOPMENT > HTTP Request

主要说下数据转换处理

数据转换处理把接口返回数据变成 n8n中 items列表
接口返回数据在n8n中默认为items,由于返回的多条数据是在json中data字段中,需要做个转换取出来转换成n8n中 items列表供下游节点使用。
items[0].json.data 取接口返回数据,json数据,data字段

let rows = []; 

for (item of items[0].json.data) {
  rows.push(item);
}

console.log('Done!');
return rows;

在n8n中的函数代码都是js编写的

数据转换处理好后,items就有多个了,本工作流中取到了3个items,对于「写入数据通过API」就会调用3次

4、参考

1. n8n官方文档

基于FreeSWITCH自建呼叫中心中台 流水理鱼|wwek PPT分享

目录导航

  • 业务需求背景情况 – 业务需求、背景情况
  • 业务系统如何外呼 – 点呼、群呼、AI
  • 呼叫中心通话链路 – 通话序列图、呼叫线路、SIP协议介绍
  • 呼叫中心中台架构 – 呼叫中心中台架构设计(基础版)
  • FreeSWITCH-介绍 – FreeSWITCH电话软交换
  • FreeSWITCH-拨通第一个电话 – SIP Hello World
  • FreeSWITCH-集成 – FreeSWITCH系统集成设计和实现
  • FreeSWITCH-中台API封装 – 把FreeSWITCH的能力封装为中台API

业务需求背景情况

  • CRM中常见需要对ToB、ToC客户进行电话回访、电话销售
  • 在打电话这个事上,企业需求比个人需求要求更多,最基本的可系统集成、有话单、有录音等
  • 无论时代如何变,传统的基于运营商电话的接通需求是一直稳定存在的
  • 三方商业呼叫系统有很多,okcc、合力忆捷、天润、容联七陌、网易七鱼等
  • 自研呼叫解决3个主要核心问题。能力上,定制开发扩展性拉满;成本上,比购买商业呼叫系统便宜;安全上,数据在手
  • 自研呼叫需要具备条件,公司业务按年为单位长期有呼叫需求,有开发人员资源

业务系统如何外呼

  • 呼叫方式上,单个点击拨打(点呼)、批量呼通再分配排队坐席(群呼)、AI自动呼叫
  • 坐席(打电话人)软电话登录,登录呼叫中心的软电话客户端(无客户端的为网页浏览器客户端)
  • 业务系统中点击拨打、或者操作建立群呼、AI呼叫任务
  • 业务系统通过API调用呼叫中心控制发起每通电话,接下来看看通话序列图

查看文章

☎️呼叫中心通话链路 – 呼叫线路示意图

“`mermaid {theme: ‘neutral’, scale: 0.66}
graph LR
A[呼叫中心系统] –>|呼叫线路选择| B(选择线路网关1)
B –> |SIP|C{呼叫线路商1线路路由}
A[呼叫中心系统] –>|呼叫线路选择| Bn(选择线路网关N)
C –>|电信运营商落地1| D[A地固话号]
C –>|电信运营商落地2| E[B地固话号]
C –>|电信运营商落地3| F[C地手机号]
Bn –> |SIP|Cn{呼叫线路商N线路路由}
Cn –>|电信运营商落地1| Dn[A地固话号]
Cn –>|电信运营商落地2| En[B地手机号]
Cn –>|电信运营商落地3| Fn[C地手机号]

<pre><code><br /><br /># 呼叫中心通话链路 – 呼叫线路介绍
– 呼叫线路商提供的线路在 呼叫中心系统 有多个称呼,线路网关、SIP中继、中继网关、落地线路
– 呼叫线路的称呼,不论叫什么,他都是电话的通道,使用SIP协议对接
– 呼叫线路商使用VOS语言运营系统作为支持软件,VOS处于VOIP运营垄断地位
– 不使用呼叫线路商,直找电信运营商对接线路,用SIP协议对接
– 这样就相当于只有一条落地线路了,没有丰富的线路资源来优化线路路由,需按业务需求选择
– **呼叫线路商的本质是聚合多条、多地、多类型电信运营商线路资源**
一个典型的线路对接信息
</code></pre>

线路备注:xx线路
IP:8.8.8.8
UDP端口:5060 (SIP协议)
主叫送:20220601
被叫加前缀:6
并发:500
限制:一天一次
盲区:北、新、西

<pre><code><br /># SIP协议介绍
SIP(Session Initiation Protocol,会话初始协议)[^1]是由IETF(Internet Engineering Task Force,因特网工程任务组)制定的多媒体通信协议。广泛应用于CS(Circuit Switched,电路交换)、NGN(Next Generation Network,下一代网络)以及IMS(IP Multimedia Subsystem,IP多媒体子系统)的网络中,可以支持并应用于语音、视频、数据等多媒体业务,同时也可以应用于Presence(呈现)、Instant Message(即时消息)等特色业务。可以说,有IP网络的地方就有SIP协议的存在。SIP类似于HTTP
– **说人话!SIP协议用来在IP网络上做电话通讯**
[^1]: [一文详解 SIP 协议](https://www.cnblogs.com/xiaxveliang/p/12434170.html)

# SIP协议介绍-SIP Server
![](https://static.iamle.com/note/202207011111385.png)
– FreeSWITCH就是一个SIP Server,也是一个B2BUA,后面具体讲 FreeSWITCH
– **呼叫中心的SIP Server桥接 A leg 和 B leg,这样A和B就建立通话了**
– B2BUA看起来唬人,靠背嘛,就是A leg 和 B leg靠背桥接起来
– 支持SIP协议的软电话客户端常用的有:Linphone、MicroSIP、Eyebeam等

# SIP协议介绍-SIP Server 和 A leg 通讯
<img class="m-1 h-110 rounded" src="https://static.iamle.com/note/202207011131334.png" />

# SIP协议介绍-SIP Server 和 B leg 通讯
<img class="m-1 h-110 rounded" src="https://static.iamle.com/note/202207011124286.png" />

# 呼叫中心中台架构 – 呼叫中心中台架构设计(基础版)目标
– 基础版设计目标,实现点击拨打(点呼)
– **呼叫能力的高级抽象**
## 呼叫(主叫号码,被叫号码,[线路网关], [拓展数据])

– 主被叫号码既可以是内部的坐席分机号码,也可以是手机号码
– 线路网关是选填参数,支持多个逗号分割,为空使用系统默认配置网关
– 拓展数据是选填参数,呼叫系统在后续通话技术后原样传回业务系统
– 拓展数据,用于业务自身逻辑

layout: center

# 呼叫中心中台架构 – 呼叫中心中台架构设计(基础版)架构图
<img class="m-1 h-110 rounded" src="https://static.iamle.com/note/202207011731635.png" />

# FreeSWITCH – 介绍
– FreeSWITCH 是一个作为背靠背用户代理实现的开源运营商级电话平台。由于这种设计,它可以执行大量不同的任务,从PBX到传输交换机、TTS(文本到语音)转换、音频和视频会议主机,甚至是VoIP电话等等。
– 是一款非常好用的电话软交换框架,支持跨平台,扩展性良好,配置灵活。
– 可以在很多平台上运行,包括 Linux、Mac OS X、BSD、Solaris,甚至 Windows。
– 可以处理来自 IP 网络 (VoIP) 和 PSTN(普通的固定电话)的语音、视频和文本通信。
– 支持所有流行的 VoIP 协议以及与 PRIs 的接口。
– 支持 OPUS、iLBC、Speex、GSM、G711、G722 等多种语音编解码,支持 G723、G729 等语音编解码的透传模式。
– 可以当作 PBX、SBC、媒体服务器、业务服务器等不同的通信节点来使用
– 本身是在 MPL 1.1 (Mozilla 公共许可证) 下许可的,但是一些单独的模块可能使用其他许可证。
– **说人话 FreeSWITCH 是一个软件实现的电话交换平台,开源、模块化、功能丰富**
– **市面上绝大多商业呼叫中心都是基于 FreeSWITCH 为核心开发的**

layout: center

# FreeSWITCH – 总体结构
<img class="m-1 h-120 rounded" src="https://static.iamle.com/note/202207011403991.png" />

layout: center

# FreeSWITCH – 总体结构
<img class="m-1 h-120 rounded" src="https://static.iamle.com/note/202207011405842.png" />

layout: center

# FreeSWITCH – 配置文件目录结构
/etc/freeswitch# tree -Ld 3
</code></pre>

├── freeswitch.xml 主xml文件,就是它将所有配置文件“粘”到一起,生成一个大的xml文件
├── vars.xml 常用变量
├── autoload_configs 一般都是模块级的配置文件,每个模块对应一个。文件名一般以 module_name.conf.xml 方式命名。
│   ├── *.conf.xml
├── chatplan 聊天计划
├── dialplan 拨号计划
├── directory 用户目录,分级用户账号
│   ├── default 默认的用户目录配置
│   │   ├── *.xml SIP用户,每用户一个文件
├── sip_profiles
│   ├── external SIP中继网关配置
│   │   ├── *.xml
│   ├── external.xml
│   └── internal.xml
├── ivr_menus IVR 菜单
├── jingle_profiles 连接Google Talk的相关配置
├── lang 多语言支持
├── mrcp_profiles MRCP的相关配置, 用于跟第三方语音合成和语音识别系统对接
├── skinny_profiles 思科SCCP协议话机的配置文件
├── tls tls证书
├── extensions.conf

<pre><code>- FreeSWITCH的配置文件由众多XML配置文件构建

layout: two-cols

# FreeSWITCH – 控制客户端和开发者接口
FreeSWITCH如何操作控制和开发对接?[^1]
– ① fs_cli 为命令行控制接口,也就是敲命令控制[^2]
– ② ESL(Event Socket Library) 通过事件接口和FreeSWITCH交互控制,fs_cli本质上也是走的ESL一样的底层流程
– ③ mod_xml_curl、mod_xml_rpc、lua脚本语言、自编写模块等更多方式实现和FreeSWITCH交互控制
::right::
</code></pre>

输入 fs_cli 进入命令行控制
.=======================================================.
| _____ ____ ____ _ ___ |
| | <strong><em>/ ___| / ___| | |</em> <em>| |
| | |</em> _</strong> \ | | | | | | |
| | <em>| <strong><em>) | | |</em></strong>| |___ | | |
| |</em>| |____/ ____|_____|___| |
| |
.=======================================================.
| Anthony Minessale II, Ken Rice, |
| Michael Jerris, Travis Cross |
| FreeSWITCH (http://www.freeswitch.org) |
| Paypal Donations Appreciated: paypal@freeswitch.org |
| Brought to you by ClueCon http://www.cluecon.com/ |
.=======================================================.
Type /help to see a list of commands
+OK log level [7]
freeswitch@callcenter>

<pre><code>[^1]: [FreeSWITCH Client and Developer Interfaces](https://freeswitch.org/confluence/display/FREESWITCH/Client+and+Developer+Interfaces)
[^2]: [Command Line Interface (fs_cli)](https://freeswitch.org/confluence/pages/viewpage.action?pageId=1048948)

# FreeSWITCH – 拨通第一个电话 – 分机和分机
<img class="m-1 h-90 rounded" src="https://static.iamle.com/note/202207011645715.jpg" />
* fs_cli 命令行让分机1000和分机1002通话&gt; originate user/1000 'bridge:user/1002' inline
* 分机1000使用软电话客户端Linphone,分机1002使用软电话客户端MicroSIP注册在了FreeSWITCH服务器

# FreeSWITCH – 拨通第一个电话 – 分机和手机
<img class="m-1 h-90 rounded" src="https://static.iamle.com/note/202207041015836.png" />
* fs_cli 命令行让分机1000通过网关和手机通话&gt; originate user/1000 'bridge:{origination_caller_id_number=网关主叫}sofia/gateway/网关名/被叫前缀+手机号码' inline
* 分机1000使用软电话客户端Linphone,被叫为手机号

# FreeSWITCH – FreeSWITCH系统集成设计
FreeSWITCH 的配置都是 XML的,最朴素的想法,如何实现动态配置能力?

和 FreeSWITCH 集成需要解决下列问题
|问题|方案|
| —- | —- |
| 分机动态配置 | mod_xml_curl 提供分机动态配置能力,开发对应的API输出分机XML配置文件 |
| 拨号计划动态配置 | mod_xml_curl 提供拨号计划动态配置能力,开发对应的API输出拨号计划XML配置文件 |
| 网关动态配置 | 低频需求暂时手工加载配置文件,开发对应的API可以一键生成网关XML配置文件 |
| CDR话单存储 | mod_xml_cdr 提供话单推送能力,开发对应的API接收话单推送 |
| Record录音对象存储生成URL | api_hangup_hook挂机后回调处理上传录音文件,开发对应API接收上传的录音文件,并上传到对象存储 |
| WebHook回调CDR和Record录音到业务系统 | 在CDR和Record都有了的时候执行回调 |

# FreeSWITCH – FreeSWITCH系统集成实现
集成实现采用golang编程语言开发,框架采用goframe v2

实现如下API,提供给FreeSWITCH集成
|API|实现方式| 说明 |
| —- | —- | —- |
| /fsapi/xml_curl | 读取数据库分机表生成分机XML配置文件 | mod_xml_curl模块对接 |
| /fsapi/cdr | 接收CDR话单并存储数据库CDR话单表 | mod_xml_cdr模块对接 |
| /fsapi/upload_audio | 接收录音文件上传,并上传到对象存储生成录音URL | api_hangup_hook挂机后回调处理上传录音文件 |


layout: full

# FreeSWITCH – 呼叫中心中台API封装实现
中台API和 FreeSWITCH系统集成API放同一个golang项目

|API|实现方式| 说明 |
| —- | —- | —- |
| /v1/call/callback| 调用ESL接口发送事件命令 |呼叫-回拨 参数:主叫号码、被叫号码、网关名称(多个逗号分割)、拓展数据|
| /v1/extension/list| 读取数据库分机表 |分机列表|
| /v1/extension/detail| 读取数据库分机表 |分机详情 参数:分机号|
| /v1/gateway/list| 读取数据库分机表 | 网关列表|
| /v1/gateway/detail| 读取数据库分机表 | 网关详情|
| /v1/gateway/detail_xml| 读取数据库分机表 | 网关详情XML配置文件|
| /v1/extension/online/list| 调用ESL接口 |在线分机列表|
| /v1/gateway/online/list| 调用ESL接口 |在线网关列表|
<!–

<style>
h1 {<br />
font-size: 20px;<br />
}<br />
</style> –>

# FreeSWITCH – 呼叫中心中台API封装实现-呼叫实现
– **呼叫实现核心逻辑**
使用拨号计划组合多个"APP"实现录音、录音采样率设置、挂机后上传录音等能力
拨号计划使用inline即内联模式[^1],ESL接口发送一条实践命令即可完成
</code></pre>

'app1:arg1,app2:arg2,app3:arg3' inline

<pre><code>呼叫模板
</code></pre>

"originate [参数]user/分机号
'
set:media_bug_answer_req=true,
set:record_sample_rate=8000,
set:RECORD_STEREO=true,
set:cc_record_filename=$${recordings_dir}/${strftime(%%Y-%%m-%%d)}/${uuid}.mp3,
export:nolocal:execute_on_answer=record_session:${cc_record_filename},
set:curl_sendfile_url=%s,
set:api_hangup_hook=system curl -XPOST -F \"files=@${cc_record_filename}\" ${curl_sendfile_url}?uuid=${uuid} &,
set:continue_on_failure=true,
set:hangup_after_bridge=true,
set:session_in_hangup_hook=true,
set:ringback=$${sounds_dir}/music/8000/ponce-preludio-in-e-major.wav,
bridge:[参数]sofia/gateway/网关名/前缀+被叫号码,
playback:$${sounds_dir}/zh/cn/link/misc/misc-your_call_has_been_terminated.wav,
info:,
hangup:
'
inline"
“`

⏬基于FreeSWITCH自建呼叫中心中台 PDF

[TOC]

0、前言

image-20210606160820147

为什么使用Elasticsearch (以下简称 ES ) ?

全文搜索能力

ES本身就是一个搜索引擎,技术上基于倒排索引实现的搜索系统。我们做业务不必自己去开发一个搜索引擎,

ES已经满足绝大多数企业的搜索场景的需求。拼多多电商在发展前期,搜索业务使用ES支撑。

复杂查询(多维度组合筛选)

假如某个实体表,例如订单日订单量几十万,运营管理后台通常有十多个维度的组合筛选搜索。你可能会通过缩小查询时间范围来把数据量级降下来,MySQL也许也能一战,但是实际业务场景业务人员不太能接受这样一个小时间段筛选。如果稍微扩大时间段,MySQL这时候就无能为力了。那么把订单表做成“宽表”存入ES,十多个维度的搜索对于ES俩说是毫无压力的。ES群集能轻松支持亿级的宽表多维度组合筛选。这种需求在CRM、BOSS等场景是刚需。

滴滴司机端早期日订单量几十万,运营管理后台大时间跨度且多维度组合筛选就是用ES来实现的。

一个产品是否成熟,公有云是否提供基于该产品的服务是一个风向标。那么ES毫无疑问是成熟的产品。

虽然ES这么好,但是我的数据都在MySQL中那么怎么让数据实时同步到ES中呢?

1、基于应用程序多写

直接通过应用程序数据双写到MySQL和ES

image-20210606174354578

记录删除机制:直接删除

一致性: 需要自行处理,需要对失败错误做好日志记录,做好异常告警并人工补偿

优点: 直接明了,能够灵活控制数据写入,延迟最低

缺点: 与业务耦合严重,逻辑要写在业务系统中

应用双写(同步)

直接通过ES API将数据写入到ES集群中,也就是写入数据库的同时调用ES API写入到ES中

这个过程是同步的

应用双写(MQ异步解耦)

对 应用双写(同步)的改进,引入MQ中间件。

把同步变为异步,做了解耦。

同时引入MQ后双写性能提高,解决数据一致性问题。

缺点是会增加延迟性,业务系统增加mq代码,而且多一个MQ中间件要维护

2、基于binlog订阅

binlog订阅的原理很简单,模拟一个MySQL slave 订阅binlog日志,从而实现CDC(change data capture)

CDC,变更数据获取的简称,使用CDC我们可以从数据库中获取已提交的更改并将这些更改发送到下游,供下游使用。这些变更可以包括INSERT,DELETE,UPDATE等。

记录删除机制:直接删除

一致性: 最终一致性

优点: 对业务系统无任何侵入

缺点: 需要维护额外增加的一套数据同步平台;有分钟级的延迟

Canal

img

https://github.com/alibaba/canal/

阿里巴巴 MySQL binlog 增量订阅&消费组件

Databus

https://github.com/linkedin/databus

Linkedin Databus 分布式数据库同步系统

Maxwell

官网:http://maxwells-daemon.io/

https://github.com/zendesk/maxwell

Flink CDC

https://github.com/ververica/flink-cdc-connectors

flink-cdc-connectors 中文教程

基于flink数据计算平台实现 MySQL binlog订阅直接写入es

Flink CDC抛弃掉其他中间件,实现 MySQL 》Flink CDC》ES 非常简洁的数据同步架构

该方式比较新2020年开始的项目,目前在一些实时数仓上有应用

DTS(阿里云)

阿里云的商业产品,具备好的易用性,省运维成本。

CloudCanal

CloudCanal官网

CloudCannal数据同步迁移系统,商业产品

3、基于SQL抽取

基于SQL查询的数据抽取同步

这种方式需要满足2个基本条件

1、MySQL的表必须有唯一键字段(和ES中_id对应)

2、MySQL的表必须有一个“修改时间”字段,该记录任何一个字段修改都需要更新“修改时间”

有了唯一键字段就可以知道修改某条记录后同步哪条ES记录,有了修改时间字段就可以知道同步到哪儿了。

满足了这2个基本条件这样就可实现增量实时同步。

记录删除机制:逻辑删除,在MySQL中增加逻辑删除字段,ES搜索时过滤状态

一致性: 依赖修改时间字段;延迟时间等于计划任务多久执行一次

优点: 对业务系统无任何侵入,简单方便;课用JOIN打宽表

缺点: MySQL承受查询压力;需要业务中满足2个基本条件

logstash

我们使用 Logstash 和 JDBC 输入插件来让 Elasticsearch 与 MySQL 保持同步。从概念上讲,Logstash 的 JDBC 输入插件会运行一个循环来定期对 MySQL 进行轮询,从而找出在此次循环的上次迭代后插入或更改的记录。如要让其正确运行,必须满足下列条件:

  1. 在将 MySQL 中的文档写入 Elasticsearch 时,Elasticsearch 中的 “_id” 字段必须设置为 MySQL 中的 “id” 字段。这可在 MySQL 记录与 Elasticsearch 文档之间建立一个直接映射关系。如果在 MySQL 中更新了某条记录,那么将会在 Elasticsearch 中覆盖整条相关记录。请注意,在 Elasticsearch 中覆盖文档的效率与更新操作的效率一样高,因为从内部原理上来讲,更新便包括删除旧文档以及随后对全新文档进行索引。
  2. 当在 MySQL 中插入或更新数据时,该条记录必须有一个包含更新或插入时间的字段。通过此字段,便可允许 Logstash 仅请求获得在轮询循环的上次迭代后编辑或插入的文档。Logstash 每次对 MySQL 进行轮询时,都会保存其从 MySQL 所读取最后一条记录的更新或插入时间。在下一次迭代时,Logstash 便知道其仅需请求获得符合下列条件的记录:更新或插入时间晚于在轮询循环中的上一次迭代中所收到的最后一条记录。
input {
  jdbc {
    jdbc_driver_library => "<path>/mysql-connector-java-8.0.16.jar"
    jdbc_driver_class => "com.mysql.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://<MySQL host>:3306/es_db"
    jdbc_user => <my username>
    jdbc_password => <my password>
    jdbc_paging_enabled => true
    tracking_column => "unix_ts_in_secs"
    use_column_value => true
    tracking_column_type => "numeric"
    schedule => "*/5 * * * * *"
    statement => "SELECT *, UNIX_TIMESTAMP(modification_time) AS unix_ts_in_secs FROM es_table WHERE (UNIX_TIMESTAMP(modification_time) > :sql_last_value AND modification_time < NOW()) ORDER BY modification_time ASC"
  }
}
filter {
  mutate {
    copy => { "id" => "[@metadata][_id]"}
    remove_field => ["id", "@version", "unix_ts_in_secs"]
  }
}
output {
  # stdout { codec =>  "rubydebug"}
  elasticsearch {
      index => "rdbms_sync_idx"
      document_id => "%{[@metadata][_id]}"
  }
}

配置Logstash的计划任务,定时执行

4、总结

前期建议采用 基于SQL抽取的方式做同步,后期数据量大了建议采用基于binlog订阅的方式同步。

如果本身有现成的Flink平台可用,推荐使用Flink CDC。

什么是最佳的 MySQL 同步 ElasticSearch 方案?

答案是选择缺点可以接受,又满足需求,拥有成本最低的方案。

“完美”的方案往往拥有成本会比较高,所以需要结合业务环境的上下文去选择。

流水理鱼觉得没有一招鲜的方案,因为每种方案都有利弊,所以选取适合你当下业务环境的方案。那么这样的方案就是最佳方案。

5、参考

MySQL 数据实时同步到 Elasticsearch 的技术方案选型和思考 by 万凯明

如何使用 Logstash 和 JDBC 确保 Elasticsearch 与关系型数据库保持同步

监听mysql的binlog日志工具分析:canal、Maxwell、Databus、DTS

0. 前言

关键字

号码隐私保护

虚拟号码保护

号码保护

手机小号

虚拟号码

隐私号码

定义

A、B一般代表用户真实号码

X一般代表PASS平台中的小号,通常和A进行绑定(也叫中间号、隐私号、虚拟号)

Y一般代表PASS平台中的小号,用于解决回呼,通常用于B回呼A

x小x代表分机号

1. 号码隐私保护通话模式介绍

1.1 AX (AXN)模式

只保护号码A,号码X不能复用

一对多隐私保护

呼叫前预先建立A和X的绑定关系

为用户A分配隐私号X,A对外的号码都以X替代,所有与A的通话都通过X建立,保护A号码不泄漏

隐私保护通话AX模式中,A为业务受益用户,为了保护A的真实号码不被泄露,在隐私保护通话平台为A绑定一个虚拟号码X。

  • 绑定关系建立后:
    • 所有人均可拨打X联系A,保护A的真实号码不被泄露。
    • 用户A呼叫其他用户时,企业需要通过API(AX模式设置临时被叫接口)指定呼叫对象(如B),然后A拨打X号码呼叫B。
  • AX模式下的X号码只专属于A号码,即一个虚拟号码X同时只能绑定一个A号码。但1个A号码可以同时绑定5个X号码。
  • 当X号码和A号码解除绑定关系后,该X号码可以被回收,供其他号码绑定。

AX模式下具有如下关键功能:

点击放大

1.2 AXB模式

保护号码A和B,号码X可以复用

一对一隐私保护

用户A与B之间建立一对一绑定关系,双方通过X号码联系对方,保护双方号码隐私。同一X号码可以复用在不同的绑定关系中

呼叫前A和B都必须是已知号码,预先建立AXB的绑定关系

用户A和B不知道对方真实号码,通过平台分配的临时隐私号X联系对方,保护双方号码不泄漏

隐私保护通话AXB模式中,A和B为相互保密的两个业务受益用户,A和B用户都不知道对方真实号码的存在,为了双方的真实号码不被泄露,在隐私保护通话平台为A和B用户绑定一个虚拟号码X,A和B用户对对方只呈现X号码,A和B之间的通信都是通过X号码进行转接。

  • AXB模式下X号码允许被多组号码(建议不超过1000组号码)绑定,即支持多路并发呼叫,但是多组号码中的A和B号码不能重复。例如,允许同时绑定AXB和CXD,但不允许同时绑定AXB和BXC。
  • 同一个A号码若要绑定不同B号码,需要使用不同的X号码进行绑定。如,若已存在AXB,还需绑定A和C,需使用别的X号码,如AX1C。
  • 当X号码和A、B号码解除绑定关系后,该X号码可以被回收,供其他号码绑定。

AXB模式下具有如下关键功能:

点击放大

1.3 X模式

该模式由企业自身维护绑定关系,控制力强

PASS平台仅提供呼叫和短信的管道能力,由企业管理隐私号X与用户的绑定关系,通过灵活利用绑定关系,节省号码资源,创新更丰富的应用

隐私保护通话X模式中,PASS平台对外提供X号码呼叫和短信能力,PASS平台侧不存储任何绑定关系,小号平台接收到呼叫或短信后到第三方系统获取绑定关系。

X模式下具有如下关键功能:

点击放大

点击放大

1.4 AXE (AXN分机、AXx分机)模式

AX模式上增加分机号E

一对多隐私保护

只保护号码A,号码X通过增加分机的方式复用

为用户A分配隐私号X+分机号,A对外的号码都以X+分机号替代,所有与A的通话都通过X+分机号建立,保护A号码不泄漏,提高号码利用率

隐私保护通话AXE模式中,A为业务受益用户,为了保护A的真实号码不被泄露,隐私保护通话平台为A绑定一个分机主号码X和一个分机号E。

  • 绑定关系建立后,其他用户拨打X号码再输入分机号E即可联系A用户。A用户拨打X号码可回呼之前通话用户或企业指定号码。
  • AXE模式下1个X号码可以绑定多个A号码,每个A号码分配不同的分机号E;分机号E最大4位(即0001~9999),但建议一个X号码不要绑定超过200个A号码。

AXE模式下具有如下关键功能:

点击放大

点击放大

1.5 AXG模式

一对组隐私保护

image-20200522234702286

1.6 AXYB模式

保护号码A和号码B

多关系隐私保护

隐私保护通话AXYB模式中,隐私保护通话平台为需要通话的一对或多对用户分别分配对应的隐私号码,保护通话的双方真实号码不被泄露。如隐私保护通话平台为A、B分别绑定隐私号码X和Y,建立了AXYB的绑定关系。

  • A用户对B用户只呈现X号码,与A用户建立的通信通过X号码建立;B用户对A用户呈现Y号码,与A用户建立的通信通过Y号码建立。
  • 一个X号码同时只能绑定一个A号码,一个A号码可以同时绑定5个X号码。Y号码可绑定的最大关系数量为1000(Y号码绑定一个AX关系计为一次绑定关系),但绑定的AX关系不可重复。例如,允许同时绑定A1X1Y1B1和A2X2Y1B2,但不允许同时绑定A1X1Y1B1和A1X1Y1B2。

AXYB模式下具有如下关键功能:

点击放大

1.7 AXxYB分机模式

AXxYB在AXYB上增加分机号码,小x就是分机号,通过增加分机号的方式节约号码X的使用

多关系隐私保护

2. 号码资源

目前号码资源有

虚商号码170/171

标准手机号码

固话号

95呼叫行业专用

3. 业务场景模式适配

AX (AXN)

1对多场景下的隐私保护,在不占用手机SIM卡槽的情况下为用户A增加一个第二号码,保护用户A的隐私,其他人都是通过拨打X号码接通用户A。

商业号码随身行

隐私号码作为商业号码绑定私人手机,随身接通客户电话。非工作时段可设置关机,防止骚扰

优势:

  • 客户资源不遗失

    员工离职,公司收回隐私号码,原有客户资源保持企业所有

  • 企业号码不变更

    客户只需拨打1个企业号码,不需随业务人员离职而频繁变更联系号码

  • 商业行为可追溯

    员工与客户的呼叫记录可追溯,方便管理;支持录音,促进企业服务质量提升

AXB

AXB中间号于1对1场景下的隐私保护,前置条件是A、B的联系方式已知;业务在绑定时候把A、B的联系方式通过api传递到号码隐私保护平台。应用场景:打车、短租、O2O服务等

用车出行、网约车出行

司机和乘客通过平台临时分配的隐私号码呼叫对方,不暴露自己的真实号码

在打车出行场景中,用车订单生成后,司机与乘客间,建立绑定关系,服务过程双方通过隐私号码联系对方,有效保护隐私,服务结束后,解除绑定,避免骚扰纠纷。

X

企业自行维护绑定关系

扫码挪车

车主申请隐私号码并和二维码绑定,其他人扫描二维码,拨打隐私号码联系车主

优势:

  • 隐私保护

    保护双方号码不泄漏,阻断第三方数据采集;可设置二维码关闭,防止骚扰

  • 方便快捷

    无繁琐的界面设定,手机扫码,一键通知挪车

  • 多管齐下

    电话和短信多渠道联系车主,确保通知到位

AXE (AXN分机、AXx分机)

一对多场景下的隐私保护;针对单一面单一利润较低,为了实现X号码复用,引入分机号概念大大降低了单一面单分摊的X号码接通用户A。应用场景:房产中介等

快递派送

快递员拨打隐私号,听到语音提示后,输入分机号转接至收件人

优势:

  • 隐私保护

保护收件人号码不泄露,派送结束隐私号码失效,防止后续骚扰

  • 节省号码资源

    1个隐私号可设置1万个分机号,提高号码复用率,降低号码成本

  • 灵活易用

    分机号根据业务规模灵活设置;交易结束快速回收隐私号,循环利用,无需设置号码冷却期

AXG

一对组场景下的隐私保护;主要针对有团队协作场景的行业,如招聘,通过API将A和企业的人事和用人部门等绑定,进行联系。应用场景:招聘、银行、保险等

AXYB

A和B双方保护

此模式主要针对企业严格把控企业会员隐私情况而定,适用场景较符合当前市场的房产中介类企业。此模式对于保护会员隐私效果最好

AXxYB

A和B双方保护

美团外卖典型的场景

骑手手机号B,拨打号码X 分机号x 转接到点餐人A,你的餐到了

点餐人A拨打 Y 转接到骑手B,我的餐为什么还没到

4. 参考

https://cloud.tencent.com/product/npp

https://support.huaweicloud.com/PrivateNumber/index.html

https://www.aliyun.com/product/pls

5. 提供商

如果你遇到了号码隐私保护上的技术问题,可以加流水理鱼的微信,拉你进技术群讨论

Android、iOS的webview注入JavaScript代码修改网页内容

需要修改webview中的网页内容、网页元素动作

1.实现原理

webview导航栏方法中执行JavaScript代码

浏览器地址栏是支持运行JavaScript代码的

javascript:开头后跟要执行的语句

// 弹窗
javascript:alert('hello world!');

ps:不可以复制粘贴的方式来测试,这样是无效的,至少“javascript:”是手写补全才可以

那么webview是否也可以呢?

在webview中同样适用本方法

虽然webview没有可见的地址栏,但是webview提供操作导航导航栏的方法

2.常用JavaScript代码片段

// 通过class查找隐藏本element
javascript:(function() {
   document.getElementsByClassName('your_class_name')[0].style.display='none'; 
})();
                
// 通过id查找因此本element
javascript:(function() {
   document.getElementById('your_id').style.display='none';
})();


// 某个element点击事件 并且修改打开一个弹窗后的页面element
javascript:(function() {
// 首次页面加载必须有的element
var url = 'https://www.baidu.com';
var text = 'p标签文本被替换了';
var bottom = document.getElementsByClassName('bottom')[0];
bottom.onclick = function(){
  // 弹窗element
    var dialogButton = document.getElementsByClassName('button')[0];
    var dialogItem = document.getElementsByClassName('item')[2].getElementsByTagName('p')[1];
    dialogButton.onclick = function(){window.open(url, '_self');}; // 点击事件跳转
    dialogItem.replaceWith(text); // 修改标签文本内容
};
})();

// 如果某些页面元素是在页面完成后出现的
// 也就是webview 到了 onPageFinished 周期中,页面元素还未加载出来,需要使用定时器来处理
// 定时器轮询检查页面元素对象,直到找到需要的处理的页面元素对象后进行处理,然后销毁定时器
var timer = setInterval(function () {
    if (document.getElementsByClassName("class name")[0]) {
      // 你的业务代码
      
      clearInterval(timer); // 销毁定时器
    }
  }, 1000);

3.Android中实现webview注入JavaScript代码

// java
final WebView webview = (WebView)findViewById(R.id.browser);
 
    webview.getSettings().setJavaScriptEnabled(true);
 
    webview.setWebViewClient(new WebViewClient() {
     @Override
    public void onPageFinished(WebView view, String url)
    {
        // hide element by class name
        webview.loadUrl("javascript:(function() { " +
                "document.getElementsByClassName('your_class_name')[0].style.display='none'; })();");
        // hide element by id
        webview.loadUrl("javascript:(function() { " +
                "document.getElementById('your_id_name').style.display='none';})();");
 
    }
    });
 
webview.loadUrl(url);

4.iOS中实现webview注入JavaScript代码

// objective-c

5.Flutter中实现webview注入JavaScript代码

参考 Android 和 iOS

6.进阶注入外部引入的hook.js代码

注入的JavaScript代码需要修改重新发包?

注入的JavaScript很大一段硬编码到APP包中如何管理?

进阶实现注入外部hook.js

把JavaScript代码单独写在hook.js并放置在cdn上

// https://www.iamle.com/hook.js
'v0.0.1 app webview hook'

;(function (window) {
    //your code
})(window)


// app 用webview.loadUrl方法中注入js
javascript:(function() {
   var script = document.createElement('script');
   script.type = 'text/javascript';
   script.src = 'https://www.iamle.com/hook.js';
     document.body.appendChild(script);
})();

// Android java为例
 @Override
    public void onPageFinished(WebView view, String url)
    {
        String js = "javascript:(function() {";
        js += "var script = document.createElement('script');";
        js += "script.type = 'text/javascript';";
        js += "script.src = 'https://www.iamle.com/hook.js';";
        js += "document.body.appendChild(script);";
        js += "})();";
        view.loadUrl(js);
    }

这样后续修改只需要修改hook.js即可,APP不用重新打包📦

7.经验

在webview中不支持window.location.href进行网址导航

但是支持使用window.open, window.open(‘https://www.iamle.com‘, ‘_self’);