iOS數(shù)據(jù)庫(kù)離線緩存思路和網(wǎng)絡(luò)層封裝

一直想總結(jié)一下關(guān)于iOS的離線數(shù)據(jù)緩存的方面的問(wèn)題,然后最近也簡(jiǎn)單的對(duì)AFN進(jìn)行了再次封裝哈肖,所有想把這兩個(gè)結(jié)合起來(lái)寫一下受啥。數(shù)據(jù)展示型的頁(yè)面做離線緩存可以有更好的用戶體驗(yàn),用戶在離線環(huán)境下仍然可以獲取一些數(shù)據(jù)炼幔,這里的數(shù)據(jù)緩存首選肯定是SQLite秋茫,輕量級(jí),對(duì)數(shù)據(jù)的存儲(chǔ)讀取相對(duì)于其他幾種方式有優(yōu)勢(shì)乃秀,這里對(duì)AFN的封裝沒(méi)有涉及太多業(yè)務(wù)邏輯層面的需求肛著,主要還是對(duì)一些方法再次封裝方便使用,解除項(xiàng)目對(duì)第三方的耦合性跺讯,能夠簡(jiǎn)單的快速的更換底層使用的網(wǎng)絡(luò)請(qǐng)求代碼枢贿。這篇主要寫離線緩存思路,對(duì)AFN的封裝只做簡(jiǎn)單的介紹抬吟。

關(guān)于XLNetworkApi

XLNetworkApi的一些功能和說(shuō)明:

  • 使用XLNetworkRequest做一些GET萨咕、POST、PUT火本、DELETE請(qǐng)求危队,與業(yè)務(wù)邏輯對(duì)接部分直接以數(shù)組或者字典的形式返回。

  • 以及網(wǎng)絡(luò)下載钙畔、上傳文件茫陆,以block的形式返回實(shí)時(shí)的下載、上傳進(jìn)度擎析,上傳文件參數(shù)通過(guò)模型XLFileConfig去存取簿盅。

  • 通過(guò)繼承于XLDataService來(lái)將一些數(shù)據(jù)處理挥下,模型轉(zhuǎn)化封裝起來(lái),于業(yè)務(wù)邏輯對(duì)接返回的是對(duì)應(yīng)的模型桨醋,減少Controllor處理數(shù)據(jù)處理邏輯的壓力棚瘟。

  • 自定義一些回調(diào)的block

/**
 請(qǐng)求成功block
 */
typedef void (^requestSuccessBlock)(id responseObj);
/**
 請(qǐng)求失敗block
 */
typedef void (^requestFailureBlock) (NSError *error);
/**
 請(qǐng)求響應(yīng)block
 */
typedef void (^responseBlock)(id dataObj, NSError *error);
/**
 監(jiān)聽進(jìn)度響應(yīng)block
 */
typedef void (^progressBlock)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
  • XLNetworkRequest.m部分實(shí)現(xiàn)
#import "XLNetworkRequest.h"
#import "AFNetworking.h"
@implementation XLNetworkRequest
 + (void)getRequest:(NSString *)url params:(NSDictionary *)params success:(requestSuccessBlock)successHandler failure:(requestFailureBlock)failureHandler {

    AFHTTPRequestOperationManager *manager = [self getRequstManager];
    
    [manager GET:url parameters:params success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {
        successHandler(responseObject);
    } failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) {
        XLLog(@"------請(qǐng)求失敗-------%@",error);
        failureHandler(error);
    }];
}
  • 下載部分代碼
 //下載文件,監(jiān)聽下載進(jìn)度
 + (void)downloadRequest:(NSString *)url successAndProgress:(progressBlock)progressHandler complete:(responseBlock)completionHandler {
   
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:sessionConfiguration];
    
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    NSProgress *kProgress = nil;
    
    NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:&kProgress destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        
        NSURL *documentUrl = [[NSFileManager defaultManager] URLForDirectory :NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
        
        return [documentUrl URLByAppendingPathComponent:[response suggestedFilename]];
        
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nonnull filePath, NSError * _Nonnull error){
        if (error) {
            XLLog(@"------下載失敗-------%@",error);
        }
        completionHandler(response, error);
    }];
    
    [manager setDownloadTaskDidWriteDataBlock:^(NSURLSession * _Nonnull session, NSURLSessionDownloadTask * _Nonnull downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
        
        progressHandler(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
        
    }];
    [downloadTask resume];
}
  • 上傳部分代碼
 //上傳文件喜最,監(jiān)聽上傳進(jìn)度
  + (void)updateRequest:(NSString *)url params:(NSDictionary *)params fileConfig:(XLFileConfig *)fileConfig successAndProgress:(progressBlock)progressHandler complete:(responseBlock)completionHandler {

    NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:url parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
        
        [formData appendPartWithFileData:fileConfig.fileData name:fileConfig.name fileName:fileConfig.fileName mimeType:fileConfig.mimeType];
        
    } error:nil];
    
    //獲取上傳進(jìn)度
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    
    [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
        
        progressHandler(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
        
    }];
    
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {
        completionHandler(responseObject, nil);
    } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
        
        completionHandler(nil, error);
        if (error) {
            XLLog(@"------上傳失敗-------%@",error);
        }
    }];
    
    [operation start];
}

  • XLDataService.m部分實(shí)現(xiàn)
  + (void)getWithUrl:(NSString *)url param:(id)param modelClass:(Class)modelClass responseBlock:(responseBlock)responseDataBlock {
        [XLNetworkRequest getRequest:url params:param success:^(id responseObj) {
        //數(shù)組偎蘸、字典轉(zhuǎn)化為模型數(shù)組
           
        dataObj = [self modelTransformationWithResponseObj:responseObj modelClass:modelClass];
        responseDataBlock(dataObj, nil);
        
    } failure:^(NSError *error) {
        responseDataBlock(nil, error);
    }];
}
  • (關(guān)鍵)下面這個(gè)方法提供給繼承XLDataService的子類重寫,將轉(zhuǎn)化為模型的代碼寫在這里瞬内,相似業(yè)務(wù)的網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求都可以用這個(gè)子類去請(qǐng)求數(shù)據(jù)迷雪,直接返回對(duì)應(yīng)的模型數(shù)組。
