美课+, 一个公司老项目,一段程序猿的技术回忆

前言

"美课+"项目从2018年3月26号开始启动到2018年6月8号结束,总计两个月多的时间,项目的时间节点比较紧张.虽然最后没有上线很遗憾,但是,不管是在流程和项目上,对自己都是一次不错的尝试.下面我就对这次项目做一下iOS端的整体总结.


#### 技术难点 *** 在iOS端,我感到的项目技术难点或者是比较费时的技术点主要有以下几点,分别为播放器的定制,下载模块逻辑,图库转场动画逻辑三大模块.下面我分别来说一下这三个模块的详情情况.
  • 播放器的定制

播放器模块是"美课+"项目的核心模块,主要有两个地方需要用到播放器,一个是课程,另外一个是直播.播放器接入的是网易点播播放器,接入集成的过程比较简单,修改播放器本身的时间大多都放在了播放器的控制器层面上了.课程模块需要使用逻辑是最多的,比如.由于课程模块本身页面需要做成滑动锁定模式,所以我们需要根据不同的状态来修改不同的控制器的各个控件的位置以及尺寸问题,同时,为了更好的解耦,这里我使用了代理模式来对业务逻辑进行解耦.例如,在状态栏的显示与隐藏上,这里就使用了回调.这里我说一下遇到的两个问题.

遇到的第一个问题就是横竖屏切换问题,一开始的时候我想用两个控制器来做横竖屏切换,后来发现这样做会出现的问题就是视频播放不同步,难道我们到做两个PlayView,显然是不符合我们的思维逻辑后,后来觉得使用一个PlayView即可,监控屏幕的旋转,然后对其使用动画即可实现横竖屏切换效果.
本来一切是正常的,可是当用户快速的从左横屏→竖屏→右横屏的时候就会出现PlayView的尺寸显示不正确的问题了.原因是当左横屏→竖屏的动画还未完成时(动画过程需要0.3s),设备已经完成了从竖屏到右竖屏的过程了,解决方案就是一个BOOL值,判断从横屏到竖屏的动画是否完成,如果完成了,再检测设备是否横屏状态,如果是横屏状态,那就再次进行横屏的动画,这样就解决了动画错乱的问题了.整体代码如下所示.

     //加入通知回调[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(rotateViews:)name:UIDeviceOrientationDidChangeNotificationobject:nil];

主要实现代码如下所示.

