開發(fā)記事本實(shí)戰(zhàn),體驗(yàn)perfect框架用swift寫服務(wù)端

Perfect

說(shuō)明

本文Demo是使用 Swift3.0 基于perfect框架用swift寫服務(wù)端的文章。對(duì)于perfect框架入門和配置方面不做過(guò)多的講解,想要了解該方面的大佬們請(qǐng)參考以下學(xué)習(xí)資料,而客戶端方面則使用RxSwift框架來(lái)寫,關(guān)于客戶端的內(nèi)容在有機(jī)會(huì)再進(jìn)行介紹,本文著重講解服務(wù)端Demo。


首先獻(xiàn)上本文Demo

GitHub:服務(wù)端Demo(Perfect)
GitHub:客戶端Demo(RxSwift+Moya)

接著再獻(xiàn)上參考的學(xué)習(xí)資料

GitHub地址,過(guò)萬(wàn)的start
官方中文文檔,教練我要學(xué)這個(gè)
perfect框架入門比較不錯(cuò)文章,配置方面講得很詳細(xì)
本文實(shí)戰(zhàn)Demo主要的參考來(lái)源,對(duì)官方文檔有一些重要的補(bǔ)充說(shuō)明,入門講解很詳細(xì)

Perfect是什么東西呢?

Perfect是一組完整、強(qiáng)大的工具箱、軟件框架體系和Web應(yīng)用服務(wù)器,可以在Linux、iOS和macOS (OS X)上使用。該軟件體系為Swift工程師量身定制了一整套用于開發(fā)輕量、易維護(hù)、規(guī)??蓴U(kuò)展的Web應(yīng)用及其它REST服務(wù)的解決方案,這樣Swift工程師就可以實(shí)現(xiàn)同時(shí)在服務(wù)器和客戶端上采用同一種語(yǔ)言開發(fā)軟件項(xiàng)目。

性能對(duì)比

性能對(duì)比

一篇性能對(duì)比的文章:不服跑個(gè)分


至于是什么原因讓我想學(xué)習(xí)perfect呢?

作為一名剛接觸ios開發(fā)沒(méi)多久的小白,回憶起當(dāng)初加入學(xué)校一個(gè)軟件開發(fā)團(tuán)隊(duì)的時(shí)候,為了能與團(tuán)隊(duì)其他方面的人相互協(xié)作,了解其他方面的一些基本知識(shí)是有必要的。就好比前后端交互,作為移動(dòng)端方面也要了解后端知識(shí),這樣在前后端交互的時(shí)候就會(huì)少很多麻煩事。于是在加入團(tuán)隊(duì)初期,師兄便要求我自己寫一個(gè)demo,服務(wù)端你用什么寫都可以。對(duì)于當(dāng)時(shí)的我來(lái)說(shuō),真的是件麻煩事了,因?yàn)閷W(xué)習(xí)ios并不像學(xué)習(xí)Android,Android使用java語(yǔ)言,Android與java服務(wù)器相互協(xié)作,所以在學(xué)習(xí)Android的同時(shí)或多或少也會(huì)學(xué)到一些后端的知識(shí)。??雖然最后我用python寫了一個(gè)很爛的后端,但是那時(shí)候便在想為啥不能寫Android那樣,能用同一種語(yǔ)言也寫后端。直到前些日子發(fā)現(xiàn)了perfect,倍感歡喜,于是便琢磨了一番。

接下來(lái)實(shí)戰(zhàn)Demo! GO! GO! GO!

第一部分:Demo演示

由于簡(jiǎn)書限制了圖片的大小,所以只能分開進(jìn)行演示。(內(nèi)心的憂傷你們應(yīng)該懂吧)

注冊(cè):
登錄:
添加筆記:
修改筆記:
刪除筆記:
數(shù)據(jù)庫(kù)中直接操作:

第二部分:初始化項(xiàng)目結(jié)構(gòu)

首先讓我們按部就班的完成初始化工作,我們的工程名就叫iNoteServer好了,所以我們創(chuàng)建一個(gè)iNoteServer,在iNoteServer里我們使用終端創(chuàng)建Package.swfit文件和一個(gè)Sources文件夾,在Sources文件夾里創(chuàng)建一個(gè)main.swift文件。你的項(xiàng)目結(jié)構(gòu)在iNoteServer文件里看起來(lái)是這樣的:


緊接著在Package.swfit文件中,寫入需要使用的倉(cāng)庫(kù)。

import PackageDescription

let versions = Version(0,0,0)..<Version(10,0,0)
let urls = [
    "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", //服務(wù)端核心框架
    "https://github.com/SwiftORM/MySQL-StORM.git", //對(duì)象關(guān)系型數(shù)據(jù)庫(kù)
]

let package = Package(
    name: "iNoteServer",
    targets: [],
    dependencies: urls.map { .Package(url: $0, versions: versions) }
)

然后我們?cè)诮K端中輸入swift build。(該過(guò)程等待的時(shí)間挺久的,畢竟網(wǎng)速慢,文件也不小...)

fetch完成之后,輸入swift package generate-xcodeproj命令創(chuàng)建iNoteServer.xcodeproj文件。打開iNoteServer.xcodeproj文件,在Build Settings中Library Search Paths檢索項(xiàng)目軟件庫(kù)中增加(不單單是編譯目標(biāo))

$(PROJECT_DIR) - Recursive

最后,我們劃分一下目錄:


DataBase目錄里存放含管理數(shù)據(jù)庫(kù)的類(DatabaseManager),一些ORM對(duì)象的數(shù)據(jù)模型(User對(duì)象,NoteContent對(duì)象)
NetworkServer目錄里存放接口API(iNoteAIP)以及HTTPServer類(NetworkServerManager)
因此在main文件中,我們只要簡(jiǎn)單的通過(guò)HTTPServer類來(lái)調(diào)用start方法就可以直接啟動(dòng)服務(wù)器了

NetworkServerManager.share.serverStart()

至此,我們項(xiàng)目的初始化已經(jīng)完成了,可喜可賀,可喜可賀。


第三部分:創(chuàng)建各功能模塊的接口

在這里,我創(chuàng)建一個(gè)iNoteAPI文件用來(lái)管理各模塊的接口

enum iNoteAIP: String {
    case base = "/iNote"            
    case register = "/register"         //注冊(cè)頁(yè)面
    case login = "/login"               //登錄頁(yè)面
    case contentList = "/contentList"   //獲取筆記列表
    case addNote = "/addNote"           //添加筆記
    case deleteNote = "/deleteNote"     //刪除筆記
    case modifyNote = "/modifyNote"     //修改筆記
}

第四部:創(chuàng)建HTTP服務(wù)器管理類

在此之前我還是先提一下使用perfect框架構(gòu)建服務(wù)器的基本流程,詳細(xì)的還是請(qǐng)看學(xué)習(xí)參考資料。
在main文件中直接寫入以下代碼:

import PerfectLib
import PerfectHTTP
import PerfectHTTPServer

// 創(chuàng)建HTTP服務(wù)器
let server = HTTPServer()
// 監(jiān)聽(tīng)8181端口
server.serverPort = 8181
//創(chuàng)建路由組,用來(lái)存放各個(gè)路由
var routes = Routes()
//注冊(cè)您自己的路由和請(qǐng)求/響應(yīng)句柄 (請(qǐng)求方法,地址,請(qǐng)求處理)
routes.add(method: .get, uri: "test") { (request, response) in
    response.setBody(string: "hello word!")
    response.completed()
}
// 將路由注冊(cè)到服務(wù)器上
server.addRoutes(routes)
// 啟動(dòng)服務(wù)器
do {
    try server.start()
} catch PerfectError.networkError(let code, let msg) {
    print("network error:\(code) \(msg)")
} catch {
    print("unknow network error: \(error)")
}

command? + R 跑起來(lái)~~~??

接著打開瀏覽器,輸入localhost:8181/test,一按回車


成功的顯示響應(yīng)句柄,我們的服務(wù)器成功的跑起來(lái)了,可喜可賀,可喜可賀。
這就是最基本的構(gòu)建流程。

回到本文的Demo中,我們?cè)趍ain文件中,只是簡(jiǎn)單的通過(guò)startServer方法啟動(dòng)服務(wù)器,因此我們創(chuàng)建NetworkServerManager類來(lái)封裝以上的流程。

