golang輕量級(jí)框架-Gin入門

本來自己打算繼續(xù)學(xué)下beanFactory源碼的惹资,但是放假了自己也沒什么精神扼脐,看源碼又要求注意力很集中纹腌,所以想著看點(diǎn)簡單點(diǎn)的內(nèi)容吧幌缝,然后就想到了golang的另一個(gè)框架-Gin灸促。假期過后可能就要開啟加班生活了,不是很開心涵卵,昨天收到老大郵件浴栽,我原來項(xiàng)目組基本上解散了,人員分到了不同項(xiàng)目組轿偎,而我到了ebay項(xiàng)目組去做微服務(wù)(如果不用加班我還是期待的)典鸡,自己浪了一個(gè)月也該收收心了。還是回歸正題坏晦,gin框架和前面學(xué)習(xí)的beego框架都是比較流行的框架萝玷,但是beego比較傳統(tǒng),模塊多功能全昆婿,而gin可以看作是一個(gè)單獨(dú)模塊的框架球碉,官方介紹說的是:Gin 是一個(gè) Go (Golang) 語言框架。 它是一個(gè)擁有更好性能的 martini-like API 框架, 由于?httprouter仓蛆,速度提高了近 40 倍汁尺。 如果你是性能和高效的追求者, 那么你會(huì)愛上 Gin。自己感覺gin更像是beego中的controller多律,主要針對用戶的request和response痴突。gin官網(wǎng),個(gè)人感覺文檔稍顯粗糙狼荞,不過勝在支持中文辽装,還是很良心的。

一相味、安裝和開始

要想使用gin必須要下載和安裝它拾积,切換到自己的工作空間,執(zhí)行g(shù)o命令

goget-u?github.com/gin-gonic/gin

但是因?yàn)榫W(wǎng)絡(luò)問題可能會(huì)失敗丰涉,實(shí)在不行就直接通過github下載也可以拓巧。

安裝好之后就可以直接使用了,打開ide創(chuàng)建一個(gè)新的項(xiàng)目helloGin一死,創(chuàng)建main.go

funcmain(){

//?Engin

router?:=?gin.Default()

//router?:=?gin.New()

router.GET("/hello",func(context?*gin.Context){

log.Println(">>>>?hello?gin?start?<<<<")

context.JSON(200,gin.H{

"code":200,

"success":true,

})

})

//?指定地址和端口號(hào)

router.Run("localhost:9090")

在main函數(shù)里面首先通過調(diào)用gin.Default()函數(shù)返回的是一個(gè)Engin指針肛度,Engin代表的是整個(gè)框架的一個(gè)實(shí)例,它包含了多路復(fù)用投慈、中間件和配置的設(shè)置承耿,其實(shí)就是封裝了我們需要的內(nèi)容冠骄。一般創(chuàng)建Engin都是使用Default()或者New(),當(dāng)然Default()本身內(nèi)部也是調(diào)用的New()函數(shù)加袋。

接著調(diào)用Engin的GET方法凛辣,這個(gè)方法兩個(gè)參數(shù),一個(gè)是相對路徑职烧,一個(gè)是多個(gè)handler扁誓,即針對用戶一個(gè)請求地址,我可以指定多個(gè)handler來處理用戶請求蚀之。但是一般情況下我們都是一個(gè)handler處理一個(gè)請求蝗敢。上面的代碼里使用了一個(gè)匿名函數(shù)處理"/hello"請求。然后以JSON格式的數(shù)據(jù)響應(yīng)用戶請求恬总,這個(gè)方法有兩個(gè)參數(shù),第一個(gè)是狀態(tài)肚邢,第二個(gè)是結(jié)果壹堰。我這里直接指定200,表示成功骡湖,或者也可以用http包的常量值http.StatusOK贱纠;gin.H其實(shí)是一個(gè)map的數(shù)據(jù)結(jié)構(gòu),然后將其轉(zhuǎn)成json格式輸出响蕴。

最后是router.Run("localhost:9090")谆焊,這個(gè)方法是指定服務(wù)的主機(jī)和端口號(hào),不過一般直接指定端口號(hào)就行了浦夷。

下面啟動(dòng)項(xiàng)目辖试,并訪問"localhost:9090/hello",訪問結(jié)果如下圖所示:

圖-1.png

二劈狐、創(chuàng)建demo

接下來創(chuàng)建項(xiàng)目來學(xué)習(xí)gin的使用罐孝,主要就是controller的使用,即將用戶請求和handler進(jìn)行映射肥缔,然后獲取不同方式請求參數(shù)莲兢。構(gòu)建項(xiàng)目結(jié)構(gòu)如下所示

圖-2.png

config主要是配置相關(guān)的文件;controller包主要放handler续膳;database包數(shù)據(jù)庫相關(guān)代碼改艇,因?yàn)槲疫@里沒有用ORM框架,所以只是數(shù)據(jù)庫連接的代碼坟岔;main包下只有main.go一個(gè)文件谒兄;model就是數(shù)據(jù)模型,即自己定義的一些結(jié)構(gòu)體社付;static下放置的是靜態(tài)文件舵变;template包下是html頁面酣溃。

