以項(xiàng)目為鑰匙開啟Go的世界

本文不同于其他Go語言學(xué)習(xí)的文章,將以項(xiàng)目開發(fā)所需為基礎(chǔ)角寸,帶你飛速踏入Go的世界菩混,成為能獨(dú)擋一面的強(qiáng)者。當(dāng)你稍微花幾分鐘的時間扁藕,看完本文的時候沮峡,或許你會發(fā)現(xiàn),駕馭Go語言為己所用是如此簡單纹磺。
山不在高帖烘,有仙則名,水不在深橄杨,有龍則靈秘症,文不在多,一篇足以式矫。希望我這些小小的經(jīng)驗(yàn)和用心的分享乡摹,能真的幫助到您。

一采转、Go 語言簡介

1 Go 語言介紹

Go 即Golang聪廉,是Google公司2009年11月正式對外公開的一門編程語言。
Go是靜態(tài)強(qiáng)類型語言故慈,是區(qū)別于解析型語言的編譯型語言板熊。

解析型語言——源代碼是先翻譯為中間代碼,然后由解析器對代碼進(jìn)行解釋執(zhí)行察绷。
編譯型語言——源代碼編譯生成機(jī)器語言干签,然后由機(jī)器直接執(zhí)行機(jī)器碼即可執(zhí)行。

2 Go語言特性

  • 跨平臺的編譯型語言
  • 語法接近C語言
  • 管道(channel)拆撼,切片(slice)容劳,并發(fā)(routine)
  • 有垃圾回收的機(jī)制
  • 支持面向?qū)ο蠛兔嫦蜻^程的編程模式

3 Go 語言特色

  • 編程模式比較簡單,沒有復(fù)雜的設(shè)計(jì)模式
  • 全部源碼編譯到一個文件闸度,編譯速度很快
  • 最新版本也有動態(tài)庫形式竭贩,對跨語言調(diào)用的支撐更到位
  • 開源框架比較成熟,新崛起的互聯(lián)網(wǎng)公司都在用
    • 如滴滴莺禁,uber留量,百度,阿里巴巴,oppo肪获,vivo等
  • 微服務(wù)的開發(fā)模式下Go語言是新寵

4 Go 擅長領(lǐng)域

  • 服務(wù)開發(fā)寝凌,web的api開發(fā)柒傻,分布式服務(wù)集群的開發(fā)
  • 容器docker是go開源的產(chǎn)品孝赫,k8s等這些都是基于go語言的
  • 對高并發(fā)、高性能的系統(tǒng)和服務(wù)支撐红符,Go語言對比其他語言有更快的開發(fā)速度青柄,更高的開發(fā)效率
  • 獨(dú)有的語言特性和設(shè)計(jì)模式routine,channel预侯,sync包支撐了海量并行的支持致开。

所以能看到這些領(lǐng)域都在使用Go語言:微服務(wù)開發(fā)模式,api開發(fā)萎馅,rpc服務(wù)開發(fā)双戳,游戲服務(wù)開發(fā)等等

二、框架選擇

Go的開發(fā)框架比較多糜芳,比較知名的幾個分別是Gin飒货、BeeGo、Iris峭竣、Echo塘辅、Revel、Buffalo皆撩。對比排名詳見: 《Go語言Web框架對比》

框架的選擇上扣墩,本人主要遵循如下幾點(diǎn)原則,分別是:

  • star多
  • 易上手
  • 性能佳
  • 持續(xù)維護(hù)

選擇過程不多說扛吞,本人最終選擇了beego作為本次入手的框架呻惕,本文余下內(nèi)容如無特別說明,均基于此框架滥比。

三亚脆、環(huán)境部署

1 Go 語言環(huán)境安裝

安裝包下載地址為:https://golang.org/dl/
如果打不開可以使用這個地址:https://golang.google.cn/dl/

【UNIX/Linux/Mac OS X, 和 FreeBSD 安裝】

  • 下載二進(jìn)制包:go1.4.linux-amd64.tar.gz
  • 將下載的二進(jìn)制包解壓至 /usr/local目錄
    • tar -C /usr/local -xzf go1.12.7.linux-amd64.tar.gz
  • 將 /usr/local/go/bin 目錄添加至PATH環(huán)境變量:
    • export PATH=$PATH:/usr/local/go/bin

注意:MAC 系統(tǒng)下你可以使用 .pkg 結(jié)尾的安裝包直接雙擊來完成安裝,安裝目錄在 /usr/local/go/ 下守呜。
本文余下內(nèi)容如無特別說明型酥,均默認(rèn)Linux環(huán)境

【W(wǎng)indows 系統(tǒng)下安裝】
Windows 下可以使用 .msi 后綴(在下載列表中可以找到該文件,如go1.12.7.windows-amd64.msi)的安裝包來安裝查乒。
默認(rèn)情況下.msi文件會安裝在 c:\Go 目錄下弥喉。你可以將 c:\Go\bin 目錄添加到 PATH 環(huán)境變量中。
添加后你需要重啟命令窗口才能生效玛迄。

安裝測試:
創(chuàng)建工作目錄 C:\>Go_WorkSpace由境。

//test.go 文件代碼:
package main

import "fmt"

func main() {
   fmt.Println("Hello, World!")
}

使用 go 命令執(zhí)行以上代碼輸出結(jié)果如下:

C:\Go_WorkSpace>go run test.go

Hello, World!

2 Go 語言環(huán)境變量

如下環(huán)境變量,都是Go編譯和運(yùn)行時必要的,如果安裝時沒有自動設(shè)置好虏杰,請務(wù)必自己手動添加讥蟆。

  • GOROOT: Go 安裝后的根目錄(例如:/usr/local/go)
  • GOPATH: Go 的工作空間,就是我們的開發(fā)和依賴包的目錄(例如:~/go)

添加環(huán)境變量方法:

  1. 命令行直接export(對當(dāng)前終端有效)
  2. 將export加到~/.bashrc文件里(對當(dāng)前用戶有效纺阔,首次添加后記得source ~/.bashrc一下)
export GOPATH=~/go
export GOROOT=/usr/local/go

3 BeeGo 框架

