UIWebView和WKWebView的cookie管理機(jī)制

Cookie

  • 關(guān)于 cookie
    cookie 是服務(wù)端為了識別終端身份,保存在終端本地的用戶憑證信息。cookie 中的字段與意義由服務(wù)端進(jìn)行定義。例如,當(dāng)用戶在進(jìn)行了登錄操作后,服務(wù)端會將cookie 信息返回給終端,終端會將這些信息進(jìn)行保存,在下一次再次訪問服務(wù)端時,終端會將保存的cookie 信息一并發(fā)送到服務(wù)端,服務(wù)端根據(jù)cookie 信息是否有效來判斷此用戶是否可以進(jìn)行一些行為。

  • 在 iOS 中如何管理cookie
    iOS 中Cookie管理主要有兩個類 NSHTTPCookieNSHTTPCookieStorage,當(dāng)你訪問一個網(wǎng)站時,NSURLRequest都會幫你主動記錄下來你訪問的站點(diǎn)設(shè)置的Cookie,因為NSHTTPCookieStorage的默認(rèn)策略是:NSHTTPCookieAcceptPolicyAlways,所以如果 Cookie 存在的話,會把這些信息放在 NSHTTPCookieStorage 容器中共享,當(dāng)你下次再訪問這個站點(diǎn)時,NSURLRequest會拿著上次保存下來了的Cookie繼續(xù)去請求。

NSHTTPCookieStorage介紹
NSHTTPCookieStorage,管理著所有HTTP請求的Cookie信息.

繼承自:NSObject
遵守協(xié)議:NSObject
導(dǎo)入聲明:@import Foundation;
適用范圍:iOS 2.0 及以后

官方解釋:NSHTTPCookieStorage 是一個用來管理 cookie 存儲的單例。一個 NSHTTPCookie 單例代表一個 cookie。通常來講,cookie 可以在應(yīng)用間共享,并且在進(jìn)程之間保持同步。 對于單進(jìn)程,Session cookies (這里的 cookie 對象的 isSessionOnly 方法返回 YES)是局部的并且不能被共享。

在 iOS 中,應(yīng)用間是不能共享 cookie 的。
在 OS X 10.9 及以后和 iOS 7 及以后,NSHTTPCookieStorage 是線程安全的。

// 獲取單例對象
+ (NSHTTPCookieStorage *)sharedHTTPCookieStorage;

// 所有Cookie數(shù)據(jù)數(shù)組 其中存放NSHTTPCookie對象
@property (nullable , readonly, copy) NSArray<NSHTTPCookie *> *cookies;

// 手動設(shè)置一條Cookie數(shù)據(jù)
- (void)setCookie:(NSHTTPCookie *)cookie;

// 刪除某條Cookie信息
- (void)deleteCookie:(NSHTTPCookie *)cookie;

// 刪除某個時間后的所有Cookie信息 iOS8后可用
- (void)removeCookiesSinceDate:(NSDate *)date NS_AVAILABLE(10_10, 8_0);

// 獲取某個特定URL的所有Cookie數(shù)據(jù)
- (nullable NSArray<NSHTTPCookie *> *)cookiesForURL:(NSURL *)URL;

// 為某個特定的URL設(shè)置Cookie
- (void)setCookies:(NSArray<NSHTTPCookie *> *)cookies forURL:(nullable NSURL *)URL mainDocumentURL:(nullable NSURL *)mainDocumentURL;

// Cookie數(shù)據(jù)的接收協(xié)議
枚舉如下:
typedef NS_ENUM(NSUInteger, NSHTTPCookieAcceptPolicy) {
    NSHTTPCookieAcceptPolicyAlways,//接收所有Cookie信息
    NSHTTPCookieAcceptPolicyNever,//不接收所有Cookie信息
    NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain//只接收主文檔域的Cookie信息
};
@property NSHTTPCookieAcceptPolicy cookieAcceptPolicy;