class NetworkServerManager {
    // 創(chuàng)建HTTP服務(wù)器
    let server = HTTPServer()
    //創(chuàng)建路由組,用來(lái)存放路由
    var routes = Routes(baseUri: iNoteAIP.base.rawValue)
    
    static let share = NetworkServerManager()
    private init() {
        //注冊(cè)您自己的路由和請(qǐng)求/響應(yīng)句柄 (請(qǐng)求方法,地址,請(qǐng)求處理)
        configure()
    }
    
    func serverStart(_ port: UInt16 = 8181) {
        // 監(jiān)聽(tīng)8181端口
        server.serverPort = port
        // 將路由注冊(cè)到服務(wù)器上
        server.addRoutes(routes)
        // 啟動(dòng)服務(wù)器
        do {
            try server.start()
        } catch PerfectError.networkError(let code, let msg) {
            print("network error:\(code) \(msg)")
        } catch {
            print("unknow network error: \(error)")
        }
    }
    
    //uri使用iNoteAIP中的枚舉值字符串
    func addRouteWith(method: HTTPMethod, uri: iNoteAIP, handler: @escaping RequestHandler) {
        routes.add(method: method, uri: uri.rawValue, handler: handler)
    }
}

我們?cè)诔跏蓟瘑卫龝r(shí),通過(guò)調(diào)用configure方法將各接口的的路由添加在路由組中,handler參數(shù)傳的是各接口的句柄處理,返回RequestHandler類型。

extension NetworkServerManager {
    //添加各模塊的路由
    func configure() {
        //登錄注冊(cè)接口的路由
        addRouteWith(method: .post, uri: .register, handler: userRegisterHandle())
        addRouteWith(method: .post, uri: .login, handler: userLoginHandle())
        //筆記的CURD接口的路由
        addRouteWith(method: .get, uri: .contentList, handler: getNoteContentListHandle())
        addRouteWith(method: .post, uri: .addNote, handler: addNoteHandel())
        addRouteWith(method: .delete, uri: .deleteNote, handler: deleteNoteHandle())
        addRouteWith(method: .post, uri: .modifyNote, handler: modifyNoteHandle())
    }
}

讓我們來(lái)看看RequestHandler是什么類型

public typealias RequestHandler = (HTTPRequest, HTTPResponse) -> ()

原來(lái)是一個(gè)閉包嘛,如果我們?cè)赾onfigure中用閉包形式寫handler,那會(huì)變得臃腫。因此我們可以通過(guò)函數(shù)來(lái)返回該閉包。

// MARK:- 注冊(cè)和登錄
extension NetworkServerManager {
    func userRegisterHandle() -> RequestHandler {
        return {[weak self] request, response in
           //TODO: 處理注冊(cè)請(qǐng)求
        }
    }
    func userLoginHandle() -> RequestHandler {
        return {[weak self] request, response in
            //TODO: 處理登錄請(qǐng)求
        }
    }
}

// MARK:- 筆記CURD
extension NetworkServerManager {
    func getNoteContentListHandle() -> RequestHandler {
        return {[weak self] request, response in
            //TODO: 處理獲取筆記列表請(qǐng)求
        }
    }
    func addNoteHandel() -> RequestHandler {
        return {[weak self] request, response in
            //TODO: 處理添加筆記請(qǐng)求
        }
    }
    func deleteNoteHandle() -> RequestHandler {
        return {[weak self] request, response in
            //TODO: 處理刪除筆記請(qǐng)求
        }
    }
    func modifyNoteHandle() -> RequestHandler {
        return {[weak self] request, response in
            //TODO: 處理修改筆記請(qǐng)求
        }
    }
}

這樣,我們就可以在main文件中通過(guò)簡(jiǎn)單的調(diào)用啟動(dòng)服務(wù)器了,并在個(gè)方法中處理相應(yīng)的客戶端請(qǐng)求,可喜可賀,可喜可賀。


第五部分:定制要返回的json格式

