日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS程序员眼中的客户端免登陆(数据迁移已更新)

發布時間:2025/7/14 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS程序员眼中的客户端免登陆(数据迁移已更新) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2017.01.15

一、前言,為什么要做免登陸

  • 2017年1月9日,蓄勢已久的小程序正式上線,著實,張小龍 用完即走 的理念發揮的淋漓盡致,無需下載,掃碼可用,用完即走
  • 2017年互聯網人口紅利結束了,那么接下來除了__內容的精耕細作__外,就是__提高流量的轉化率__,然而在流量轉化為真實用戶的道路上,一個登陸注冊的入口擋住了運營活動多少真金白銀砸出來的流量?
  • 在談免登陸之前呢,我想先大概說下客戶端登錄,想必大家都耳熟能詳,一般情況下需要包含以下幾個方面【括號內部分為可選項】:
  • SNS 第三方快捷登陸
  • 郵箱+(驗證碼)+密碼 登錄注冊
  • 手機號+驗證碼+(密碼)登錄注冊
  • (提示用戶上次在本機登錄方式 && 賬號)

  • 毫無疑問,相比于手機號、郵箱的登錄注冊,第三方登錄是最方便的,在第三方app已經登錄的前提下,需要以下兩步操作:
  • 用戶第一次打開app需要小手點一下第三方圖標
  • 跳轉到對應app后,點一下 確認授權 按鈕即可返回自己的app完成登錄

但是!!!

  • 在用戶還沒體驗到你app任何亮點之前,憑什么讓用戶進行如此繁雜的操作,不要讓用戶思考!不要讓用戶麻煩!尤其是用戶對隱私日漸重視的今天!!且不說某麥某東等用戶賬號密碼泄露,就說前幾天某德利用手中大數據強行一把秀優越。。。

我就問你要是凱迪拉克車主你還會用高德么?!(默默掏出褲兜里的地鐵卡看了一眼。。)

  • 結論是用戶是越來越重視自己的隱私的,用戶在使用 app 的時候也不想進行任何多余的思考
  • 因此在用戶下載 app 之后第一次打開,要狠下心去掉一切不必要的彈框(除國行iOS10必須彈出的蜂窩網絡權限之外,其他接收通知、定位等權限最好放在需要的時候再彈出)
  • 除特殊軟件(如網絡電話)必須使用電話號碼注冊的,其他類似電商、內容瀏覽、交友軟件、工具類等 app,都應該進行免登陸操作先讓用戶體驗 app 的基本功能,在一些深度使用的高級功能上個添加門檻,提示用戶進行登錄注冊操作

二、來幾個常用 app 的例子

1. 今日頭條:
  • 打開 app 后以游客身份進入,可以進行常規的新聞瀏覽、查看評論、收藏、分享、消息反饋等操作
  • 進行爆料、評論、查看閱讀歷史等操作的時候彈出登錄框

  • 登錄成功后,之前收藏的數據已遷移到正式用戶名下
  • 如果實在發送評論的時候觸發的登錄操作,登錄成功后評論發出,提示用戶評論發送成功
2. 每日開眼
  • 同樣的,進入 app 后可正常瀏覽,視頻狀態下進行點贊操作觸發登錄,你看這位女施主懸浮在泳池中,享受著柔和的陽光和微微清風,那曼妙的身材真是讓作為用戶的我忍不住登錄,再退出,再登錄。。。

但是!!!

如果你覺得我是因為女主人公的照片才舉這個例子,呵呵,在下可不是那么膚淺的人,開眼的內容和設計以及 app 整體流暢度都很棒,但是免登陸這里有兩個小瑕疵,在游客+橫屏狀態下

  • 觀看視頻的時候,點擊收藏按鈕,直接modal出豎屏的登錄框,這點對用戶不是很友好
  • 登錄成功后,沒有自動延續用戶在登錄之前的操作(收藏)

關于這兩點的技術實現后面會講

