融云 Global IM UIKit,灵活易用的即时通讯组件设计思路和最佳实践

(全网都在找的《社交泛娱乐出海作战地图》,点击获取👆)

融云近期推出的 Global IM UIKit,支持开发者高效满足海外用户交互体验需求,且保留了相当的产品张力赋予开发者更多自由和灵活性,是实现全球化社交组件的不二之选。关注【融云全球互联网通信云】了解更多

点击查看

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 语言开发,所有组件基于原生框架实现。

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

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

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

/// 系统的本地化接口
NSLocalizedStringFromTable(key, tbl, comment)

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

导航条

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

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

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

@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。

@interface RCBarTitleView : UIView/// 标题 label
@property (nonatomic, strong) UILabel *titleLabel;
/// 状态 label
@property (nonatomic, strong) UILabel *statusLabel;@end

列表

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

@interface RCBaseListView : UIView// 列表
@property (nonatomic, strong) UITableView *tableView;// 占位页面
@property (nonatomic, strong) UIView *emptyView;@end

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

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

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

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

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

工具栏

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

@interface RCTabBar : UIView/// 按钮容器,用于布置按钮
@property (nonatomic, strong) UIStackView *containerHStackView;/// 顶部线条
@property (nonatomic, strong) UIView *topLineView;/// 按钮
@property (nonatomic, copy) NSArray<RCBarItem *> *items;@end

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

输入框

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

@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 中,开发者可以通过重写父方法,实现自定义操作:

@interface TestChatViewController : RCChatViewController@end@implementation TestChatViewController- (void)reloadHeaderView {[super reloadHeaderView];// TODO custom
}@end

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

@interface TestChatHeaderView : RCChatHeaderView@end@implementation TestChatHeaderView- (void)configure:(RCChatModel *)model {[super configure:model];
}@endTestChatViewController *controller = [[TestChatViewController alloc] initWithChatModel:model];
controller.headerView = [[TestChatHeaderView alloc] init];

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

/// 列表事件代理
@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 实际高度会由内容自动撑开。

@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 即可:

@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 会将处理结果通过回调接口,分发给对应组件刷新。

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

数据加载

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

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

@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 卡顿,甚至崩溃。

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

- (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];}];}
}

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

__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 刷新和动画。

@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 中添加如下内容:

pod 'RongCloudGlobal/IMUIKit'

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

pod install

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

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

实现聊天功能

☑ 初始化

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