此刻,我們暫且先停下來(lái)思考一些問(wèn)題,例如處理一個(gè)客戶端的請(qǐng)求我們應(yīng)該做些什么事情呢?客戶端傳過(guò)來(lái)的參數(shù)缺少必要的字段時(shí)我們?cè)摲祷厥裁葱畔??正確時(shí)我們又該返回什么信息?錯(cuò)誤信息和成功的信息格式是如何的?又是否大致相同?
于是我便采用一種最簡(jiǎn)單的格式來(lái)進(jìn)行演示(反正是演示嘛,將就一下),json格式看起來(lái)像是這樣的:

{
  "status": "SUCCESS",
  "data": [],
  "message": "注冊(cè)成功",
  "result": true
}

{
  "status": "FAILURE",
  "data": [],
  "message": "缺少對(duì)應(yīng)參數(shù)",
  "result": false
}

data中的數(shù)據(jù)根據(jù)相應(yīng)的接口來(lái)構(gòu)建。所以我們寫一個(gè)枚舉值來(lái)返回status狀態(tài),寫一個(gè)函數(shù)用來(lái)處理生成該json格式的字典。

// MARK:- status狀態(tài)
enum ResponseStatus: String {
    case success = "SUCCESS"
    case failure = "FAILURE"
}
extension NetworkServerManager {
    // 處理要返回的響應(yīng)體,構(gòu)建json格式
    func requestHandle(request: HTTPRequest, response: HTTPResponse, status: ResponseStatus, result: Bool, resultMessage: String, data:[[String:Any]]?) {
        let jsonDic: [String:Any]
        jsonDic = [
            "status":status.rawValue,
            "result": result,
             "message":resultMessage,
             "data":data ?? []
        ]
        do {
        //jsonEncodedString: 對(duì)字典的擴(kuò)展方法,返回對(duì)應(yīng)json格式的字符串
            let json = try jsonDic.jsonEncodedString() 
            response.setBody(string: json)
        } catch {
            print(error)
        }
        response.completed()
    }
}

json格式已經(jīng)有了,緊接著我們要對(duì)客戶端請(qǐng)求的參數(shù)表格中取出必要的參數(shù)進(jìn)行合法判斷。以注冊(cè)為例:

func userLoginHandle() -> RequestHandler {
        return {[weak self] request, response in
            guard let phoneNum =  request.param(name: "phoneNum"),
                  let password = request.param(name: "password"),
                  phoneNum.characters.count > 0,
                  password.characters.count > 0
            else {
                self?.requestHandle(request: request, response: response, status: .failure, result: false, resultMessage: "缺少對(duì)應(yīng)參數(shù)", data: nil)
                return
            }
            
             //TODO: 參數(shù)合法則進(jìn)行數(shù)據(jù)庫(kù)對(duì)應(yīng)操作
            
        }
}

其他的接口獲取參數(shù)后的處理也與注冊(cè)相似,至此,我們的NetworkServerManager類在邏輯上基本完成了,接下來(lái)要做的事是跟數(shù)據(jù)庫(kù)打交道了,我們?cè)贒ataBase文件夾中創(chuàng)建數(shù)據(jù)庫(kù)管理類來(lái)為我們進(jìn)行處理數(shù)據(jù),畢竟我們不可能在服務(wù)器類寫數(shù)據(jù)庫(kù)對(duì)吧...


第六部分:數(shù)據(jù)庫(kù)

現(xiàn)在,我們?cè)贒ataBase文件夾中創(chuàng)建DatabaseManager類來(lái)管理數(shù)據(jù)庫(kù),這里我們使用得是ORM數(shù)據(jù)庫(kù),同樣我們使用單例來(lái)進(jìn)行調(diào)用。在初始化配置時(shí)我們對(duì)MySQLConnector進(jìn)行配置(密碼記得填你們自己的),這里我們并找不到類似start的方法來(lái)啟動(dòng)數(shù)據(jù)庫(kù)連接,因?yàn)樗鼤?huì)在適當(dāng)?shù)臅r(shí)候便自行建立連接,例如調(diào)用單例的時(shí)候,因此我們不必操心建立連接、關(guān)閉連接、打開數(shù)據(jù)庫(kù)、關(guān)閉數(shù)據(jù)庫(kù)等。

數(shù)據(jù)庫(kù)的配置根據(jù)自己的信息進(jìn)行對(duì)應(yīng)的配置。

