swift實現一個與智能機器人聊天的app(一)

截圖

你在本系列文章中將會學到

  • 如何安裝和使用cocoapods來集成第三方庫
  • 如何搭建一個類似于iOS短信app的界面,以及使用SnapKit來用代碼設置autolayout
  • 如何使用Parse云服務平臺存儲和同步聊天信息,學習相應地數據庫知識
  • 如何使用Parse的遠程推送功能
  • 如何使用Alamofire實現與智能機器人聊天功能
    初始項目下載地址:
    百度網盤下載地址

配置初始項目

1.cocoapods的安裝
cocoapods的安裝是通過ruby,幸運的是Mac電腦都是默認安裝ruby的,所以安裝ruby的過程就省去了,唯一的前提就是安裝Xcode的CommandLineTools。
commandLineTools的安裝也很簡單,只要在終端輸入以下命令:

$ xcode-select --install

如果確實沒有安裝commandLineTools會提示你要安裝它,點安裝就可以開始下載,然后等待下載完成后安裝即可
下面開始安裝cocoapods,本來只需要簡單地在終端輸入以下命令即可:

$ sudo gem install cocoapods

但是由于中國的互聯網是"自由的"。??瓤?,所以呢,你要改變gem的默認下載源:

$ gem sources -a https://ruby.taobao.org

看到以下結果

http://ruby.taobao.org added to sources

將淘寶的ruby源加入進來,看來淘寶也不光是賣東西哈,也是對開發者做了一些貢獻的~
刪除原來的下載源:

$ gem sources -r https://rubygems.org/

看到以下結果就說明已經成功

https://rubygems.org/ removed from sources

然后呢,就可以愉快地安裝上cocoapods了!

$ sudo gem install cocoapods
Password:(你的管理員密碼,這里不會顯示出來)
Fetching: cocoapods-core-0.38.2.gem (100%)
Successfully installed cocoapods-core-0.38.2
Fetching: claide-0.9.1.gem (100%)
Successfully installed claide-0.9.1
Fetching: xcodeproj-0.26.3.gem (100%)
Successfully installed xcodeproj-0.26.3
Fetching: cocoapods-downloader-0.9.3.gem (100%)
Successfully installed cocoapods-downloader-0.9.3
Fetching: cocoapods-stats-0.5.3.gem (100%)
Successfully installed cocoapods-stats-0.5.3
Fetching: cocoapods-try-0.4.5.gem (100%)
Successfully installed cocoapods-try-0.4.5
Fetching: cocoapods-trunk-0.6.4.gem (100%)
Successfully installed cocoapods-trunk-0.6.4
Fetching: molinillo-0.3.1.gem (100%)
Successfully installed molinillo-0.3.1
Fetching: cocoapods-0.38.2.gem (100%)
Successfully installed cocoapods-0.38.2
Parsing documentation for cocoapods-core-0.38.2
Installing ri documentation for cocoapods-core-0.38.2
Parsing documentation for claide-0.9.1
Installing ri documentation for claide-0.9.1
Parsing documentation for xcodeproj-0.26.3
Installing ri documentation for xcodeproj-0.26.3
Parsing documentation for cocoapods-downloader-0.9.3
Installing ri documentation for cocoapods-downloader-0.9.3
Parsing documentation for cocoapods-stats-0.5.3
Installing ri documentation for cocoapods-stats-0.5.3
Parsing documentation for cocoapods-try-0.4.5
Installing ri documentation for cocoapods-try-0.4.5
Parsing documentation for cocoapods-trunk-0.6.4
Installing ri documentation for cocoapods-trunk-0.6.4
Parsing documentation for molinillo-0.3.1
Installing ri documentation for molinillo-0.3.1
Parsing documentation for cocoapods-0.38.2
Installing ri documentation for cocoapods-0.38.2
9 gems installed

OK,cocoapods順利安裝完畢!
2.cocoapods的使用
那么cocoapods怎么用呢,當然第一次使用會覺得它非常麻煩,但是呢漸漸地你會發現這是一個非常好用的工具,可以說是iOS開發者必備!
首先創建我們的Xcode工程:File/New/Project.../Single View Application
起名叫圖靈聊天。

