灵活易用的即时通讯组件设计思路和最佳实践

Global IM UIKit 采用"一键开关、二级重写、三级自定义"来扩充自定义能力,提升产品集成灵活性,更好支持业务层创新。

一键开关: 对于通用的、可枚举的交互和 UI 自定义范围,Global IM UIKit 内部已经帮开发者实现了相关功能和适配,只需一行代码,即可实现多种交互和 UI 的样式切换。

二级重写: 支持 Module(页面)和 Component(组件)重写,包含 ViewModel、ViewController、View 的继承与重写。适用于对默认实现方式的极度个性化定制,扩展性极高。

三级自定义: 针对通用的、不可枚举的自定义范围,提供 API 自定义能力,例如:导航栏的操作项、输入框扩展功能入口等。

本文将以 iOS 端集成为例,分享 Global IM UIKit 中组件的灵活拆分和高扩展性的设计思路,以及快速集成的实战教程。


主要功能和核心架构

在即时通讯组件中,主要的功能可聚合为两大页面:

会话列表: 以会话对象为元素,展示最近的会话对象,也是进行会话的主要入口。Global IM UIKit 针对会话项目支持左滑编辑和右滑编辑,提供置顶、免打扰、标记已读等操作。

会话: 以消息对象为元素,展示所有消息对象,也是所有消息的发起入口。Global IM UIKit 中常用的消息类型有:文本、图片、视频、文件、语音、贴纸等,支持回复、转发、复制等常用快捷交互。

除以上两大页面之外,还有很多其他衍生页面,如:多媒体、预览、用户列表、用户信息等。当然,作为国际化产品,多语言和主题也是不可少的。

在 Global IM UIKit 中,虽然核心页面结构相对简单,但其社交功能聚合度非常高。尤其是在会话页面中,消息的接收、发送、操作等,不仅仅是数据和 UI 的交互,还涉及消息的时序、安全、性能等,以及 SDK 的定制化需求。

使用传统的 MVC 框架会撑爆 Controller,给研发和维护产生很多后患。所以,融云 Global IM UIKit 采用 MVVM 架构,将消息数据处理与 UI 渲染和交互合理解耦,降低代码逻辑复杂度,配合灵活的组件拆分,提高扩展性。

MVVM 也是一个普及度高、接受度好的框架,开发者可以快速上手。以会话列表和会话的对外接口类为例展示如下图:


UI 组件

Global IM UIKit 的一个页面对应一个 Module,Module 中包含一个或多个 Component,并管理这些 Component 的整体布局。

例如,整个会话页面是一个 Module,其又可以被划分为三个 Component,分别是:顶部导航栏、底部输入组件和中间的滚动区。

在 UI 组件中,PaaS 服务的难点在于:需要考虑与各种业务场景的适配,通过高可重用的组件和高开放性的接口,尽可能多地支持定制化需求。

以"底层精简+让渡自由"为产品设计核心思路,Global IM UIKit 将 IM 会话页面 Module 和各组件 Component 的重写自由给到开发者,开发者可灵活自定义而不影响可用性。

为实现这一点,Global IM UIKit 的会话页面需按业务逻辑拆分成独立功能组件。每个模块可以作为重用单元,有助于解耦,降低系统的复杂性,同时隔离也有助于提升开发效率。

在模块中整体采用 MVVM 的设计模式,对于功能较简单的组件依然采用了 MVC 的设计模式。 当组件没有复杂的交互和业务逻辑时,使用 MVC 会更加简洁明了。

多语言和主题作为全局性的功能,会渗透到每个组件之中。在设计组件时,需要为多语言和主题的一键切换能力做铺垫。

组件拆分

根据列表页面显示,可以划分为导航条(Header)、列表(List)、工具栏(ToolBar)和会话输入框(Input),如下图示:

组件中会根据功能聚合再次拆分为更细粒度的组件,例如:输入框的 Reference、TextInput、Sticker、Recorder 等子组件。

这样,Module 与 Component 的基本组件关系就整理拆分完毕了。开发者可以用继承 Module,新增或修改 Component 的方法,实现组件的二级重写能力。

Component 就是 Global IM UIKit 模块化定义中的最小组成单元,也是开发者可以重写的最小单元。同时,重写的组件可以在上一级组件中,通过 setter 方法,设置为自定义组件。

组件实现

Global IM UIKit 采用 Objective-C 语言开发,所有组件基于原生框架实现。