// MARK:- 數(shù)據(jù)庫(kù)管理類
class DatabaseManager {
    static let share = DatabaseManager()
    private init() {
        MySQLConnector.host = "127.0.0.1" 
        MySQLConnector.username = "root"
        MySQLConnector.password = "此處填你自己的mysql密碼"
        MySQLConnector.database = "iNote" //MySql中創(chuàng)建的iNote數(shù)據(jù)庫(kù)
        MySQLConnector.port = 3306
    }
}

既然是ORM數(shù)據(jù)庫(kù),我們便不需要寫讓人眼花繚亂的sql語(yǔ)句,而是簡(jiǎn)單的通過(guò)調(diào)用對(duì)象的方法進(jìn)行數(shù)據(jù)庫(kù)的操作,以登錄為例:

// MARK:- User
extension DatabaseManager {
    // 返回登錄操作后的結(jié)果(result, message, userInfo)
    func loginWith(phoneNum: String, password: String) -> (Bool, String, [String:String]) {
        return User.userLoginWith(phone: phoneNum, pwd: password) // <-- TODO:
    }
}

在外部的NetworkServerManager類中我們便可以調(diào)用DatabaseManager了,以登錄為例:

func userLoginHandle() -> RequestHandler {
        return {[weak self] request, response in
            guard let phoneNum =  request.param(name: "phoneNum"),
                  let password = request.param(name: "password"),
                  phoneNum.characters.count > 0,
                  password.characters.count > 0
            else {
                self?.requestHandle(request: request, response: response, status: .failure, result: false, resultMessage: "缺少對(duì)應(yīng)參數(shù)", data: nil)
                return
            }
            // 操作是否成功, 結(jié)果信息, 用戶信息
            let (result, msg, info) = DatabaseManager.share.loginWith(phoneNum: phoneNum, password: password)
            let status: ResponseStatus = result ? .success : .failure
            self?.requestHandle(request: request, response: response, status: status, result: result, resultMessage: msg, data: [info])
    }
}

其他接口的處理與此類似,可以查看本文的服務(wù)端Demo,現(xiàn)在我們繼續(xù)以登錄為例,接下來(lái)的事情只剩下User類對(duì)數(shù)據(jù)庫(kù)的操作了。成功近在咫尺,可喜可賀,可喜可賀。


第七部分:MySQLStORM對(duì)象

使用ORM數(shù)據(jù)庫(kù)實(shí)際上是操作ORM對(duì)象,perfect框架已經(jīng)幫我們實(shí)現(xiàn)所需要的CURD方法,我們直接調(diào)用方法的方式來(lái)操作數(shù)據(jù)庫(kù)即可。我們只需寫對(duì)應(yīng)的模型類,繼承MySQLStORM類,實(shí)現(xiàn)要求重寫的父類方法即可。該模型對(duì)應(yīng)的屬性名、屬性類型便是數(shù)據(jù)庫(kù)中對(duì)應(yīng)的字段名以及字段類型。這里引入官方文檔的一個(gè)重要要求:

?注意? 該對(duì)象的第一個(gè)屬性將成為對(duì)應(yīng)數(shù)據(jù)表的主索引 —— 傳統(tǒng)的方式就是給主索引列起名叫做 id,雖然您可以為主索引字段設(shè)置任何有效的名字。SQL這種關(guān)系數(shù)據(jù)庫(kù)的主索引典型類型是整型、字符串或者UUID編碼。如果您的主索引不是自動(dòng)遞增的整數(shù),則一定要設(shè)置好這個(gè)id值,以保證數(shù)據(jù)的完整性和一致性。

以處理用戶注冊(cè)登錄的User模型為例(這里只是為了簡(jiǎn)單演示也沒(méi)弄UUID、token之類的字段):

import Foundation
import MySQLStORM
import StORM

class User: MySQLStORM {
    // ?注意?:第一個(gè)屬性將成為主索引字段,所以應(yīng)該是ID
    var id: Int = 0
    var phoneNum: String = ""
    var password: String = ""
    var registerTime: String = ""
    
    fileprivate override init() {
        super.init()
        do {
            //確保該模型的表格存在
            try setupTable()
        } catch {
            print(error)
        }
    }
    
    //給對(duì)象的表名
    override func table() -> String {
        return "User"
    }
    
