融云 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)cellClass
      forMessageClass:(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];
}

@end

TestChatViewController *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 *> *)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 显示出来。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mybatis 的快速入门以及基于spring boot整合mybatis

MyBatis基础 MyBatis是一款非常优秀的持久层框架&#xff0c;用于简化JDBC的开发 准备工作&#xff1a; 1&#xff0c;创建sprong boot工程&#xff0c;引入mybatis相关依赖2&#xff0c;准备数据库表User&#xff0c;实体类User3&#xff0c; 配置MyBatis&#xff08;在applic…

移动端APP测试方法

1 APP测试基本流程 1.1 测试周期 测试周期可按项目的开发周期来确定测试时间&#xff0c;一般测试时间为两三周&#xff08;即15个工作日&#xff09;&#xff0c;根据项目情况以及版本质量可适当缩短或延长测试时间。正式测试前先向主管确认项目排期。 1.2 测试资源 测试任…

DNS服务器配置与分析

目录 实验目的&#xff1a; 实验原理&#xff1a; 实验步骤&#xff1a; 步骤1&#xff1a;创建拓扑 步骤2&#xff1a;为PC、Client和Server配置IPv4地址、子网掩码和域名服务器 步骤3&#xff1a;启动设备和服务器 步骤4&#xff1a;测试PC-1、Client-1和Server-1之间…

【ArcGIS Pro微课1000例】0050:如何清除坐标系信息

文章目录 一、目的二、方法1. 使用【定义投影】工具2. 清除数据的投影信息3. 删除坐标文件 一、目的 地理信息数据的坐标系是将地理信息数据进行融合、叠加、分析的重要数学框架&#xff0c;而其描述信息是非常重要的元数据&#xff0c;涉及整个国家的测绘坐标系统&#xff0c…

bootstrap中的图标元素可以免费使用

Available glyphsIncludes over 250 glyphs in font format from the Glyphicon Halflings set. Glyphicon 网址如下&#xff1a; Components Bootstrap

「词令」2023年12月6日蚂蚁庄园今日问题答案是什么?支付宝蚂蚁庄园今日答案12.6

问题&#xff1a;千页豆腐的主要原料是豆腐吗&#xff1f; 选项&#xff1a;A、不是哦 B、当然是 答案&#xff1a;不是哦 解析&#xff1a;千页豆腐是素食新产品&#xff0c;以大豆分离蛋白和水为主要原料&#xff0c;食用植物油、淀粉等为辅料;添加或不添加稳定剂和凝固剂…

精准测试:提升测试流程的效率与质量

在软件开发的过程中&#xff0c;测试是确保软件质量的关键步骤之一。然而&#xff0c;传统的测试方法往往依赖于测试人员的经验和直觉&#xff0c;效率和准确性存在一定的局限性。为了解决这一问题&#xff0c;精准测试应运而生。精准测试是一种基于数据驱动的测试方法&#xf…

从零开始学习 JS APL(四):完整指南和实例解析

目录 学习目标&#xff1a;学习内容&#xff1a;学习时间&#xff1a;学习内容&#xff1a;时间戳:DOM 节点&#xff1a;插件&#xff1a; 综合案例 &#xff1a; 学习目标&#xff1a; 1. 理解节点(标签)的增删改查 2. 具备编写增加学生信息表案例的能力 学习内容&#xf…

电脑CentOS 7.6与Windows系统对比:使用方式、优缺点概述

在多操作系统环境中&#xff0c;CentOS 7.6和Windows系统各自独占鳌头&#xff0c;它们在功能、稳定性、兼容性以及安全性等方面都有着各自的优点。这篇文章将对比分析这两个操作系统&#xff0c;以便用户能更好地了解它们的特点和使用方式。 一、使用方式 CentOS 7.6 CentO…

数据结构中处理散列冲突的四种方法