//屏幕旋转的主要操作方法
- (void) rotateViews:(NSObject *)sender {if (_screenControlView.lockButton.selected) {return;}if (!_isStartPLay) {return;}UIDevice* device = [sender valueForKey:@"object"];_screenControlView.menuViewHidden = YES;if (device.orientation == UIDeviceOrientationPortrait && _isFullScreen) {CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间_isFullScreen = NO;_screenControlView.isFullScreen = NO;[UIView animateWithDuration:duration animations:^{CGFloat nowWidth = 0;if (KmainWidth > KmainHeight) {nowWidth = KmainHeight;}else{nowWidth = KmainWidth;}NSLog(@"竖进行");if (self.isError) {self.errorView.frame = CGRectMake(0, 0, nowWidth, nowWidth/16.0*9);self.reloadButton.center =  self.errorView.center;self.errorLabel.frame = CGRectMake(_errorView.center.x - self.errorLabel.frame.size.width/2.0, CGRectGetMinY(_reloadButton.frame) - 21 - 18, self.errorLabel.frame.size.width, 21);}self.playView.frame = CGRectMake(0, 0, nowWidth, nowWidth/16.0*9);self.screenControlView.frame = CGRectMake(0, 0, nowWidth, nowWidth/16.0*9);self.screenControlView.shadeView.frame = CGRectMake(0, 0, nowWidth, nowWidth/16.0*9);self.isPortraitFinished = NO;} completion:^(BOOL finished) {NSLog(@"竖完成");self.isPortraitFinished = YES;[[UIApplication sharedApplication] setStatusBarHidden:NO];[self portraitFinishaNextAction];}];}if (device.orientation == UIDeviceOrientationLandscapeLeft && !_isFullScreen) {CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间_isFullScreen = YES;_screenControlView.isFullScreen = YES;[_playView addSubview:self.screenControlView];[UIView animateWithDuration:duration animations:^{if (self.isPortraitFinished != NO) {if (self.isError) {self.errorView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.reloadButton.center =  self.errorView.center;self.errorLabel.frame = CGRectMake(_errorView.center.x - self.errorLabel.frame.size.width/2.0, CGRectGetMinY(_reloadButton.frame) - 21 - 18, self.errorLabel.frame.size.width, 21);}self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.screenControlView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.screenControlView.shadeView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);NSLog(@"左右进行");}}completion:^(BOOL finished) {[[UIApplication sharedApplication] setStatusBarHidden:YES];NSLog(@"左右完成");}];}if (device.orientation == UIDeviceOrientationLandscapeRight && !_isFullScreen) {CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间_isFullScreen = YES;_screenControlView.isFullScreen = YES;[_playView addSubview:self.screenControlView];[UIView animateWithDuration:duration animations:^{if (self.isPortraitFinished != NO) {if (self.isError) {self.errorView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.reloadButton.center =  self.errorView.center;self.errorLabel.frame = CGRectMake(_errorView.center.x - self.errorLabel.frame.size.width/2.0, CGRectGetMinY(_reloadButton.frame) - 21 - 18, self.errorLabel.frame.size.width, 21);}self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.screenControlView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.screenControlView.shadeView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);NSLog(@"左右进行");}} completion:^(BOOL finished) {[[UIApplication sharedApplication] setStatusBarHidden:YES];NSLog(@"左右完成");}];}
}//竖屏完成之后判断现在的手机设备的方向,如果是横竖屏,那么需要再次进行动画操作.
-(void)portraitFinishaNextAction{UIDeviceOrientation  orient = [UIDevice currentDevice].orientation;if (orient == UIDeviceOrientationLandscapeLeft || orient == UIDeviceOrientationLandscapeRight) {CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;//时间[UIView animateWithDuration:duration delay:duration options:UIViewAnimationOptionLayoutSubviews animations:^{if (self.isPortraitFinished != NO) {if (self.isError) {self.errorView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.reloadButton.center =  self.errorView.center;self.errorLabel.frame = CGRectMake(_errorView.center.x - self.errorLabel.frame.size.width/2.0, CGRectGetMinY(_reloadButton.frame) - 21 - 18, self.errorLabel.frame.size.width, 21);}self.playView.frame = self.playView.frame;self.screenControlView.frame = self.playView.frame;self.screenControlView.shadeView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);NSLog(@"左右进行");}} completion:^(BOOL finished) {NSLog(@"next左右完成");[[UIApplication sharedApplication] setStatusBarHidden:YES];self.playView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);self.screenControlView.frame = CGRectMake(0, 0, KmainWidth, KmainHeight);}];}
}

遇到了另外一个问题,那就是关于自定义音量调节UI,实现的功能点就是不使用系统本身自带的音量调节UI控件.而是使用我们自己的音量播放UI控件.

系统本身的音量控制UI

我们需要定制的音量控制UI

这里需要使用的主要的控件就是MPVolumeView,这个控件有个特点,当它检测到当前系统有音量改变的时候,如果它没有被添加到当前最上层的View上时,那它会自动添加到View上来做展示.

实际上,对于MPVolumeView的隐藏,网上很多的博客都已经写到不能单单的隐藏这个控件,而是要对其的Frame进行修改,让它显示在屏幕外边即可. 这时候我就出现了一个问题,那就是不能隐藏音量控制View. 后来发现问题出现在自己的大意,因为自己对其其中的音量大小显示控件(UISlider控件)进行了Frame的改变,导致UI一直是错误的,后来通过分析,重新审核代码,解决这一问题.下面是对MPVolumeView控件的初始化,也是解决方案.

- (MPVolumeView *)volumeView {if (_volumeView == nil) {_volumeView  = [[MPVolumeView alloc] init];[_volumeView setFrame:CGRectMake(-100, -100, 40, 40)];//设置Frame即可.[_volumeView setHidden:NO];for (UIView *view in [_volumeView subviews]){if ([view.class.description isEqualToString:@"MPVolumeSlider"]){self.volumeViewSlider = (UISlider*)view;break;}}}return _volumeView;
}

  • 下载模块逻辑(断点续传,后台下载,数据库下载Model状态改变)

下载模块是用了iOS本身的网络请求类NSURLSession和Realm数据库来实现的. 下载本身没有什么难点,但是下载模块逻辑之多,可以说是"美课+"项目之最了.造成逻辑过多的原因主要是下载状态的原因. 这里我就用表格的形式对其逻辑进行一个整体状态的阐述.

状态处理情况
当App启动时判断当前是否是WIFI网络,接着判断当前下载队列中是否有未下载,如果有未下载的任务,那么启动后台下载
当某个课时下载完成时首先先判断当前课程是否还有未下载的课时(不包含已经被用户手动暂停的课时),如果没有的话,那就去遍历外围的课程数组找到合适的未下载完成的课程往队列当中添加新的队列任务进行下载.
当某个课时被暂停时从队列中移除当前暂停的下载任务,然后接下来的过程与 某个课时下载完成时类似.
当某个课时从暂停→重新下载时从队列中移除当前的下载任务,然后把当前点击的下载任务加入到队列当中进行下载
在课程页面点击全部暂停按钮时暂停当前课程的所有下载(实质:修改下载状态),遍历外围的课程数组找到合适的未下载完成的课程往队列当中添加新的队列任务进行下载.
在课程页面点击全部开始按钮时移除队列中的下载任务,遍历当前课程,找到未下载的课时(不包含已经被用户手动暂停的课时),如果没有的话,那就去遍历外围的课程数组找到合适的未下载完成的课程往队列当中添加新的队列任务进行下载.
当某个课程下载完成时候遍历课程数组找到合适的未下载完成的课程往队列当中添加新的队列任务进行下载.
在下载页面点击全部暂停按钮移除队列中所有的下载任务.修改课程的下载状态
在下载页面点击全部开始按钮遍历课程数组找到合适的下载任务,把下载任务加入到队列中.开始下载

  • 图库转场动画逻辑

图库转场动画部分的实现是对presentViewControllerdismissViewController的转场动画进行了自定义.自定义动画类主要实现的协议是UIViewControllerAnimatedTransitioning中的两个方法- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext.动画的实现过程比较简单.这是只拿presentViewController的转场动画为例,代码如下所示.

.h文件代码部分如下所示.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>@interface PresentAnimator : NSObject<UIViewControllerAnimatedTransitioning>@property (nonatomic, strong) UIImageView *transitionImgView;   //转场图片@property (nonatomic, assign) CGRect transitionBeforeImgFrame;  //转场前图片的frame@property (nonatomic, assign) CGRect transitionAfterImgFrame;   //转场后图片的frame@end

.m文件代码部分如下所示.

#import "PresentAnimator.h"@implementation PresentAnimator- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{//转场过渡的容器viewUIView *containerView = [transitionContext containerView];//FromVCUIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];UIView *fromView = fromViewController.view;[containerView addSubview:fromView];//ToVCUIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];UIView *toView = toViewController.view;[containerView addSubview:toView];toView.hidden = YES;//图片背景的空白view (设置和控制器的背景颜色一样,给人一种图片被调走的假象 [可以换种颜色看看效果])UIView *imgBgWhiteView = [[UIView alloc] initWithFrame:self.transitionBeforeImgFrame];imgBgWhiteView.backgroundColor = [UIColor whiteColor];[containerView addSubview:imgBgWhiteView];//有渐变的黑色背景UIView *bgView = [[UIView alloc] initWithFrame:containerView.bounds];bgView.backgroundColor = [UIColor whiteColor];bgView.alpha = 0;[containerView addSubview:bgView];//定制UIButton *returnButton = [UIButton buttonWithType:UIButtonTypeCustom];returnButton.frame = CGRectMake(5, StatusHeight, 49, 44);[returnButton setImage:[UIImage imageNamed:@"common_return"] forState:UIControlStateNormal];UIButton *shareButton = [UIButton buttonWithType:UIButtonTypeCustom];shareButton.frame = CGRectMake(KmainWidth-5-49, StatusHeight, 49, 44);[shareButton setImage:[UIImage imageNamed:@"home_class_share_black"] forState:UIControlStateNormal];UIButton *commentButton = [UIButton buttonWithType:UIButtonTypeCustom];commentButton.frame = CGRectMake(KmainWidth-5-49-49, StatusHeight, 49, 44);[commentButton setImage:[UIImage imageNamed:@"photo_find_no_like"] forState:UIControlStateNormal];[bgView addSubview:returnButton];[bgView addSubview:shareButton];[bgView addSubview:commentButton];//过渡的图片UIImageView *transitionImgView = [[UIImageView alloc] initWithImage:self.transitionImgView.image];transitionImgView.frame = self.transitionBeforeImgFrame;[transitionContext.containerView addSubview:transitionImgView];[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:1 initialSpringVelocity:1 options:UIViewAnimationOptionCurveLinear animations:^{transitionImgView.frame = self.transitionAfterImgFrame;bgView.alpha = 1;} completion:^(BOOL finished) {toView.hidden = NO;[imgBgWhiteView removeFromSuperview];[bgView removeFromSuperview];[transitionImgView removeFromSuperview];BOOL wasCancelled = [transitionContext transitionWasCancelled];//设置transitionContext通知系统动画执行完毕[transitionContext completeTransition:!wasCancelled];}];}@end

通过上面的的跳转动画我们可以达到我们预期的动画效果.实现动画的代码如下所示.

//点击某个图片Cell时
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{PhotosDetailsArrayViewController *photosDetailsArrayViewController = [[PhotosDetailsArrayViewController alloc] init];photosDetailsArrayViewController.delegate = self;if (self.photosDataSourceArray.count != 0) {photosDetailsArrayViewController.dataArray = self.photosDataSourceArray;photosDetailsArrayViewController.indexRow = indexPath.row;}PhotoModel *photoModel = self.photosDataSourceArray[indexPath.row];self.animatedTransition = nil;FindCollectionCell *cell = (FindCollectionCell *)[collectionView cellForItemAtIndexPath:indexPath];//1. 传入必要的3个参数[self.animatedTransition setTransitionImgView:cell.mainImageView];UIWindow * window=[[[UIApplication sharedApplication] delegate] window];CGRect frame= [cell.mainImageView convertRect: cell.mainImageView.bounds toView:window];[self.animatedTransition setTransitionBeforeImgFrame:frame];NSNumber *imageWidth = photoModel.origin[@"w"];NSNumber *imageHeight = photoModel.origin[@"h"];float scale = [imageHeight floatValue]/[imageWidth floatValue];CGFloat imageViewHeight = KmainWidth*scale;[self.animatedTransition setTransitionAfterImgFrame:CGRectMake(0, StatusHeight+44, KmainWidth, imageViewHeight)];photosDetailsArrayViewController.transitioningDelegate = self.animatedTransition;[self presentViewController:photosDetailsArrayViewController animated:YES completion:nil];
}

对于查看图片详情页面的控制上,这里我的实现逻辑是图片详情页面的控制器View整体是一个左右滑动的CollectionView,每一张图片的详情以及其推荐列表都是一个Cell,每一个Cell的尺寸就是当前CollectionView的Size.这样做的好处是利用CollectionView的重用机制,不断的从重用池里面取出Cell来做图片页面的展示.减少一定的性能损耗.


在转场动画实现过程,也是遇到几个问题,这里我做一下说明.

第一个问题.当前这个问题只有一行代码.如下所示.

        [_mainCollectionView layoutIfNeeded];

当从图片详情页面返回的图片列表的时,需要让当前列表页面的控制器中的集合视图所对应的Cell滚动到视图中心,然后进行转场动画.这时,需要调用如下的方法.

[_mainCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:selectIndexPath inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];

然后使用下面的方法不管如何获取到的Cell有时候就为nil.

    FindCollectionCell *cell = (FindCollectionCell *)[_mainCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:selectIndexPath inSection:0]];

出现这个问题的原因主要在于collectionView.visibleCells没有这个cell,需要从重用池获取,所以要滚动到这个cell位置后走layoutSubViews布局,这样才拿的到cell.所以就要使用到上面的那一行代码**[_mainCollectionView layoutIfNeeded];**来主动的刷新UI了.然后才能获取到Cell.

第二个问题,一开始实现转场动画的时候,由于前后的ViewController要保持数据的一致性和同步性,所以在这里的时候我就把数据源给抽离了出来,单独的做成了一个单例,本来是想着更好的解耦,没想到在个人页面,图库分类等等页面都需要这样的跳转页面,那么每一种情况都需要一个单例来支持,这样会造成页面逻辑代码臃肿不堪.不合乎架构思想,出于这样的目的,我使用的代理模式 + 布尔值 进行逻辑代码的修改 部分代码如下所示.

详情页面所需要实现的协议

@class PhotosDetailsArrayViewController;
@protocol PhotosDetailsArrayViewControllerDelegate<NSObject>//加载了更多的数据 通过回调让上一个页面进行数据的加载,必须实现的代理方法
- (void)loadMoreDataSourceWithPhotoArray:(NSArray <PhotoModel *>*)photoArray;@optional
- (void)scrollToItemAtIndexPathWithImageView:(UIImageView *)imageView selectIndexPath:(NSInteger)selectIndexPath withVC:(PhotosDetailsArrayViewController *)vc;@end

BOOL值控制的逻辑代码的修改

@property(nonatomic,assign)BOOL isShowWithSearch;//搜索进入
@property(nonatomic,assign)BOOL isShowWithAlbum;//图集进入

新技能:LLDB调试台

LLDB调试台以前在工程中一直用的不多,不过经过一段时间的使用,发现使用LLDB调试台调试程序非常的方便.主要在我们需要调试的代码位置打上断点,我们就可以对其进行各种调试.最大的好处就是我们不用停用程序就可以重新修改UI(因为UI渲染是一个单独的线程,并不会应为主线程的阻塞就停止渲染),这里写一下最常用的几个命令.

  • expression命令 : expression命令的作用是执行一个表达式,并将表达式返回的结果输出.
  • print命令 : 打印某个东西,可以是变量和表达式.
  • print命令 : 打印某个东西,可以是变量和表达式.
  • [CATransaction flush] : UI刷新方法.

其他问题

  • AFNetworking3.0存在的内存泄漏问题

在项目最后的测试阶段,我使用了Leak内存泄漏检测工具检测内存泄漏的时候,每当我进行一个新的页面都会存在内存泄漏问题,排除问题之后,发现是AFNetworking3.0造成的,打开监控页面之后,如下所示.

那么为什么AFNetworking3.0会造成内存泄漏呢?原因是manager类每一次网络请求都是初始化一个实例对象,但是该对象在工程中得不到释放,造成了内存泄漏.解决方法是创建一个继承与AFHTTPSessionManager 的单例对象,每次网络请求都调用这个单例方法.如下所示.

static AFHTTPSessionManager *httpManager;
//创建单例 解决循环引用问题
+ (AFHTTPSessionManager *)sharedHttpManager {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 初始化请求管理类httpManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:KmainURL]];httpManager.requestSerializer = [AFJSONRequestSerializer serializer];// 设置15秒超时 - 取消请求httpManager.requestSerializer.timeoutInterval = 15.0;// 编码httpManager.requestSerializer.stringEncoding = NSUTF8StringEncoding;// 缓存策略httpManager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;httpManager.responseSerializer = [AFJSONResponseSerializer serializer];// 支持内容格式httpManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/plain", @"text/javascript", @"text/json", @"text/html", nil];});return httpManager;
}