组件中的颜色和图片,使用系统的动态接口,对应当前设置的主题:

objectivec 复制代码
/// 根据主题切换颜色
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider;
/// 为 Image 注册不同主题对应的图片,根据主题切换
- (void)registerImage:(UIImage *)image withConfiguration:(UIImageConfiguration *)configuration;

组件中的文字是经过本地化后的:

scss 复制代码
/// 系统的本地化接口
NSLocalizedStringFromTable(key, tbl, comment)

导航条、列表、工具栏、输入框是 Global IM UIKit 中最核心的几大 Component。

导航条

Header 组件是对系统导航条 UINavigationBar 的自定义,通过定义左右按钮和标题,在 ViewController 加载时赋值给 UINavigationBar。

less 复制代码
@interface RCBaseHeaderView : NSObject
/// 左侧按钮
@property (nonatomic, strong) NSMutableArray<RCBarItem *> *leftItems;
/// 右侧按钮,默认编辑按钮
@property (nonatomic, strong) NSMutableArray<RCBarItem *> *rightItems;
/// 标题 baseView
@property (nonatomic, strong) RCBarTitleView *titleView;

@end

按钮支持标题、图片和自定义视图显示,点击事件通过 Action 抛出。

objectivec 复制代码
@interface RCBarItem : NSObject

/// 名称
@property (nonatomic, copy, nullable) NSString *title;

/// 图标
@property (nonatomic, strong, nullable) UIImage *image;

/// 事件回调,如果自定义视图,这里不会有事件回调
@property (nonatomic, copy, nullable) RCBarItemAction action;

/// 自定义视图
/// 自定义视图的点击事件也需要自定义,当点击时,不会触发 action 调用
@property (nonatomic, strong, nullable) UIView *customView;

/// 标签
@property (nonatomic, assign) RCBarItemTag tag;

@end

标题是自定义视图,支持标题和副标题两个 Label。

less 复制代码
@interface RCBarTitleView : UIView

/// 标题 label
@property (nonatomic, strong) UILabel *titleLabel;
/// 状态 label
@property (nonatomic, strong) UILabel *statusLabel;

@end

列表

在 List 组件中,使用 UITableView 实现列表展示,支持设置占位视图:

less 复制代码
@interface RCBaseListView : UIView

// 列表
@property (nonatomic, strong) UITableView *tableView;

// 占位页面
@property (nonatomic, strong) UIView *emptyView;

@end

无论会话还是消息,都需要分页加载,UITableView 的 insert 方法非常契合会话列表的动态加载效果,可以流畅的插入下页数据。

在会话列表中,每个会话对应一个 Cell,会话的展示样式基本一致,在列表中使用 RCChatCell 展示。开发者可以注册自定义 Cell:

less 复制代码
@interface RCChatListView : RCBaseListView
...
/// 将会话 Cell 替换为继承 RCChatCell 的自定义 Cell
- (void)replaceChatCellWithClass:(Class)cellClass;

@end

在会话页面中,不同消息的展示样式区别很大,每个消息 Cell 都会继承于 RCMessageCell 自定义样式。同样,开发者也可以注册自定义 Cell:

less 复制代码
@interface RCMessageListView : RCBaseListView
...
/// 注册自定义消息 Cell
/// - Parameters:
///   - cellClass: 自定义 Cell,需要继承 RCBaseMessageCell
///   - messageClass: 消息体,支持自定义消息和内置消息
- (void)registerClass:(Class)cellClass
      forMessageClass:(Class)messageClass;

@end

工具栏

工具栏通常用于多选操作,根据业务需求,设置编辑按钮,使用工具栏需要考虑系统的 tabBar。

less 复制代码
@interface RCTabBar : UIView

/// 按钮容器,用于布置按钮
@property (nonatomic, strong) UIStackView *containerHStackView;

/// 顶部线条
@property (nonatomic, strong) UIView *topLineView;

/// 按钮
@property (nonatomic, copy) NSArray<RCBarItem *> *items;

@end

在会话列表中,编辑栏固定为底部显示,会向上推起会话列表。如果有系统 tabBar,就会将 tabBar 隐藏,结束选择后,恢复 tabBar。在会话页面中,工具栏会直接覆盖输入框。

输入框

Input 是一个功能聚合度比较高的组件,包含了文本输入、多媒体选择、录音、引用、贴纸等子组件。