/**
 數(shù)組虫蝶、字典轉(zhuǎn)化為模型
 */
  + (id)modelTransformationWithResponseObj:(id)responseObj modelClass:(Class)modelClass {
       return nil;
}
關(guān)于離線數(shù)據(jù)緩存

當(dāng)用戶進(jìn)入程序的展示頁(yè)面章咧,有三個(gè)情況下可能涉及到數(shù)據(jù)庫(kù)存取操作,簡(jiǎn)單畫了個(gè)圖來(lái)理解能真,思路比較簡(jiǎn)單赁严,主要是一些存取的細(xì)節(jié)處理。

  • 進(jìn)入展示頁(yè)面

    進(jìn)入頁(yè)面.png
  • 下拉刷新最新數(shù)據(jù)


    下拉刷新.png
  • 上拉加載更多數(shù)據(jù)


    上拉加載更多.png
  • 需要注意的是舟陆,上拉加載更多的時(shí)候误澳,每次從數(shù)據(jù)庫(kù)返回一定數(shù)量的數(shù)據(jù),而不是一次性將數(shù)據(jù)全部加載秦躯,否則會(huì)有內(nèi)存問(wèn)題,直到數(shù)據(jù)庫(kù)中沒(méi)有更多數(shù)據(jù)時(shí)再發(fā)生網(wǎng)絡(luò)請(qǐng)求裆装,再次將新數(shù)據(jù)存入數(shù)據(jù)庫(kù)踱承。這里存儲(chǔ)數(shù)據(jù)的方式是將服務(wù)器返回每組數(shù)據(jù)的字典歸檔成二進(jìn)制作為數(shù)據(jù)庫(kù)字段直接存儲(chǔ),這樣存儲(chǔ)在模型屬性比較多的情況下更有優(yōu)勢(shì)哨免,避免每一個(gè)屬性作為一個(gè)字段茎活,另外增加了一個(gè)idStr字段用來(lái)判斷數(shù)據(jù)的唯一性,避免重復(fù)存儲(chǔ)琢唾。
    首先定義一個(gè)工具類XLDataBase來(lái)做數(shù)據(jù)庫(kù)相關(guān)的操作载荔,這里用的是第三方的FMDB。

#import "XLDataBase.h"
#import "FMDatabase.h"
#import "Item.h"
#import "MJExtension.h"

@implementation XLDataBase

static FMDatabase *_db;

+ (void)initialize {
    
    NSString *path = [NSString stringWithFormat:@"%@/Library/Caches/Data.db",NSHomeDirectory()];
    _db = [FMDatabase databaseWithPath:path];
    [_db open];
    [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_item (id integer PRIMARY KEY, itemDict blob NOT NULL, idStr text NOT NULL)"];
}

//存入數(shù)據(jù)庫(kù)
+ (void)saveItemDict:(NSDictionary *)itemDict {
    //此處把字典歸檔成二進(jìn)制數(shù)據(jù)直接存入數(shù)據(jù)庫(kù)采桃,避免添加過(guò)多的數(shù)據(jù)庫(kù)字段
    NSData *dictData = [NSKeyedArchiver archivedDataWithRootObject:itemDict];
    
    [_db executeUpdateWithFormat:@"INSERT INTO t_item (itemDict, idStr) VALUES (%@, %@)",dictData, itemDict[@"id"]];
}

//返回全部數(shù)據(jù)
+ (NSArray *)list {

    FMResultSet *set = [_db executeQuery:@"SELECT * FROM t_item"];
    NSMutableArray *list = [NSMutableArray array];
    
    while (set.next) {
        // 獲得當(dāng)前所指向的數(shù)據(jù)
        
        NSData *dictData = [set objectForColumnName:@"itemDict"];
        NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:dictData];
        [list addObject:[Item mj_objectWithKeyValues:dict]];
    }
    return list;
}