1 开放定址法 1.1 定义 开放定址法就是一旦发生了冲突&#xff0c;就去寻找下一个空的散列地址 1.2 要求 只要散列表足够大 空的散列地址总能找到&#xff0c;并将记录存入 1.3 线性探测法 使用该公式用于解决冲突的开放定址法称为线性探测法 对于线性探测法&#xff0c…

Linux下安装MySQL 5.6

1、下载二进制安装文件 使用wget下载MySQL 5.6.35二进制安装文件并存放在/root目录下。 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz ll mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz 2、创建mysql用户 先创建mysql…

跨语种「AI同传」颠覆语音翻译!Meta谷歌连发重大突破

Meta谷歌接连放出重磅成果&#xff01;Meta开源无缝交流语音翻译模型&#xff0c;谷歌放出无监督语音翻译重大突破Translation 3。 就在Meta AI成立10周年之际&#xff0c;研究团队重磅开源了在语音翻译领域的突破性进展——「无缝交流」&#xff08;Seamless Communication&a…

http面试题,三次握手四次挥手

在浏览器中输入网址按下回车经历了一个怎样的过程&#xff1f; 总的来说分为以下几个过程&#xff1a; 1、DNS解析&#xff1a;将域名解析为IP地址; 2、TCP连接&#xff1a;TCP三次握手; 3、发生HTTP请求; 4、服务器处理请求并返回HTTP报文; 5、浏览器解析渲染页面; 6、断开连接…

二叉树的基本概念(详解)

树的定义 树是一种非线性数据结构&#xff0c;由n&#xff08;n>1&#xff09;个节点以及n-1条边组成&#xff0c;其中有且仅有一个节点作为根节点。树的定义具有以下特点&#xff1a; 每个节点具有零个或多个子节点。除了根节点外&#xff0c;每个节点有且仅有一个父节点…

【江科大--32课程中讲解到的外部设备】

一、传感器模块&#xff08;GPIO模块&#xff09; 1.基本介绍 传感器模块&#xff1a;传感器元件&#xff08;光敏电阻/热敏电阻/红外接收管等&#xff09;的电阻会随外界模拟量的变化而变化&#xff0c;通过与定值电阻分压即可得到模拟电压输出&#xff0c;再通过电压比较器进…

资料分析(花生)

基期A&#xff08;给出BR或BX&#xff09; 前期&#xff1a;代入、直除、假设分配隔年前期&#xff1a;求出间隔增长率&#xff0c;再变成第一类考法前期差值&#xff1a;假设分配法求得两个前期作差。 现期B 有增量求现期&#xff1a;求出 X&#xff0c;列不等式即可有增速求现…

子集(回溯、图解)

78. 子集 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 样例输入 示例 1&#xff1a;…

【人体解剖学与组织胚胎学】练习一高度相联知识点整理及对应习题

文章目录 [toc]骨性鼻旁窦填空题问答题 关节填空题简答题 胸廓填空题简答题![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/827e7d1db3af42858d8734bb81911fea.jpeg)补充 骨性鼻旁窦 填空题 问答题 关节 填空题 简答题 胸廓 填空题 简答题 补充 第二肋对应胸骨…

Day02 Liunx高级程序设计2-文件IO

系统调用 概念 是操作系统提供给用户使其可以操作内核提供服务的一组函数接口 用户态和内核态 其中 ring 0 权限最高&#xff0c;可以使用所有 CPU 指令&#xff0c; ring 3 权限最低&#xff0c;仅能使用 常规 CPU 指令&#xff0c;这个级别的权限不能使用访问硬件资…

外贸平台辅助工具常见代码有哪些?

在当今的数字化时代&#xff0c;外贸平台已成为企业开展国际贸易的重要渠道之一&#xff0c;为了提高外贸平台的运营效率和客户满意度&#xff0c;企业需要借助各种外贸平台辅助工具&#xff0c;这些工具可以帮助企业自动化、智能化地完成各种外贸业务流程&#xff0c;如产品发…