go語言Gin框架教程

本文作者:陳進(jìn)堅(jiān)
個人博客:https://jian1098.github.io
CSDN博客:https://blog.csdn.net/c_jian
簡書:http://www.lxweimin.com/u/8ba9ac5706b6
聯(lián)系方式:jian1098@qq.com

下載使用

$ go get -u github.com/gin-gonic/gin
import "github.com/gin-gonic/gin"

HTTP服務(wù)

func main() {
    router := gin.Default()
    //路由
    router.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "hello gin",
        })
    })
    router.Run(":8080")
}

路由綁定

router.GET("/", index)表示用GET方式接收路由,如果路由是根目錄,那么直接執(zhí)行index控制器方法,index控制器必須含有gin.Context參數(shù),也可以向上面一樣將index控制器的內(nèi)容寫成匿名函數(shù)。

router.GET("/", index)

func index(c *gin.Context) {
    c.JSON(200, gin.H{
        "msg": "hello gin",
    })
}

路由分離

為了更好的管理路由,最好將路由和控制器分開不同的文件

在根目錄下新建router.go

package gin

import (
    "github.com/gin-gonic/gin"
    "os"
    "path/filepath"
)

func initRouter() *gin.Engine {
    //路由
    router := gin.Default()
    router.GET("/", index)
    return router
}

main方法中進(jìn)行初始化

func main() {
    router := initRouter()
    router.Run(":8080")
}

此時目錄結(jié)構(gòu)如下

--src
    --gin
        --gin.go     #用于存放控制器
        --router.go  #用于存放路由
    --main.go

路由組

一些情況下,我們會有統(tǒng)一前綴的 url 的需求,典型的如 Api 接口版本號 /v1/something。Gin 可以使用 Group 方法統(tǒng)一歸類到路由組中

func main() {
    router := gin.Default()

    // /v1/login 就會匹配到這個組
    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }
    
    // 不用花括號包起來也是可以的。上面那種只是看起來會統(tǒng)一一點(diǎn)。看你個人喜好
    v2 := router.Group("/v2")
    v2.POST("/login", loginEndpoint)
    v2.POST("/submit", submitEndpoint)
    v2.POST("/read", readEndpoint)

    router.Run(":8080")
}

異步處理

goroutine 機(jī)制可以方便地實(shí)現(xiàn)異步處理

func main() {
    r := gin.Default()
    //1. 異步
    r.GET("/long_async", func(c *gin.Context) {
        // goroutine 中只能使用只讀的上下文 c.Copy()
        cCp := c.Copy()
        go func() {
            time.Sleep(5 * time.Second)

            // 注意使用只讀上下文
            log.Println("Done! in path " + cCp.Request.URL.Path)
        }()
    })
    //2. 同步
    r.GET("/long_sync", func(c *gin.Context) {
        time.Sleep(5 * time.Second)

        // 注意可以使用原始上下文
        log.Println("Done! in path " + c.Request.URL.Path)
    })

    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

接收參數(shù)

接收GET參數(shù)

訪問鏈接: http://localhost:8080/user?firstname=jian&lastname=chen

路由

router.GET("/user", hello)

控制器 (這里用的接收方法是Query)

func hello(c *gin.Context) {
    firstname := c.DefaultQuery("firstname", "Guest") //設(shè)置默認(rèn)參數(shù)值
    lastname := c.Query("lastname")                   //獲取參數(shù)值,c.Request.URL.Query().Get("lastname")的縮寫
    c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
}

或者

訪問鏈接:http://localhost:8080/user/jian/eat

路由 (參數(shù)名用:號標(biāo)記)

router.GET("/user/:name/:action", user)

或者

router.GET("/user/:name/*action", user)

上面這個寫法將會匹配/user/:name/開頭的所有路由

控制器 (這里的接收方法是Param)

func user(c *gin.Context) {
    name := c.Param("name")
    action := c.Param("action")
    message := name + " is " + action
    c.String(http.StatusOK, message)
}

接收POST參數(shù)

訪問鏈接:http://localhost:8080/post

路由

router.POST("/post", post)

控制器

func post(c *gin.Context) {
    name := c.PostForm("name")
    age := c.PostForm("age")
    fmt.Printf("name: %s, age: %s;", name, age)
}

文件上傳

單文件上傳

路由

router.POST("/upload", upload)

控制器

func upload(c *gin.Context) {
    file, _ := c.FormFile("file") //表單的文件name="file"
    //文件上傳路徑和文件名
    c.SaveUploadedFile(file, "./upload/"+file.Filename)
    c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
}

多文件上傳

注意多文件上傳表單<form>標(biāo)簽需要注明屬性enctype="multipart/form-data" method="post"<input>標(biāo)簽的name屬性值必須相同,例如全部為name="file"

路由

router.POST("/multiupload", multiupload)

控制器

func multiupload(c *gin.Context) {
    // 文件上傳大小限制 8 MB,在路由注冊時設(shè)定
    //router.MaxMultipartMemory = 8 << 20
    formdata := c.Request.MultipartForm
    files := formdata.File["file"] //表單的文件name="file"
    for i, _ := range files {
        file, err := files[i].Open()
        defer file.Close()
        if err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("get file err: %s", err.Error()))
            return
        }
        //文件上傳路徑和文件名
        out, err := os.Create("./upload/" + files[i].Filename)
        defer out.Close()
        if err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("upload err: %s", err.Error()))
            return
        }
        _, err = io.Copy(out, file)
        if err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("save file err: %s", err.Error()))
            return
        }
        c.String(http.StatusOK, "upload successful")
    }
}