系統(tǒng)下面的兩個通知與Cookie管理有關(guān):
據(jù)說,在Mac OS是cookie可以共享的(Session cookies 不能共享),在Mac OS app中更改cookie的接收策略會影響到其他正在運(yùn)行的在使用cookie storage的app.這時NSHTTPCookieStorage會發(fā)出兩個通知:
// Cookie數(shù)據(jù)的接收協(xié)議改變時發(fā)送的通知
FOUNDATION_EXPORT NSString * const NSHTTPCookieManagerAcceptPolicyChangedNotification;
// 管理的Cookie數(shù)據(jù)發(fā)生變化時發(fā)送的通知
FOUNDATION_EXPORT NSString * const NSHTTPCookieManagerCookiesChangedNotification;

NSHTTPCookie介紹
NSHTTPCookie是具體的HTTP請求Cookie數(shù)據(jù)對象.

// 下面兩個方法用于對象的創(chuàng)建和初始化 都是通過字典進(jìn)行鍵值設(shè)置
- (nullable instancetype)initWithProperties:(NSDictionary<NSString *, id> *)properties;
+ (nullable NSHTTPCookie *)cookieWithProperties:(NSDictionary<NSString *, id> *)properties;

// 返回Cookie數(shù)據(jù)中可用于添加HTTP頭字段的字典
+ (NSDictionary<NSString *, NSString *> *)requestHeaderFieldsWithCookies:(NSArray<NSHTTPCookie *> *)cookies;

// 從指定的響應(yīng)頭和URL地址中解析出Cookie數(shù)據(jù)
+ (NSArray<NSHTTPCookie *> *)cookiesWithResponseHeaderFields:(NSDictionary<NSString *, NSString *> *)headerFields forURL:(NSURL *)URL;

// Cookie數(shù)據(jù)中的屬性字典
@property (nullable, readonly, copy) NSDictionary<NSString *, id> *properties;

// 請求響應(yīng)的版本
@property (readonly) NSUInteger version;

// 請求相應(yīng)的名稱
@property (readonly, copy) NSString *name;

// 請求相應(yīng)的值
@property (readonly, copy) NSString *value;

// 過期時間
@property (nullable, readonly, copy) NSDate *expiresDate;

// 請求的域名
@property (readonly, copy) NSString *domain;

//請求的路徑
@property (readonly, copy) NSString *path;

// 是否是安全傳輸
@property (readonly, getter=isSecure) BOOL secure;

// 是否只發(fā)送HTTP的服務(wù)
@property (readonly, getter=isHTTPOnly) BOOL HTTPOnly;

// 響應(yīng)的文檔
@property (nullable, readonly, copy) NSString *comment;

// 相應(yīng)的文檔URL
@property (nullable, readonly, copy) NSURL *commentURL;

// 服務(wù)端口列表
@property (nullable, readonly, copy) NSArray<NSNumber *> *portList;

HTTP cookie的屬性鍵

屬性 解讀
NSHTTPCookieName Cookie的名字
NSHTTPCookieValue Cookie的值
NSHTTPCookieOriginURL 和域名一樣,NSHTTPCookieDomain或NSHTTPCookieOriginURL必須指定一個值
NSHTTPCookieVersion 接收器的版本
NSHTTPCookieDomain 域名
NSHTTPCookiePath Cookie 存放路徑
NSHTTPCookieSecure Cookie是否只應(yīng)通過安全通道發(fā)送,設(shè)置Cookie的secure屬性為true。只會在HTTPS和SSL等安全協(xié)議中傳輸此類Cookie。默認(rèn)為false
NSHTTPCookieComment 包含Cookie的評論,只有有效的版本1的cookies或更高版本。 這頭字段是可選的
NSHTTPCookieCommentURL 接收器的評論URL
NSHTTPCookieDiscard Cookie是否應(yīng)在會議結(jié)束時丟棄NSString,字符串值必須是“true”或“假”。 這個字段是可選的。 默認(rèn)為“假”,除非這是Cookie是第1版或以上,NSHTTPCookieMaximumAge未指定,在這種情況下,它被假定為“TRUE”
NSHTTPCookieMaximumAge NSString對象,包含一個整數(shù),在Cookie內(nèi)保持最多幾秒 。僅適用于第1版和更高版本的有效。 默認(rèn)為“0”。 此字段是可選的
NSHTTPCookiePort 接收機(jī)的端口