三、整體流程

  • 用戶首次進入 app 之后,判斷之前是否在本機登錄過,如果是用戶首次登錄,就調用 游客登錄API,當然這個游客 guestId 是服務器根據設備號生成的,一般情況下,一個設備對應一個游客 guestId,而且這個游客 guestId 當然是不能展示給用戶的(也可以在該接口返回一個上次登錄信息,提示用戶上次登錄方式)
  • iPhone設備各種信息獲取傳送門

    • 然后使用這個游客 guestId 進行各項參數的初始化,比如數據庫存取地址、下載文件路徑、瀏覽記錄等各方面操作的統計,當然該游客在進行一般操作的時候,就是使用這個游客 guestId 與服務器進行交互
    • 接著就要考慮彈出登錄框的具體時機,當然每個 app 的產品特性不一樣,一般會在以下幾種情況下彈出登錄框:收藏、評論、購買會員、下單購買商品等深度操作。
    • 還有就是萬萬不能在以下幾種情況下彈出登錄框:分享、用戶反饋、添加到購物車等,因為這些操作是用戶主動幫助分享app,提出意見,這時候彈出登錄框,簡直是搞事情!
    • 彈出登錄框(注意橫豎屏的適配),用戶選擇進行登錄后,獲取到一個正式的用戶 userId,重新初始化各項參數,隱藏登錄頁,進行數據庫遷移合并、下載內容路徑遷移(大多下載需要用戶相應的權限,防止作弊)、歷史記錄遷移合并、購物車內容遷移合并等
    • 最后繼續進行用戶需要登錄之前的操作(通過block來實現)
    • 若用戶進行退出登錄操作,先調用退出登錄的api,然后再調用游客登錄的api

    四、上代碼之前,談談登錄注冊的一些小細節

    • 進入到登錄注冊頁后,鍵盤應立刻彈出,需要郵箱的彈出字母鍵盤,需要手機號的彈出數字鍵盤
    • 當 兩個輸入框內容沒有都達標之前,action按鈕應該設置為disabled
    • 輸入內容的時候考慮小屏幕適配,自動滑動到合適位置
    • 在文本輸入框有內容之后,右側應該設置?按鈕,供用戶一鍵刪除
    • 賬號有沒有長度限制,類似電話格式的判斷在前端做比較方便,比如在密碼框 becomeFirstResponder 的時候,就直接判斷賬號格式,如果錯誤需提示用戶
    • 密碼輸入框需要設置明文暗文按鈕,以供用戶隨時校驗
    • 點擊登錄按鈕后彈出菊花(當然我指的是 UIActivityIndicatorView,不是那個肥皂那個菊花)或者動畫,防止多次發送網絡請求
    • 對于登錄注冊信息出錯,這個最好是能做到及時反饋,考慮下web端注冊賬戶的時候,昵稱是否已被占用能夠在用戶輸入就提示,如果每次興沖沖輸入一大堆消息后,滿懷期待的點擊注冊按鈕,結果提示“您的昵稱已被占用”,你對這個網站的好感是不是會降低那么一丟丟?因此最好能夠在保證用戶行為流暢的基礎上提示用戶,比如
      • 昵稱限制10位,那么輸入第11位的時候就應該是無效的
      • 最好統一登錄注冊界面:用戶輸入手機號、郵箱之后,實時查詢數據庫是否已注冊,然后更新按鈕狀態
    • 也需要考慮網絡超時、請求出錯、服務器宕機、短信未發送成功等異常信息
    • 對于一些金融類相關的app,為了防止服務器被攻擊(當然也),是不是要考慮同一IP請求兩次后添加驗證碼(倒計時一般是前端固定的代碼)
    • 如果登錄失敗,提示的信息一定要準確,比如是驗證碼錯誤,還是賬戶名密碼錯誤雖然這個提示信息一般都是服務器同學來做

    五、代碼設計:啥都別說了,都在代碼里

    1. 首先在全局的控制器管理類寫一個彈出 view 的方法
    /** 大多情況下默認的添加方式,直接添加到最頂層的控制器上* title:彈出登錄框的提示語,如登錄后方可進行評論* block:用戶被登錄框所阻攔的操作(注意循環引用)*/ - (void)transferControlToPortalViewWithTitle:(NSString *)title block:(void(^)())block; 復制代碼
    2. 然后在收藏等深度操作需要提示游客登錄的點擊事件里面判斷
    - (void)favoredBtnTapped:(UIButton *)sender {// 如果是游客賬戶,就提示用戶進行登錄操作,否則就進行正常的收藏按鈕點擊事件if ([self.systemAccountManager isGuest]) {[self.systemVCManager transferControlToPortalViewWithTitle:@"登錄后可進行收藏操作" block:^{[weakSelf doFavoredAction];}];} else {[self doFavoredAction];} } 復制代碼
    3. 比如要實現上文中提到的 今日頭條 樣式的登錄框,不能用 present 也不能用 modal,因為那樣的話上一級的控制器視圖就會被移到另外一個 Window 上,不能實現其在原界面添加半透明遮罩的效果,因此采用下列方式
    [fatherVC addChildViewController:portalVC]; [fatherVC.view addSubview:portalVC.view]; 復制代碼

    此處更正一下,感謝 CZAnchor 提出的方法,這里是可以通過 present 方式實現的,代碼如下:

    UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; UIViewController *baseVC = rootVC;while (baseVC.presentedViewController) {baseVC = baseVC.presentedViewController; }if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {portalVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;baseVC.definesPresentationContext = YES;[baseVC presentViewController:portalVC animated:NO completion:^{}]; } else {baseVC.modalPresentationStyle = UIModalPresentationCurrentContext;[baseVC presentViewController:portalVC animated:NO completion:^{}]; } 復制代碼
    4. 在調用登錄接口的成功回調里面,需要進行兩個操作
    4.1 首先進行數據遷移:
    • 已下載內容文件 的遷移,由于某些下載內容是需要相應權限的,因此都是每個賬號對應一個存儲路徑,也是在一定程度上防止賬號過分共享造成的利益損失
    /**?遷移已下載的文件?*/
    #warning?關于游客狀態下下載的內容,需要考慮兩部分:
    1.?登錄的正式用戶之前未在本機上登錄過,創建用戶的下載路徑后直接將游客的下載內容全部遷移過去(若只是登錄過沒有下載內容,就直接全部遷移過去);
    2.?登錄的正式有用戶之前在本機登錄過并有下載內容,則需要將兩個路徑下的下載內容合并
    -?(void)transferDownLoadedFile?{
    ????//?獲取下載文件根路徑
    ????NSArray?*paths?=?NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,?NSUserDomainMask,?YES);
    ????NSString?*libraryDir?=?[paths?objectAtIndex:0];
    ????NSString?*rootFilePath?=?[NSString?stringWithFormat:@"%@/%@",libraryDir,@"##?這里是項目中下載文件的路徑?##"];
    ????
    ????//?分別獲取游客和正式用戶的下載路徑(方便起見直接使用對應ID作為路徑名稱)
    ????NSString?*guestPath?=?[NSString?stringWithFormat:@"%@/%@",?rootFilePath,?self.accountManager.guestId];
    ????NSString?*userPath?=?[NSString?stringWithFormat:@"%@/%@",?rootFilePath,?self.accountManager.userId];

    ????//?獲取文件管理器
    ????NSFileManager?*manager?=?[NSFileManager?defaultManager];

    ????//?獲取游客的下載文件數組?
    ????NSError?*error?=?nil;
    ????NSArray?*guestFilesArr?=?[[NSFileManager?defaultManager]?contentsOfDirectoryAtPath:guestPath?error:&error];
    ????if?(error)?{
    ????????NSLog(@"contentsOfDirectoryAtPath?guestPath:%@",?error);
    ????}
    ????
    ????//?遍歷游客的文件
    ????for?(NSString?*fileName?in?guestFilesArr)?{
    ????????//??拼接處?該文件在?游客狀態?&&?正式用戶狀態?的存儲路徑
    ????????NSString?*guestFileDir?=?[guestPath?stringByAppendingPathComponent:fileName];
    ????????NSString?*userFileDir?=?[userPath?stringByAppendingPathComponent:fileName];
    ????????//?如果正式用戶?下載文件中不包含該文件,就創建一下
    ????????if?(![manager?fileExistsAtPath:userFileDir])?{
    ????????????[manager?createDirectoryAtPath:userFileDir?withIntermediateDirectories:YES?attributes:nil?error:&error];
    ????????}

    ????????BOOL?isDir;
    ????????if?([manager?fileExistsAtPath:guestFileDir?isDirectory:&isDir]?&&?isDir)?{
    ????????????error?=?nil;
    ????????????NSArray?*childFiles?=?[[NSFileManager?defaultManager]?contentsOfDirectoryAtPath:guestFileDir?error:&error];
    ????????????if?(error)?{
    ????????????????NSLog(@"contentsOfDirectoryAtPath?dir:%@",?error);
    ????????????}
    ????????????//?遍歷該文件夾內子文件,全部遷移到?正式用戶?名下的文件
    ????????????for?(NSString?*childFile?in?childFiles)?{
    ????????????????NSString?*filePath?=?[guestFileDir?stringByAppendingPathComponent:childFile];
    ????????????????NSString?*destPath?=?[userFileDir?stringByAppendingPathComponent:childFile];
    ????????????????error?=?nil;
    ????????????????[manager?moveItemAtPath:filePath?toPath:userFileDir?error:&error];
    ????????????????if?(error)?{
    ????????????????????DDLogError(@"moveItemAtPath?to?path?error:%@",?error);
    ????????????????????//如果正式用戶下該文件存在(即用戶之前在本機登錄并下載過該文件)會報錯,那么就將游客路徑下的改文件刪除
    ????????????????????[manager?removeItemAtPath:filePath?error:&error];
    ????????????????}
    ????????????}
    ????????}
    ????}
    }
    復制代碼
    • 遷移數據庫:這部分內容著實跟項目本分的業務、封裝關系太大,在這里以一個 video 文件的下載記錄為例,以 FMDB 為載體大概講一下思路
    // 1. 獲取游客的 db 文件路徑 guestDataBasePath // 2. 打開游客該 db 文件 fmDataQueue = [FMDatabaseQueue databaseQueueWithPath:path];[fmDataQueue inDatabase:^(FMDatabase *fmDatabase) {if ([fmDatabase open]) {[fmDatabase setShouldCacheStatements:YES];// 創建 SQL 語句NSString *sqlStr = [NSString stringWithFormat:@"%@%@%@%@%@%@%@",@"CREATE TABLE IF NOT EXISTS MYVIDEO (VIDEOID TEXT PRIMARY KEY ",@",videoname TEXT",@",info TEXT",@",coverfilename TEXT",@",urlpath TEXT")"];BOOL isExecute = [fmDatabase executeUpdate:createStatement];if (isExecute) {// 如有必要,可檢查一下表結構是否已升級,此處不再贅述} else {NSLog(@"error occured while creating MYVIDEO table");}} else {NSLog(@"open datebase failed");} }// 3. 查詢游客賬戶下已下載的 video // 創建空數組用于存放 video 對象 NSMutableArray *videoArray = [[NSMutableArray alloc] init]; [fmDataQueue inDatabase:^(FMDatabase *fmDatabase) {// 書寫 sql 語句NSString *query = [NSString stringWithFormat:@"SELECT videoid,videoname,info,coverfilename,urlpath, FROM MYVIDEO "];NSString *sqlQuery;if (wheresql != nil) {sqlQuery = [NSString stringWithFormat:@"%@%@", query, wheresql];} else {sqlQuery = query;}// 按時間降序排序sqlQuery = [sqlQuery stringByAppendingString:@" ORDER BY time DESC "];FMResultSet *resultSet = [fmDatabase executeQuery:sqlQuery];if ([fmDatabase hadError]) {NSLog(@"FMDB Error %d: %@", [fmDatabase lastErrorCode], [fmDatabase lastErrorMessage]);}// 取出查詢的結果集while ([resultSet next]) {VideoClass *video = [[VideoClass alloc] init];video.videoId = [resultSet stringForColumn:@"videoid"];video.videoTitle = [resultSet stringForColumn:@"songname"];video.videoDescription = [resultSet stringForColumn:@"info"];video.coverFileName = [resultSet stringForColumn:@"coverfilename"];video.path = [resultSet stringForColumn:@"urlpath"];[videoArray addObject:video];}[resultSet close]; }];// 4. 關閉游客 db [fmDataQueue inDatabase:^(FMDatabase* fmDatabase) {if ([fmDatabase close]) {NSLog(@"close MYVIDEO succes ....");}else {NSLog(@"close MYVIDEO error");} }]; [fmDataQueue close]; fmDataQueue = nil;// 5. 打開 正式用戶 下的 db 文件(獲取游客 db 路徑后,代碼同上打開 游客 db)// 6. 將 游客 下載的video 數據插入到 正式用戶的 db 中 [fmDataQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {[array enumerateObjectsUsingBlock:^(VideoClass *video, NSUInteger idx, BOOL * _Nonnull stop) {[self insertOrUpdateCourse:video withDB:db];// 創建插入數據的 sql 語句NSString *insertSql = @"INSERT OR REPLACE INTO MYVIDEO (videoid,videoname,info,coverfilename,urlpath,) VALUES(?,?,?,?,?)";BOOL result = [fmDatabase executeUpdate:insertSql,video.videoId,video.videoTitle,video.videoDescription,video.coverFileName,video.urlPath];if (!result) {NSLog(@"操蛋!插入 MYVIDEO data failed");} else {NSLog(@"牛逼!Insert MYVIDEO data success, U did it!");}}]; }];// 7. 合并數據庫成功后,根據游客 db 路徑,刪除 游客 db 文件 NSFileManager *fm = [NSFileManager defaultManager]; BOOL success = [fm removeItemAtPath:fullPath error:&error]; if (error) {NSLog(@"怎么會刪除失敗了,難道我姿勢不對?delete file at path error:%@", error); }復制代碼
    4.2 然后進行隱藏登錄界面,并調用一下之前傳進來的 block,繼續用戶之前的操作
    - (void)hidePortalView {if (self.loginSucessBlock) {self.loginSucessBlock();}UIView animateWithDuration:0.2 animations:^{self.portalVC.view.alpha = 0;} completion:^(BOOL finished) {[self.portalVC.view removeFromSuperview];[self.portalVC removeFromParentViewController];} } 復制代碼
    5. 進行橫豎屏適配
    • 由于帶有半透明背景的遮罩的視圖是以addChildViewController方式實現,因此自動適應父控制器的橫豎屏,這里主要講一下再次點擊其他登錄方式 進行賬號密碼輸入的傳統登錄注冊頁 的橫豎屏適配
    - (void)signInWithAccountBtnTapped:(UIButton *)sender {SignInController *signInVC = [[SignInController alloc] initWithType:InputViewLogin];// 設置控制器的 modal 方式為遵循當前控制器的環境,實現當前是橫(豎)屏就以橫(豎)屏方式modalsignInVC.modalPresentationStyle = UIModalPresentationCurrentContext;[self presentViewController:signInVC animated:YES completion:nil]; } 復制代碼
    • 當然,在 SignInController 內部也要進行一些 UI 層級適配,在其 viewWillAppear 方法內部實現以下方法
    // 根據狀態欄方向得到當前頁面橫豎屏信息 UIDeviceOrientation deviceOrientation = (UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation; // 根據橫豎屏狀態,做出相應的 UI 層級調整,并做出相應標記 if (deviceOrientation == UIDeviceOrientationPortrait ||deviceOrientation ==UIDeviceOrientationPortraitUpsideDown) {[self doPortraitUIAdjustment];self.isLandScape = NO; } else {[self doLandScapeUIAdjustment];self.isLandScape = YES; } 復制代碼
    • 然鵝,跑一下代碼發現,雖然橫豎屏的展示沒錯了,可是點擊輸入框后,鍵盤還是以豎屏的方式進行展現,因為我們只是把 SignInController 的 modal 方式和 UI 適配做了,此時控制器本身并不知道自己是橫屏還是豎屏,因此要重寫下面三個控制器方法
    // 在橫屏狀態下,應該可以隨設備重力感應進行 LandscapeRight 和 LandscapeLeft 兩個方向的自動翻轉 - (BOOL)shouldAutorotate {if (self.isLandScape) {return YES;} else {return NO;} }// 如果是橫屏狀態,應該支持 LandscapeRight 和 LandscapeLeft 兩個方向,豎屏狀態下只支持 Portrait - (UIInterfaceOrientationMask)supportedInterfaceOrientations {if (self.isLandScape) {return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;} else {return UIInterfaceOrientationMaskPortrait;} }// 默認的方向 -(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {if (self.isLandScape) {return UIInterfaceOrientationLandscapeRight;;} else {return UIInterfaceOrientationPortrait;} }#warning 至此,橫豎屏適配算是大功告成了 復制代碼

    大概的思路就是這些,由于跟項目相關性比較大,而且代碼實現方式也比較簡單,因此木有 demo,如果有其他問題歡迎在留言區進行交流

    總結

    以上是生活随笔為你收集整理的iOS程序员眼中的客户端免登陆(数据迁移已更新)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。