ChatMessageModel.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. //
  2. // ChatMessageModel.m
  3. // AIIM
  4. //
  5. // Created by qitewei on 2025/5/14.
  6. //
  7. #import "ChatMessageModel.h"
  8. #import "CryptoAES.h"
  9. #import "ChatsStore.h"
  10. #import "AVFoundation/AVFoundation.h"
  11. #import "FileNetApi.h"
  12. #import <SDWebImage/SDImageCache.h>
  13. #import "OSSManager.h"
  14. @implementation ChatMessageModel
  15. + (instancetype)modelWithDictionary:(NSDictionary *)dict {
  16. ChatMessageModel *model = [[ChatMessageModel alloc] init];
  17. [model setupWithDictionary:dict];
  18. return model;
  19. }
  20. - (void)setupWithDictionary:(NSDictionary *)dict {
  21. NSMutableDictionary * mutableDict = [NSMutableDictionary dictionaryWithDictionary:dict];
  22. [mutableDict jk_each:^(id k, id v) {
  23. if ([v isKindOfClass:[NSNull class]]) {
  24. [mutableDict setObject:@"" forKey:k];
  25. }
  26. }];
  27. //原消息
  28. self.formerMessage = dict;
  29. self.chatId = mutableDict[@"chatId"];
  30. // 消息类型
  31. NSInteger type = [mutableDict[@"messageType"] integerValue];
  32. self.messageType = (ChatMessageType)type;
  33. self.type = [mutableDict[@"type"] integerValue];
  34. self.nickName = mutableDict[@"fromName"]?:@"";
  35. self.avatar = mutableDict[@"fromAvatar"]?:@"";
  36. // 通用内容
  37. // self.content = mutableDict[@"content"] ?: @"";//本地明文存储
  38. self.content =[CryptoAES.shareInstance decryptDataL:mutableDict[@"content"] ?: @""];//本地加密存储
  39. self.msgId = mutableDict[@"id"];
  40. self.fromId = mutableDict[@"fromId"];
  41. // 发送方标识
  42. NSDictionary *userinfo = [UDManager.shareInstance getDDManager:dkuserinfo];
  43. NSString *userId = userinfo[@"id"];
  44. if([self.fromId isEqualToString:userId]){
  45. self.isSender=true;
  46. }
  47. else{
  48. self.isSender=false;
  49. }
  50. // 处理媒体尺寸
  51. CGFloat width = [mutableDict[@"width"] floatValue];
  52. CGFloat height = [mutableDict[@"height"] floatValue];
  53. if (width > 0 && height > 0) {
  54. self.mediaSize = CGSizeMake(width, height);
  55. } else {
  56. // 默认尺寸
  57. self.mediaSize = CGSizeMake(200, 150);
  58. }
  59. self.placeholderImage = kImageMake(@"pictrue_placeholder");
  60. // 文件消息
  61. NSDictionary * extend = [mutableDict[@"extend"] isKindOfClass:NSDictionary.class]?mutableDict[@"extend"]:@{};
  62. self.fileName = extend[@"fileName"] ?: @"未知文件";
  63. self.fileSize = extend[@"size"] ?: @"0KB";
  64. self.url = extend[@"url"] ?: @"";
  65. // NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet];
  66. // self.url = [self.url stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
  67. self.localurl = extend[@"localurl"] ?: @"";
  68. self.quoteMessage = ![extend jk_hasKey:@"quoteMessage"] ?NULL: [ChatMessageModel modelWithDictionary:extend[@"quoteMessage"]];
  69. // 语音消息
  70. NSInteger duration = [extend[@"time"] integerValue];
  71. if (duration<=60) {
  72. self.voiceDuration = duration;
  73. }else{
  74. self.voiceDuration = duration/1000;
  75. }
  76. self.voiceWidth = [self voiceBubbleWidth];
  77. //文件传输成功或失败
  78. self.fileError = [extend[@"fileError"] integerValue];
  79. //合并转发
  80. self.forwardMsgArray = extend[@"messageList"] ?:@[];
  81. // 通话记录
  82. self.callDuration = [mutableDict[@"duration"] integerValue];
  83. self.isCallSuccess = [mutableDict[@"result"] boolValue];
  84. self.isVideo = [mutableDict[@"video"] boolValue];
  85. //@相关
  86. self.atAll = extend[@"atAll"]?[extend[@"atAll"] boolValue]:NO;
  87. self.atUserIds = extend[@"atUserIds"]?:@[];
  88. // 时间戳(如果需要)
  89. self.timestamp = [mutableDict[@"timestamp"] longLongValue];
  90. self.localtime = [mutableDict[@"localtime"] longLongValue];
  91. self.formatterTime = [self timeF:self.timestamp];
  92. // 其他自定义字段...
  93. self.readStatus = [self getReadStatus];
  94. NSString *localPath = [self checkHasLocalPath];
  95. if (localPath.length) {
  96. self.localurl = localPath;
  97. [self updateFileSizeString];
  98. }
  99. }
  100. - (void)exchangeModelWithModel:(ChatMessageModel *)model{
  101. self.msgId = model.msgId;
  102. self.formerMessage = model.formerMessage;
  103. if(self.messageType!=model.messageType){
  104. self.cellHeight=0;
  105. }
  106. self.messageType = model.messageType;
  107. self.type = model.type;
  108. self.isSender = model.isSender;
  109. self.content = model.content;
  110. self.msgId = model.msgId;
  111. self.mediaSize = model.mediaSize;
  112. self.placeholderImage = model.placeholderImage;
  113. self.fileName = model.fileName;
  114. self.fileSize = model.fileSize;
  115. self.url = model.url;
  116. self.localurl = model.localurl;
  117. //文件传输成功或失败
  118. self.fileError = model.fileError;
  119. // 通话记录
  120. self.callDuration = model.callDuration;
  121. self.isCallSuccess = model.isCallSuccess;
  122. self.isVideo = model.isVideo;
  123. self.avatar = model.avatar;
  124. self.nickName = model.nickName;
  125. //@相关
  126. self.atAll = model.atAll;
  127. self.atUserIds = model.atUserIds;
  128. // 时间戳(如果需要)
  129. self.timestamp = model.timestamp;
  130. self.localtime = model.localtime;
  131. self.formatterTime = model.formatterTime;
  132. // // 其他自定义字段...
  133. // 其他自定义字段...
  134. self.readStatus = [self getReadStatus];
  135. }
  136. -(NSString *)timeF:(NSInteger)time{
  137. //NSLog(@"time:%ld",(long)time);
  138. // 创建日期格式器
  139. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  140. [formatter setDateFormat:@"dd"]; // 设置你想要的日期格式
  141. long long timestamp =(long)time/1000; // 例如:2021-10-01 00:00:00 UTC
  142. // 转换为NSDate
  143. NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp];
  144. NSString *dateString = [formatter stringFromDate:date];
  145. NSDate *now = [NSDate date];
  146. NSTimeInterval trt = [now timeIntervalSince1970];
  147. date = [NSDate dateWithTimeIntervalSince1970:trt];
  148. NSString *tdateString = [formatter stringFromDate:date];
  149. NSInteger shijiancha = trt-timestamp;
  150. if(shijiancha<60){
  151. return NSLocalizedString(@"time-oneminint", @"");
  152. }
  153. if(shijiancha>24*3600||dateString.intValue!=tdateString.intValue){
  154. // 假设这是你的时间戳(以秒为单位)
  155. long long timestamp =(long)time/1000; // 例如:2021-10-01 00:00:00 UTC
  156. // 转换为NSDate
  157. NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp];
  158. // 创建日期格式器
  159. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  160. [formatter setDateFormat:@"yyyy-MM-dd HH:mm"]; // 设置你想要的日期格式
  161. // 转换为字符串
  162. NSString *dateString = [formatter stringFromDate:date];
  163. return dateString;
  164. }
  165. else{
  166. NSInteger xiaoshi = shijiancha/3600;
  167. NSInteger fenzhong = shijiancha/60;
  168. if(xiaoshi>0){
  169. // 假设这是你的时间戳(以秒为单位)
  170. long long timestamp =(long)time/1000; // 例如:2021-10-01 00:00:00 UTC
  171. // 转换为NSDate
  172. NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp];
  173. // 创建日期格式器
  174. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  175. [formatter setDateFormat:@"HH:mm"]; // 设置你想要的日期格式
  176. // 转换为字符串
  177. NSString *dateString = [formatter stringFromDate:date];
  178. return dateString;
  179. }
  180. else{
  181. return [NSString stringWithFormat:@"%ld%@",(long)fenzhong,NSLocalizedString(@"time-outmin", @"")];
  182. }
  183. }
  184. return @"";
  185. }
  186. - (NSString *)getReadStatus{
  187. if(self.isSent){
  188. //判断已读未读
  189. if(self.timestamp>ChatsStore.shareInstance.lastreadTime){
  190. return @"未读";
  191. }
  192. else{
  193. return @"已读";
  194. }
  195. }
  196. return @"";
  197. }
  198. - (void)generateThumbnailWithCompletion:(void(^)(UIImage *thumbnail))completion {
  199. // 如果内存中已有缩略图,直接返回
  200. if (self.videoThumbnailImage) {
  201. if (completion) completion(self.videoThumbnailImage);
  202. return;
  203. }
  204. // 生成缓存 key,优先使用 msgId,如果没有则使用 URL 的 hash
  205. NSString *cacheKey = nil;
  206. if (self.msgId.length > 0) {
  207. cacheKey = [NSString stringWithFormat:@"video_thumbnail_%@", self.msgId];
  208. } else if (self.url.length > 0) {
  209. cacheKey = [NSString stringWithFormat:@"video_thumbnail_%lu", (unsigned long)[self.url hash]];
  210. }
  211. // 从 SDWebImage 缓存中查找
  212. if (cacheKey) {
  213. SDImageCache *imageCache = [SDImageCache sharedImageCache];
  214. UIImage *cachedImage = [imageCache imageFromCacheForKey:cacheKey];
  215. if (cachedImage) {
  216. NSLog(@"从缓存加载缩略图: %@", cacheKey);
  217. self.videoThumbnailImage = cachedImage;
  218. if (completion) completion(cachedImage);
  219. return;
  220. }
  221. }
  222. // 没有缓存,需要生成缩略图
  223. NSURL *videoURL = nil;
  224. if (self.localurl.length != 0 && [[NSFileManager defaultManager] fileExistsAtPath:self.localurl]) {
  225. // 从本地文件生成缩略图
  226. NSLog(@"generateThumbnailWithCompletion localurl:%@",self.localurl);
  227. videoURL = [NSURL fileURLWithPath:self.localurl];
  228. NSString *finalCacheKey = cacheKey;
  229. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  230. AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
  231. AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
  232. generator.appliesPreferredTrackTransform = YES;
  233. CMTime acttime;
  234. NSError *error = nil;
  235. CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMake(0, 600) actualTime:&acttime error:&error];
  236. NSLog(@"error:%@",error);
  237. if (!error && imageRef) {
  238. UIImage *thumbnail = [[UIImage alloc] initWithCGImage:imageRef];
  239. CGImageRelease(imageRef);
  240. // 缓存到内存
  241. self.videoThumbnailImage = thumbnail;
  242. // 使用 SDWebImage 缓存到磁盘和内存
  243. if (finalCacheKey) {
  244. SDImageCache *imageCache = [SDImageCache sharedImageCache];
  245. [imageCache storeImage:thumbnail forKey:finalCacheKey completion:^{
  246. NSLog(@"缩略图已缓存: %@", finalCacheKey);
  247. }];
  248. }
  249. if (completion){
  250. completion(thumbnail);
  251. }
  252. } else {
  253. if (completion) completion(nil);
  254. }
  255. });
  256. } else {
  257. // 从远程 URL 生成缩略图
  258. NSLog(@"generateThumbnailWithCompletion url");
  259. if (self.url.length == 0){
  260. if (completion) completion(nil);
  261. return;
  262. }
  263. NSDictionary *options = @{AVURLAssetAllowsCellularAccessKey: @YES,
  264. AVURLAssetPreferPreciseDurationAndTimingKey: @YES};
  265. AVURLAsset *asset = [AVURLAsset URLAssetWithURL:getURL(self.url) options:options];
  266. // 创建图像生成器
  267. AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
  268. generator.appliesPreferredTrackTransform = YES; // 应用视频的方向
  269. generator.maximumSize = CGSizeMake(320, 0); // 设置最大尺寸,保持宽高比
  270. NSString *finalCacheKey = cacheKey;
  271. // 异步生成缩略图
  272. [generator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:kCMTimeZero]]
  273. completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
  274. __block UIImage *thumbnail = nil;
  275. // 处理生成结果
  276. if (result == AVAssetImageGeneratorSucceeded && image) {
  277. // 创建UIImage
  278. thumbnail = [UIImage imageWithCGImage:image];
  279. self.videoThumbnailImage = thumbnail;
  280. // 使用 SDWebImage 缓存到磁盘和内存
  281. if (finalCacheKey) {
  282. SDImageCache *imageCache = [SDImageCache sharedImageCache];
  283. [imageCache storeImage:thumbnail forKey:finalCacheKey completion:^{
  284. NSLog(@"缩略图已缓存: %@", finalCacheKey);
  285. }];
  286. }
  287. }
  288. dispatch_async(dispatch_get_main_queue(), ^{
  289. if (completion) completion(thumbnail);
  290. });
  291. }];
  292. }
  293. }
  294. //下载视频文件
  295. -(void)downloadFileIfNeed:(void(^)(NSInteger persent))loading{
  296. if ([[NSFileManager defaultManager] fileExistsAtPath:self.localurl]){
  297. if (loading) {
  298. loading(100);
  299. }
  300. NSLog(@"downloadFileIfNeed 0: %@", self.localurl);
  301. return;
  302. }
  303. if (![self.url hasPrefix:@"http"]) {
  304. NSLog(@"文件URL异常:%@", self.formerMessage);
  305. return;
  306. }
  307. [FileNetApi downLoadWToken:getURL(self.url) thrid:self.msgId succ:^(int code, NSDictionary * res) {
  308. if(res!=nil){
  309. NSMutableDictionary *mutableDict = [self.formerMessage mutableCopy];
  310. NSMutableDictionary *extend = [self.formerMessage[@"extend"] mutableCopy];
  311. [extend setObject:res[@"filePath"] forKey:@"localurl"];
  312. [mutableDict setObject:extend forKey:@"extend"];
  313. self.localurl = res[@"filePath"];
  314. NSDictionary *NewMsg = [mutableDict copy];
  315. [ChatsStore.shareInstance reciveMsg:NewMsg];
  316. [self updateFileSizeString];
  317. if (loading) {
  318. loading(100);
  319. }
  320. }
  321. else{
  322. if (code>=0&&code<200) {
  323. if (loading) {
  324. loading(code);
  325. }
  326. }
  327. }
  328. } fail:^(NSError * _Nonnull error) {
  329. NSLog(@"downLoadWToken error:%@",error);
  330. }];
  331. }
  332. // 根据语音时长计算气泡宽度
  333. - (CGFloat)voiceBubbleWidth {
  334. // 基础宽度
  335. CGFloat minWidth = 60.0f;
  336. CGFloat maxWidth = 200.0f;
  337. // 最大限制时长(如60秒)
  338. NSInteger maxDuration = 60;
  339. NSInteger duration = MIN(self.voiceDuration, maxDuration);
  340. // 线性增长计算
  341. CGFloat width = minWidth + (maxWidth - minWidth) * (duration / (CGFloat)maxDuration);
  342. return width;
  343. }
  344. #pragma mark - 消息状态管理
  345. - (void)updateFileSizeString {
  346. NSString *filePath = self.localurl;
  347. NSFileManager *fileManager = [NSFileManager defaultManager];
  348. // 获取文件属性
  349. NSError *error = nil;
  350. NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:&error];
  351. NSString *string = @"0KB";
  352. if (error) {
  353. NSLog(@"获取文件属性失败: %@", error);
  354. } else {
  355. // 获取文件大小(单位:字节)
  356. unsigned long long fileSize = [attributes fileSize];
  357. NSLog(@"文件大小: %llu 字节", fileSize);
  358. // 可选:转换为 KB、MB
  359. double fileSizeKB = fileSize / 1024.0;
  360. double fileSizeMB = fileSizeKB / 1024.0;
  361. NSLog(@"文件大小: %.2f KB (%.2f MB)", fileSizeKB, fileSizeMB);
  362. string = [NSString stringWithFormat:@"%.2fMB", fileSizeMB];
  363. }
  364. _customFileSize = string;
  365. }
  366. - (NSString *)checkHasLocalPath {
  367. if (!self.isUploadFile) {
  368. return nil;
  369. }
  370. NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
  371. NSString *fileName;
  372. NSURL *localURL = [NSURL URLWithString:self.localurl];
  373. if (localURL) {
  374. fileName = localURL.lastPathComponent;
  375. } else {
  376. fileName = [NSURL URLWithString:self.url].lastPathComponent;
  377. }
  378. if (fileName.length == 0) {
  379. return nil;
  380. }
  381. NSString *path = [documentsDirectoryURL URLByAppendingPathComponent:fileName].path;
  382. if ([[NSFileManager defaultManager] fileExistsAtPath:path]){
  383. NSLog(@"checkHasLocalPath: %@", path);
  384. return path;
  385. }
  386. return nil;
  387. }
  388. - (BOOL)isUploadFile {
  389. return self.messageType == ChatMessageTypeFile ||
  390. self.messageType == ChatMessageTypeVoice ||
  391. self.messageType == ChatMessageTypeImage ||
  392. self.messageType == ChatMessageTypeVideo;
  393. }
  394. - (BOOL)isSent {
  395. return ![self.msgId isEqualToString:[NSString stringWithFormat:@"%ld", self.localtime]];
  396. }
  397. - (BOOL)checkIsSendFail {
  398. // NSDate *now = [NSDate date];
  399. // NSTimeInterval trt = [now timeIntervalSince1970];
  400. // NSInteger time = trt*1000;
  401. // NSInteger outTime =time-self.localtime;
  402. // if(outTime>33000){
  403. // return YES;
  404. // }
  405. return NO;
  406. }
  407. - (FileUploadStatus)fileUploadStatus {
  408. if (self.messageType == ChatMessageTypeText) {
  409. return FileUploading;
  410. }
  411. NSString *key = [self getKey];
  412. NSInteger value = [[[NSUserDefaults standardUserDefaults] valueForKey:key] integerValue];
  413. NSLog(@"checkIsNeedResend key: %@ value: %ld", key, value);
  414. if (value >= 100 || value == 0) {
  415. return FileUploadCompleted;
  416. }
  417. if (value == -1) {
  418. return FileUploadFail;
  419. }
  420. return FileUploading;
  421. }
  422. - (void)updateFileStatusToUploading {
  423. [[NSUserDefaults standardUserDefaults] setValue:@(0) forKey:[self getKey]];
  424. }
  425. -(NSString *)getKey {
  426. NSString *localtime = [NSString stringWithFormat:@"%ld", self.localtime];
  427. return [[OSSManager sharedManager] fileMessageKeyWith:localtime];
  428. }
  429. @end