視圖模板

  • 目錄結(jié)構(gòu)

    在根目錄下新建templates文件夾用于存放html頁面,為了便于管理在templates目錄下再創(chuàng)建一個index文件夾存放與index控制器相關(guān)的頁面,在index目錄下新建index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>
        {{.title}}
    </h1>
</body>
</html>

此時目錄結(jié)構(gòu)如下

- src
    --gin
        --main.go
        --router.go
        --templates
            --index
                --index.html

加載視圖

在初始化路由的位置加載所有視圖模板,其中**表示各個控制器或路由組對應(yīng)的視圖目錄,*表示該目錄下所有文件

router := gin.Default()
//加載模板
router.LoadHTMLGlob(filepath.Join(os.Getenv("GOPATH"), "./src/gin/templates/**/*"))
router.GET("/", index)

控制器綁定視圖

func index(c *gin.Context) {
    c.HTML(http.StatusOK, "index.html", gin.H{
        "title": "Main website",
    })
}

靜態(tài)文件

網(wǎng)頁開發(fā)離不開css和圖片等靜態(tài)資源文件,我們必須設(shè)置好路徑才能正確訪問,例如我的目錄結(jié)構(gòu)為(縮進(jìn)表示二級目錄)

--src
    --gin
        --static
            --css
                --index.css
        --templates
            --index
                --index.html
        --router.go
        --main.go

如果index.html文件需要引入index.css文件,則在路由申請的地方聲明

//加載模板
router.LoadHTMLGlob(filepath.Join(os.Getenv("GOPATH"), "./src/gin/templates/**/*"))
//加載靜態(tài)文件
router.Static("/static", filepath.Join(os.Getenv("GOPATH"), "./src/gin/static"))

然后index.css文件中這樣調(diào)用就可以了,其他靜態(tài)資源用法類似

<link rel="stylesheet" href="/static/css/index.css">

參數(shù)傳遞

在控制器傳遞參數(shù)

c.HTML(http.StatusOK, "index.html", gin.H{
    "title": "Main website",
})

在視圖渲染參數(shù)

<h1>
    {{.title}}
</h1>

重定向

r.GET("/redirect", func(c *gin.Context) {
    //支持內(nèi)部和外部的重定向
    c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})

中間件

使用中間件

router := gin.Default()
router.Use(middleware.IPLimit()) //使用自定義中間件:IP驗(yàn)證

中間件實(shí)現(xiàn)

package middleware

import (
    "core"
    "github.com/gin-gonic/gin"
    "net/http"
    "strings"
)

//訪問ip限制
func IPLimit() gin.HandlerFunc {
    return func(c *gin.Context) {
        ip := c.ClientIP()
        ipList := strings.Split(core.Config["allow_ip"], "|")
        flag := false
        for i := 0; i < len(ipList); i++ {
            if ip == ipList[i] {
                flag = true
                break
            }
        }

        if !flag {
            c.Abort()
            c.JSON(http.StatusUnauthorized, gin.H{
                "code": 0,
                "msg":  "IP " + ip + " 沒有訪問權(quán)限",
                "data": nil})
            return // return也是可以省略的,執(zhí)行了abort操作,會內(nèi)置在中間件defer前,return,寫出來也只是解答為什么Abort()之后,還能執(zhí)行返回JSON數(shù)據(jù)
        } 
    }
}

數(shù)據(jù)綁定和驗(yàn)證