总结

做一下总结,"美课+"项目整体上还存在着不少的Bug,自己也会不断的去调整项目.

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

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

相关文章

鸿蒙应用开发:数据持久化

最近在搞公司项目用到了鸿蒙端的数据持久化&#xff0c;特来跟大家分享一下。 在鸿蒙开发中&#xff0c;可以使用以下几个包来实现数据的持久化处理&#xff1a; Data Ability 通过数据能力组件&#xff0c;开发者可以实现复杂的数据操作&#xff0c;包括增、删、改、查等功…

【国潮来袭】华为原生鸿蒙 HarmonyOS NEXT(5.0)正式发布:鸿蒙诞生以来最大升级,碰一碰、小艺圈选重磅上线

在昨日晚间的原生鸿蒙之夜暨华为全场景新品发布会上&#xff0c;华为原生鸿蒙 HarmonyOS NEXT&#xff08;5.0&#xff09;正式发布。 华为官方透露&#xff0c;截至目前&#xff0c;鸿蒙操作系统在中国市场份额占据 Top2 的领先地位&#xff0c;拥有超过 1.1 亿 的代码行和 6…

Linux如何安装“ServerAgent“并使用?

1、cd /home/ 2、上传文件到项目文件下 3、解压 unzip ServerAgent-2.2.3.zip 4、打开文件 cd ServerAgent-2.2.3/ 5、赋权&#xff08;测试环境&#xff09; chmod -R 777 *6、启动 ./startAgent.sh