【安裝】
安裝方式非常簡單瘸彤,只要敲擊如下命令即可(bee工具可以快速創(chuàng)建項(xiàng)目和自動監(jiān)測運(yùn)行,記得要安裝哦):

go get github.com/astaxie/beego
# bee 工具
go get github.com/beego/bee

安裝完之后笛钝,bee 可執(zhí)行文件默認(rèn)存放在 $GOPATH/bin 里面质况,所以您需要把 $GOPATH/bin 添加到您的環(huán)境變量PATH中: export PATH=$PATH:$GOPATH/bin

常見問題:

  1. git 沒有安裝,請自行安裝不同平臺的 git玻靡,如何安裝請看《Git官方安裝說明》结榄。
  2. git https 無法獲取,請配置本地的 git囤捻,關(guān)閉 https 驗(yàn)證:git config --global http.sslVerify false

【創(chuàng)建項(xiàng)目】
打開終端臼朗,進(jìn)入 $GOPATH/src 所在的目錄,用bee工具創(chuàng)建一個項(xiàng)目:bee new firstPro

【目錄結(jié)構(gòu)】
這是一個典型的 MVC 架構(gòu)的應(yīng)用蝎土,main.go 是入口文件视哑。目錄結(jié)構(gòu)如下所示:

.
├── conf
│   └── app.conf
├── controllers
│   └── default.go
├── main.go
├── models
├── routers
│   └── router.go
├── static
│   ├── css
│   ├── img
│   └── js
│       └── reload.min.js
├── tests
│   └── default_test.go
└── views
    └── index.tpl

10 directories, 7 files

【編譯運(yùn)行】
cd $GOPATH/src/firstPro進(jìn)入我們創(chuàng)建的項(xiàng)目,使用 bee run 來運(yùn)行該項(xiàng)目瘟则,這樣我們的應(yīng)用就在 8080 端口(beego 的默認(rèn)端口)跑起來了黎炉,讓我們打開瀏覽器看看效果吧:

image

4 命令使用

Go指令

  • 下載:go get
  • 編譯:go build
  • 運(yùn)行:go run

Bee工具

  • 新建項(xiàng)目:bee apibee new 分別創(chuàng)建 api應(yīng)用 和 web項(xiàng)目,位于 $GOPATH/src 目錄下
  • 運(yùn)行:bee run 該命令必須在 $GOPATH/src/appname 下執(zhí)行
  • 打包:bee pack

以上是一些常用的命令醋拧,介紹雖比較簡單慷嗜,但只要大概記住他的作用即可。
沒有把所有指令都列出來丹壕,因?yàn)槲覀儠簳r還用不到庆械,這些已經(jīng)足夠我們擺平項(xiàng)目的開發(fā)了,其他的指令和高級用法菌赖,有興趣的小伙伴們可以動動手自行查閱缭乘。

四、Go 基礎(chǔ)語法

1 變量

Go 語言變量名由字母琉用、數(shù)字堕绩、下劃線組成,其中首個字符不能為數(shù)字邑时。

【聲明變量】

  1. 使用 var 關(guān)鍵字:
    var name type
  2. 一次聲明多個變量:
    var name1, name2 type
  3. 省略var形式:
    name := value

【零值】
指定變量類型奴紧,如果沒有初始化,則變量默認(rèn)為零值晶丘。

  • 數(shù)值類型(包括complex64/128)為 0
  • 布爾類型為 false
  • 字符串為 ""(空字符串)
  • 以下幾種類型為 nil:
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

【示例】

package main
import "fmt"
func main() {
    var a string = "yisonli"
    fmt.Println(a)

    var b, c int = 1, 2
    fmt.Println(b, c)
    
    d := []int{10,9,8,7}
    fmt.Println(d)
    
    var e = map[string]int{"one":1, "two":2}
    fmt.Println(e)
}

以上示例輸出結(jié)果為:

yisonli
1 2
[10 9 8 7]
map[one:1 two:2]

2 數(shù)據(jù)類型

類型 描述
布爾型 布爾型的值只可以是常量 true 或者 false
一個簡單的例子:var b bool = true
數(shù)字類型 有符號整型: int黍氮、int8唐含、int16、int32沫浆、int64
無符號整型: uint捷枯、uint8、uint16专执、uint32淮捆、uint64
浮點(diǎn)型: float32、float64
復(fù)數(shù): complex64他炊、complex128
其他: byte (類似uint8)争剿、uintptr (存指針)已艰、rune (類似int32)
字符串類型 一串固定長度的字符連接起來的字符序列痊末,使用 UTF-8 編碼標(biāo)識 Unicode 文本
派生類型 包括:
(a) 指針類型(Pointer)
(b) 數(shù)組類型
(c) 結(jié)構(gòu)化類型(struct)
(d) Channel 類型
(e) 函數(shù)類型
(f) 切片類型
(g) 接口類型(interface)
(h) Map 類型

3 函數(shù)

Go 語言函數(shù)定義格式如下:

func function_name( [parameter list] ) [return_types] {
   函數(shù)體
}

函數(shù)定義解析:

  • func:函數(shù)聲明關(guān)鍵字
  • function_name:函數(shù)名稱
  • parameter list:參數(shù)列表
    • 參數(shù)就像一個占位符,當(dāng)函數(shù)被調(diào)用時哩掺,你可以將值傳遞給參數(shù)凿叠,這個值被稱為實(shí)際參數(shù)。
    • 參數(shù)列表指定的是參數(shù)類型嚼吞、順序盒件、及參數(shù)個數(shù)。
    • 參數(shù)是可選的舱禽,也就是說函數(shù)也可以不包含參數(shù)炒刁。
  • return_types:返回類型
    • 函數(shù)返回一列值, return_types 是該列值的數(shù)據(jù)類型。
    • 有些功能不需要返回值誊稚,這種情況下 return_types 不是必須的翔始。
  • 函數(shù)體:具體的代碼邏輯

【示例】

package main

import "fmt"

func swap(x, y string) (string, string) {
   return y, x
}

func main() {
   a, b := swap("Golang", "yisonli")
   fmt.Println(a, b)
}

以上示例輸出結(jié)果為:

yisonli Golang

4 循環(huán)

for循環(huán)是一個循環(huán)控制結(jié)構(gòu),可以執(zhí)行指定次數(shù)的循環(huán)里伯。Go語言的For循環(huán)有3種形式城瞎,具體定義如下:

// 1. 類似 C 語言的 for
for init; condition; post { }

// 2. 類似 C 的 while
for condition { }

// 3. 類似 C 的 for(;;)
for { }
  • init: 一般為賦值表達(dá)式,給控制變量賦初值疾瓮;
  • condition: 關(guān)系表達(dá)式或邏輯表達(dá)式脖镀,循環(huán)控制條件;
  • post: 一般為賦值表達(dá)式狼电,給控制變量增量或減量蜒灰。

【示例】

package main

import "fmt"

func main() {

   var b int = 7
   var a int

   numbers := [6]int{1, 2, 3, 5} 

   /* for 循環(huán) */
   for a := 0; a < 5; a++ {
      fmt.Printf("a 的值為: %d\n", a)
   }

   for a < b {
      a++
      fmt.Printf("a 的值為: %d\n", a)
   }

   for i,x:= range numbers {
      fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
   }   
}

以上示例輸出結(jié)果為:

a 的值為: 0
a 的值為: 1
a 的值為: 2
a 的值為: 3
a 的值為: 4
a 的值為: 1
a 的值為: 2
a 的值為: 3
a 的值為: 4
a 的值為: 5
a 的值為: 6
a 的值為: 7
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0

5 條件語句

語句 描述
if 語句 if 語句 由一個布爾表達(dá)式后緊跟一個或多個語句組成。
if...else 語句 if 語句 后可以使用可選的 else 語句,
else 語句中的表達(dá)式在布爾表達(dá)式為 false 時執(zhí)行肩碟。
if 嵌套語句 你可以在 if 或 else if 語句中嵌入一個或多個 if 或 else if 語句强窖。
switch 語句 switch 語句用于基于不同條件執(zhí)行不同動作。
select 語句 select 語句類似于 switch 語句腾务,但是select會隨機(jī)執(zhí)行一個可運(yùn)行的case毕骡。
如果沒有case可運(yùn)行,它將阻塞,直到有case可運(yùn)行未巫。

注意:Go 沒有三目運(yùn)算符窿撬,所以不支持 ?: 形式的條件判斷。

【示例】

package main

import "fmt"

func main() {
   /* 局部變量定義 */
   var a int = 100;
 
   /* 判斷布爾表達(dá)式 */
   if a < 20 {
       /* 如果條件為 true 則執(zhí)行以下語句 */
       fmt.Printf("a 小于 20\n" );
   } else {
       /* 如果條件為 false 則執(zhí)行以下語句 */
       fmt.Printf("a 不小于 20\n" );
   }
   fmt.Printf("a 的值為 : %d\n", a);

}

以上示例輸出結(jié)果為:

a 不小于 20
a 的值為 : 100

6 Package包

【包的定義和特性】

  • 包是結(jié)構(gòu)化代碼的一種方式
  • 每個程序都由包(通常簡稱為 pkg)的概念組成
  • 每個 Go 文件都屬于且僅屬于一個包
  • 一個包可以由許多以 .go 為擴(kuò)展名的源文件組成
  • 你必須在源文件中非注釋的第一行指明這個文件屬于哪個包叙凡,如:package main
  • 每個 Go 應(yīng)用程序都包含一個名為 main 的包
  • 通過 import 關(guān)鍵字可以將一組包鏈接在一起劈伴,在你的應(yīng)用中導(dǎo)入后方可使用

【注意事項(xiàng)】

  1. 如果你導(dǎo)入了一個包卻沒有使用它,則會在構(gòu)建程序時引發(fā)錯誤握爷,如 imported and not used: os跛璧,這正是遵循了 Go 的格言:“沒有不必要的代碼!“新啼。
  2. 可見性規(guī)則
  • 當(dāng)標(biāo)識符(包括常量追城、變量、類型燥撞、函數(shù)名座柱、結(jié)構(gòu)字段等等)以一個大寫字母開頭,如:Group1物舒,那么使用這種形式的標(biāo)識符的對象就可以被外部包的 代碼所使用(客戶端程序需要先導(dǎo)入這個包)色洞,這被稱為導(dǎo)出(像面向?qū)ο笳Z言中的 public);
  • 標(biāo)識符如果以小寫字母開頭冠胯,則對包外是不可見的火诸,但是他們在整個包的內(nèi)部是可見并且可用的(像面向?qū)ο笳Z言中的 private )。

7 類型轉(zhuǎn)換

【普通類型轉(zhuǎn)換】

type_name(expression)

其中type_name 為類型荠察,expression 為表達(dá)式置蜀。

【格式化輸出】
格式化在邏輯中非常常用。使用格式化函數(shù)割粮,要注意寫法:

fmt.Sprintf(格式化樣式, 參數(shù)列表…)
  • 格式化樣式:字符串形式盾碗,格式化動詞以%開頭。
  • 參數(shù)列表:多個參數(shù)以逗號分隔舀瓢,個數(shù)必須與格式化樣式中的個數(shù)一一對應(yīng)廷雅,否則運(yùn)行時會報(bào)錯。

【json轉(zhuǎn)換】
這里說的json其實(shí)是json格式的string字符串類型京髓,通常json需要轉(zhuǎn)換成struct結(jié)構(gòu)體航缀、或者轉(zhuǎn)換成map映射,正向轉(zhuǎn)換和反向轉(zhuǎn)換其實(shí)會經(jīng)常用到堰怨。
在此芥玉,我們需要借助Go的標(biāo)準(zhǔn)庫 "encoding/json" 來幫我們完成轉(zhuǎn)換的操作了。

// struct或map 轉(zhuǎn)成 json字符串
str, err := json.Marshal(object)

// json字符串 轉(zhuǎn)成 struct或map
err := json.Unmarshal([]byte(str), &object)

擴(kuò)展:網(wǎng)上有些Go的開發(fā)小伙伴們在抱怨說標(biāo)準(zhǔn)庫的json效率比較低备图,而且有幾個不錯的開源json庫可以提升2-3倍的轉(zhuǎn)換性能灿巧。該如何取舍就全憑個人喜好了赶袄,有興趣的小伙伴們可以自行去深入了解,本文就不再展開了抠藕。