清除Cookie步驟

// 清除所有的cookie 方法:
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];  
    if (url) {  
        NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url];  
        for (int i = 0; i < [cookies count]; i++) {  
            NSHTTPCookie *cookie = (NSHTTPCookie *)[cookies objectAtIndex:i];  
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];  
        }  
  }  

// 清除某一個特定的cookie方法:
NSArray * cookArray = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:self.loadURL]]; 
NSString * successCode = @""; 
for (NSHTTPCookie*cookie in cookArray) { 
  if ([cookie.name isEqualToString:@"cookiename"]) { 
      [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; 
  } 
} 

// 清除某一個url緩存的方法:
[[NSURLCache sharedURLCache] removeCachedResponseForRequest:[NSURLRequest requestWithURL:url]]; 

// 清除所有緩存方法:        
[[NSURLCache sharedURLCache] removeAllCachedResponses]; 
  • 在iOS中如何使用cookie

(1) 可以在 App 啟動時或者在webview的viewDidLoad中判斷用戶是否登錄,本地是否記錄了登錄后的cookie,并且cookie是否失效,如果是登錄狀態(tài),本地?zé)ocookie,可以手動自定義cookie,并存入NSHTTPCookieStorage中,如果本地有cookie記錄,判斷cookie是否失效,設(shè)置有效的cookie數(shù)據(jù)。
(2) 在UIWebView或者WKWebView的加載前的代理方法中判斷是否已經(jīng)登錄,如果沒有登錄,則直接跳轉(zhuǎn)到app的登錄頁面。在登錄成功之后發(fā)送通知,在webview中進(jìn)行頁面刷新。
(3) 在登錄完成時存 cookie 。
(4) 在退出時清除 cookie 。

// 可以在登錄完成時,或者已登錄但本地沒有記錄cookie時設(shè)置
// 如果服務(wù)器沒有定義cookie,則可以本地自定義
// 自定義cookie:手動設(shè)置的Cookie不會自動持久化到沙盒,需要利用NSUserDefaults進(jìn)行存儲
    NSMutableDictionary*cookieProperties = [NSMutableDictionary dictionary];
    [cookieProperties setObject:@"token"  forKey:NSHTTPCookieName]; //給cookie取名
    NSString * token =[[NSUserDefaults standardUserDefaults] objectForKey:@"token"];
    [cookieProperties setObject:token forKey:NSHTTPCookieValue];
// 存放目錄
    [cookieProperties setObject:@"/" forKey:NSHTTPCookiePath]; 
    NSString * domain = [NSURL URLWithString:@"http://www.baidu.com/"].host;
    [cookieProperties setObject:domain forKey:NSHTTPCookieDomain]; //設(shè)置域名
// 將可變字典轉(zhuǎn)化為cookie
    NSHTTPCookie * httpCookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
// 將cookie存入倉庫
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:httpCookie]; 
    NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: httpCookie];
    [[NSUserDefaults standardUserDefaults]  setObject:cookiesData forKey:@"cookie"];
    [[NSUserDefaults standardUserDefaults]  synchronize];
