当前位置: 首页 > wzjs >正文

秦皇岛市教育考试院网站apache wordpress 404

秦皇岛市教育考试院网站,apache wordpress 404,烟台市建设局网站,百度搜索引擎平台iOS 聊天 IM 消息收发管理工具 连续疯狂加班告一段落,趁着离职前夕的空闲时间,整理一下重构相关的文档。之前写过两篇文章 iOS 客户端 IM 以及列表 UI 框架 、iOS 客户端 IM 消息卡片插件化,突然发现时间过的真的很快,这都已经是…

iOS 聊天 IM 消息收发管理工具

连续疯狂加班告一段落,趁着离职前夕的空闲时间,整理一下重构相关的文档。之前写过两篇文章 iOS 客户端 IM 以及列表 UI 框架
、iOS 客户端 IM 消息卡片插件化,突然发现时间过的真的很快,这都已经是两年多以前的事情了,我居然没再写点什么,自责三秒钟。iOS 客户端 IM 以及列表 UI 框架
总体讲了探探这边 IM 整体框架架构消息的收发、存储基本流程 以及 UI框架的接口设计,iOS 客户端 IM 消息卡片插件化讲的比较微观,是说消息列表中的一条消息,如何通过插件支持交互以及与其他消息联动。

本篇想讲的是消息的收发、存储基本流程 的实现,算是给 IM 部分收个尾。

1. 简介

之前提过消息列表是由数据驱动的,这次说的聊天消息收发管理工具就是驱动列表展示的数据引擎。与iOS 客户端 IM 以及列表 UI 框架
里提到的 ChatContext 是同一个,它作为新聊天消息列表的数据引擎,内部无业务属性,可以接入到多种聊天场景,比如单聊、群聊、临时会话等等。

2. 消息收发基本流程

这里在iOS 客户端 IM 以及列表 UI 框架
中已经写过,为方便阅读,姑且粘过来一份吧

2.1 消息发送过程

消息的发送是基于 HTTP 请求,主要包含两种类型:一种是同步参数类型(简单文本)、一种是异步参数类型的消息(多媒体,或者文本消息增加各种检查等)。

  1. 同步参数类型,是组装好参数后,直接通过 POST 请求发送到服务器;
  2. 异步参数类型消息,在调用发送消息的接口之前,需要先将多媒体数据上传到 CDN 服务器,然后把返回的地址链接拼到发送消息的请求参数中,再请求发送消息接口,实现消息的发送;
  3. 异步参数类型消息还包括一些特殊的多媒体信息,比如第三方服务提供的大表情消息,在调用发送消息接口之前,就需要把第三方获数据另保存一份到自己的 CDN 服务器,然后把返回的数据拼接到发送消息参数中,请求发送消息接口,实现消息的发送。
    消息发送流程图

2.2 消息接收过程

消息的接收是基于 WebSocket 长连接和 HTTP 请求相互配合来实现的。WebSocket 是 App 内用于服务端向客户端发送通知消息的单向通讯服务工具

  1. 有了新的消息,服务端会通过 WebSocket 向接收方主动发送通知消息;
  2. 做为主动通知的补充,客户端端上还有轮询任务,以固定时间间隔来通过 HTTP 请求服务端询问是否有新的消息;
  3. 客户端收到通知消息之后,再通过 HTTP 请求新消息数据,来展示到对应的 UI;
    消息接收流程图

2.3 消息的存储

消息的存储底层采用 sqlite 和第三方 FMDB 开源框架,再此基础之上开发了一套支持 ORM 以及 SQL 语句生成工具的基础库。每个支持数据库保存的 Model 类内部保存了具体表名、不同字段与表字段的映射,进行数据库操作的时候可以根据这些信息生成对应的 SQL 语句来保存到本地数据库。本地消息的保存就是直接通过框架保存到数据库即可,网络部分是请求到数据后,通过前后端约定的 envelop key 确定到具体数据 Model 的类,从而根据 ORM 信息来保存到数据库,大概就介绍到这里吧,不再做过多的介绍了。

3. ChatContext