【示例】

package main 
import ( 
    "encoding/json" 
    "fmt" 
) 
func main () {
    // 1. 普通類型轉(zhuǎn)換
    var sum int = 17
    var count int = 5
    var mean float32
    
    mean = float32(sum)/float32(count)
    fmt.Printf("mean 的值為: %f\n",mean)
    fmt.Println()

    // 2. 格式化
    title := fmt.Sprintf("已采集%d個藥草, 還需要%d個完成任務(wù)饿肺。", sum, count)
    fmt.Println(title)
    fmt.Println()

    // 3. json字符串 轉(zhuǎn)成 struct或map
    var jsonBlob = [] byte (`[
        { "Name" : "Platypus" , "Order" : "Monotremata" } , 
        { "Name" : "Quoll" ,     "Order" : "Dasyuromorphia" } 
    ]`) 
    type Animal struct {
        Name  string 
        Order string 
    } 
    var animals [] Animal
    err := json.Unmarshal(jsonBlob, &animals) 
    if err != nil { 
        fmt.Println("error:", err) 
    } 
    fmt.Printf("%+v\n", animals) 

    var animalsMap []map[string]interface{}
    err1 := json.Unmarshal(jsonBlob, &animalsMap) 
    if err1 != nil { 
        fmt.Println("error:", err1) 
    } 
    fmt.Printf("%+v\n", animalsMap) 
    fmt.Println()

    // 4. struct或map 轉(zhuǎn)成 json字符串
    type ColorGroup struct { 
        ID     int 
        Name   string 
        Colors [] string 
    } 
    group := ColorGroup { 
        ID :     1 , 
        Name :   "Reds" , 
        Colors : [] string {"Crimson", "Red", "Ruby", "Maroon"} , 
    } 
    groupStr , err2 := json.Marshal(group) 
    if err2 != nil { 
        fmt.Println("error:", err2) 
    } 
    fmt.Printf("%s\n", groupStr) 

    var groupMap = map[string]interface{} {"ID":1,"Name":"Reds","Colors":[] string {"Crimson", "Red", "Ruby", "Maroon"}}
    groupStr1 , err3 := json.Marshal(groupMap) 
    if err3 != nil { 
        fmt.Println("error:", err3) 
    } 
    fmt.Printf("%s\n", groupStr1) 
}

以上示例輸出結(jié)果為:

mean 的值為: 3.400000

已采集17個藥草, 還需要5個完成任務(wù)。