//生成的cookie:version: 0  name:token   value : token值  expiresDate:cookie有效截止日期  created :創(chuàng)建日期  sessionOnly: 返回接收器是否應(yīng)該在會話結(jié)束時被丟棄 domain :域名  partition: 端口  path:路徑  isSecure:返回他的cookie不只應(yīng)通過安全通道發(fā)送
<NSHTTPCookie version:0 name:"token" value:"b2a8f8523ab41f8b4b9b2a79ff47c3f1" expiresDate:(null) created:2016-11-09 03:02:41 +0000 sessionOnly:TRUE domain:"api.test.com" partition:"none" path:"/" isSecure:FALSE>
  • cookie的持久化存儲
    對于獲取服務(wù)器直接返回的cookie,或者手動定義的cookie

如果設(shè)置了Cookie失效時間expiresDate ,sessionOnly:FALSE,會被持久化到文件中,kill掉后系統(tǒng)自動保存,下次啟動app會自動加載沙盒的 /Library/Cookies中的Cookies

如果沒有設(shè)置Cookie失效時間expiresDate:(null),sessionOnly:TURE,kill掉后系統(tǒng)不會自動保存Cookie,如果想持久化Cookie 模仿瀏覽器存住Cookie,可以使用NSUserDefaults進(jìn)行本地存儲

UIWebView的cookie機(jī)制

UIWebView會將NSHttpRequest的所有請求產(chǎn)生的cookie自動保存到NSHTTPCookieStorage容器中,并且在同一個app內(nèi)多個UIWebView之間共享,不需要我們做任何操作,在后續(xù)訪問中會將 cookie 自動帶到 request 請求當(dāng)中。
UIWebView 在瀏覽網(wǎng)頁后會將網(wǎng)頁中的 cookie 自動存入 NSHTTPCookieStorage 標(biāo)準(zhǔn)容器中。在后續(xù)訪問中會將 cookie 自動帶到 request 請求當(dāng)中。比如,NSHTTPCookieStorage 中存儲了一個Cookie,name=a;value=b;domain=y.qq.com;expires=Sat,02 May 2017 23:20:25 GMT; 則通 過 UIWebView 發(fā)起請求 http://y.qq.com,則請求頭會自動帶上cookie,而通過 WKWebView 發(fā)起請求,請求頭不會自動帶上該cookie。

WKWebView的cookie機(jī)制

業(yè)界普遍認(rèn)為 WKWebView 擁有自己的私有存儲,不會將 Cookie 存入到標(biāo)準(zhǔn)的 Cookie 容器 NSHTTPCookieStorage 中。

實踐發(fā)現(xiàn) WKWebView 實例其實也會將 Cookie 存儲于 NSHTTPCookieStorage 中,但存儲時機(jī)有延遲,在iOS 8上,當(dāng)頁面跳轉(zhuǎn)的時候,當(dāng)前頁面的 Cookie 會寫入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 執(zhí)行 document.cookie 或服務(wù)器 set-cookie 注入的 Cookie 會很快同步到 NSHTTPCookieStorage 中,F(xiàn)ireFox 工程師曾建議通過 reset WKProcessPool 來觸發(fā) Cookie 同步到 NSHTTPCookieStorage 中,實踐發(fā)現(xiàn)不起作用,并可能會引發(fā)當(dāng)前頁面 session cookie 丟失等問題。

WKWebView Cookie 問題在于 WKWebView 發(fā)起的請求不會自動帶上存儲于 NSHTTPCookieStorage 容器中的 Cookie。
比如,NSHTTPCookieStorage 中存儲了一個 Cookie:

 name=Nicholas;value=test;domain=y.qq.com;expires=Sat, 02 May 2019 23:38:25 GMT;

通過 UIWebView 發(fā)起請求http://y.qq.com/, 則請求頭會自動帶上 cookie: Nicholas=test;
而通過 WKWebView發(fā)起請求http://y.qq.com/, 請求頭不會自動帶上 cookie: Nicholas=test。

WKWebView中注入Cookie
如果你在Native層面做了登陸操作,獲取了Cookie信息,也使用 NSHTTPCookieStorage 存到了本地,但是使用 WKWebView 打開對應(yīng)網(wǎng)頁時,網(wǎng)頁依然處于未登陸狀態(tài)。如果是登陸也在 WebView 里做的,就不會有這個問題。