ChatContext 内部主要有以下三个模块:
1、ChatMessenger,负责消息发送,失败重试等
2、ChatMessageListLoader,负责加载新消息以及历史消息,不负责存储,内部会根据锚点使用 LocalLoader(本地数据库加载)或者RemoteLoader(远程加载)来加载数据
3、ChatDataManager 负责存储管理内存中的消息数据,内部

通过协调这三个模块来实现消息收发消息加载消息数据变更回调消息列表 会监听消息数据变更来实现UI更新,从而达到数据驱动的目的

3.1 架构设计图

请添加图片描述

3.2 接口设计

	/// 消息数据private(set) var messages: [Message]/// 消息发送工具let messenger: ChatMessenger/// 是否有更多历史消息var hasMorePreviousMessages: Bool/// 注册消息数组数据变更监听者func registerMsgDataObserver(observer: ChatContextMsgDataObservable)/// 清空消息历史消息func clearAllMessages() async/// 拉取历史消息/// TODO: 从当前消息拉取到指定时间的历史消息func fetchMorePreviousMessages() async -> [Message]

3.3 ChatMessenger

该模块主要负责消息发送,以及发送流程控制,其中发送流程控制是指:
1、发送消息前拼装各业务提供的基础参数
2、将要发送时询问业务方是否拦截
3、发送成功回调或者发送失败时修改返回给业务的错误信息

接口设计

/// 发送同步类型参数消息public func sendMessage(withSynParameterBuilder builder: BaseMessageParameterBuilder & MessageSynParameterBuilder) async -> (PUGMessage?, TXTResponse?, PUGError?) {// 询问业务组装基础参数setup(messageParameterBuilder: builder)return await sendMessage(synParameterBuilder: builder)}/// 发送异步类型参数消息public func sendMessage(withAsynParameterBuilder builder: BaseMessageParameterBuilder & MessageAsynParameterBuilder) async -> (PUGMessage?, TXTResponse?, PUGError?) {setup(messageParameterBuilder: builder)return await sendMessage(asynParameterBuilder: builder)}private func sendMessage(synParameterBuilder: MessageSynParameterBuilder) async -> (PUGMessage?, TXTResponse?, PUGError?) {// 通知业务消息即将发送observableTable?.allObjects.forEach({ observer inobserver.chatMessenger?(self, willSendMessageWithBuilder: synParameterBuilder)})let ret = await basicMessenger.sendMessage(synParameterBuilder: synParameterBuilder,willSendMessageChecker: { [weak self] localMessage in// 业务检查消息是否可以进入正常发送流程return self?.checkSending(localMsg: localMessage)}, failedMessageModifier: { [weak self] result in// 消息发送失败,通知业务修改错误信息return self?.modify(failedMsg: result.message, error: result.error)})// 通知业务消息发送完成(包括成功、失败)publish(didSentMessageWithResult: ret)return ret.toTuple()}

消息发送流程图
请添加图片描述

3.4 ChatMessageListLoader

该模块负责消息数据的加载,并不负责存储,ChatContext 的拉取消息接口内部调用 ChatMessageListLoader 加载消息,并存储在 ChatDataManager 中,如下所示


func loadNewMessages() asyc {let msgs = await loader.loadNewMessages()dataManager.append(msgs)...
}

下面是 ChatMessageListLoader 支持的功能以及接口设计


/// 是否可以加载更旧的消息 = canLoadOldRemoteMessages || canLoadOldLocalMessages
var canLoadOldMessages: Bool/// 是否可以从本地数据库加载更旧的消息
var canLoadOldLocalMessages: Bool/// 是否可以从远端加载更旧的消息
var canLoadOldRemoteMessages: Bool/// 加载新消息,只从后端拉取
func loadNewMessages() asyc -> Result/// 加载旧消息,优先从数据库获取,然后从后端获取旧消息
func loadOldMessages() asyc -> Result/// 清空消息时调用
func clear() asyc

3.5 ChatDataManager

消息数据的存储工具,负责数据去重、排序,提供增删改查功能