使用 c.ShouldBind方法,可以將參數(shù)自動綁定到 struct,該方法是會檢查 Url 查詢字符串和 POST 的數(shù)據(jù),而且會根據(jù) content-type類型,優(yōu)先匹配JSON或者 XML,之后才是 Form。數(shù)據(jù)綁定可以用來做數(shù)據(jù)驗(yàn)證,例如

路由

router.POST("/binding", binding)

控制器

//數(shù)據(jù)結(jié)構(gòu)體,username為表單字段,required表示必須參數(shù),可選的話binding留空即可
type Login struct {
    Username string `form:"username" binding:"required"`
    Password string `form:"password" binding:"required"`
}

//控制器
func binding(c *gin.Context) {
    var form Login
    // 驗(yàn)證數(shù)據(jù)并綁定
    if err := c.ShouldBind(&form); err == nil {
        if form.Username == "manu" && form.Password == "123" {
            c.JSON(http.StatusOK, gin.H{"msg": "Login successfully"})
        } else {
            c.JSON(http.StatusUnauthorized, gin.H{"msg": "username or password error"})
        }
    } else {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
}
  • 當(dāng)沒有接收到參數(shù)時返回

    {
        "error": "Key: 'Login.Username' Error:Field validation for 'Username' failed on the 'required' tag"
    }
    
  • 當(dāng)參數(shù)錯誤時返回

    {
        "msg": "username or password error"
    }
    
  • 當(dāng)接收到參數(shù)且正確時放回

    {
        "msg": "Login successfully"
    }
    

    不用在接收參數(shù)時用if逐個進(jìn)行驗(yàn)證,除了binding:"required"屬性外還有更多的校驗(yàn)規(guī)則,可以參考 https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags

日志

自帶日志

Gin日志默認(rèn)只在控制臺顯示,如果要寫入文件需要在main方法中聲明

gin.DisableConsoleColor()   //關(guān)掉控制臺顏色,可省略
f, _ := os.Create("gin.log")    //日志文件
//gin.DefaultWriter = io.MultiWriter(f) //將日志寫入文件
gin.DefaultWriter = io.MultiWriter(f, os.Stdout) //將日志寫入文件同時在控制臺輸出

Logrus 日志庫

Gin自帶的日志系統(tǒng)只支持簡單的功能,需要更強(qiáng)大的功能還需要用到第三方日志庫,這里選擇github上面star最多的Logrus

下載

go get github.com/sirupsen/logrus

使用

package main

import (
    log "github.com/Sirupsen/logrus"
)

func main() {
    log.Trace("Something very low level.")
    log.Debug("Useful debugging information.")
    log.Info("Something noteworthy happened!")
    log.Warn("You should probably take a look at this.")
    log.Error("Something failed but I'm not quitting.")
    // Calls os.Exit(1) after logging
    log.Fatal("Bye.")
    // Calls panic() after logging
    log.Panic("I'm bailing.")
}

數(shù)據(jù)庫

Gin框架沒有自帶的數(shù)據(jù)庫封裝,導(dǎo)入的數(shù)據(jù)庫驅(qū)動由開發(fā)使用的數(shù)據(jù)庫類型決定,例如開發(fā)使用mysql就直接import _ "github.com/go-sql-driver/mysql";但是訪問數(shù)據(jù)庫都是直接用寫 sql,取出結(jié)果然后自己拼成對象,使用上面不是很方便,可讀性也不好。這里使用目前githubstar數(shù)量最多的https://github.com/jinzhu/gorm

gorm的詳細(xì)教程參考http://gorm.book.jasperxu.com/models.html#md

下載

go get -u github.com/jinzhu/gorm

連接mysql

package gin

import (
    "github.com/jinzhu/gorm"
)

func Index() {
    db, err := gorm.Open("mysql", "root:root@(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        panic(err)
    }
    defer db.Close()
}

數(shù)據(jù)表

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "time"
)

//定義數(shù)據(jù)表模型
type User struct {
    Id      uint      `gorm:"primary_key;AUTO_INCREMENT"` // 自增主鍵
    Name    string    `gorm:"size:255"`                   // string默認(rèn)長度為255, 使用這種tag重設(shè)。
    Address string    `gorm:"not null;unique"`            // 設(shè)置字段為非空并唯一
    Addtime time.Time `gorm:"default:'2019-03-11 13:19:40'"` //默認(rèn)值
}