Prompt-Tuning方法学习

文章目录 一、背景1.1 Pre-training1.2 Fine-Tuning1.3 高效微调&#xff08;SOTA PEFT&#xff09;1.4 基于强化学习的进阶微调方法&#xff08;RLHF&#xff09; 二、Prompt-Tuning技术2.1 发展历程2.2 Prompt模板构建方式 三、基于连续提示的Prompt Tuning四、Q&A 一、背…

程序员节日的日期是10月24日‌程序员日

‌程序员节日的日期是10月24日。‌ 这一天被称为‌中国程序员日或‌1024程序员节&#xff0c;由‌博客园、‌CSDN等自发组织设立&#xff0c;旨在纪念程序员对科技世界的贡献。 程序员节日的由来和意义 1024程序员节的由来可以追溯到2010年&#xff0c;最初由网友提出设立一个…

RocketMQ消息处理详解!

文章目录 引言同步发送原理分析优缺点优点缺点 使用场景 异步发送原理分析优缺点优点缺点使用场景 单向发送原理分析优缺点优点缺点 使用场景 三种方式对比如何选择同步发送异步发送单向发送 总结 引言 在 RocketMQ 中&#xff0c;有 3种简单的消息发送方式&#xff1a;同步发…

计算服务器:开启科学计算新变革的强大引擎