我們將要使用圖靈機器人的api進行開發:
圖靈機器人官網

打開項目,新建一個空文件:File/New/File.../ iOS/Others/Empty
起名叫Podfile,這一點非常重要,因為這是cocoapods的配置文件,也就是指定你要使用哪些第三方庫!
我們要使用以下幾個庫:

  • Alamofire,網絡請求庫,用來調用圖靈機器人的api
  • SnapKit,用代碼進行autolayout設置
  • Parse ,Parse云服務平臺的SDK
  • ParseUI,Parse提供的便捷UI組件
    在Podfile中,輸入以下代碼:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.4'
use_frameworks!

指定下載源,指定平臺版本,使用framework進行集成

由于swift的特殊性,某些第三方庫必須使用framework來集成,但是這樣也有一個好處,我也是最近才發現,就是Parse和ParseUI其實是OC編寫的庫,但是呢卻不需要OC-Swift的橋接文件了!可以直接當做swift庫來使用!

pod 'Alamofire', '~> 1.3'
pod 'SnapKit', '~> 0.12.0'
pod 'Parse','~>1.7.1'
pod 'ParseUI','~>1.1.3'

選擇指定的第三方庫及其版本

開始安裝第三方庫,打開終端,將當前目錄轉到Podfile所在目錄:

$ cd <Podfile所在目錄>

輸入以下命令開始配置第三方庫:

$ pod install
Analyzing dependencies
Downloading dependencies
Using Alamofire (1.3.1)
Using Bolts (1.2.1)
Using Parse (1.7.5.3)
Using ParseUI (1.1.4)
Using SnapKit (0.12.0)
Generating Pods project
Integrating client project
Sending stats

然后等待幾分鐘,如果一切正常,沒有出現錯誤的話,打開項目文件后你會看到workspace的文件,以后都要使用這個文件來打開項目。


圖1

打開項目,看一下項目的結構:

項目結構.png

點一下Pods項目,你會發現所需的framework已經編譯好了,只要在使用前import他們就可以了:


Pods

OK,到此我們的項目就配置好了,在我們開始搭建UI之前,先了解一下Parse的使用和一些必要配置

配置Parse

首先打開Parse的官網:
點我
注冊一個新的用戶,點擊右上角的sign up :

Parse注冊.png

以上用戶名只是示例,但是app名稱輸入TuringChat。
注冊完畢后,用你剛才注冊的用戶名登陸,應該會出現以下界面:

主界面

然后導入我們的示例數據:
點我下載
點擊import按鈕:

導入數據

選擇剛才下載的文件:


數據導入成功.png

然后剛才導入的數據就會顯示出來,并自動新建了一個數據庫類:Messages


數據.png

我們來看一眼Messages類里都有什么:
名稱 類型 含義 備注
objectId String 系統默認鍵 每一條數據都對應一個獨一無二的id
incoming Boolean 用來確定該條信息是發送給我們的還是發送出去的 true就是發送來的反之就是我們發送出去的
sentDate Date 消息發送時間
text String 消息的內容
createdAt Date 系統默認鍵 數據創建時間
updatedAt Date 系統默認鍵 數據上一次更新的時間
ACL ACL 系統默認鍵 數據的讀寫模式

接下來我們來測試一下能否讀取到這些數據,首先要獲得該app的application ID和Client Key:

獲取Key

紅線劃掉的那兩行就是我們需要的。
然后打開項目中的AppDelegate.swift,增加對Parse庫的引用:

import Parse

找到以下方法

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool 

在里面添加代碼:

    Parse.setApplicationId("CYdFL9mvG8jHqc4ZA5PJsWMInBbMMun0XCoqnHgf", clientKey: "6tGOC1uIKeYp5glvJE6MXZOWG9pmLtMuIUdh2Yzo")

連接Parse的服務器

        var query = PFQuery(className: "Messages")
        query.orderByAscending("sentDate")
        query.findObjectsInBackgroundWithBlock { (objects,error) -> Void in
            for object in objects as! [PFObject]{
            let incoming:Bool = object["incoming"] as! Bool
            let text:String = object["text"] as! String
            let sentDate:NSDate = object["sentDate"] as! NSDate
            println("\\(object.objectId!)\\n\\(incoming)\\n\\(text)\\n\\(sentDate)")
        }
     }