[{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
[map[Name:Platypus Order:Monotremata] map[Name:Quoll Order:Dasyuromorphia]]

{"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
{"Colors":["Crimson","Red","Ruby","Maroon"],"ID":1,"Name":"Reds"}

五盾似、項(xiàng)目開發(fā) - 必備知識點(diǎn)

根據(jù)項(xiàng)目難易程度敬辣,需要掌握的技術(shù)知識點(diǎn)會有所不同;本文會根據(jù)項(xiàng)目中常用的零院、基礎(chǔ)的溉跃、必須掌握的功能點(diǎn),逐個進(jìn)行講解和示例告抄,希望我這些小小的經(jīng)驗(yàn)和用心的分享撰茎,能真的幫助到你,讓你在Go的世界里翱翔玄妈。

1 BeeGo的執(zhí)行過程

image
  • main 函數(shù)是入口函數(shù)
  • main 引入了一個路由包:_ "firstPro/routers"
  • 路由包執(zhí)行了路由注冊乾吻,定位了對應(yīng)的 Controller:beego.Router("/", &controllers.MainController{})
  • Controller 負(fù)責(zé)完成具體的業(yè)務(wù)邏輯

2 路由配置

BeeGo支持的路由配置方式有很多,但正因?yàn)槎嗨远虝r間理解起來會比較難拟蜻,所以這里只列出了大致的分類,以及最為基本的配置方式枯饿。先把簡單的理解了酝锅,如果個人確實(shí)有額外特殊的需求,再另行深入奢方,也會清晰得多搔扁。

【基礎(chǔ)路由】
普通的Get和Post,參見如下代碼:

beego.Get("/",func(ctx *context.Context){
     ctx.Output.Body([]byte("this is get"))
})

beego.Post("/save",func(ctx *context.Context){
     ctx.Output.Body([]byte("this is post"))
})

【RESTful路由】

// 全匹配, 自動找Controller內(nèi)對應(yīng)REST的方法
beego.Router("/", &controllers.MainController{})

// 自定義規(guī)則, 第三個參數(shù)就是用來設(shè)置對應(yīng) method 到函數(shù)名
beego.Router("/api/list",&RestController{},"*:List")
beego.Router("/api/create",&RestController{},"post:Create")

【注解路由】
用戶無需在 router 中注冊路由蟋字,只需要 Include 相應(yīng)地 controller稿蹲,然后在 controller 的 method 方法上面寫上 router 注釋(// @router)就可以了。

func init() {
    ns :=
        beego.NewNamespace("/v1",
            beego.NSNamespace("/customer",
                beego.NSInclude(
                    &controllers.CustomerController{},
                    &controllers.CustomerCookieCheckerController{},
                ),
            ),
            beego.NSNamespace("/cms",
                beego.NSInclude(
                    &controllers.CMSController{},
                ),
            ),
        )
    beego.AddNamespace(ns)
}

注意:為了生成swagger自動化文檔鹊奖,只支持 Namespace+Include 寫法的解析苛聘,其他寫法函數(shù)不會自動解析,而且只支持二級解析(一級版本號忠聚,二級分別表示應(yīng)用模塊)

3 請求數(shù)據(jù)處理

【參數(shù)獲取】
我們經(jīng)常需要獲取用戶傳遞的數(shù)據(jù)设哗,包括 Get、POST 等方式的請求两蟀,beego 里面會自動解析這些數(shù)據(jù)网梢,你可以通過如下方式獲取數(shù)據(jù):

GetString(key string) string
GetStrings(key string) []string
GetInt(key string) (int64, error)
GetBool(key string) (bool, error)
GetFloat(key string) (float64, error)

【解析到Struct】
適用于Form表單提交的形式,使用方法也很簡單赂毯,先定義個結(jié)構(gòu)體战虏,然后調(diào)用 this.ParseForm(結(jié)構(gòu)體指針) 即可拣宰。

注意:

  • 定義 struct 時,字段名后如果有 form 這個 tag烦感,則會以把 form 表單里的 name 和 tag 的名稱一樣的字段賦值給這個字段徐裸,否則就會把 form 表單里與字段名一樣的表單內(nèi)容賦值給這個字段。
  • 調(diào)用 ParseForm 這個方法的時候啸盏,傳入的參數(shù)必須為一個 struct 的指針重贺,否則對 struct 的賦值不會成功并返回 xx must be a struct pointer 的錯誤。
  • 如果要忽略一個字段回懦,有兩種辦法气笙,一是:字段名小寫開頭,二是:form 標(biāo)簽的值設(shè)置為 -

【原始請求數(shù)據(jù)】

  1. 在配置文件 app.conf 里設(shè)置 copyrequestbody = true
  2. 在 Controller 中使用 this.Ctx.Input.RequestBody 獲取

更多其他的 request 的信息怯晕,用戶可以通過 this.Ctx.Request 獲取

【json參數(shù)返回】
在 Controller 中給 this.Data["json"] 賦值潜圃, 然后調(diào)用 this.ServeJSON() 即可

【示例】

package controllers

import (
    "github.com/astaxie/beego"
    "fmt"
)

type MainController struct {
    beego.Controller
}

type user struct {
    Id    int         `form:"-"`
    Name  interface{} `form:"username"`
    Age   int         `form:"age"`
    Email string
}

func (this *MainController) Post() {
    email := this.GetString("Email")
    fmt.Println(email)

    body := this.Ctx.Input.RequestBody
    fmt.Printf("%s\n", string(body))
    
    u := user{}
    if err := this.ParseForm(&u); err != nil {
        //handle error
        this.Data["json"] = map[string]interface{}{"code":-1, "message":"ParseForm fail", "result":err}
   } else {
           fmt.Printf("%+v\n", u)
        this.Data["json"] = map[string]interface{}{"code":0, "message":"ok"}
    }
    this.ServeJSON()
    this.StopRun()
}

以上示例控制臺結(jié)果為:

// 模擬請求:curl -X POST -d "username=yisonli&age=18&Email=yisonli@vip.qq.com" "http://127.0.0.1:8080"
yisonli@vip.qq.com
username=yisonli&age=18&Email=yisonli@vip.qq.com
{Id:0 Name:yisonli Age:18 Email:yisonli@vip.qq.com}

4 數(shù)據(jù)庫

使用數(shù)據(jù)庫前需先裝好數(shù)據(jù)庫驅(qū)動,其實(shí)只要執(zhí)行 go get -u 指令即可舟茶。
已支持?jǐn)?shù)據(jù)庫驅(qū)動:

【連接數(shù)據(jù)庫】

  1. 將你需要使用的 driver 加入 import 中
import (
    _ "github.com/go-sql-driver/mysql"
    _ "github.com/lib/pq"
    _ "github.com/mattn/go-sqlite3"
)

按個人項(xiàng)目需求來選擇即可谭期,本文將以mysql為例進(jìn)行演示和說明

  1. 以自己實(shí)際的數(shù)據(jù)庫配置,初始化并連接數(shù)據(jù)庫
orm.RegisterDriver("mysql", orm.DRMySQL)
    
orm.RegisterDataBase("default", "mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&loc=Local")

注:

  1. loc=Local 是將操作數(shù)據(jù)庫的時區(qū)吧凉,設(shè)置成跟本地時區(qū)一樣隧出。
  2. 如果你想像我一樣,在本機(jī)運(yùn)行mysql的服務(wù)端阀捅,而你又恰好裝了Docker胀瞪,那么恭喜你,只需要一條指令啟動docker鏡像即可:
    docker run --name mysqlserver -e MYSQL_ROOT_PASSWORD=123456 -d -i -p 3306:3306 mysql:5.7

【原生CRUD】

package controllers

import (
    "github.com/astaxie/beego"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

type TestController struct {
    beego.Controller
}

func (this *TestController) Get() {
    orm.RegisterDriver("mysql", orm.DRMySQL)
    orm.RegisterDataBase("default", "mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&loc=Local")
    
    orm.Debug = true        //是否開啟sql調(diào)試饲鄙,開啟時可以打印所有執(zhí)行的sql語句凄诞,不設(shè)置時默認(rèn)為false
    o := orm.NewOrm()
    o.Using("default")        //如果連接了多個數(shù)據(jù)庫,此方法可以用來切換忍级,不設(shè)置時默認(rèn)是default的DataBase
    
    var maps []orm.Params
    num1, err1 := o.Raw("SELECT * FROM users").Values(&maps)
    if num1 > 0 && err1 == nil {
        for _,term := range maps{
            fmt.Printf("%+v\n",term)
        }
    }

    res2, err2 := o.Raw("INSERT INTO `users` (`name`, `age`, `email`) VALUES ('Test', 27, 'test@gmail.com')").Exec();
    if err2 == nil {
        num2, _ := res2.RowsAffected()
        fmt.Println("mysql row affected nums: ", num2)
    }
    
    res3, err3 := o.Raw("UPDATE `users` SET `age`=18 WHERE `name`='Test'").Exec()
    if err3 == nil {
        num3, _ := res3.RowsAffected()
        fmt.Println("mysql row affected nums: ", num3)
    }

    res4, err4 := o.Raw("DELETE FROM `users` WHERE `name`='Test'").Exec()
    if err4 == nil {
        num4, _ := res4.RowsAffected()
        fmt.Println("mysql row affected nums: ", num4)
    }
    
    this.Data["json"] = map[string]interface{}{"code":0, "message":"ok"}
    this.ServeJSON()
}

運(yùn)行結(jié)果:

[ORM]2019/07/17 15:30:51  -[Queries/default] - [  OK /    db.Query /     0.9ms] - [SELECT * FROM users]
map[email:Lily@qq.com id:1 name:Lily age:18]
map[id:2 name:Lucy age:20 email:Lucy@gmail.com]
map[email:Honey@foxmail.com id:3 name:Honey age:30]
[ORM]2019/07/17 15:30:51  -[Queries/default] - [  OK /     db.Exec /     6.9ms] - [INSERT INTO `users` (`name`, `age`, `email`) VALUES ('Test', 27, 'test@gmail.com')]
mysql row affected nums:  1
[ORM]2019/07/17 15:30:51  -[Queries/default] - [  OK /     db.Exec /     3.1ms] - [UPDATE `users` SET `age`=18 WHERE `name`='Test']
mysql row affected nums:  1
[ORM]2019/07/17 15:30:51  -[Queries/default] - [  OK /     db.Exec /     6.4ms] - [DELETE FROM `users` WHERE `name`='Test']
mysql row affected nums:  1
2019/07/17 15:30:51.054 [D] [server.go:2741]  |      127.0.0.1| 200 |  28.352094ms|   match| GET      /test/   r:/test

【ORM】
ORM是一個比較強(qiáng)大的功能帆谍,他可以讓我們的表結(jié)構(gòu),通過Struct定義的方式表現(xiàn)&關(guān)聯(lián)起來轴咱,方便使用汛蝙。
目前該框架仍處于開發(fā)階段,讓我們來看看官網(wǎng)的示例吧:

models.go

package main

import (
    "github.com/astaxie/beego/orm"
)

type User struct {
    Id          int
    Name        string
    Profile     *Profile   `orm:"rel(one)"` // OneToOne relation
    Post        []*Post `orm:"reverse(many)"` // 設(shè)置一對多的反向關(guān)系
}

type Profile struct {
    Id          int
    Age         int16
    User        *User   `orm:"reverse(one)"` // 設(shè)置一對一反向關(guān)系(可選)
}

type Post struct {
    Id    int
    Title string
    User  *User  `orm:"rel(fk)"`    //設(shè)置一對多關(guān)系
    Tags  []*Tag `orm:"rel(m2m)"`
}

type Tag struct {
    Id    int
    Name  string
    Posts []*Post `orm:"reverse(many)"`
}

func init() {
    // 需要在init中注冊定義的model
    orm.RegisterModel(new(User), new(Post), new(Profile), new(Tag))
}

main.go

package main

import (
    "fmt"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
)

func init() {
    orm.RegisterDriver("mysql", orm.DRMySQL)

    orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8")
}

func main() {
    o := orm.NewOrm()
    o.Using("default") // 默認(rèn)使用 default嗦玖,你可以指定為其他數(shù)據(jù)庫

    profile := new(Profile)
    profile.Age = 30

    user := new(User)
    user.Profile = profile
    user.Name = "slene"

    fmt.Println(o.Insert(profile))
    fmt.Println(o.Insert(user))
}

BeeGo還封裝了很多高級的查詢方法患雇,有興趣的小伙伴可以額外深入了解一下;因?yàn)槠邢抻畲欤@里就不再展開了苛吱。

5 Http請求

httplib 庫主要用來模擬客戶端發(fā)送 HTTP 請求,類似于 Curl 工具器瘪,支持 JQuery 類似的鏈?zhǔn)讲僮鳌?/p>