class ChatDataManager<Item> {/// 当前存储的数据private(set) var items: [Item]/// 初始化方法,sorter: 用于排序init(sorter: Comparator)/// 头部添加func prepend(_ items: [Item])/// 尾部添加func append(_ items: [Item])/// 更新func update(_ items: [Item])/// 删除func deletedItems(_ itemIDs: [String])/// 移除所有元素func removeAllItems()
}

4. 发送消息部分,接入 ChatContext 前后对比

4.1 使用 ChatContext 发送消息

重构后不在需要了解消息发送具体原理即可快速上手,仅需要关注与后端新增的参数字段即可,下面看一下新增一种消息发送类型所需要做:

4.1.1 创建发送参数
@interface PUGLocationMessageParameterBuilder : PUGBaseMessageParameterBuilder<PUGMessageSynParameterBuilder>@property (strong) PUGLocationMessageInfo *locationInfo; @end@implementation PUGLocationMessageParameterBuilder// 用于保存到数据库的消息
- (nullable PUGMessage *)buildMessage {PUGMessageBuilder *builder = [self generateBaseMessageBuilder];builder.locationDictionary = self.locationInfo.toDictionary;builder.retryParameter = [self buildParameter];return builder.build;
}// 发送给后端的参数
- (nonnull NSDictionary *)buildParameter {NSMutableDictionary *para = [self generateBaseParameter].mutableCopy;// 仅需要关注与后端新增的参数字段即可[para txt_setObjectSafely:self.locationInfo.toDictionary forKey:@"location"];return para.copy;
}@end
2.1.2 调用发送消息接口
  1. 可以在全局获取发送工具
  2. 消息发送失败重试逻辑,不需要额外处理
  3. 调用完发送后,所有相关业务处理以及消息刷新都会处理,不需要额外调用
PUGLocationMessageParameterBuilder *paraBuilder = [PUGLocationMessageParameterBuilder alloc] init];
paraBuilder.locationInfo = locationInfo;
[PUGChatContext context:@""].messageSender sendMessageWithSynParameterBuilder:paraBuilder];

4.2 重构前的发送消息

重构前需要关注消息发送流程中的各种细节,包括所有网络请求的基础参数、何时保存数据库、何时处理通用错误、消息重试发送等等。下面是重构前需要开发的五个部分

4.2.1 在 MessageNetworkInterface 中新增一个发送消息请求

并且很多比较难懂的公共参数不能丢失

