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

1. 需求

业务上对业务表就行重构,重新设计了新表,需要原有的数据准实时同步到新表(目标表)

字段类型和数据值等做了修改需要处理映射

例如

1是,2否需要映射为 1是,0否

varchar存储的时间转换为datetime

int存储的时间戳要转换为datetime

新表设计了新状态码,需要按照对照表做数据值映射

三表合一逻辑结构为

源表主键/唯一建 类别 需要的字段 源表 目标表 目标表主键
id 事实主表 10+ old_orders new_orders id(订单号)
order_id 业务延迟产生数据 2+ old_orders_extend1
order_id 业务延迟产生数据 1个 old_orders_extend2

需要实现,三张源表的的insert和update操作准实时的方式同步到同目标目标表中

2.设计

2.1 功能性设计

准实时,必须使用MySQL binlog订阅CDC的方式,使用CloudCanal作为准实时同步数据平台

字段类型和数据值的转化映射,使用CloudCanal的自定义数据处理插件功能,自编写业务处理代码(java)实现

2.2 鲁棒性设计

建表

null处理,目标表字段设置为允许为null,虽然在数据库上常规来讲设置为not null更性能,但是考虑综合收益新表就尽量不使用 not null,这样有更好的数据导入容错性

容量,目标表表字段容量大小必须>= 源表对应的字段,一般情况保持原有大小一致

数据转换

格式化,时间字段格式化,文本格式的字段类型转换为datatime类型时做好异常捕获,确保格式化为正确的日期,保障数据写入更新不出问题

脏数据,其他脏数据的预处理

2.3 CloudCanal任务设计

事实主表建立一个任务实例,这样做保障事实主表独立

业务延迟产生数据的表建立一个任务实例,这样做和事实主表的同步独立,互不影响

3.实现

3.1 目标表建表

注意符合鲁棒性设计中的建表设计

具体内容略…

3.2 编写自定义数据处理插件

常规的一些转换操作看CloudCanal提供的示例代码即可

处理技术重点在,业务延迟产生数据的时候,需要把insert和update都转换为update

处理insert时候,需要同时保留getAfterColumnMap和getBeforeColumnMap,由于insert是没有变更前数据的,所以设置getBeforeColumnMap为getAfterColumnMap即可

核心代码为

//  本文来自 www.iamle.com 流水理鱼
public static void changeOrderExtend1FillColumnMap(CustomFieldV2 oriBf, LinkedHashMap<String, CustomFieldV2> target) {
    CustomFieldV2 bf = CustomFieldV2.buildField(oriBf.getFieldName(), oriBf.getValue(), oriBf.getSqlType(), oriBf.isKey(), oriBf.isNull(), oriBf.isUpdated());
    target.put(oriBf.getFieldName(), bf);
}

private static void processOrderExtend1InsertAndUpdateToUpdate(CustomData data, List<CustomData> re, EventTypeInSdk eventType) {
    for (CustomRecordV2 oriRecord : data.getRecords()) {
        CustomRecordV2 updateRecord = new CustomRecordV2();

        if (eventType.equals(EventTypeInSdk.INSERT)) {
            // 需要处理 afterColumnMap > beforeColumnMap
            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeColumnMap());
            }

            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterKeyColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeKeyColumnMap());
            }
        }

        if (eventType.equals(EventTypeInSdk.UPDATE)) {
            // 需要处理 beforeColumnMap
            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getBeforeColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeColumnMap());
            }

            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getBeforeKeyColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeKeyColumnMap());
            }
        }

        for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterColumnMap().entrySet()) {
            changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getAfterColumnMap());
        }

        for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterKeyColumnMap().entrySet()) {
            changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getAfterKeyColumnMap());
        }

        List<CustomRecordV2> updateRecords = new ArrayList<>();
        updateRecords.add(updateRecord);

        SchemaInfo updateSchemaInfo = data.cloneSchemaInfo(data.getSchemaInfo());
        CustomData updateData = new CustomData(updateSchemaInfo, EventTypeInSdk.UPDATE, updateRecords);

        re.add(updateData);
    }
}

工程项目代码参考:

cloudcanal-data-process 本工程汇集了 CloudCanal 数据处理插件,以达成数据自定义 transformation 目标