剛才上面處理"hello"請求使用的是一個(gè)匿名函數(shù),下面為非匿名函數(shù)來處理纪隙,代碼修改成下面:

funcmain(){

//?Engin

router?:=?gin.Default()

router.GET("/hello",?hello)//?hello函數(shù)處理"/hello"請求

//?指定地址和端口號(hào)

router.Run(":9090")

}

funchello(context?*gin.Context){

println(">>>>?hello?function?start?<<<<")

context.JSON(http.StatusOK,gin.H{

"code":200,

"success":true,

})

}

這樣好了一點(diǎn)點(diǎn)赊豌,但是想想spring controller,一般會(huì)在類上加上一個(gè)@requestMapping注解绵咱,然后方法上也會(huì)加上一個(gè)@requestMapping注解碘饼,之所以在類上加@requestMapping主要是這個(gè)controller處理的是同一類型問題,比如和用戶相關(guān)的controller悲伶,請求路徑都是/user/….艾恼,同樣gin也支持,這就是路由組麸锉,我們看下官方文檔的示例:

funcmain(){

router?:=?gin.Default()

//?Simple?group:?v1

v1?:=?router.Group("/v1")

{

v1.POST("/login",?loginEndpoint)

v1.POST("/submit",?submitEndpoint)

v1.POST("/read",?readEndpoint)

}

//?Simple?group:?v2

v2?:=?router.Group("/v2")

{

v2.POST("/login",?loginEndpoint)

v2.POST("/submit",?submitEndpoint)

v2.POST("/read",?readEndpoint)

}

router.Run(":8080")

}

根據(jù)這個(gè)事例钠绍,將代碼重新構(gòu)建,這里構(gòu)建兩個(gè)路由組花沉。并且在controller包下新建了UserController和FileController文件柳爽,分別處理不同路由組請求,分別作一些不同的操作碱屁,另外將每個(gè)路由對應(yīng)的函數(shù)按照路由組進(jìn)行劃分磷脯,另外有兩個(gè)靜態(tài)的html頁面,做form表單提交的操作娩脾。gin提供了兩個(gè)方法用戶加載靜態(tài)html赵誓,即LoadHTMLGlob()或LoadHTMLFiles(),第一個(gè)方法制定一個(gè)通配符路徑即可柿赊,而后面的方法則是需要指定所有需要加載的html文件名稱俩功。修改后代碼如下:

funcmain(){

//?Engin

//router?:=?gin.Default()

router?:=?gin.New()

//?加載html文件,即template包下所有文件

router.LoadHTMLGlob("template/*")

router.GET("/hello",?hello)

//?路由組

user?:=?router.Group("/user")

{//?請求參數(shù)在請求路徑上

user.GET("/get/:id/:username",controller.QueryById)

user.GET("/query",controller.QueryParam)

user.POST("/insert",controller.InsertNewUser)

user.GET("/form",controller.RenderForm)//?跳轉(zhuǎn)html頁面

user.POST("/form/post",controller.PostForm)

//可以自己添加其他碰声,一個(gè)請求的路徑對應(yīng)一個(gè)函數(shù)

//?...

}

file?:=?router.Group("/file")

{

//?跳轉(zhuǎn)上傳文件頁面

file.GET("/view",controller.RenderView)//?跳轉(zhuǎn)html頁面

//?根據(jù)表單上傳

file.POST("/insert",controller.FormUpload)

file.POST("/multiUpload",controller.MultiUpload)

//?base64上傳

file.POST("/upload",controller.Base64Upload)

}

//?指定地址和端口號(hào)

router.Run(":9090")

}

關(guān)于獲取用戶請求參數(shù)我還是寫了幾種情況绑雄,一是傳統(tǒng)的URL查詢參數(shù),例如:localhost:9090/user/query?id=2&name=hello奥邮;另外一種就是URL路徑參數(shù)万牺,例如localhost:9090/user/2/hello(也是id=2,name=hello)。上面這兩種是get請求洽腺,post請求我也寫了兩種形式脚粟,一種就是傳統(tǒng)的form表單提交,另外就是json格式參數(shù)提交蘸朋,等下通過代碼看下核无。