iOS11
iOS11 的 API 可以解決該問題,只要是存在 WKHTTPCookieStore 里的 cookie,WKWebView 每次請求都會攜帶,存在 NSHTTPCookieStorage 的cookie,并不會每次都攜帶。于是會發(fā)生首次 WKWebView 請求不攜帶 Cookie 的問題。

解決方法:

在執(zhí)行 -[WKWebView loadReques:] 前將 NSHTTPCookieStorage 中的內(nèi)容復(fù)制到 WKHTTPCookieStore 中,以此來達(dá)到 WKWebView Cookie 注入的目的。示例代碼如下:

[self copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:^{
            NSURL *url = [NSURL URLWithString:@"https://www.v2ex.com"];
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
            [_webView loadRequest:request];
        }];
- (void)copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:(nullable void (^)())theCompletionHandler; {
    NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
    WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
    if (cookies.count == 0) {
        !theCompletionHandler ?: theCompletionHandler();
        return;
    }
    for (NSHTTPCookie *cookie in cookies) {
        [cookieStroe setCookie:cookie completionHandler:^{
            if ([[cookies lastObject] isEqual:cookie]) {
                !theCompletionHandler ?: theCompletionHandler();
                return;
            }
        }];
    }
}

iOS11之前
通過讓所有 WKWebView 共享同一個 WKProcessPool 實例,可以實現(xiàn)多個 WKWebView 之間共享 Cookie(session Cookie and persistent Cookie)數(shù)據(jù)。不過 WKWebView WKProcessPool 實例在 app 殺進(jìn)程重啟后會被重置,導(dǎo)致 WKProcessPool 中的 Cookie、session Cookie 數(shù)據(jù)丟失,目前也無法實現(xiàn) WKProcessPool 實例本地化保存??梢圆扇?cookie 放入 Header 的方法來做。

WKWebView * webView = [WKWebView new]; 
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx.com/login"]]; 
NSString *cookieStr = [NSString stringWithFormat:@"%@=%@",cookie.name,cookie.value];
 [request addValue:cookieStr forHTTPHeaderField:@"Cookie"]; 
 [webView loadRequest:request];

cookieStr的獲取可以從本地存儲的自定義cookie獲取,也可以通過以下方法獲取:

HTTPDNSCookieManager.h
#ifndef HTTPDNSCookieManager_h
#define HTTPDNSCookieManager_h
// URL匹配Cookie規(guī)則
typedef BOOL (^HTTPDNSCookieFilter)(NSHTTPCookie *, NSURL *);
@interface HTTPDNSCookieManager : NSObject
+ (instancetype)sharedInstance;
/**
 指定URL匹配Cookie策略
 @param filter 匹配器
 */
- (void)setCookieFilter:(HTTPDNSCookieFilter)filter;
/**
 處理HTTP Reponse攜帶的Cookie并存儲
 @param headerFields HTTP Header Fields
 @param URL 根據(jù)匹配策略獲取查找URL關(guān)聯(lián)的Cookie
 @return 返回添加到存儲的Cookie
 */
- (NSArray<NSHTTPCookie *> *)handleHeaderFields:(NSDictionary *)headerFields forURL:(NSURL *)URL;
/**
 匹配本地Cookie存儲,獲取對應(yīng)URL的request cookie字符串
 @param URL 根據(jù)匹配策略指定查找URL關(guān)聯(lián)的Cookie
 @return 返回對應(yīng)URL的request Cookie字符串
 */
- (NSString *)getRequestCookieHeaderForURL:(NSURL *)URL;
/**
 刪除存儲cookie
 @param URL 根據(jù)匹配策略查找URL關(guān)聯(lián)的cookie
 @return 返回成功刪除cookie數(shù)
 */