#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 连接。

    [[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 的头像、昵称。

[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 类,示例如下:

@interface TestChatListViewController : RCChatListViewController
@end

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

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 技术架构,以及与海外用户使用体验对齐的交互设计。

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

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

相关文章

朝花夕拾华山平台流水账

2022年8月25日&#xff0c;我加入了诚迈科技&#xff08;南京&#xff09;&#xff0c;加入了华山平台。 跟我一起入职平台的还有三个小伙伴&#xff1a;小帅、小阳、小甘。 小帅能力很强&#xff0c;前后端都会&#xff0c;入职各种考试工具人。 小阳毕业没多久&#xff0c;一…

现货黄金会面临哪些风险?

进行现货黄金投资&#xff0c;我们除了要了解怎么找到交易机会以外&#xff0c;也要知道我们交易会面临哪些风险&#xff0c;了解风险就是做到知己知彼&#xff0c;了解风险才能控制风险。控制住风险&#xff0c;才能为我们稳定盈利打好基础&#xff0c;那么下面我们就来看看在…

ESP32-Web-Server编程-在网页中插入图片

ESP32-Web-Server编程-在网页中插入图片 概述 图胜与言&#xff0c;在网页端显示含义清晰的图片&#xff0c;可以使得内容更容易理解。 需求及功能解析 本节演示在 ESP32 Web 服务器上插入若干图片。在插入图片时还可以对图片设置一个超链接&#xff0c;用户点击该图片时&a…

本地ip查询介绍(包含公开免费的API接口)-本地ip查询API接口

本机ip和网络ip的区别 网络地址和主机地址 IP 地址&#xff08;Internet Protocol Address&#xff09;&#xff0c;即互联网协议地址&#xff0c;是一种用于唯一标识互联网上设备的地址。它包括网络地址和主机地址两部分&#xff0c;通常用“IPv4”或“IPv6”表示。 本机 …

使用axios处理Cookie、Session和Token(jwt)

在Vue中&#xff0c;可以使用JavaScript来处理Cookie、Session和Token。我们还是以登录为例介绍它们的使用 Cookie&#xff1a; 在Vue中&#xff0c;可以使用JavaScript内置的document.cookie来读取和设置Cookie。在登录过程中&#xff0c;可以将用户的身份信息存储在Cookie中…

Web API

JavaScript中有些API可能使用率比较低 Blob API Blob API 用于处理二进制数据&#xff0c;可以方便地将数据转换为Blob对象或从Blob对象读取数据。 // 创建一个Blob对象 const myBlob new Blob(["Hello, world!"], { type: "text/plain" }); // 读取Bl…

Oracle merge into语句(merge into Statement)

在Oracle中&#xff0c;常规的DML语句只能完成单一功能&#xff0c;&#xff0c;例如insert/delete/update只能三选一&#xff0c;而merge into语句可以同时对一张表进行更新/插入/删除。 目录 一、基本语法 二、用法示例 2.1 同时更新和插入 2.2 where子句 2.3 delete子句 2.4…

Gitee项目推荐-HasChat

最近由于使用的局域网通信工具总是出问题&#xff0c;就在考虑有没有好的替代品。搜索了一番&#xff0c;发现这个还不错&#xff1a; HasChat: 一款极简聊天应用&#xff0c;比较完整&#xff0c;略好看 页面简洁&#xff0c;功能也比较齐全&#xff0c; 感兴趣的小伙伴可以…

Java常识

初识Java 01 特点 面向对象的 跨平台的 02 核心机制 java虚拟机&#xff08;JVM&#xff09;。 java垃圾回收&#xff08;GC&#xff09;&#xff1a;c语言中由程序员负责回收无用的内存链&#xff0c;java垃圾回收在java程序运行的过程中自动进行。 03 命名原则(一) 由26个…

【Redis】redis 高性能--线程模型以及epoll网络框架

目录 一.前言 二.多线程的弊端 2.1 锁的开销问题 2.2 多线程上下文切换带来的额外开销 2.3 多线程占用内存成本增高 三.基本IO模型与epoll 模式 3.1 基本IO模型 3.2 单线程处理机制 四.总结 一.前言 我们经常讨论到&#xff0c;redis 是单线程&#xff0c;那为什么单线…

【1day】致远 A8系统getAjaxDataServlet-xxe接口任意文件读取学习

注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞描述 二、影响版本 三、资产测绘 四、漏洞复现

反汇编看指令重排

背景: 这也是一个真实客户案例,但我不能透露代码及问题细节,只讲思想。 问题: 客户说他们现场经常打印一句warning,我们对应源码查看了代码。伪代码如下: void pin() {while (!flag){ sleep(1);}if (hold > 0) {printf("warning: holder already set\n"…

sizeof()、strlen()、length()、size()的区别(笔记)

​ 上面的笔记有点简陋&#xff0c;可以看一下下面这个博主的&#xff1a; c/c中sizeof()、strlen()、length()、size()详解和区别_csize,sizeof,length_xuechanba的博客-CSDN博客

python遇到bug问题汇总

文章目录 dateutil.parser._parser.ParserError: Unknown string format: 20222022/07/19 17:06:59.78 修改前后 dat_df2[time_col] pd.to_datetime(dat_df2[time_col]) # 前 dat_df2[time_col] pd.to_datetime(dat_df2[time_col], errorscoerce) # 后

the name of a constructor must match the name of the enclosing class

构造器名匹配封闭类名 命令码的位置关系不对 解决&#xff1a;调整 命令码所在层级

xxljob学习笔记02(小滴课堂)

分布式调度参数传递和调度日志配置讲解 可以设置任务参数。 代码层面&#xff1a; 可以这样传递参数。 我们在xxljob页面去设置参数&#xff1a; 我们执行一次任务&#xff1a; 我们这里就拿到了参数。 这样我们就能拿到参数了。 日志打印&#xff1a; 在代码中也可以实现&…

第7章 交换与网管

文章目录 7.1 交换技术概述 177 7.1.1 交换技术的发展、基本概念和系统架构 177 7.1.2 电路交换与分组交换技术 178 7.1.3 程控交换原理 180 7.1.4 电话信令的概念 182 7.2 现代交换技术 183 7.2.1 软交换技术 184 7.2.2 IP交换技术 189 7.2.3 IMS技术 189 7.2.4 路由…

GPT-Crawler一键爬虫构建GPTs知识库

GPT-Crawler一键爬虫构建GPTs知识库 写在最前面安装node.js安装GPT-Crawler启动爬虫结合 OpenAI自定义 assistant自定义 GPTs&#xff08;笔者用的这个&#xff09; 总结 写在最前面 GPT-Crawler一键爬虫构建GPTs知识库 能够爬取网站数据&#xff0c;构建GPTs的知识库&#xf…

npm : 无法加载文件 D:\nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本。

今天在使用vscode下载项目的依赖时&#xff0c;输入 pnmp install,结果报错: npm : 无法加载文件 D:\nodejs\node_global\npm.ps1&#xff0c;因为在此系统上禁止运行脚本。原因&#xff1a; 因为在此系统上禁止运行脚本&#xff0c;也就是说没有权限&#xff0c;查一下&#…

HarmonyOS应用开发者认证:开启全新的智能设备开发之旅

随着科技的不断发展&#xff0c;人工智能、物联网等技术逐渐渗透到我们的日常生活中。在这个智能化的时代&#xff0c;华为推出了一款全新的操作系统——HarmonyOS&#xff0c;旨在为各种智能设备提供统一的操作系统&#xff0c;实现设备之间的无缝连接和协同工作。作为开发者&…