func Index() {
    //連接數(shù)據(jù)庫
    db, err := gorm.Open("mysql", "root:root@(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        panic(err)
    }
    //關(guān)閉數(shù)據(jù)庫
    defer db.Close()

    //設(shè)置默認(rèn)表名前綴
    gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
        return "test_" + defaultTableName
    }

    //創(chuàng)建表
    if !db.HasTable(&User{}) { //檢查表是否存在
        if err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").CreateTable(&User{}).Error; err != nil {
            panic(err)
        }
    }

    // 刪除表users
    db.DropTable("test_users")
        
    // 修改模型`User`的"addtime"列類型
    db.Model(&User{}).ModifyColumn("addtime", "text")

    // 刪除模型`User`的addtime列
    db.Model(&User{}).DropColumn("addtime")
    
    // 為`name`列添加索引`idx_user_name`
    db.Model(&User{}).AddIndex("idx_user_name", "name")
    
    // 刪除索引
    db.Model(&User{}).RemoveIndex("idx_user_name")
}

添加記錄

//添加記錄
user := User{Name: "jian", Address: "1234", Addtime: time.Now()}
if err := db.Create(&user).Error; err != nil {
panic(err)
}

查詢數(shù)據(jù)

無條件查詢
// 獲取第一條記錄,按主鍵排序
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 獲取最后一條記錄,按主鍵排序
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// 獲取所有記錄
db.Find(&users)
// SELECT * FROM users;

// 使用主鍵獲取記錄
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
Where查詢條件
user := User{}

// 獲取第一個匹配記錄
db.Where("name = ?", "jinzhu").First(&user)
fmt.Println(user.Name)  //讀取數(shù)據(jù)
// SELECT * FROM users WHERE name = 'jinzhu' limit 1;

// 獲取最后一條記錄,按主鍵排序
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// 獲取所有匹配記錄
db.Where("name = ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu';

db.Where("name <> ?", "jinzhu").Find(&users)

// IN
db.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)

db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)

// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// 主鍵的Slice
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
Not條件查詢
db.Not("name", "jinzhu").First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;

// Not In
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3);

db.Not([]int64{}).First(&user)
// SELECT * FROM users;

// Plain SQL
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT(name = "jinzhu");

// Struct
db.Not(User{Name: "jinzhu"}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu";
Or條件查詢
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users)
指定字段和表
db.Select("name, age").Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
Order條件查詢
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
Limit條件查詢
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
Offset條件查詢

指定在開始返回記錄之前要跳過的記錄數(shù)

db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
Count條件查詢
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
多條件查詢
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jinzhu").Find(&users)
db.Where("name <> ?","jinzhu").Where("age >= ? and role <> ?",20,"admin").Find(&users)

更新數(shù)據(jù)

 // 使用組合條件更新單個屬性
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

// 使用`map`更新多個屬性,只會更新這些更改的字段
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})

// 使用`struct`更新多個屬性,只會更新這些更改的和非空白字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})

刪除數(shù)據(jù)

db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
// DELETE from emails where email LIKE "%jinhu%";

db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinhu%";

執(zhí)行原生SQL語句

Scan()是將結(jié)果掃描到另一個結(jié)構(gòu)中。

db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now, []int64{11,22,33})

db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
row.Scan(&name, &age)

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
    ...
    rows.Scan(&name, &age, &email)
    ...
}

連接池

db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)

鎖行

注意:加行鎖的表必須是InnoDB并且要加索引,否則無效;語句必須在事務(wù)里面,必須提交或回滾

// 為Select語句添加擴(kuò)展SQL選項(xiàng)
db.Set("gorm:query_option", "FOR UPDATE").First(&user, 10)
// SELECT * FROM users WHERE id = 10 FOR UPDATE;

鎖表

db.Exec("LOCK TABLES real_table WRITE, insert_table WRITE;")  //鎖定real_table和insert_table表
UNLOCK TABLES;  //解鎖

日志

// 啟用Logger,顯示詳細(xì)日志
db.LogMode(true)

// 禁用日志記錄器,不顯示任何日志
db.LogMode(false)

// 調(diào)試單個操作,顯示此操作的詳細(xì)日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})

//自定義日志
db.SetLogger(gorm.Logger{revel.TRACE})
db.SetLogger(log.New(os.Stdout, "\r\n", 0))

事務(wù)

// 開始事務(wù)
tx := db.Begin()
// 注意,一旦你在一個事務(wù)中,使用tx作為數(shù)據(jù)庫句柄,而不再是上面的db
// 在事務(wù)中做一些數(shù)據(jù)庫操作(從這一點(diǎn)使用'tx',而不是'db')
tx.Create(...)

// ...

// 發(fā)生錯誤時回滾事務(wù)
tx.Rollback()

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