https://gitee.com/clougence/cloudcanal-data-process

问题!
insert改写为update在增量同步阶段工作正常,在全量迁移阶段会报错!
截止2023年3月代码中没有办法判断任务阶段是在 全量迁移还是增量同步,所以只能在第一次建立任务的时候不要改写insert成为update,待任务正常执行时候,再重新上传激活代码处理包,
让insert改写update在增量同步阶段工作
由于全量迁移阶段的insert数据没同步,这个暂时只有单独手动处理更新到目标表去

2023-03-14 14:22:50.791 [full-task-executor-7-thd-1] INFO  c.c.c.b.service.task.parser.full.RdbSinglePkPageScanner - [FINISH SCAN!]null.你的库.old_orders_extend1, cost time 222 ms.migrated count is 1774
2023-03-14 14:22:51.660 [full-apply-disruptor-6-thd-3] ERROR c.c.c.task.applier.full.FullDisruptorExceptionHandler - disruptor process full event error,msg:IllegalArgumentException: unsupported one FullEvent with multi CustomData.
java.lang.IllegalArgumentException: unsupported one FullEvent with multi CustomData.
 at com.clougence.cloudcanal.task.data.process.pkg.editorv2.FullDataDeSerializer.deserialize(FullDataDeSerializer.java:59)
 at com.clougence.cloudcanal.task.data.process.pkg.editorv2.FullDataDeSerializer.deserialize(FullDataDeSerializer.java:18)
 at com.clougence.cloudcanal.task.data.process.pkg.V2PkgProcessor.process(V2PkgProcessor.java:125)
 at com.clougence.cloudcanal.task.data.process.pkg.CustomPkgProcessor.process(CustomPkgProcessor.java:73)
 at com.clougence.cloudcanal.task.data.process.DataProcHandlerImpl.process(DataProcHandlerImpl.java:19)
 at com.clougence.cloudcanal.task.data.process.FullDataProcWorkerHandler.onEvent(FullDataProcWorkerHandler.java:32)
 at com.clougence.cloudcanal.task.data.process.FullDataProcWorkerHandler.onEvent(FullDataProcWorkerHandler.java:12)
 at com.lmax.disruptor.WorkProcessor.run(WorkProcessor.java:143)
 at java.lang.Thread.run(Thread.java:748)

3.3 CloudCanal任务建立

事实主表建立一个任务实例

old_orders 到 new_orders,并配置好字段映射

业务延迟产生数据的表建立一个任务实例

old_orders_extend1 到 new_orders,并配置好字段映射

old_orders_extend2 到 new_orders,并配置好字段映射

建立任务时在配置时候 数据处理 界面上传自定义代码的jar包

自定义代码jar包更新

激活后需要重启任务(PS:需要重启至少2次才生效,这个是CloudCanal bug,截止2023-3-10 还未解决)

3.4 实现结果

源表的insert和update都会准实时的同步到目标表达到设计目标

1. 数据平台数仓平台架构设计大图

1.1 基于Apache Doris(以下简称Doris)的实时数仓架构大图

基于Apache Doris的实时数仓架构图 流水理鱼 wwek

1.1 架构图说明

数据源
主要是业务数据库MySQL,当然也可以是其他的关系型数据库

数据集成和处理
实时,原封不动同步的数据使用CloudCanal;需要复杂的数据加工处理,使用Flink SQL ,并用Dinky FlinkSQL Studio 实时计算平台来开发、管理、运行Flink SQL
离线,使用DataX、SeaTunel,并用海豚调度(DolphinScheduler)编排任务调度进行数据集成和处理

数据仓库
主体是使用Doris作为数据仓库
ES、MySQL、Redis作为辅助数仓,同时ES也作为搜索引擎使用,数据同步复用该套架构

数据应用
总体分类2大类
自己开发API、BI等数据应用服务
三方BI ,商业Tableau、帆软BI、乾坤,开源Superset、Metabase等之上构建数据应用服务

2. 为什么是Doris?

2.1 Doris核心优势

Apache Doris 简单易用、高性能和统一的分析数据库,他是开源的!

简单易用
部署只需两个进程,不依赖其他系统;在线集群扩缩容,自动副本修复;兼容 MySQL 协议,并且使用标准 SQL