// 首先導(dǎo)入包
import (
    "github.com/astaxie/beego/httplib"
)

// 然后初始化請求方法翠储,返回對象
req := httplib.Get("http://yyeer.com/")
    
// 超時時間绘雁、Header頭都可以按需設(shè)置
req.SetTimeout(100 * time.Second, 30 * time.Second)
req.Header("Host","yyeer.com")
    
// 然后我們就可以獲取數(shù)據(jù)了
str, err := req.String()
if err != nil {
    fmt.Println(err)
}
fmt.Println(str)

【支持的方法對象】

  • Get(url string)
  • Post(url string)
  • Put(url string)
  • Delete(url string)
  • Head(url string)

【獲取返回結(jié)果】

  • 返回 Response 對象,req.Response() 方法
    • 這個是 http.Response 對象援所,用戶可以自己讀取 body 的數(shù)據(jù)等庐舟。
  • 返回 bytes, req.Bytes() 方法
  • 返回 string,req.String() 方法
  • 保存為文件住拭,req.ToFile(filename) 方法
  • 解析為 JSON 結(jié)構(gòu)挪略,req.ToJSON(&result) 方法
  • 解析為 XML 結(jié)構(gòu),req.ToXml(&result) 方法

6 Swagger文檔

BeeGo框架內(nèi)自動集成了swagger模塊滔岳,要使得文檔工作杠娱,你需要做幾個事情,

  1. 第一開啟應(yīng)用內(nèi)文檔開關(guān)谱煤,在配置文件中設(shè)置:EnableDocs = true,
  2. 然后在你的 main.go 函數(shù)中引入 _ "beeapi/docs"(beego 1.7.0 之后版本不需要添加該引用)摊求。
  3. 這樣你就已經(jīng)內(nèi)置了 docs 在你的 API 應(yīng)用中,然后你就使用 bee run -gendoc=true -downdoc=true, 讓我們的 API 應(yīng)用跑起來
  • -gendoc=true 表示每次自動化的 build 文檔刘离,
  • -downdoc=true 就會自動的下載 swagger 文檔查看器
  1. 最后室叉,router 和 controller 內(nèi)的配置和注釋按照規(guī)范編寫,即可

【全局注釋】
必須設(shè)置在 routers/router.go 中硫惕,文件的注釋茧痕,最頂部:

// @APIVersion 1.0.0
// @Title mobile API
// @Description mobile has every tool to get any job done, so codename for the new mobile APIs.
// @Contact astaxie@gmail.com
package routers

全局的注釋如上所示,是顯示給全局應(yīng)用的設(shè)置信息疲憋,有如下這些設(shè)置

  • @APIVersion
  • @Title
  • @Description
  • @Contact
  • @TermsOfServiceUrl
  • @License
  • @LicenseUrl

【應(yīng)用注釋】

// CMS API
type CMSController struct {
    beego.Controller
}

// @Title getStaticBlock
// @Description get all the staticblock by key
// @Param   key     path    string  true        "The email for login"
// @Success 200 {object} models.ZDTCustomer.Customer
// @Failure 400 Invalid email supplied
// @Failure 404 User not found
// @router /staticblock/:key [get]
func (c *CMSController) StaticBlock() {

}

首先是 CMSController 定義上面的注釋凿渊,這個是用來顯示這個模塊的作用。