//取出某個(gè)范圍內(nèi)的數(shù)據(jù)
+ (NSArray *)listWithRange:(NSRange)range {
    
    NSString *SQL = [NSString stringWithFormat:@"SELECT * FROM t_item LIMIT %lu, %lu",range.location, range.length];
    FMResultSet *set = [_db executeQuery:SQL];
    NSMutableArray *list = [NSMutableArray array];
    
    while (set.next) {
        NSData *dictData = [set objectForColumnName:@"itemDict"];
        NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:dictData];
        [list addObject:[Item mj_objectWithKeyValues:dict]];
    }
    return list;
}

//通過(guò)一組數(shù)據(jù)的唯一標(biāo)識(shí)判斷數(shù)據(jù)是否存在
+ (BOOL)isExistWithId:(NSString *)idStr
{
    BOOL isExist = NO;
    
    FMResultSet *resultSet= [_db executeQuery:@"SELECT * FROM t_item where idStr = ?",idStr];
    while ([resultSet next]) {
        if([resultSet stringForColumn:@"idStr"]) {
            isExist = YES;
        }else{
            isExist = NO;
        }
    }
    return isExist;
}
@end

  • 一些繼承于XLDataService的子類的數(shù)據(jù)庫(kù)存儲(chǔ)和模型轉(zhuǎn)換的邏輯代碼
#import "GetTableViewData.h"
#import "XLDataBase.h"

@implementation GetTableViewData

//重寫父類方法
+ (id)modelTransformationWithResponseObj:(id)responseObj modelClass:(Class)modelClass {
    NSArray *lists = responseObj[@"data"][@"list"];
    NSMutableArray *array = [NSMutableArray array];
    for (NSDictionary *dict in lists) {
        [modelClass mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
            return @{ @"ID" : @"id" };
        }];
        [array addObject:[modelClass mj_objectWithKeyValues:dict]];
        
        //通過(guò)idStr先判斷數(shù)據(jù)是否存儲(chǔ)過(guò)懒熙,如果沒(méi)有,網(wǎng)絡(luò)請(qǐng)求新數(shù)據(jù)存入數(shù)據(jù)庫(kù)
        if (![XLDataBase isExistWithId:dict[@"id"]]) {
            //存數(shù)據(jù)庫(kù)
            NSLog(@"存入數(shù)據(jù)庫(kù)");
            [XLDataBase saveItemDict:dict];
        }
    }
    return array;
}

  • 下面是一些控制器的代碼實(shí)現(xiàn):
#import "ViewController.h"
#import "GetTableViewData.h"
#import "Item.h"
#import "XLDataBase.h"
#import "ItemCell.h"
#import "MJRefresh.h"
#define URL_TABLEVIEW @"https://api.108tian.com/mobile/v3/EventList?cityId=1&step=10&theme=0&page=%lu"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
{
    NSMutableArray *_dataArray;
    UITableView *_tableView;
    NSInteger _currentPage;//當(dāng)前數(shù)據(jù)對(duì)應(yīng)的page
}
@end

@implementation ViewController
#pragma mark Life cycle
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self createTableView];
    _dataArray = [NSMutableArray array];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSRange range = NSMakeRange(0, 10);
    //如果數(shù)據(jù)庫(kù)有數(shù)據(jù)則讀取普办,不發(fā)送網(wǎng)絡(luò)請(qǐng)求
    if ([[XLDataBase listWithRange:range] count]) {
        [_dataArray addObjectsFromArray:[XLDataBase listWithRange:range]];
        NSLog(@"從數(shù)據(jù)庫(kù)加載");
    }else{
        [self getTableViewDataWithPage:0];
    }
}

#pragma mark UI
- (void)createTableView {
    _tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    _tableView.delegate = self;
    _tableView.dataSource = self;
    _tableView.rowHeight = 100.0;
    [self.view addSubview:_tableView];
    
    _tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        [self loadNewData];
    }];
    _tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
        [self loadMoreData];
    }];
}