高性能
依托列式存储引擎、现代的 MPP 架构、向量化查询引擎、预聚合物化视图、数据索引的实现,在低延迟和高吞吐查询上, 都达到了极速性能

统一数仓
单一系统,可以同时支持实时数据服务、交互数据分析和离线数据处理场景

联邦查询
支持对 Hive、Iceberg、Hudi 等数据湖和 MySQL、Elasticsearch 等数据库的联邦查询分析

多种导入
支持从 HDFS/S3 等批量拉取导入和 MySQL Binlog/Kafka 等流式拉取导入;支持通过HTTP接口进行微批量推送写入和 JDBC 中使用 Insert 实时推送写入

生态丰富
Spark 利用 Spark Doris Connector 读取和写入 Doris;Flink Doris Connector 配合 Flink CDC 实现数据 Exactly Once 写入 Doris;利用 DBT Doris Adapter,可以很容易的在 Doris 中完成数据转化

2.2 实际使用体验

查询性能满足需求
实战下来,千万级数据主表,再join几个小表,聚合查询,8C16G硬件配置单机运行Doris,查询能在3s内
join支持友好,一个查询关联10+个表后查询也毫无压力

文本作者为 流水理鱼 wwek https://www.iamle.com

2.3 杀鸡用牛刀Hadoop、Hive

Hadoop、Hive大象固然好,但是对于大部分中小型企业来说,这个就是牛刀,现在的主流数据湖也是牛刀,大部分中小公司研发都会面临杀鸡难道用牛刀的情况。
传统的大数据基本都是玩离线的,业务要求的实时性如何做到
所以TB级别的数据应该用对应的解决方案,那就是MPP架构的Doris

2.4 平替商业分析型数据库

能够平替阿里云ADB(阿里云的分析型数据库)
和相同的数据库表,想同的服务器配置,在体验上大部分Doris比阿里云ADB更快,小部分相当或更慢(非严格的测试对比,仅仅是自己特定的场景下的结果)

3、让架构实际落地的渐进式方案

3.1 V1.0 解决MySQL不能做OLAP分析查询的问题

绝大多数中小公司都有朴实的需求,我要实时大屏,业务数据统计报表
而这个时候你发现MySQL已经不支持当前的报表统计查询了,已经卡爆了,加从库也不行
这就是V1.0 落地方案要解决的问题

MySQL是行存数据库也即是面向OLTP的,不是面向OLAP分析查询的,所以不适合做数据报表等数据应用,数据量不大,时间跨度不大用MySQL从库还能一战,但终归你会遇到MySQL已经不支持你的报表查询SQL的时候

如何最低成本的让各类数据应用能实时、高性能的查询,答案是Doirs
V1.0 落地数仓核心是:原封不动的实时同步了一份业务数据库MySQL中的表到 Doris

对应架构图中
数据源 数据集成 数据仓库 数据应用
业务MySQL 》 CloudCanal 》 Doris 》 数据查询应用
其实这就相当于完成了数据仓库的ODS层,直接用ODS层,在数仓中使用原始业务库表,是最简单的开始,这个时候已经支持绝大多数据应用了,业务需求大多数都能得到满足。

数据应用方面可以很易用,因为Doirs支持MySQL协议,又支持标准SQL
所以不管是商业或开源BI软件,自己程序开发API都能快速进行数据应用服务支持

在落地成本方面,1-2人开发者(甚至还不是数据开发工程师),服务器16C32G * 3 人、机资源即可落地

3.2 V2.0 解决更大的数据、更复杂的数据加工的问题

如果有更多的数据集成的需求,还有更复杂的ETL数据加工的需求
推荐使用:
实时 Flink SQL 使用 Dinky FlinkSQL Studio 实时计算平台来开发、管理、运行Flink SQL
离线 DataX、SeaTunnel 使用海豚(DolphinScheduler)调度编排和调度

实时场景下一般使用CDC,离线场景下一般使用SQL查询抽取

3.3 V3.0 走向成熟的数仓分层、数据治理

该阶段建立 数仓分层模型
建立数仓分层模型的好处:数据结构化更清晰、数据血缘追踪、增强数据复用能力、简化复杂问题、减少业务影响、统一数据口径

ADS层
数据集市