接下來就是每一個函數(shù)上面的注釋缚柳,這里列出來支持的各種注釋:

  • @Title
    • 這個 API 所表達(dá)的含義,是一個文本搪锣,空格之后的內(nèi)容全部解析為 title
  • @Description
    • 這個 API 詳細(xì)的描述秋忙,是一個文本,空格之后的內(nèi)容全部解析為 Description
  • @Param
    • 參數(shù)构舟,表示需要傳遞到服務(wù)器端的參數(shù)灰追,有五列參數(shù),使用空格或者 tab 分割狗超,五個分別表示的含義如下
    • 參數(shù)名
    • 參數(shù)類型弹澎,可以有的值是 formDataquery努咐、path苦蒿、bodyheader渗稍,formData 表示是 post 請求的數(shù)據(jù)佩迟,query 表示帶在 url 之后的參數(shù)团滥,path 表示請求路徑上得參數(shù),例如上面例子里面的 key报强,body 表示是一個 raw 數(shù)據(jù)請求灸姊,header 表示帶在 header 信息中得參數(shù)。
    • 參數(shù)類型
    • 是否必須
    • 注釋
  • @Success
    • 成功返回給客戶端的信息秉溉,三個參數(shù)力惯,三個參數(shù)必須通過空格分隔
      • 第一個是 status code。
      • 第二個參數(shù)是返回的類型召嘶,必須使用 {} 包含父晶,
      • 第三個是返回的對象或者字符串信息,如果是 {object} 類型苍蔬,那么 bee 工具在生成 docs 的時候會掃描對應(yīng)的對象诱建。
  • @Failure
    • 失敗返回的信息,包含兩個參數(shù)碟绑,使用空格分隔
      • 第一個表示 status code俺猿,
      • 第二個表示錯誤信息
  • @router
    • 路由信息,包含兩個參數(shù)格仲,使用空格分隔押袍,
      • 第一個是請求的路由地址,支持正則和自定義路由凯肋,和之前的路由規(guī)則一樣谊惭,
      • 第二個參數(shù)是支持的請求方法,放在 [] 之中,如果有多個方法侮东,那么使用 , 分隔圈盔。

7 日志

【常規(guī)使用】
首先引入包:

import (
    "github.com/astaxie/beego/logs"
)

然后添加輸出引擎:

  • 第一個參數(shù)是引擎名:
    • logs.SetLogger("console")
  • 第二個參數(shù),用來表示配置信息:
logs.SetLogger(logs.AdapterFile,`{"filename":"project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10,"color":true}`)

log 支持同時輸出到多個引擎悄雅,包括:console驱敲、file揍魂、conn柄延、smtp、es阻逮、multifile

使用方式:

beego.Emergency("this is emergency")
beego.Alert("this is alert")
beego.Critical("this is critical")
beego.Error("this is error")
beego.Warning("this is warning")
beego.Notice("this is notice")
beego.Informational("this is informational")
beego.Debug("this is debug")

【自定義格式】
如果框架自帶的日志功能還無法滿足你的需求容诬,那可能就得麻煩一點(diǎn)娩梨,自定義日志格式。

操作流程大致為:

  1. 打開要寫入的文件览徒,不存在則創(chuàng)建狈定,os.OpenFile
  2. 創(chuàng)建一個自定義的日志對象 log.New
  3. 使用時按所需格式輸出日志 Printf
File, err := os.OpenFile(logdir, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if nil != err {
    log.Fatal(err)
}
MyLog = log.New(io.MultiWriter(File,os.Stderr), "", 0)

MyLog.Printf("自己定義的格式... %s\n", 自己定義的參數(shù), fmt.Sprintf(formating, args...))

實(shí)際使用時, 可以把 MyLog.Printf 封裝成一個全局方法,這樣整個項(xiàng)目里都可使用吱殉。

8 加解密

加解密在多方對接的時候掸冤,經(jīng)常會用到厘托,所以稍微了解一點(diǎn)也是必要的。
因?yàn)檫@塊使用的人比較多稿湿,所以只要肯稍微花點(diǎn)時間铅匹,在網(wǎng)上都是可以找到答案的。

【MD5】
MD5主要是用作簽名饺藤,具體生成方法如下:

import (
    "crypto/md5"
    "encoding/hex"
)

func MyMd5(Str string, Key string) string {
    md5ctx := md5.New()
    md5ctx.Write([]byte(Str + Key))
    return hex.EncodeToString(md5ctx.Sum(nil))
}

【AES】
AES主要是用作加解密包斑,還分好幾種不同的模式如:ECB、CBC涕俗、CFB等罗丰。
網(wǎng)上找到一份看起來還不錯的《AES的加解密實(shí)現(xiàn)》,原諒我的小偷懶(還沒有在實(shí)際項(xiàng)目驗(yàn)證過)再姑,God Bless萌抵。

其他還有SHA、DES元镀、RSA等加解密方式绍填,具體就得根據(jù)各小伙伴不同項(xiàng)目所需了。
Go Go Go 栖疑!去吧讨永,你可以的!

9 緩存使用

// 首先引入包:
import (
    "github.com/astaxie/beego/cache"
)

// 然后初始化一個全局變量對象:
bm, err := cache.NewCache("memory", `{"interval":60}`)

// 然后我們就可以使用bm增刪改緩存:
bm.Put("astaxie", 1, 10*time.Second)
bm.Get("astaxie")
bm.IsExist("astaxie")
bm.Delete("astaxie")

【配置說明】

  • memory
    • 配置信息如下所示遇革,配置的信息表示 GC 的時間卿闹,表示每個 60s 會進(jìn)行一次過期清理:
    • {"interval":60}
  • file
    • 配置信息如下所示,配置 CachePath 表示緩存的文件目錄萝快,F(xiàn)ileSuffix 表示文件后綴锻霎,DirectoryLevel 表示目錄層級,EmbedExpiry 表示過期設(shè)置
    • {"CachePath":"./cache","FileSuffix":".cache","DirectoryLevel":"2","EmbedExpiry":"120"}
  • redis
    • 配置信息如下所示揪漩,redis 采用了庫 redigo:
    • {"key":"collectionName","conn":":6039","dbNum":"0","password":"thePassWord"}
      • key: Redis collection 的名稱
      • conn: Redis 連接信息
      • dbNum: 連接 Redis 時的 DB 編號. 默認(rèn)是0.
      • password: 用于連接有密碼的 Redis 服務(wù)器.
  • memcache
    • 配置信息如下所示量窘,memcache 采用了 vitess的庫,表示 memcache 的連接地址:
    • {"conn":"127.0.0.1:11211"}