    override func to(_ this: StORMRow) {
        id = numericCast(this.data["id"] as! Int32)
        phoneNum = this.data["phoneNum"] as! String
        password = this.data["password"] as! String
        registerTime = this.data["registerTime"] as! String
    }
    
    fileprivate func rows() -> [User] {
        var rows: [User] = []
        for r in results.rows {
            let row = User()
            row.to(r)
            rows.append(row)
        }
        return rows
    }
}

在這里著重說(shuō)明一下在to方法中為什么使用numericCastnumericCast是用于整型之間的轉(zhuǎn)換的,在實(shí)戰(zhàn)的過(guò)程中,起初直接用this.data["id"] as! Int是沒(méi)問(wèn)題,可是當(dāng)從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)時(shí)就報(bào)了一個(gè)錯(cuò)誤。

Could not cast value of type 'Swift.Int32' (0x1014c1df0) to 'Swift.Int' (0x1014c2430).
2017-11-03 20:50:23.014244+0800 iNoteServer[54873:750541] Could not cast value of type 'Swift.Int32' (0x1014c1df0) to 'Swift.Int' (0x1014c2430).

從數(shù)據(jù)庫(kù)讀取出來(lái)的id類型變成了Int32了。(當(dāng)時(shí)我臉就是這么黑的),所以用numericCast來(lái)轉(zhuǎn)換一下類型即可。

//API相關(guān)操作
extension User {
    //驗(yàn)證用戶是否存在
    fileprivate func findUserWith(_ phone: String) {
        // fine: 如果在數(shù)據(jù)庫(kù)中匹配到了,則將字段的內(nèi)容賦值給對(duì)象中的屬性,否則什么都不做
        do {
            try find([("phoneNum",phone)])
        } catch {
            print(error)
        }
    }
    
    //登錄 -> 返回(操作結(jié)果, 結(jié)果信息, 用戶信息)
    static func userLoginWith(phone: String, pwd: String) -> (Bool, String, [String:String]) {
        let user = User()
        user.findUserWith(phone)
        if user.phoneNum == phone && user.password == pwd {
            let info = [
                            "userId": "\(user.id)",
                            "phoneNum": user.phoneNum,
                            "registerTime": user.registerTime
                        ]
            return (true, "登錄成功", info)
        } else {
            let info = ["userId": "", "phoneNum": "", "registerTime": ""]
            return (false, "用戶名或密碼錯(cuò)誤", info)
        }
    }
}

至此,用戶登錄功能已經(jīng)基本完成了。現(xiàn)在到了測(cè)試接口的時(shí)候了,成敗在此一舉。command? + R 跑起來(lái)~~~~??。在這里我們使用Paw(測(cè)接口的神器)來(lái)測(cè)試我們的接口:

成功了~~

其他接口的實(shí)現(xiàn)方式也按照同樣的套路實(shí)現(xiàn)就可以,至此,本文基于perfect框架用swift寫服務(wù)端也在此處告一段落。接下來(lái)則會(huì)寫一篇與這個(gè)iNoteServer服務(wù)端相對(duì)應(yīng)的iNoteClient。
至于本人才疏學(xué)淺,對(duì)后端只是略知一二,斗膽嘗試,如果錯(cuò)漏,懇請(qǐng)各位大佬多多包涵與明示。??

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

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,781評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,630評(píng)論 25 708
  • 官方網(wǎng)站文檔詳情: 官方中文文檔 實(shí)踐Demo Perfect實(shí)踐Demo 簡(jiǎn)介 Perfect是一組完整、強(qiáng)大的...
    LuisX閱讀 19,327評(píng)論 58 101
  • 在做自己的項(xiàng)目的時(shí)候遇到這樣一個(gè)需求,遂即使來(lái)紀(jì)錄一下 2015年12月31號(hào) 周四 我需要得到的是 2015年...
    Easy_VO閱讀 14,115評(píng)論 1 8
  • 今天上午去了依然是理論課,我就服了,一上午理論課我真的覺(jué)得就沒(méi)時(shí)間約客戶,每天大好時(shí)光都被理論課占了,主管也啥...
    王紫彥的老公閱讀 214評(píng)論 0 0