less 复制代码
@interface RCInputBar : UIView

/// 引用 view
@property (nonatomic, readonly) RCReferenceView *referenceView;

/// 输入框
@property (nonatomic, readonly) RCInputTextView *textView;

/// 贴纸 view
@property (nonatomic, readonly) RCStickerBoardView *stickerBoardView;

/// 输入框左边 items,默认为 addItem
@property (nonatomic, copy) NSArray<RCBarItem *> *leftItems;

/// 输入框右边 items,默认为 recordItem,当输入文本时,变为 sendItem
@property (nonatomic, copy) NSArray<RCBarItem *> *rightItems;

/// 加号按钮扩展 items,默认为:相册、相机、文件
/// 在每次扩展面板展示前设置生效
@property (nonatomic, strong) NSMutableArray<RCBarItem *> *addExpandItems;

@end

组件定制化

为了与应用中的社交模块平滑衔接,Global IM UIKit 的 UI 组件需要支持灵活定制和高重用。开发者可以通过继承、布局和配置的二级重写和三级自定义方式,定制符合业务场景和体验的功能。

继承

推荐开发者使用继承的方式跳转会话列表和会话页面,在自定义的 ViewController 中,开发者可以通过重写父方法,实现自定义操作:

less 复制代码
@interface TestChatViewController : RCChatViewController

@end

@implementation TestChatViewController

- (void)reloadHeaderView {
    [super reloadHeaderView];
    // TODO custom
}

@end

也可以继承组件来实现自定义业务逻辑,在页面展示时通过 setter 的方法,设置为自定义的组件:

less 复制代码
@interface TestChatHeaderView : RCChatHeaderView

@end

@implementation TestChatHeaderView

- (void)configure:(RCChatModel *)model {
    [super configure:model];
}

@end

TestChatViewController *controller = [[TestChatViewController alloc] initWithChatModel:model];
controller.headerView = [[TestChatHeaderView alloc] init];

重写事件代理回调,也可以添加自定义操作,如列表的代理事件:

objectivec 复制代码
/// 列表事件代理
@protocol RCMessageListViewDelegate <NSObject>
...
/// 列表中的 Cell 将要加载
/// - Parameters:
///   - listView: 列表页面
///   - messageModel: 消息对象
- (void)listView:(RCMessageListView *)listView willLoadCell:(UITableViewCell *)cell forMessageModel:(RCMessageModel *)messageModel;

/// 列表中的 Cell 加载完成
/// - Parameters:
///   - listView: 列表页面
///   - messageModel: 消息对象
- (void)listView:(RCMessageListView *)listView didLoadCell:(UITableViewCell *)cell forMessageModel:(RCMessageModel *)messageModel;

/// 列表中的 Cell 将要展示
/// - Parameters:
///   - listView: 列表页面
///   - messageModel: 消息对象
- (void)listView:(RCMessageListView *)listView willDisplayCell:(UITableViewCell *)cell forMessageModel:(RCMessageModel *)messageModel;
/// 更多回调接口,请参考 SDK 接口
...
@end

布局

在各组件中,会使用 StackView 容器承载 View,可以在 StackView 中直接添加更多的 View。

这种方式主要用在列表的 Cell 中,Cell 使用 Estimate 高度,充分应用 iOS 的 Auto Layout 功能特性,Cell 实际高度会由内容自动撑开。

objectivec 复制代码
@interface RCChatCell : RCTableViewCell

/// 会话容器,头像、会话内容
@property (nonatomic, readonly) UIStackView *containerStackView;
...
/// 会话内容,在 containerStackView 中
@property (nonatomic, readonly) UIStackView *contentStackView;

/// 会话上部分内容,在 containerStackView 中,用户信息、时间
@property (nonatomic, readonly) UIStackView *contentTopStackView;
...
/// 会话下部分内容,在 containerStackView 中,消息预览、已读
@property (nonatomic, readonly) UIStackView *contentBotStackView;
...
@end

开发者需要熟悉 StackView 的基础使用方法,注意 StackView 的布局方向。

配置

通过配置的方式增删改现有的功能逻辑,主要适用基于 RCBarItem 的按钮事件,例如:导航条左右按钮是可变数组,可以直接修改该数组,并刷新 UI 即可:

objectivec 复制代码
@implementation TestChatViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    RCBarItem *item = [RCBarItem itemWithTitle:nil image:CustomImage action:^(RCBarItem *item) {
        // TODO custom
    }];
    [self.headerView.leftItems addObject:item];
    [self reloadHeaderView];
}

@end

会话和消息列表的展示样式是需求最多的自定义组件,开发者可以通过 List 提供的注册方法,完全自定义 Cell 的展示样式。

支持 Module 和 Component 的二级重写之外,如文章开头所述,融云 Global IM UIKit 亦对某些通用功能支持一键切换修改。

在支持开发者全球化业务的多样实践中,融云发现有些通用功能也需要跟随业务实现多样化。例如,单群聊自己的头像和昵称是否展示等。

针对这些功能,开发者通过重写组件来实现多样化的成本比较高,融云 Global IM UIKit 直接将此类功能直接封装成开关(与多语言、主题切换类似),开发者可根据业务场景一键切换使用。


数据处理

除了用户可以直观看到的 UI 组件外,数据流处理也是 Global IM UIKit 中的重要环节。

数据处理的主要难点在于:需要将复杂的业务状态计算放入串行队列中异步处理。通过节流优化,批量处理消息接收或同步已读和回执。

主要的数据处理有消息接收、消息加载、未读数、消息已读回执、消息已读同步等,需要考虑消息的时序、IO 访问的频率、消息属性的缓存、UI 和数据逻辑的交互。

数据队列

消息的接收、发送、加载和操作等都是并发事件,为了能够将这些事件有序、安全地传递给 UI 组件显示,需要有一个统一可调度的串行队列,确保数据和 UI 的之间的相互映射。

在 ViewModel 中,有专门处理数据的串行队列 dataQueue,所有事件都会放入到串行队列中处理,ViewModel 会将处理结果通过回调接口,分发给对应组件刷新。

objectivec 复制代码
/// ViewModel 基础类
@interface RCBaseViewModel : NSObject

/// 操作数据的线程,所有对数据的操作必须在该线程中处理
@property (nonatomic, readonly) dispatch_queue_t dataQueue;

#pragma mark - Perform -

- (void)asyncPerformBlock:(dispatch_block_t)block;
- (void)syncPerformBlock:(dispatch_block_t)block;

@end

/// 会话数据回调接口
@protocol RCChatViewModelDelegate <NSObject>
...
/// 列表数据刷新
/// - Parameters:
///   - viewModel: ViewModel
///   - messages: 消息对象数组
- (void)viewModel:(RCChatViewModel *)viewModel
didReloadMessages:(NSArray<RCMessageModel *> *)messages;

/// 插入列表数据
/// - Parameters:
///   - viewModel: ViewModel
///   - messages: 消息对象数组
///   - indexSet: 消息对象数组对应的位置
- (void)viewModel:(RCChatViewModel *)viewModel
didInsertMessages:(NSArray<RCMessageModel *> *)messages
       atIndexSet:(NSIndexSet *)indexSet;

/// 列表数据更新
/// - Parameters:
///   - viewModel: ViewModel
///   - messages: 消息对象数组
///   - indexSet: 消息对象数组对应的位置
- (void)viewModel:(RCChatViewModel *)viewModel
didUpdateMessages:(NSArray<RCMessageModel *> *)messages
       atIndexSet:(NSIndexSet *)indexSet;
/// 更多回调接口,请参考 SDK 接口
...
@end

数据加载

在列表中,为了流畅地滚动预加载体验,ViewModel 中除了 reload 方法外,也提供了加载上一页和下一页的方法。

随列表滚动,当达到加载下一页的条件时,会调用 loadNextPage 方法加载下一页数据。ViewModel 会通过代理回调的方法,将下一页的数据传递给 List 显示出来。

objectivec 复制代码
@interface RCChatListViewModel : RCBaseViewModel
...
/// 加载会话数据
- (void)reloadData;

/// 拉取下一页数据
- (void)loadNextPage;
...
@end

@protocol RCChatListViewModelDelegate <NSObject>

/// 刷新会话列表
/// - Parameter viewModel: ViewModel
/// - Parameter chatModels: 会话数据
- (void)viewModel:(RCChatListViewModel *)viewModel chatListDidReload:(NSArray<RCChatModel *> *)chatModels;

/// 插入会话
/// - Parameter viewModel: ViewModel
/// - Parameter indexSet: 会话 index list
- (void)viewModel:(RCChatListViewModel *)viewModel chatListDidInsertAtIndexSet:(NSIndexSet *)indexSet;
...
@end