【示例】

import (
    "fmt"
    "github.com/astaxie/beego"
    "github.com/astaxie/beego/cache"
    _ "github.com/astaxie/beego/cache/redis"
    "time"
)

func (this *TestController) Test() {
    bm, err := cache.NewCache("redis", `{"key":"127.0.0.1","conn":":6379","dbNum":"0","password":""}`)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("bm = %+v\n",bm)
        
    err1 := bm.Put("yisonli", 1, 10*time.Second)
    if err1 != nil {
        fmt.Println(err1)
    }
    redisValue := bm.Get("yisonli")
    fmt.Printf("redisValue = %s\n",redisValue)
    
    this.Ctx.Output.Body([]byte("OK"))
}

執(zhí)行效果:

bm = &{p:0xc000210240 conninfo::6379 dbNum:0 key:127.0.0.1 password: maxIdle:3}
redisValue = 1

注:

  1. 示例中使用了redis作為緩存氢拥,如果不依賴beego的cache模塊,redis還有很多很好用的數(shù)據(jù)類型和功能方法锨侯,如:Hash散列嫩海、List列表、Set集合囚痴、SortedSet有序集合叁怪。
  2. 本機(jī)啟動redis服務(wù),和上文啟動mysql類似深滚,只需要一條指令啟動docker鏡像即可:
    docker run --name local-redis -p 6379:6379 -v $PWD/data:/data -d redis redis-server --appendonly yes

10 Session

beego 內(nèi)置了 session 模塊奕谭,使用 session 相當(dāng)方便涣觉。

方式1、 在 main 入口函數(shù)中設(shè)置:
beego.BConfig.WebConfig.Session.SessionOn = true

方式2血柳、 通過配置文件配置:
sessionon = true

session默認(rèn)采用 memory 的方式進(jìn)行存儲官册,如果需要更換別的引擎(以redis為例),需要修改&設(shè)置如下配置:

beego.BConfig.WebConfig.Session.SessionProvider = "redis"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "127.0.0.1:6379"

【示例】

func (this *MainController) Get() {
    v := this.GetSession("asta")
    if v == nil {
        this.SetSession("asta", int(1))
        this.Data["num"] = 0
    } else {
        this.SetSession("asta", v.(int)+1)
        this.Data["num"] = v.(int)
    }
    this.Data["Website"] = "beego.me"
    this.Data["Email"] = "astaxie@gmail.com"
    fmt.Printf("%+v\n", this.Data)
    this.TplName = "index.tpl"
}

運(yùn)行后难捌,多次訪問首頁的debug輸出結(jié)果:

2019/07/17 20:25:20.235 [I] [asm_amd64.s:1333]  http server Running on http://:8080
map[RouterPattern:/ num:0 Website:beego.me Email:astaxie@gmail.com]
2019/07/17 20:25:25.141 [D] [server.go:2741]  |      127.0.0.1| 200 |    4.63737ms|   match| GET      /     r:/
map[num:1 Website:beego.me Email:astaxie@gmail.com RouterPattern:/]
2019/07/17 20:25:46.021 [D] [server.go:2741]  |      127.0.0.1| 200 |   5.116566ms|   match| GET      /     r:/
map[RouterPattern:/ num:2 Website:beego.me Email:astaxie@gmail.com]
2019/07/17 20:26:00.084 [D] [server.go:2741]  |      127.0.0.1| 200 |   1.573909ms|   match| GET      /     r:/
map[RouterPattern:/ num:3 Website:beego.me Email:astaxie@gmail.com]
2019/07/17 20:26:12.470 [D] [server.go:2741]  |      127.0.0.1| 200 |   2.652028ms|   match| GET      /     r:/

掌握了以上那么多技能膝宁,現(xiàn)在,你對項(xiàng)目的開發(fā)有多少把握了呢根吁?
其實(shí)员淫,基本已經(jīng)差不多了,剩下的就是動手實(shí)踐了击敌。

古人學(xué)問無遺力介返,少壯工夫老始成。
紙上得來終覺淺沃斤,絕知此事要躬行圣蝎。

我是Yison,如果我有幫助到你轰枝,也請你幫助一下我捅彻,隨手一個贊對你來說無足輕重,但卻是使我不斷前行的動力鞍陨!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末步淹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子诚撵,更是在濱河造成了極大的恐慌缭裆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寿烟,死亡現(xiàn)場離奇詭異澈驼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)筛武,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門缝其,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徘六,你說我怎么就攤上這事内边。” “怎么了待锈?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵漠其,是天一觀的道長。 經(jīng)常有香客問我,道長和屎,這世上最難降的妖魔是什么拴驮? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮柴信,結(jié)果婚禮上套啤,老公的妹妹穿的比我還像新娘。我一直安慰自己颠印,他們只是感情好纲岭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著线罕,像睡著了一般止潮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钞楼,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天喇闸,我揣著相機(jī)與錄音,去河邊找鬼询件。 笑死燃乍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宛琅。 我是一名探鬼主播刻蟹,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嘿辟!你這毒婦竟也來了舆瘪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤红伦,失蹤者是張志新(化名)和其女友劉穎英古,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昙读,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡召调,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛮浑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唠叛。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沮稚,靈堂內(nèi)的尸體忽然破棺而出玻墅,到底是詐尸還是另有隱情,我是刑警寧澤壮虫,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響囚似,放射性物質(zhì)發(fā)生泄漏剩拢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一饶唤、第九天 我趴在偏房一處隱蔽的房頂上張望徐伐。 院中可真熱鬧,春花似錦募狂、人聲如沸办素。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽性穿。三九已至,卻和暖如春雷滚,著一層夾襖步出監(jiān)牢的瞬間需曾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工祈远, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呆万,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓车份,卻偏偏與公主長得像谋减,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扫沼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354