1983 年&#xff0c;著名数学家 Lax 为首的调研小组指出&#xff0c;大型科学计算对国家安全、科技进步与经济发展至关重要&#xff0c;从美国国家利益出发&#xff0c;大型计算的绝对优势不容动摇。 科学计算是什么&#xff1f;为何在 20 世纪 80 年代就被提升到美国国家利益层…

Pytest日志收集器配置

前言 在pytest框架中&#xff0c;日志记录&#xff08;logging&#xff09;是一个强大的功能&#xff0c;它允许我们在测试期间记录信息、警告、错误等&#xff0c;从而帮助调试和监控测试进度。 pytest与Python标准库中的logging模块完美集成&#xff0c;因此你可以很容易地在…

vmware虚拟机linux系统安装

一、下载linux镜像安装包 步骤1---网址地址下载镜像 地址&#xff1a;Index of /ubuntu-releases/22.04/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 步骤2---下载linux版本号 步骤3---查看下载的linuxiso linux镜像操作系统 二、vmware新建安装linux操作系统…

边缘计算技术的优势与挑战

如今&#xff0c;随着5G快速无线网络的到来&#xff0c;将计算存储和物联网&#xff08;IoT&#xff09;分析的部署放在靠近数据产生的地方&#xff0c;使得边缘计算成为可能。 物联网设备和新应用的扩展需要实时计算能力。5G无线正在考虑边缘系统&#xff0c;以快速跟踪支持实…