#pragma mark GetDataSoure
- (void)getTableViewDataWithPage:(NSInteger)page {
    NSLog(@"發(fā)送網(wǎng)絡(luò)請(qǐng)求工扎!");
    NSString *url = [NSString stringWithFormat:URL_TABLEVIEW, page];
    [GetTableViewData getWithUrl:url param:nil modelClass:[Item class] responseBlock:^(id dataObj, NSError *error) {
        [_dataArray addObjectsFromArray:dataObj];
        [_tableView reloadData];
        [_tableView.mj_header endRefreshing];
        [_tableView.mj_footer endRefreshing];
    }];
}

- (void)loadNewData {
    NSLog(@"下拉刷新");
    _currentPage = 0;
    [_dataArray removeAllObjects];
    [self getTableViewDataWithPage:_currentPage];
}

- (void)loadMoreData {
    NSLog(@"上拉加載");
    _currentPage ++;
    NSRange range = NSMakeRange(_currentPage * 10, 10);
    if ([[XLDataBase listWithRange:range] count]) {
        [_dataArray addObjectsFromArray:[XLDataBase listWithRange:range]];
        [_tableView reloadData];
        [_tableView.mj_footer endRefreshing];
        NSLog(@"數(shù)據(jù)庫(kù)加載%lu條更多數(shù)據(jù)",[[XLDataBase listWithRange:range] count]);
    }else{
        //數(shù)據(jù)庫(kù)沒(méi)更多數(shù)據(jù)時(shí)再網(wǎng)絡(luò)請(qǐng)求
        [self getTableViewDataWithPage:_currentPage];
    }
}

#pragma mark UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ItemCell *cell = [ItemCell itemCellWithTableView:tableView];
    cell.item = _dataArray[indexPath.row];
    return cell;
}
@end

最后附上代碼的下載地址,重要的部分代碼中都有相應(yīng)的注釋和文字打印衔蹲,運(yùn)行程序可以很直觀的表現(xiàn)肢娘。

https://github.com/ShelinShelin/OffLineCache.git
有考慮不周的地方,希望大家能提出一些意見,很樂(lè)意與大家互相交流橱健。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末而钞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拘荡,更是在濱河造成了極大的恐慌臼节,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俱病,死亡現(xiàn)場(chǎng)離奇詭異官疲,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)亮隙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門途凫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人溢吻,你說(shuō)我怎么就攤上這事维费。” “怎么了促王?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵犀盟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蝇狼,道長(zhǎng)阅畴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任迅耘,我火速辦了婚禮贱枣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颤专。我一直安慰自己纽哥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布栖秕。 她就那樣靜靜地躺著春塌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪簇捍。 梳的紋絲不亂的頭發(fā)上只壳,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音垦写,去河邊找鬼吕世。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梯投,可吹牛的內(nèi)容都是我干的命辖。 我是一名探鬼主播况毅,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼尔艇!你這毒婦竟也來(lái)了尔许?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤终娃,失蹤者是張志新(化名)和其女友劉穎味廊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棠耕,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炊林,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年还绘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了峰鄙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殊者。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蕊退,靈堂內(nèi)的尸體忽然破棺而出郊楣,到底是詐尸還是另有隱情,我是刑警寧澤瓤荔,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布净蚤,位于F島的核電站,受9級(jí)特大地震影響输硝,放射性物質(zhì)發(fā)生泄漏今瀑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一点把、第九天 我趴在偏房一處隱蔽的房頂上張望放椰。 院中可真熱鬧,春花似錦愉粤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至压恒,卻和暖如春影暴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背探赫。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工型宙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伦吠。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓妆兑,卻偏偏與公主長(zhǎng)得像魂拦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搁嗓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 當(dāng)說(shuō)話人對(duì)自己的想法不能做出斷定時(shí)芯勘,可以使用「だろう」和「と思う」來(lái)表示推測(cè)。 だろう 「だろう」前面可以接名詞腺逛,...
    不帥任你踹閱讀 5,383評(píng)論 1 6
  • 拗口令問(wèn)題 身邊有幾個(gè)小毛孩 我念了一則拗口令 是這樣的: 洪小波和白小果 拿著笸籮拔蘿卜 洪小波拔的是白蘿卜 白...
    女兒灘閱讀 983評(píng)論 0 0
  • 夜游烏鎮(zhèn) 艄公搖櫓穿石橋荷愕, 微波驚擾小魚跳。 水閣秀窗燈靜照棍矛, 江南遠(yuǎn)比紫禁好安疗。 蘇杭水秀人更嬌, 歷代...
    日月武閱讀 253評(píng)論 0 3
  • 應(yīng)該是第一次這樣叫你吧,哈兒 看到你回去了慨绳,都不能安心的和你聊天了掉冶。 本來(lái)今天是想請(qǐng)彬彬吃飯的,但是彬彬出去打牌了...
    xiao錢錢閱讀 399評(píng)論 0 0