新建查詢,查詢我們剛才所建的Messages類,用findObjectsInBackgroundWithBlock方法取出查詢結果,并用一個循環全部打印出來。
cmd+R運行一下,如果沒有問題會輸出類似下面的內容:

oYtildSAOz
false
你叫什么名字?
2015-08-28 06:42:00 +0000
LX7kxmmiEp
true
我叫靈靈,聰明又可愛的靈靈
2015-08-28 06:43:00 +0000
p62dmgGIAS
false
你愛不愛我?
2015-08-28 06:43:00 +0000
oWReOM43Nf
true
愛你么么噠
2015-08-28 06:44:00 +0000
mtl2BGt3Mu
false
今天北京天氣如何?
2015-08-29 03:59:00 +0000
DikAu5P2Nn
true
北京:08/29 周六,20-29° 28° 雷陣雨 微風小于3級;08/30 周日,19-27° 雷陣雨 微風小于3級;08/31 周一,19-27° 雷陣雨 微風小于3級;09/01 周二,20-26° 雷陣雨 微風小于3級;
2015-08-29 03:59:01 +0000

很好,我們的數據庫連接沒有問題,那么下面開始搭建我們的UI。

搭建UI

我們需要搭建的UI只是聊天頁面,我們首先來看一看聊天頁面的結構:
界面主要由以下三個部分組成

UI結構

那么這三部分怎樣去實現呢,我先向大家做一些簡單的介紹:
1.導航欄
這一部分實現比較簡單,只要把視圖控制器嵌套在一個導航控制器(UINavigationController)中即可,然后對其外觀進行一些定制化操作。
2.聊天窗口
這一部分用UITableView來構建。仔細觀察你會發現這里一共有三種UITableViewCell:

  • 用來顯示消息發送日期的cell
  • 發送消息氣泡的cell
  • 接收消息氣泡的cell
    但其實我們只需要兩個,因為后兩種cell區別只是是顏色和位置,我們只要判斷一下該消息是發送的還是接收的,然后相應進行處理即可!
    兩種cell都是用的以下這個素材:
    MessageBubble.png

    但是,你會問,它為啥是黑色的!怎么讓他變成圖中的兩種顏色呢?還有明明聊天氣泡的大小是不定的,這樣一張圖怎么能滿足所有尺寸呢?
    有疑問很好,因為它可以成為你學習的動力,我們會在接下來向大家解釋這是如何實現的!Be patient!
    3.輸入框
    這里我們要通過重寫UIResponder類的inputAccessoryView屬性來自定義我們的輸入框,這樣做的好處是我們的輸入框會和系統的鍵盤結合起來,可以讓其成為第一響應者(first responder),一旦它成為第一響應者,我們自定義的輸入框會跟隨鍵盤一同彈出和收回,就像真正的短信app那樣,這個方法比我有一篇文章所寫的實現類似微信的輸入框跟隨鍵盤彈出的效果的方法還要更好一些,所以說方法不是絕對的,因為你總是能夠找到更好的方法,所以,編程的時候要經常在腦子里想"嗯,一定還有更好的方法"。

嗯好嘞,廢話不多說,下面我們就來一步一步地一一實現它們!

首先從最簡單的做起,實現自定義導航欄:
打開初始項目你會看到模板文件已經全部建好:
找到AppDelegate.swift文件中的以下方法:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool

在其中添加如下代碼:

        var ChatVC:ChatViewController = ChatViewController()
        ChatVC.title = "靈靈"
        
        UINavigationBar.appearance().tintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0)
        UINavigationBar.appearance().barTintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0)
        UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
        UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent
        var navigationVC:UINavigationController = UINavigationController(rootViewController: ChatVC)
        
        let frame = UIScreen.mainScreen().bounds
        window = UIWindow(frame: frame)
        window!.rootViewController = navigationVC
        window!.makeKeyAndVisible()