数据节流

虽然数据线程能够确保消息有序展示到 UI 中,但在接收消息时,需要考虑消息风暴的情况。即,接收大量消息直接刷新 UI,会导致 UI 卡顿,甚至崩溃。

因此,需要添加节流操作,在延时一定时间后,进行批量处理。

ini 复制代码
- (void)onReceived:(RCMessage *)message left:(int)nLeft object:(id)object {
    self.cachedMessages[@(message.messageId)] = message;
    if (nLeft == 0) {
        __weak typeof(self) weakSelf = self;
        [self.throttle scheduleThrottleWithBlock:^{
            [weakSelf rc_throttleUpdateChatList];
        }];
    }
}

在消息接收和发送时,还会触发消息已读回执、未读数清理、已读多端同步。已读回执和已读同步在接口调用时,需要通过节流汇总,在一定时间后统一上报。

ini 复制代码
__weak typeof(self) weakSelf = self;
[self.readReceiptThrottle scheduleThrottleWithBlock:^{
    [weakSelf rc_sendReadReceipt];
}];

__weak typeof(self) weakSelf = self;
[self.readSyncThrottle scheduleThrottleWithBlock:^{
    [weakSelf rc_syncReadTimeThrottle];
}];

消息处理

会话支持置顶、免打扰、标记未读和删除,当 ViewModel 中调用接口后,会触发远端和数据库状态更新,加载到内存的数据也需要维护更新。UI 的刷新直接依赖于内存数据,内存数据需要确保和本地数据库一致,本地数据库频繁触发 IO 访问。

可以将 ViewModel 中部分数据操作分配到内存中,同时内存根据处理结果和 UI 配合刷新展示。例如:置顶操作,调用设置接口后,在内存中更新状态和重新排序,并将结果反馈给 List 进行 UI 刷新和动画。

objectivec 复制代码
@interface RCChatListViewModel : RCBaseViewModel
...
/// 移除数据,根据 models 批量移除对应会话,同时删除会话中的消息
/// 数据添加完成后,会触发 viewModel:chatListDidRemoveAtIndexSet: 回调
- (void)deleteChatModels:(NSArray<RCChatModel *> *)models;

/// 标记已读未读
/// 标记完成后,会触发 viewModel:chatListDidUpdateAtIndexSet: 回调
/// - Parameter model: 会话对象
- (void)toggleRead:(RCChatModel *)model;
...
/// 设置/取消置顶
- (void)toggleTop:(RCChatModel *)model;
...
/// 设置/取消免打扰
- (void)toggleNotification:(RCChatModel *)model;

@end

接入指南

Global IM UIKit SDK 的核心集中于即时通讯的业务功能界面的实现,基于融云 RongCloudIM 提供的高并发、高可用通讯能力,以适应海外用户使用习惯的交互设计完成封装。开发者采用 Global IM UIKit SDK,在 快速上手文档支持下,"分钟级"接入即可实现单群聊完整功能。

集成准备

创建 融云开发者账号,获取App Key

开始之前,需创建融云开发者账号并获取 App Key。在开发者后台,系统会自动为新账号创建一个应用。默认使用国内数据中心,并提供开发环境。如果您已经有融云开发者账号,可以直接创建新应用。

集成 SDK

推荐使用 Pod 集成,如需手动集成,请移步官网下载 Framework,将 Framework 直接导入 App 即可。

☑ 在 podfile 中添加如下内容:

arduino 复制代码
pod 'RongCloudGlobal/IMUIKit'

☑ 请在终端中运行以下命令:

pod install

如果出现找不到相关版本的问题,可先执行 pod repo update,再执行 pod install。

☑ 上一步完成后,CocoaPods 会在您的工程根目录下生成一个 xcworkspace 文件,只需通过 XCode 打开该文件即可加载工程。

实现聊天功能

☑ 初始化

Global IM UIKit SDK 依赖于 IMLibCore 的即时通讯能力,使用前需要对 IMLibCore 进行初始化。IMLibCore 核心类为 RCCoreClient,初始化时,需要传入生产或开发环境的 App Key。

ini 复制代码
#import <RongIMLibCore/RongIMLibCore.h>

NSString *appKey = @"Your_AppKey"; // example: bos9p5rlcm2ba
RCInitOption *initOption = nil;
[[RCCoreClient sharedCoreClient] initWithAppKey:appKey option:initOption];