+ (nullable TXTRESTApiRequest *)postTextMessage:(NSString *)content conversaitonID:(NSString *)conversationID primaryKeyID:(NSString *)primaryKey referenceDictionary:(NSDictionary *)referenceDictionary parameters:(nullable NSDictionary *)parameters completion:(void(^)(PUGError *error, PUGMessage *data, NSDictionary *retryParameter))completion {NSAssert(parameters[@"msgType"] != nil, @"Has no relevant key: 'msgType'");if (content.length < 1 || completion == NULL || conversationID.length == 0) { // 文本消息没有内容直接returnif (completion != NULL) {completion([PUGError errorWithCode:PUGCommonErrorInvalidParameters description:@"Invalid Parameters"], nil, nil);}return nil;}TXTRESTApiRequest *req = [TXTRESTApiRequest requestOfPOSTMethod];req.urlString = [self messagesForConversationWithID:conversationID withWithParameters:@[@(PUGBusinessKeyLiterature)]];NSDictionary *params;NSDictionary *idempotentInfo = @{@"id" : mNonnilString(primaryKey)};if (referenceDictionary) {params = @{@"xx": content,@"xxx": referenceDictionary,@"xxx": idempotentInfo};} else {params = @{@"xx": content,@"xxx": idempotentInfo};}if (parameters.count > 0) {NSMutableDictionary *tmp = params.mutableCopy;[tmp addEntriesFromDictionary:parameters];params = tmp;}req.paramsDictionary = params;req.responseSerializerType = TXTResponseSerializerTypeCustomize;req.responseSerializer = [self postMessageParser:primaryKey];[req startWithResponseBlock:^(TXTResponse *response) {PUGError *error = [PUGErrorParser errorFromResponse:response];completion(error, error == nil ? response.responseObject : nil, params);}];return req;
}
3.2.2 新建一个 PUGLocationMessagePlugin 实现发送新消息以及重试发送消息

需要熟悉消息发送流程,进行保存数据库,网络请求,刷新UI 等,包括同步数据库,调用 PUGMessageNetworkInterface 发送接口,还要关注通用逻辑的细节,以及刷新列表等

- (void)sendLocationInfo:(PUGLocationMessageInfo *)locationInfo {@weakify(self);[self sendLocation:locationInfo addCompletion:^(PUGError *error, PUGMessage *message, NSInteger index) {@strongify(self);[self.controller showNewMessage];} completion:^(PUGError *error, PUGMessage *message, NSInteger index) {@strongify(self);[self.controller sendMessageCompletion:message];if ([self processSendMessageError:error message:message]) {//common error} else {[self.controller reloadMessages];}}];
}- (void)sendLocationMessage:(PUGMessage *)locationMessage completion:(void (^)(PUGError *error,PUGMessage *message,NSInteger index))sentCompletion {void (^messageSendStatusCheckCompletion)(PUGError *error, PUGMessage *message) = [self.useCase messageSendStatusCheckCompletion];/**NOTIC: 需要关注各种细节经后端确认,发消息接口发送的内容如果携带xxx字段并且满足xxx或者sticker或者media三字段中任意字段不为空,会被判定为回答xxxx对应的那个真心话,此时如果value不为空,会被认为是一条普通的文字消息,收消息一方收到的message消息体内将不包含xxx字段。*/NSDictionary *networkParametersDictionary = @{@"xxx": [NSDictionary dictionaryWithDictionary:locationMessage.locationDictionary],@"xxx": mNonnilString(locationMessage.msgType),@"xxx": mNonnilString([TXTStatistics currentPageID])};[PUGMessageNetworkInterface postTextMessage:@"location" conversaitonID:self.useCase.conversationID messageReferenceID:nil primaryKeyID:[(PUGMessage *)locationMessage primaryKeyID] parameters:networkParametersDictionary completion:^(PUGError *error, PUGMessage *data, NSDictionary *retryParameter) {if (messageSendStatusCheckCompletion) {messageSendStatusCheckCompletion(error, data);}[self.useCase updateSentMessage:data error:error sentCompletion:sentCompletion originalMessage:locationMessage retryParameter:retryParameter];if ([self.controller respondsToSelector:@selector(messageDidSend:)]) {[self.controller messageDidSend:self.useCase];}}];
}- (void)sendLocation:(PUGLocationMessageInfo *)location addCompletion:(void (^)(PUGError *error, PUGMessage *message, NSInteger index))addCompletion  completion:(void (^)(PUGError *error, PUGMessage *message, NSInteger index))sentCompletion {PUGMessage *reference =  [self.useCase findLatestQuestionMessage];PUGMessage *locationMessage = [PUGMessageDatabaseInterface locationMessage:location referenceMessage:reference otherUser:self.useCase.otherUser conversationID:self.useCase.conversationID];NSInteger index = [self.useCase addOrReplaceMessage:locationMessage toFront:NO];[self.useCase updateCurrentConversation:locationMessage addedMessagesCount:1];if (addCompletion) {addCompletion(nil,locationMessage,index);}@weakify(self);[PUGMessageDatabaseInterface asyncSaveMessage:locationMessage completion:^(BOOL result) {@strongify(self);if (![self.useCase checkIfUnmatchedByOtherUserWithMessage:locationMessage completion:sentCompletion]) {[self sendLocationMessage:locationMessage completion:sentCompletion];}}];
}- (void)retryFailedMessage:(PUGMessage *)message updated:(void (^)(PUGMessage *message,NSInteger index))updatedBlock completion:(void (^)(PUGError *error, PUGMessage *message, NSInteger index))sentCompletion {PUGMessage *updatedMessage = [message copyWithBlock:^(PUGMessageBuilder * _Nonnull builder) {builder.apiObjectState = PUGObjectStatePending;builder.createdTime = [NSDate pug_serverDate];}];NSInteger index = [self.useCase updateMessage:updatedMessage];if (updatedBlock) {updatedBlock(updatedMessage,index);}void (^messageSendStatusCheckCompletion)(PUGError *error, PUGMessage *message) = [self.useCase messageSendStatusCheckCompletion];void (^completion)(PUGError *error, PUGMessage *message, NSInteger index) = ^(PUGError *error, PUGMessage *message, NSInteger index) {if (sentCompletion) {sentCompletion(error, message, index);}};@weakify(self);[PUGMessageDatabaseInterface asyncSaveMessage:updatedMessage completion:^(BOOL result) {@strongify(self);if ([updatedMessage.msgType isEqualToString:PUGMessageMsgTypeLocation]) {[self sendLocationMessage:updatedMessage completion:^(PUGError *error, PUGMessage *message,NSInteger index) {if (completion) {completion(error, message, index);}if (messageSendStatusCheckCompletion) {messageSendStatusCheckCompletion(error, message);}}];}}];
}
3.2.3 只能在消息详情页,或者能获取到消息详情页的类中,调用发送请求

在 PUGMessageViewController、PUGGroupMessageViewController 等消息详情页暴露发送位置消息的接口,内部调用 PUGLocationMessagePlugin 实现发送方法

// 在 PUGGroupMessagesViewController 内部实现- (void)sendLocationMessageInfo:(PUGLocationMessageInfo *)locationMessageInfo {[self.tableViewPlugin.locationPlugin sendLocationInfo:locationMessageInfo];[self.commercializeService messageVCDidSendMessageAction];
}
3.2.4 管理维护传递调用链

让后将 PUGMessageViewController、PUGGroupMessageViewController 等消息详情页传给 PUGChooseLocationHandler 类,通过这些消息详情页暴露的发送接口进行调用发送

// 在 PUGChooseLocationHandler 中实现- (void)sendLocation {if(![self checkLocationPermissionAlert]) {return;}PUGChooseLocationViewController *chooseLocationVC = [[PUGChooseLocationViewController alloc] init];chooseLocationVC.shouldGetMapItemsFromBackground = PUGDeviceInfo.isChineseMobileNumber;if ([self.useCase isGroupChat]) {chooseLocationVC.sourceName = @"GROUP_CHAT_VIEW";} else {chooseLocationVC.sourceName = @"CHAT_VIEW";}@weakify(self);chooseLocationVC.sendLocationHandler = ^(MKMapItem *mapItem) {@strongify(self);[self.controller sendLocationMessageInfo:[PUGLocationMessageInfo locationMessageInfoFromMapItem:mapItem]];};UINavigationController *navi = [PUGThemeManager createNavigationController];if (!navi) {return;}navi.navigationBar.translucent = YES;navi.viewControllers = @[chooseLocationVC];[self.controller presentViewController:navi animated:YES completion:nil];chooseLocationVC.title = PUGChatLocalizedString(@"LOCATION_CHOOSE_VIEW_TITLE");
}
3.2.5 处理自动发送以及重试逻辑

#pragma mark - 重传消息
- (void)retrySendMessage:(PUGMessage *)message {if ([message.msgType isEqualToString:PUGMessageMsgTypeImage] || [message.msgType isEqualToString:PUGMessageMsgTypeExchangePhoto]) {[self retrySendImageMessage:message];} else if ([message.msgType isEqualToString:PUGMessageMsgTypeAudio]) {[self retrySendAudioMessage:message];} else if ([message.msgType isEqualToString:PUGMessageMsgTypeVideo]) {[self retrySendVideoMessage:message];} else if ([message.msgType isEqualToString:PUGMessageMsgTypeRealShot]) {PUGPictureInfo *pictureInfo = [message realShotPictureInfos].firstObject;if (pictureInfo.hasVideo) {[self retrySendVideoMessage:message];} else {[self retrySendImageMessage:message];}} else {[self retrySendNormalMessage:message];}
}#pragma mark - 重传普通消息
- (void)retrySendNormalMessage:(PUGMessage *)message {@weakify(self);[PUGMessageRetryNetworkInterface retrySendMessage:message completion:^(PUGError * _Nonnull error, PUGMessage * _Nonnull data) {@strongify(self);[self done];if (error) {[self uploadMediaFailedWithMessage:message error:error];} else if (data) {NSDictionary *info = @{@"newMessage" : data,@"originalMessage" : message};[[NSNotificationCenter defaultCenter] postNotificationName:PUGRetrySendMessageSuccessNotification object:nil userInfo:info];[self logRetryMessageSCWithMessage:message];}}];
}

总结

这里介绍了消息收发以及数据管理部分的基本设计,并对重构前后做了对比,可以明显看出来良好架构对于开发效率以及维护成本方面的优势,由于个人水平有限,以及篇幅限制,这里没有列出一些巧妙设计的细节,包括参数构造器设计,这个与具体业务场景有关,有兴趣可以私信


文章转载自:

http://DvDCG7Kf.jtszm.cn
http://pOZEm418.jtszm.cn
http://EhbqWEJ2.jtszm.cn
http://p6gQMrKJ.jtszm.cn
http://myDTGwYk.jtszm.cn
http://YXxKaAvW.jtszm.cn
http://fUD6JbJ2.jtszm.cn
http://1uh63OMm.jtszm.cn
http://85MmIQY7.jtszm.cn
http://FEaBpmUQ.jtszm.cn
http://GSrlPjik.jtszm.cn
http://1Bt5qxMV.jtszm.cn
http://CMZgYNpf.jtszm.cn
http://n6ldl6uS.jtszm.cn
http://Hx5YRQio.jtszm.cn
http://QFEZV9SG.jtszm.cn
http://mR8HaRIW.jtszm.cn
http://4vC8wxDI.jtszm.cn
http://wvN0ITy8.jtszm.cn
http://6bIaTc2y.jtszm.cn
http://fIlvqU0l.jtszm.cn
http://QXOZXBpT.jtszm.cn
http://KJ2sSy1l.jtszm.cn
http://jBU8tHfe.jtszm.cn
http://bD4mwhSb.jtszm.cn
http://XFtq2oOD.jtszm.cn
http://otxW1ZuB.jtszm.cn
http://Zv4lyM0x.jtszm.cn
http://svLymgP7.jtszm.cn
http://cNlLpSOb.jtszm.cn
http://www.dtcms.com/wzjs/673176.html

相关文章:

  • 网站建设 睿达科中山网络推广公司
  • 典当行网站源码百度alexa排名
  • 如何免费创建网站平台牡丹江信息网0453免费发布信息
  • 西宁企业网站营销推广福州php做网站
  • 深圳网站制作作wordpress首页加广告位
  • 外贸网站建设智能建站网站上添加子栏目
  • 百度快速收录开通安庆网站关键词优化
  • 上海装修公司做网站关键词优化时间
  • 做网站怎么接业务免费海外网络连接器
  • 优秀品牌网站案例分析兴文移动网站建设
  • 四川建设学网官方网站登录网站建设月总结
  • 如何选择扬中网站建设房地产店铺首页设计过程
  • 炫酷的html5网站做网站兴趣爱好
  • 重庆网站建设哪家公司那家好如何申请域名
  • 广西网站建设服务好网站可信认证必须做吗
  • 如何做一个内部网站app定制开发免费
  • 昌平网站设计收录网址教程
  • 青岛建设交易中心网站首页网络规划设计师通过率多少
  • 有哪些外国网站做精油的北京高端网站开发公司
  • No餐饮网站建设网站专题制作软件
  • 网站制作平台公司包头网络
  • 设计网站大全免费安徽省建设工程造价信息网官网
  • 江苏城乡与住房建设部网站wordpress
  • 如何做好网站推泾阳网站建设
  • 建设京东商城网站成都网站建设的费用
  • 天河网站建设企业长沙网页设计培训班在哪里
  • 网站备案 法人金融网站策划
  • 网站建设:化工简约网站内容布局
  • 学院路网站建设网站建设响应式是什么
  • 网站建设岗位工作范围用discuz好还是WordPress好