基于SpringBoot+Vue的厨艺交流系统的设计与实现(源码+定制开发)厨艺知识与美食交流系统开发、在线厨艺分享与交流平台开发、智能厨艺交流与分享系统开发

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

爬虫中代理ip选择和使用实战

一、爬虫中的反爬问题 爬虫技术不仅是一种工具&#xff0c;更像是一门捕捉信息的艺术。通过它&#xff0c;我们能够从浩瀚的互联网中&#xff0c;精确获取到所需的有价值数据。对于那些需要进行数据分析或模型训练的人来说&#xff0c;爬虫技术几乎是必备的技能。虽然网上公开…

git提交到github个人记录

windows下git下载 1.进入git官网https://git-scm.com/downloads/win 一直默认选项即可 2.在settings中SSH and GPG keys中Add SSH key 3.选择git cmd git使用 1.配置用户名&#xff0c;和邮箱 git config --global user.email "youexample.com" git config --g…

Director3D: Real-world Camera Trajectory and 3DScene Generation from Text 论文解读

目录 一、概述 二、相关工作 1、文本到3D生成 2、3DGS 三、Director3D 1、Cinematographer 2、Decorator 3、Detailer 4、Loss 一、概述 该论文提出利用真实世界数据集&#xff0c;设计一个从文本生成真实世界3D场景和自适应相机轨迹的强大的开放世界文本到3D生成框架…