☑ 连接

用户 Token 是与用户 ID 对应的身份验证令牌,是应用程序的用户在融云的唯一身份标识。应用客户端在使用融云即时通讯功能前必须与融云建立 IM 连接,连接时必须传入 Token。

用户可以在开发者后台快速创建一个用户 John Doe,创建后返回 Token,使用该 Token 连接。

go 复制代码
    [[RCCoreClient sharedCoreClient] connectWithToken:@"John Doe token" dbOpened:^(RCDBErrorCode code) {
        //消息数据库打开,可以进入到主页面
    } success:^(NSString *userId) {
        //连接成功
    } error:^(RCConnectErrorCode errorCode) {
        if (status == RC_CONN_TOKEN_INCORRECT) {
            //从 APP 服务获取新 token,并重连
        } else {
            //无法连接到 IM 服务器,请根据相应的错误码作出对应处理
        }
    }];

☑ 用户信息

要在 Global IM UIKit 上展示用户、群组的头像、名称等,需要应用层(App)主动向 IMKit SDK 提供用户信息。为了完整体验 Global IM UIKit 的 UI,我们将直接在用户信息数据库中写入对应用户 ID 的头像、名称信息。

下面设置了本地登录用户 John Doe 和另一个用户 Jane Smith 的头像、昵称。

ini 复制代码
[RCIMKitClient shared].enableUserInfoPersistence = YES;

RCUserInfo *currentUserJohnDoe = [[RCUserInfo alloc] initWithUserId:@"userIdJohnDoe" name:@"John Doe" portrait:userPortraitUri];
[RCIMKitClient shared].currentUserInfo = currentUserJohnDoe;

RCUserInfo *JaneSmith = [[RCUserInfo alloc] initWithUserId:@"userIdJaneSmith" name:@"Jane Smith" portrait:@"http://portrait"];
[[RCIMKitClient shared] refreshUserInfoCache:JaneSmith];

☑ 会话列表

Global IM UIKit 已默认提供会话列表页面和会话页面。会话列表页面展示当前用户参与的所有单聊、群聊、系统会话,在会话页面可进行消息编辑、查看、回复、发送等活动。

连接成功后即可跳转到默认会话列表界面。推荐继承 SDK 中的 RCChatListViewController,例如 TestChatListViewController 类,示例如下:

less 复制代码
@interface TestChatListViewController : RCChatListViewController
@end

初始化自定义的会话列表页面 TestChatListViewController,会话列表支持显示单聊、群聊、系统会话。会话列表详细使用方法,参见**会话列表页面**。

ini 复制代码
TestChatListViewController *listVC = [[TestChatListViewController alloc] init];
[self.navigationController pushViewController:listVC animated:YES];

☑ 会话

为了快速体验,可以从开发者后台【北极星】开发者工具箱【IM Server API 调试】页面模拟用户 Jane Smith 向当前登录的用户 John 发送一条文本消息。

只要提供对方的 userId,融云就可支持跟对方发起聊天。应用客户端可以通过用户 ID、群聊会话 ID 接收消息。

以上便是 Global IM UIKit SDK 的全部集成步骤,"分钟级"接入即可感受最具灵活性的 IM UI 技术架构,以及与海外用户使用体验对齐的交互设计。

相关推荐
爱上语文2 小时前
Springboot三层架构
java·开发语言·spring boot·spring·架构
ღ᭄ꦿ࿐Never say never꧂4 小时前
微服务架构中的负载均衡与服务注册中心(Nacos)
java·spring boot·后端·spring cloud·微服务·架构·负载均衡
CaritoB4 小时前
中台架构下的数据仓库与非结构化数据整合
数据仓库·架构
逆风就重开5 小时前
【软件基础知识】什么是 API,详细解读
api
canonical_entropy15 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
沛沛老爹16 小时前
服务监控插件全览:提升微服务可观测性的利器
微服务·云原生·架构·datadog·influx·graphite
huaqianzkh17 小时前
了解华为云容器引擎(Cloud Container Engine)
云原生·架构·华为云
Kika写代码18 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
刘某某.18 小时前
使用OpenFeign在不同微服务之间传递用户信息时失败
java·微服务·架构
迪捷软件18 小时前
知识|智能网联汽车多域电子电气架构会如何发展?
架构·汽车