DWS层
分析主题域,“轻粒度汇总表”

DWD层
业务主题划分域,并打成“事实明细宽表”

ODS层
贴源层,也就是业务数据原封不动的同步过来存储到数仓

DIM层
维度数据

具体的细节怎么设计、规范怎么定,这已超出了本文的范围,到了这个阶段您也不需要看本文了

1. 问题和解决办法

1.1 SELECT 字段中的非聚合函数包裹的字段,必须在 GROUP BY中申明

# SQLSTATE[HY000]: General error: 1064 '`u`.`name`' must be an aggregate expression or appear in GROUP BY clause

这个问题会是遇到最多的问题(实际上在大数据场景,例如hive中也有这个要求的)
因为ADB(MySQL)引擎是MySQL所以对这个没要求,导致迁移到Doris的时候这个问题最为突出
时间处理函数、字符串处理函数这种是非聚合函数,所以也是需要在GROUP BY中申明的

1.2 Doris的 GROUP_CONCAT函数中字段前不支持用distinct去重 #11932

https://github.com/StarRocks/starrocks/issues/8079
array_distinct(array_agg(str_col))
to get a distinct a array value.
And if you want to make it a value you can use the following function
array_join(array_distinct(array_agg(str_col)), ‘,’)

也就是先取出列为数组,然后去重数组,再把数组拼接字符串

ps:新版本的Doris已经支持,见
[Bug] doris的group_concat函数不支持distinct #11932

1.3 GROUP_CONCAT函数中的字段不支持int类型

需要拼接的字段如果为int类型那么必须先强转成字符串,使用CAST函数
GROUP_CONCAT(CAST( id整形字段 as STRING), ‘,’)

1.4 SUBSTRING_INDEX函数不支持

截止2022年11月11日需要使用UDF,也就是用户定义函数解决
Apache Doris 1.1 版本只支持原生UDF,也就是需要重新编译整个Doris,1.2 版本开始支持Java UDF 可以动态挂载
StarRocks 2.2.0 版本开始支持Java UDF 可以动态挂载

提供一个已经实现好的StarRocks Java UDF,可直接使用(由同事贡献)
代码

package com.starrocks.udf;  

import org.apache.commons.lang3.StringUtils;  

/**  
 * 根据下标截取  
 *  
 * @author dingyoukun  
 * @date 2022-10-26 14:30  
 **/public class SubStringByIndex {  
    public final String evaluate (String targetStr, String str, Integer index) {  
        Boolean desc = false;  
        if (index < 0 ) {  
            desc = true;  
            index = Math.abs(index);  
        }  

        String result = targetStr;  
        String partStr = str;  

        if (StringUtils.isBlank(targetStr)) {  
            return result;  
        }  

        if (index == 0) {  
            return targetStr;  
        }  

        if (desc) {  
            targetStr = new StringBuffer(targetStr).reverse().toString();  
            partStr = new StringBuffer(partStr).reverse().toString();  
        }  
        int beginIndex = 0;  
        int count = 0;  
        while ((beginIndex = targetStr.indexOf(partStr, beginIndex)) != -1) {  
            count++;  
            if (count == index) {  
                if (desc) {  
                    targetStr = new StringBuffer(targetStr).reverse().toString();  
                    result = targetStr.substring(targetStr.length() - beginIndex);  
                } else {  
                    result = targetStr.substring(0, beginIndex);  
                }  
                return result;  
            }  
            beginIndex = beginIndex + partStr.length();  
        }  
        return result;  
    }  
}

2. 参考

https://docs.starrocks.io/zh-cn/latest/introduction/StarRocks_intro
https://github.com/StarRocks/starrocks/issues

https://github.com/apache/doris/issues
https://doris.apache.org/zh-CN/docs/summary/basic-summary

[Apache Doris Java UDF https://doris.apache.org/zh-CN/docs/dev/ecosystem/udf/java-user-defined-function] (https://doris.apache.org/zh-CN/docs/dev/ecosystem/udf/java-user-defined-function)
[StarRocks Java UDF https://docs.starrocks.io/zh-cn/latest/sql-reference/sql-functions/JAVA_UDF] (https://docs.starrocks.io/zh-cn/latest/sql-reference/sql-functions/JAVA_UDF)


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