- (NSInteger)deleteCookieForURL:(NSURL *)URL;
@end
#endif /* HTTPDNSCookieManager_h */
HTTPDNSCookieManager.m
#import <Foundation/Foundation.h>
#import "HTTPDNSCookieManager.h"
@implementation HTTPDNSCookieManager
{
    HTTPDNSCookieFilter cookieFilter;
}
- (instancetype)init {
    if (self = [super init]) {
        /**
            此處設(shè)置的Cookie和URL匹配策略比較簡單,檢查URL.host是否包含Cookie的domain字段
            通過調(diào)用setCookieFilter接口設(shè)定Cookie匹配策略,
            比如可以設(shè)定Cookie的domain字段和URL.host的后綴匹配 | URL是否符合Cookie的path設(shè)定
            細(xì)節(jié)匹配規(guī)則可參考RFC 2965 3.3節(jié)
         */
        cookieFilter = ^BOOL(NSHTTPCookie *cookie, NSURL *URL) {
            if ([URL.host containsString:cookie.domain]) {
                return YES;
            }
            return NO;
        };
    }
    return self;
}
+ (instancetype)sharedInstance {
    static id singletonInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!singletonInstance) {
            singletonInstance = [[super allocWithZone:NULL] init];
        }
    });
    return singletonInstance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}
- (id)copyWithZone:(struct _NSZone *)zone {
    return self;
}
- (void)setCookieFilter:(HTTPDNSCookieFilter)filter {
    if (filter != nil) {
        cookieFilter = filter;
    }
}
- (NSArray<NSHTTPCookie *> *)handleHeaderFields:(NSDictionary *)headerFields forURL:(NSURL *)URL {
    NSArray *cookieArray = [NSHTTPCookie cookiesWithResponseHeaderFields:headerFields forURL:URL];
    if (cookieArray != nil) {
        NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (NSHTTPCookie *cookie in cookieArray) {
            if (cookieFilter(cookie, URL)) {
                NSLog(@"Add a cookie: %@", cookie);
                [cookieStorage setCookie:cookie];
            }
        }
    }
    return cookieArray;
}
- (NSString *)getRequestCookieHeaderForURL:(NSURL *)URL {
    NSArray *cookieArray = [self searchAppropriateCookies:URL];
    if (cookieArray != nil && cookieArray.count > 0) {
        NSDictionary *cookieDic = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieArray];
        if ([cookieDic objectForKey:@"Cookie"]) {
            return cookieDic[@"Cookie"];
        }
    }
    return nil;
}
- (NSArray *)searchAppropriateCookies:(NSURL *)URL {
    NSMutableArray *cookieArray = [NSMutableArray array];
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
        if (cookieFilter(cookie, URL)) {
            NSLog(@"Search an appropriate cookie: %@", cookie);
            [cookieArray addObject:cookie];
        }
    }
    return cookieArray;
}
- (NSInteger)deleteCookieForURL:(NSURL *)URL {
    int delCount = 0;
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
        if (cookieFilter(cookie, URL)) {
            NSLog(@"Delete a cookie: %@", cookie);
            [cookieStorage deleteCookie:cookie];
            delCount++;
        }
    }
    return delCount;
}
@end

使用方法示例:

WKWebView * webView = [WKWebView new]; 
 NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx.com/login"]]; 
NSString *value = [[HTTPDNSCookieManager sharedInstance] getRequestCookieHeaderForURL:url];
[request setValue:value forHTTPHeaderField:@"Cookie"];
 [webView loadRequest:request];

文中有錯誤的地方,還望大家包涵并指正,謝謝.

參考鏈接
iOS 關(guān)于token、cookie的那些事
詳解iOS App開發(fā)中Cookie的管理方法
iOS WKWebView 與 UIWebView Cookie機(jī)制的同步
iOS WebView 中的 Cookie 處理業(yè)務(wù)場景“IP直連”方案說明

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容