067_基于springboot的HSK学习平台

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

【进阶OpenCV】 (18)-- Dlib库 --人脸关键点定位

文章目录 人脸关键点定位一、作用二、原理三、代码实现1. 构造人脸检测器2. 载入模型&#xff08;加载预测器&#xff09;3. 获取关键点4. 显示图像5. 完整代码 总结 人脸关键点定位 在dlib库中&#xff0c;有shape_predictor_68_face_landmarks.dat预测器&#xff0c;这是一个…

安装vue发生异常: idealTree:nodejs: sill idealTree buildDeps

一、异常 C:\>npm install vue -g npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIREDnpm ERR! request to https://registry.npm.taobao.org/vue failed, reason: certificate has expired 二、原因 请求 https://registry.npm.taobao.org 失败&#xff0c;证…

Spring Boot与Flyway实现自动化数据库版本控制

一、为什么使用Flyway 最简单的一个项目是一个软件连接到一个数据库&#xff0c;但是大多数项目中我们不仅要处理我们开发环境的副本&#xff0c;还需要处理其他很多副本。例如&#xff1a;开发环境、测试环境、生产环境。想到数据库管理&#xff0c;我们立刻就能想到一系列问…

网站漏扫:守护网络安全的关键防线

网站漏洞扫描&#xff0c;简称漏扫&#xff0c;是一种针对网站进行漏洞检测的安全服务。网站漏洞扫描在网络安全中占据着至关重要的地位。 网站漏扫在及时发现和修复漏洞方面发挥着关键作用 通过对网站和系统的全面扫描&#xff0c;能够快速识别出各种潜在的漏洞&#xff0c;…

jmeter中发送post请求遇到的问题

用jmeter发送post请求&#xff0c;把请求参数放在Body Data处&#xff0c;参数都写得正确&#xff0c;但没想到结果每次都报错&#xff0c;直接响应结果乱七八糟&#xff0c;改成用Parameters,反而不乱报错了。 上图 请求里如下 另外一些请求也是这样 这个响应结果也是错误的…