下面是UserController的代碼內(nèi)容:

funcinit(){

log.Println(">>>>?get?database?connection?start?<<<<")

db?=?database.GetDataBase()

}

//?localhost:9090/user/query?id=2&name=hello

funcQueryParam(context?*gin.Context){

println(">>>>?query?user?by?url?params?action?start?<<<<")

id?:=?context.Query("id")

name?:=?context.Request.URL.Query().Get("name")

varu?model.User

context.Bind(&u)

println(u.Username)

rows?:=?db.QueryRow("select?username,address,age,mobile,sex?from?t_user?where?id?=?$1?and?username?=?$2",id,name)

varuser?model.User

err?:=?rows.Scan(&user.Username,&user.Address,&user.Age,&user.Mobile,&user.Sex)

checkError(err)

checkError(err)

context.JSON(200,gin.H{

"result":user,

})

}

//?localhost:9090/user/get/2/hello

funcQueryById(context?*gin.Context){

println(">>>>?get?user?by?id?and?name?action?start?<<<<")

//?獲取請求參數(shù)

id?:=?context.Param("id")

name?:=?context.Param("username")

//?查詢數(shù)據(jù)庫

rows?:=?db.QueryRow("select?username,address,age,mobile,sex?from?t_user?where?id?=?$1?and?username?=?$2",id,name)

varuser?model.User

err?:=?rows.Scan(&user.Username,&user.Address,&user.Age,&user.Mobile,&user.Sex)

checkError(err)

context.JSON(200,gin.H{

"result":user,

})

}

//?json格式數(shù)據(jù)