設置app啟動時顯示我們自定義的視圖控制器,并設置一下導航欄的外觀。
ok,第一部分完成。
接下來我們來實現一下第三部分:輸入框,我們要把最難的第二部分留在最后( ⊙ o ⊙ )
打開ChatViewController.swift文件:
添加一些全局常量,在import下面class的定義之上:

let messageFontSize: CGFloat = 17
let toolBarMinHeight: CGFloat = 44

第一個是消息所用的字體大小,第二個是我們輸入框的高度。
添加一些組成輸入框的組件:

    var toolBar: UIToolbar!
    var textView: UITextView!
    var sendButton: UIButton!

toolBar用來承載輸入框中的組件,之所以用UIToolbar是因為它默認出現在屏幕最下方,就像你的短信輸入框那樣。
textView是我們輸入文字的地方,而sendButton則是我們的發送按鈕。
下面實現我們重寫的inputAccessoryView,在這之前先讓我們的視圖控制器遵循UITextViewDelegate協議:

class ViewController: UIViewController,UITextViewDelegate {
....
....
}

下面添加以下代碼來聲明對inputAccessoryView的重寫:

 override var inputAccessoryView: UIView! {

}

用get的方式將輸入框的組件進行配置:
在大括號內部添加代碼:

        get {
            if toolBar == nil {
                
                toolBar = UIToolbar(frame: CGRectMake(0, 0, 0, toolBarMinHeight-0.5))
                
                textView = InputTextView(frame: CGRectZero)
                textView.backgroundColor = UIColor(white: 250/255, alpha: 1)
                textView.delegate = self
                textView.font = UIFont.systemFontOfSize(messageFontSize)
                textView.layer.borderColor = UIColor(red: 200/255, green: 200/255, blue: 205/255, alpha:1).CGColor
                textView.layer.borderWidth = 0.5
                textView.layer.cornerRadius = 5
                //            textView.placeholder = "Message"
                textView.scrollsToTop = false
                textView.textContainerInset = UIEdgeInsetsMake(4, 3, 3, 3)
                toolBar.addSubview(textView)
                
                sendButton = UIButton.buttonWithType(.System) as! UIButton
                sendButton.enabled = false
                sendButton.titleLabel?.font = UIFont.boldSystemFontOfSize(17)
                sendButton.setTitle("發送", forState: .Normal)
                sendButton.setTitleColor(UIColor(red: 142/255, green: 142/255, blue: 147/255, alpha: 1), forState: .Disabled)
                sendButton.setTitleColor(UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0), forState: .Normal)
                sendButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 6)
                sendButton.addTarget(self, action: "sendAction", forControlEvents: UIControlEvents.TouchUpInside)
                toolBar.addSubview(sendButton)
                
                // 對組件進行Autolayout設置
                textView.setTranslatesAutoresizingMaskIntoConstraints(false)
                sendButton.setTranslatesAutoresizingMaskIntoConstraints(false)
   
                toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Left, relatedBy: .Equal, toItem: toolBar, attribute: .Left, multiplier: 1, constant: 8))
                toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Top, relatedBy: .Equal, toItem: toolBar, attribute: .Top, multiplier: 1, constant: 7.5))
                toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Right, relatedBy: .Equal, toItem: sendButton, attribute: .Left, multiplier: 1, constant: -2))
                toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Bottom, relatedBy: .Equal, toItem: toolBar, attribute: .Bottom, multiplier: 1, constant: -8))
                toolBar.addConstraint(NSLayoutConstraint(item: sendButton, attribute: .Right, relatedBy: .Equal, toItem: toolBar, attribute: .Right, multiplier: 1, constant: 0))
                toolBar.addConstraint(NSLayoutConstraint(item: sendButton, attribute: .Bottom, relatedBy: .Equal, toItem: toolBar, attribute: .Bottom, multiplier: 1, constant: -4.5))
            }
            return toolBar
        }

你會發現有一個錯誤,這是因為我們的InputTextView是一個單獨定義的類,它還沒有定義,我們在之后會對他做一些操作,目前先不用管它,不過我們先把它定義出來,在視圖控制器類之外定義該類:

class InputTextView: UITextView {
    
    
    
}

還有一個問題,用系統默認的代碼實現autolayout看起來很難理解,所以這里可以用第三方庫SnapKit來實現,把上面設置autolayout的代碼替換成以下代碼:

textView.setTranslatesAutoresizingMaskIntoConstraints(false)             
sendButton.setTranslatesAutoresizingMaskIntoConstraints(false)

 textView.snp_makeConstraints({ (make) -> Void in
                    
                    make.left.equalTo(self.toolBar.snp_left).offset(8)
                    make.top.equalTo(self.toolBar.snp_top).offset(7.5)
                    make.right.equalTo(self.sendButton.snp_left).offset(-2)
                   make.bottom.equalTo(self.toolBar.snp_bottom).offset(-8)
                  
                
                })
                sendButton.snp_makeConstraints({ (make) -> Void in
                    make.right.equalTo(self.toolBar.snp_right)
                     make.bottom.equalTo(self.toolBar.snp_bottom).offset(-4.5)
                    
                })

是不是看起來簡潔多了?我們來解釋一下這段代碼:
每一個組件都有一個 snp_makeConstraints的閉包方法,用來設置約束,textView.snp_makeConstraints就是來設置textView的約束
閉包中make.left.equalTo(self.toolBar.snp_left).offset(8)這行代碼可以用公式來表示:
也就是textView.left = self.toolBar.left + 8,這樣一看就很直觀了,文字框的左側距輸入框左側8點。
make.top.equalTo(self.toolBar.snp_top).offset(7.5)可以用公式
textView.top = self.toolBar.top +7.5表示,剩下的代碼以此類推,如下圖所示:

autoLaout

sendButton的部分也是如此:
make.right.equalTo(self.toolBar.snp_right)表示發送按鈕右側直接貼輸入框的右側,沒有位移
make.bottom.equalTo(self.toolBar.snp_bottom).offset(-4.5)發送按鈕底部距離輸入框底部4.5點
這樣是不是讓autoLayout變得簡單很多了?后面的項目我們就一直使用它來進行autoLayout設置了!
現在沒有錯誤了,cmd+R運行一下,啊哦,為啥是空白!作者你騙人!( ⊙ o ⊙ )
= =好吧,我們還差一步,記得嗎,它要變成第一響應者才能彈出鍵盤哦,我們要重寫一個方法它才能生效!在視圖控制器類中增加以下方法:

   override func canBecomeFirstResponder() -> Bool {
        return true
    }

告訴我們的系統我們自定義的輸入框可以成為第一響應者,我們也是有身份證的!
然后在運行一下,如果沒有錯誤,應該會有以下效果:

chat.gif

忽略黑洞洞的背景,因為我們還沒有添加內容。。。
但是你會發現一個問題,鍵盤怎么回來啊。。不管怎么點都沒有反應?。?/p>

好吧,下面我們來用一個巧妙的辦法來解決它。由于聊天頁面是一個UITableView,所以我們可以使用UITableViewContoller來替代我們的UIViewContoller,這樣我們的頁面中就默認有了一個UITableView,然后它有一個非常實用的屬性---keyboardDismissMode,我們把它設置為.Interactive也就是鍵盤的彈出和收回狀態可以根據你對tableView的拖拽進行改變,也就是你的手指拖到哪里你的鍵盤就到哪里,是不是很酷。
改變視圖控制器的類型:

class ChatViewController:UITableViewController,UITextViewDelegate {
....
....
....
}

在viewDidLoad里添加一行代碼來設置keyboardDismissMode:

tableView.keyboardDismissMode = .Interactive

再次運行,你會發現黑洞洞的背景不見了,取而代之的是空白的TableView!而且鍵盤也實現了炫酷的效果!

chat.gif

文章本部分源代碼
好的,第三部分順利實現!第二部分是我們的重頭戲,內容較多,所以我把它放到教程的第二部分中。
第二部分教程已經出爐,歡迎圍觀!
swift實現一個與智能機器人聊天的app(二)

如果該文章對你有幫助,請點一下喜歡!您的支持是我繼續寫作的動力!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容