funcInsertNewUser(context?*gin.Context){

println(">>>>?insert?controller?action?start?<<<<")

varuser?model.User

//?使用ioutile讀取二進(jìn)制數(shù)據(jù)

//bytes,err?:=?ioutil.ReadAll(context.Request.Body)

//if?err?!=?nil?{

//??log.Fatal(err)

//}

//err?=?json.Unmarshal(bytes,&user)

//?直接將結(jié)構(gòu)體和提交的json參數(shù)作綁定

err?:=?context.ShouldBindJSON(&user)

//?寫入數(shù)據(jù)庫

res,err?:=?db.Exec("insert?into?t_user?(username,sex,address,mobile,age)?values?($1,$2,$3,$4,$5)",

&user.Username,&user.Sex,&user.Address,&user.Mobile,&user.Age)

varcountint64

count,err?=?res.RowsAffected()

checkError(err)

ifcount?!=1{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

//?form表單提交

funcPostForm(context?*gin.Context){

println(">>>>?bind?form?post?params?action?start?<<<<")

varu?model.User

//?綁定參數(shù)到結(jié)構(gòu)體

context.Bind(&u)

res,err?:=?db.Exec("insert?into?t_user?(username,sex,address,mobile,age)?values?($1,$2,$3,$4,$5)",

&u.Username,&u.Sex,&u.Address,&u.Mobile,&u.Age)

varcountint64

count,err?=?res.RowsAffected()

checkError(err)

ifcount?!=1{

context.JSON(200,gin.H{

"success":false,

})

}else{

//context.JSON(200,gin.H{

//??"success":true,

//})

//?重定向

context.Redirect(http.StatusMovedPermanently,"/file/view")

}

}

//?跳轉(zhuǎn)html

funcRenderForm(context?*gin.Context){

println(">>>>?render?to?html?action?start?<<<<")

context.Header("Content-Type","text/html;?charset=utf-8")

context.HTML(200,"insertUser.html",gin.H{})

}

funccheckError(e?error){

ife?!=nil{

log.Fatal(e)

}

}

UserController里面定義一個(gè)init方法,主要獲取數(shù)據(jù)庫連接藕坯,一邊后面的函數(shù)對數(shù)據(jù)庫進(jìn)行操作团南。

在QueryParam函數(shù)中噪沙,獲取URL查詢參數(shù)其實(shí)用多種方法,一種直接使用context.Query("參數(shù)名稱")吐根,另外就是context.Request.URL.Query().Get("參數(shù)名稱")正歼,但是明顯第二個(gè)更麻煩一點(diǎn)。此外還有一種就是將參數(shù)綁定到結(jié)構(gòu)體拷橘,context.Bind()或者context.ShouldBind()或者ShouldBindQuery()局义,然后對結(jié)構(gòu)體進(jìn)行操作就行了,需要注意一點(diǎn)就是ShouldBindQuery()只能綁定GET請求的查詢參數(shù)冗疮,POST請求不行萄唇。其實(shí)使用哪種方式還是看個(gè)人習(xí)慣,參數(shù)少的話感覺第一種更直觀一些术幔。

QueryById函數(shù)獲取的是URL路徑參數(shù)另萤,和QueryParam獲取方法不同,可以通過context.Param("參數(shù)名稱")獲取诅挑,后來看gin文檔四敞,發(fā)現(xiàn)也提供了一種參數(shù)綁定的方法,即context.ShouldBindUri()揍障,這個(gè)方法也會(huì)把結(jié)構(gòu)體和URL路徑參數(shù)做一個(gè)綁定目养。

InsertNewUser函數(shù)俩由,獲取的是提交的JSON格式參數(shù)毒嫡,使用rest client可以模擬,獲取參數(shù)也不止一種幻梯,可以使用比較基礎(chǔ)的方法獲取兜畸,即使用ioutil.ReadAll(context.Request.Body),讀取字節(jié)流,然后使用go內(nèi)置的json庫將數(shù)據(jù)綁定到結(jié)構(gòu)體碘梢。最簡單方法就是調(diào)用ShouldBindJSON()咬摇,將用戶提交的JSON參數(shù)綁定結(jié)構(gòu)體。

PostForm函數(shù)就是一個(gè)傳統(tǒng)的form表單提交煞躬,使用context.Bind()或者context.ShouldBind()就好了肛鹏。

關(guān)于Bind和ShouldBind,其實(shí)這兩個(gè)方法基本上都是一樣的恩沛,根據(jù)具體的請求頭選擇不同綁定引擎去處理在扰,比如用戶請求的Content-Type為"application/json",那么就由JSON的綁定引擎處理雷客,如果為為"application/xml"芒珠,就由XML綁定引擎處理。這兩個(gè)方法的差別在于ShouldBind方法不會(huì)將response狀態(tài)值設(shè)為400搅裙,當(dāng)請求的json參數(shù)無效的時(shí)候皱卓,即請求參數(shù)無法綁定到結(jié)構(gòu)體裹芝。

RenderForm函數(shù)主要是跳轉(zhuǎn)到html頁面,當(dāng)時(shí)這里遇到一個(gè)問題娜汁,就是context.HTML方法嫂易,指定具體html頁面,因?yàn)閙ain函數(shù)使用時(shí)是router.LoadHTMLGlob("template/*")存炮,我覺得可以理解指定了具體html的前綴炬搭,所以跳轉(zhuǎn)時(shí)只需要html的相對template的路徑即可。

FileController主要是處理文件上傳穆桂,其實(shí)也沒什么特別內(nèi)容宫盔,無非就是單個(gè)上傳還是多個(gè)上傳的問題,另外就是使用base64上傳圖片享完。代碼如下:

constBASE_NAME?="./static/file/"

funcRenderView(context?*gin.Context){

println(">>>>?render?to?file?upload?view?action?start?<<<<")

context.Header("Content-Type","text/html;?charset=utf-8")

context.HTML(200,"fileUpload.html",gin.H{})

}

//?單個(gè)文件上傳

funcFormUpload(context?*gin.Context){

println(">>>>?upload?file?by?form?action?start?<<<<")

fh,err?:=?context.FormFile("file")

checkError(err)

//context.SaveUploadedFile(fh,BASE_NAME?+?fh.Filename)

file,err?:=?fh.Open()

deferfile.Close()

bytes,e?:=?ioutil.ReadAll(file)

e?=?ioutil.WriteFile(BASE_NAME?+?fh.Filename,bytes,0666)

checkError(e)

ife?!=nil{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

//?多個(gè)文件上傳

funcMultiUpload(context?*gin.Context){

println(">>>>?upload?file?by?form?action?start?<<<<")

form,err?:=?context.MultipartForm()

checkError(err)

files?:=?form.File["file"]

varer?error

for_,f?:=rangefiles?{

//?使用gin自帶保存文件方法

er?=?context.SaveUploadedFile(f,BASE_NAME?+?f.Filename)

checkError(err)

}

ifer?!=nil{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

funcBase64Upload(context?*gin.Context){

println(">>>>?upload?file?by?base64?string?action?start?<<<<")

bytes,err?:=?ioutil.ReadAll(context.Request.Body)

iferr?!=nil{

log.Fatal(err)

}

strs?:=?strings.Split(string(bytes),",")

head?:=?strs[0]

body?:=?strs[1]

println(head?+"?|?"+?body)

start?:=?strings.LastIndex(head,"/")

end?:=?strings.LastIndex(head,";")

tp?:=?head[start?+1:end]

err?=?ioutil.WriteFile(BASE_NAME?+?strconv.Itoa(time.Now().Nanosecond())?+"."+?tp,[]byte(body),0666)

checkError(err)

//bys,err?:=?base64.StdEncoding.DecodeString(string(bytes))

//err?=?ioutil.WriteFile("./static/file/"?+?strconv.Itoa(time.Now().Nanosecond()),bys,0666)

iferr?!=nil{

context.JSON(200,gin.H{

"success":false,

})

}else{

context.JSON(200,gin.H{

"success":true,

})

}

}

FormUpload函數(shù)處理單個(gè)文件上傳灼芭,先從context.FormFile("file")獲取文件,獲取到的是一個(gè)FileHeader指針般又,F(xiàn)ileHeader封裝了文件內(nèi)容彼绷、名稱、類型茴迁、大小等信息寄悯,結(jié)構(gòu)如下:

typeFileHeaderstruct{

Filenamestring

Header???textproto.MIMEHeader

Sizeint64

content?[]byte

tmpfilestring

}

保存文件可以直接使用SaveUploadedFile方法,也可以使用ioutil相關(guān)方法進(jìn)行保存堕义。

MultiUpload多文件上傳猜旬,先通過context.MultipartForm()獲取Form對象,然后根據(jù)參數(shù)名獲取到多個(gè)FileHeader指針倦卖,接下去保存文件和單個(gè)上傳是一樣的洒擦。

Base64Upload函數(shù)本來是想通過使用base64上傳圖片,函數(shù)內(nèi)先獲取整個(gè)字符串怕膛,然后分割成head和body熟嫩,然后判斷圖片類型,最后使用ioutil.WriteFile保存文件褐捻,但是實(shí)際操作好像出了點(diǎn)問題掸茅,文件保存到本地打開顯示內(nèi)容丟失,不知道是怎么回事柠逞。

三昧狮、總結(jié)

當(dāng)然gin內(nèi)容不止這些,還有一些中間件的內(nèi)容也是值得一看的边苹,比如BasicAuth陵且、Logger等等,但是總感覺gin似乎太輕了一點(diǎn),基本上就是一個(gè)MVC框架慕购,還是要結(jié)合其他框架使用聊疲。beego感覺更好一些,但是MVC這部分好像gin更強(qiáng)大點(diǎn)沪悲,總之都很優(yōu)秀吧获洲,畢竟GitHub上star那么多。今天的學(xué)習(xí)就到這里了殿如,本次學(xué)習(xí)的代碼已經(jīng)上傳到我的GitHub贡珊,我已經(jīng)很久沒有提交過代碼了…….

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涉馁,隨后出現(xiàn)的幾起案子门岔,更是在濱河造成了極大的恐慌,老刑警劉巖烤送,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寒随,死亡現(xiàn)場離奇詭異,居然都是意外死亡帮坚,警方通過查閱死者的電腦和手機(jī)妻往,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來试和,“玉大人讯泣,你說我怎么就攤上這事≡暮罚” “怎么了好渠?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長溉箕。 經(jīng)常有香客問我晦墙,道長悦昵,這世上最難降的妖魔是什么肴茄? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮但指,結(jié)果婚禮上寡痰,老公的妹妹穿的比我還像新娘。我一直安慰自己棋凳,他們只是感情好拦坠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剩岳,像睡著了一般贞滨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天晓铆,我揣著相機(jī)與錄音勺良,去河邊找鬼。 笑死骄噪,一個(gè)胖子當(dāng)著我的面吹牛尚困,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播链蕊,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼事甜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滔韵?” 一聲冷哼從身側(cè)響起逻谦,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陪蜻,沒想到半個(gè)月后跨跨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡囱皿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年勇婴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘱腥。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耕渴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出齿兔,到底是詐尸還是另有隱情橱脸,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布分苇,位于F島的核電站添诉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏医寿。R本人自食惡果不足惜栏赴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望靖秩。 院中可真熱鬧须眷,春花似錦、人聲如沸沟突。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惠拭。三九已至扩劝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棒呛。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工葡公, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人条霜。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓催什,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宰睡。 傳聞我的和親對象是個(gè)殘疾皇子蒲凶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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