快速上手go-doudou開發(fā)gRPC服務(wù)

image.png

go-doudou從v2版本開始已經(jīng)支持開發(fā)gRPC服務(wù)。開發(fā)流程跟v1版本是一致的吃既,都是先在svc.go文件里的interface里定義方法渺杉,然后執(zhí)行g(shù)o-doudou代碼生成命令生成代碼,最后你再寫自定義業(yè)務(wù)邏輯實現(xiàn)接口砚偶。go-doudou的學(xué)習(xí)曲線非常平滑批销,對新手極其友好,特別是具有其他編程語言開發(fā)背景的開發(fā)者染坯,比如從Java均芽、Nodejs或是Python轉(zhuǎn)過來的。go-doudou非常易學(xué)单鹿,但是卻能帶來極高的生產(chǎn)力掀宋。

本文中我將通過一個小demo來向各位同學(xué)展示開發(fā)流程是什么樣的,同時也提供了一些最佳實踐仲锄。我們將采用go-doudou開發(fā)一個通過最大余額法算法解決計算占比不等于100%的問題的gRPC服務(wù)劲妙,然后通過測試看一下效果。完整代碼托管在github儒喊。

準(zhǔn)備

安裝go

go-doudou僅支持go 1.16及以上版本镣奋。

安裝gRPC編譯器和插件

安裝編譯器protoc

安裝Protobuf編譯器protoc,可參考官方文檔怀愧,這里貼一下常見操作系統(tǒng)下的安裝命令:

  • Ubuntu系統(tǒng):
$ apt install -y protobuf-compiler
$ protoc --version  # 確保安裝v3及以上版本
  • Mac系統(tǒng)侨颈,需要先安裝Homebrew:
$ brew install protobuf
$ protoc --version  # 確保安裝v3及以上版本

安裝插件

  1. 安裝插件:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
  1. 配置環(huán)境變量:
$ export PATH="$PATH:$(go env GOPATH)/bin"

最新版本號請?zhí)D(zhuǎn) https://grpc.io/docs/languages/go/quickstart/ 查找蛤奢。

安裝go-doudou

  • 如果Go版本低于1.17
go get -v github.com/unionj-cloud/go-doudou/v2@v2.0.3
  • 如果Go版本 >= 1.17鬼癣,推薦采用如下命令全局安裝go-doudou命令行工具
go install -v github.com/unionj-cloud/go-doudou/v2@v2.0.3

推薦采用如下命令下載go-doudou作為項目的依賴

go get -v -d github.com/unionj-cloud/go-doudou/v2@v2.0.3

如果遇到410 Gone error報錯陶贼,請先執(zhí)行如下命令,再執(zhí)行上述的安裝命令

export GOSUMDB=off

安裝完成后待秃,如果遇到go-doudou: command not found報錯拜秧,請將$HOME/go/bin配置到~/.bash_profile文件里,例如:

# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:/usr/local/go/bin
PATH=$PATH:$HOME/go/bin

export PATH

我們可以執(zhí)行以下命令確認(rèn)一下安裝是否成功:

?  go-doudou -v
go-doudou version v2.0.3

初始化項目

做完準(zhǔn)備工作我們就可以開始coding了章郁。首先我們需要初始化項目枉氮。

go-doudou svc init go-stats -m go-doudou-tutorials/go-stats

go-stats是項目的根路徑,go-doudou會逐層創(chuàng)建文件夾暖庄,底層類似執(zhí)行unix命令mkdir -p 聊替。-m 可以設(shè)置模塊名,這里是go-doudou-tutorials/go-stats

go-doudou會生成以下的項目結(jié)構(gòu)培廓。

?  go-stats git:(master) ? tree -L 2
.
├── Dockerfile
├── go.mod
├── svc.go
└── vo
    └── vo.go

1 directory, 4 files

svc.go文件里已經(jīng)聲明了一個接口惹悄,用于定義方法,go-doudou會通過這些方法提供的信息來生成proto文件里的rpc代碼肩钠。vo包是用來定義go語言結(jié)構(gòu)體的泣港,go-doudou會掃描整個包里聲明的所有結(jié)構(gòu)體來生成proto文件里的message代碼。

定義服務(wù)

下面我們來看一下svc.go文件价匠。

/**
* Generated by go-doudou v2.0.3.
* You can edit it as your need.
*/
package service

import (
    "context"
    "go-doudou-tutorials/go-stats/vo"
)

//go:generate go-doudou svc http -c
//go:generate go-doudou svc grpc

type GoStats interface {
    // You can define your service methods as your need. Below is an example.
    // You can also add annotations here like @role(admin) to add meta data to routes for 
    // implementing your own middlewares
    PageUsers(ctx context.Context, query vo.PageQuery) (data vo.PageRet, err error)
}

有兩個//go:generate 指令当纱,分別是用來生成RESTful和gRPC服務(wù)代碼的,主要是為了方便執(zhí)行代碼生成命令踩窖。如果你用goland的話坡氯,你可以像下面的截圖所示那樣操作界面執(zhí)行命令。

image.png

PageUsers是一個示例毙石,我們刪掉它寫上我們的方法廉沮。

/**
* Generated by go-doudou v2.0.3.
* You can edit it as your need.
 */
package service

import (
    "context"
    "go-doudou-tutorials/go-stats/vo"
)

//go:generate go-doudou svc http -c
//go:generate go-doudou svc grpc

// GoStats is a demo gRPC service developed by go-doudou
type GoStats interface {
    // LargestRemainder implements Largest Remainder Method https://en.wikipedia.org/wiki/Largest_remainder_method
    LargestRemainder(ctx context.Context, payload vo.PercentageReqVo) (data []vo.PercentageRespVo, err error)
}

我們還需要在vo包里聲明PercentageReqVo和PercentageRespVo這兩個vo結(jié)構(gòu)體。需要注意的是徐矩,GoStats接口的方法里出現(xiàn)的結(jié)構(gòu)體類型參數(shù)必須聲明在vo包里滞时,否則go-doudou獲取不到字段信息,就無法在proto文件里生成對應(yīng)的message滤灯。

/**
* Generated by go-doudou v2.0.3.
* You can edit it as your need.
 */
package vo

//go:generate go-doudou name --file $GOFILE

// request vo
type PercentageReqVo struct {
    // key value pairs
    Data []PercentageVo `json:"data"`
    // digit number after dot
    Places int `json:"places"`
}

// key value pair
type PercentageVo struct {
    // number for something
    Value int `json:"value"`
    // unique key for distinguishing something
    Key string `json:"key"`
}

// result vo
type PercentageRespVo struct {
    Value   int     `json:"value"`
    Key     string  `json:"key"`
    Percent float64 `json:"percent"`
    // formatted percentage
    PercentFormatted string `json:"percentFormatted"`
}

第7行的go-doudou name 命令是go-doudou內(nèi)置的一個小工具坪稽,可以一把生成或更新結(jié)構(gòu)體字段后面的json標(biāo)簽,默認(rèn)是首字母小寫的駝峰命名格式鳞骤。

生成代碼

現(xiàn)在我們可以執(zhí)行go-doudou svc grpc 命令生成proto文件和gRPC相關(guān)的服務(wù)端窒百、客戶端代碼了。執(zhí)行命令后的項目結(jié)構(gòu)如下所示:

?  go-stats git:(master) ? tree -L 3 -a
.
├── .dockerignore
├── .env
├── .gitignore
├── Dockerfile
├── cmd
│   └── main.go
├── config
│   └── config.go
├── db
│   └── db.go
├── go.mod
├── svc.go
├── svcimpl.go
├── transport
│   └── grpc
│       ├── annotation.go
│       ├── gostats.pb.go
│       ├── gostats.proto
│       └── gostats_grpc.pb.go
└── vo
    └── vo.go

13 directories, 16 files

go-doudou為我們生成了一些新的文件夾和文件豫尽。

  • .dockerignore : 用于打包docker鏡像的時候忽略**/*.local文件
  • .env : 配置文件
  • cmd : 里面有main.go文件
  • config : 用于將環(huán)境變量映射到config結(jié)構(gòu)體
  • db : 用于建立數(shù)據(jù)庫連接實例篙梢,我們這個demo用不到
  • svcimpl.go : 用于編寫自定義的業(yè)務(wù)邏輯,實現(xiàn)GoStats接口
  • transport : proto文件和gRPC相關(guān)的服務(wù)端美旧、客戶端代碼在這里

在我們實現(xiàn)接口之前渤滞,需要執(zhí)行 go mod tidy 命令來下載依賴贬墩。然后我們啟動程序,先看一下是否一切正常妄呕。

?  go-stats git:(master) ? go run cmd/main.go 
2022/11/23 11:07:45 maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined
                           _                    _
                          | |                  | |
  __ _   ___   ______   __| |  ___   _   _   __| |  ___   _   _
 / _` | / _ \ |______| / _` | / _ \ | | | | / _` | / _ \ | | | |
| (_| || (_) |        | (_| || (_) || |_| || (_| || (_) || |_| |
 \__, | \___/          \__,_| \___/  \__,_| \__,_| \___/  \__,_|
  __/ |
 |___/
2022-11-23 11:07:45 INF ================ Registered Services ================
2022-11-23 11:07:45 INF +------------------------------------------+----------------------+
2022-11-23 11:07:45 INF |                 SERVICE                  |         RPC          |
2022-11-23 11:07:45 INF +------------------------------------------+----------------------+
2022-11-23 11:07:45 INF | grpc.reflection.v1alpha.ServerReflection | ServerReflectionInfo |
2022-11-23 11:07:45 INF | go_stats.GoStatsService                  | LargestRemainderRpc  |
2022-11-23 11:07:45 INF +------------------------------------------+----------------------+
2022-11-23 11:07:45 INF ===================================================
2022-11-23 11:07:45 INF Grpc server is listening at [::]:50051
2022-11-23 11:07:45 INF Grpc server started in 1.110168ms

go-doudou依賴了 go.uber.org/automaxprocs 這個包來根據(jù)我們給容器做的資源限制來設(shè)置處理器P的數(shù)量陶舞,所以會有第2行的日志輸出。

我們再看一下transport/grpc/gostats.proto文件绪励。

/**
* Generated by go-doudou v2.0.3.
* Don't edit!
*
* Version No.: v20221123
*/
syntax = "proto3";

package go_stats;
option go_package = "go-doudou-tutorials/go-stats/transport/grpc";



message LargestRemainderRpcResponse {
  repeated PercentageRespVo data = 1 [json_name="data"];
}

// request vo
message PercentageReqVo {
  // key value pairs
  repeated PercentageVo data = 1 [json_name="data"];
  // digit number after dot
  int32 places = 2 [json_name="places"];
}

// result vo
message PercentageRespVo {
  int32 value = 1 [json_name="value"];
  string key = 2 [json_name="key"];
  double percent = 3 [json_name="percent"];
  // formatted percentage
  string percentFormatted = 4 [json_name="percentFormatted"];
}

// key value pair
message PercentageVo {
  // number for something
  int32 value = 1 [json_name="value"];
  // unique key for distinguishing something
  string key = 2 [json_name="key"];
}

service GoStatsService {
  // LargestRemainder implements Largest Remainder Method https://en.wikipedia.org/wiki/Largest_remainder_method
  rpc LargestRemainderRpc(PercentageReqVo) returns (LargestRemainderRpcResponse);
}

如代碼所示肿孵,所有message里的字段名稱都是首字母小寫的駝峰命名。其實Protobuf官方給出的規(guī)范是下劃線分隔單詞的蛇形命名疏魏。因為我們vo包里的結(jié)構(gòu)體的字段的json標(biāo)簽是首字母小寫的駝峰命名停做,所以我們這里為了保持一致,就沒有遵循規(guī)范蠢护。保持一致的作用是我們后面如果需要將protoc生成的結(jié)構(gòu)體轉(zhuǎn)成vo包里的結(jié)構(gòu)體的時候雅宾,可以直接用json序列化反序列化的方式做深拷貝,而無需手工一個個字段去賦值葵硕。如果你擔(dān)心這個辦法的性能開銷而選擇其他辦法的話眉抬,就無所謂了。

go-doudou在生成proto文件的時候會將方法出參中的除error類型之外的其他類型參數(shù)都封裝到一個單獨的message中懈凹,如這里的LargestRemainderRpcResponse蜀变。

go-doudou只支持Protobuf v3。

實現(xiàn)接口

下面我們在svcimpl.go文件中編寫我們的業(yè)務(wù)代碼介评。先看一下現(xiàn)在的代碼库北。

/**
* Generated by go-doudou v2.0.3.
* You can edit it as your need.
 */
package service

import (
    "context"
    "go-doudou-tutorials/go-stats/config"
    pb "go-doudou-tutorials/go-stats/transport/grpc"
)

var _ pb.GoStatsServiceServer = (*GoStatsImpl)(nil)

type GoStatsImpl struct {
    pb.UnimplementedGoStatsServiceServer
    conf *config.Config
}

func (receiver *GoStatsImpl) LargestRemainderRpc(ctx context.Context, request *pb.PercentageReqVo) (*pb.LargestRemainderRpcResponse, error) {
    //TODO implement me
    panic("implement me")
}

func NewGoStats(conf *config.Config) *GoStatsImpl {
    return &GoStatsImpl{
        conf: conf,
    }
}

var _ pb.GoStatsServiceServer = (*GoStatsImpl)(nil) 將*GoStatsImpl類型的nil賦值給pb.GoStatsServiceServer接口類型的_的作用是確保指針類型的GoStatsImpl結(jié)構(gòu)體實例始終都實現(xiàn)pb.GoStatsServiceServer接口。相當(dāng)于一種約束们陆。

我們可以根據(jù)實際需求往GoStatsImpl結(jié)構(gòu)體里聲明任何字段來存放各種資源寒瓦,比如外部服務(wù)的客戶端,數(shù)據(jù)庫連接坪仇,任意的隊列或者池等等杂腰。這里有一個包級別的工廠方法NewGoStats用來注入各種依賴,創(chuàng)建一個指針類型的GoStatsImpl結(jié)構(gòu)體實例椅文。

接下來我們實現(xiàn)LargestRemainderRpc方法喂很。因為go-doudou不支持grpc-gateway和grpc-web等RESTful轉(zhuǎn)gRPC的轉(zhuǎn)發(fā)器,如果需要在一套程序同時支持RESTful服務(wù)和gRPC服務(wù)皆刺,go-doudou提供的解決方案是分別綁定兩個端口少辣,啟動http服務(wù)器和gRPC服務(wù)器,復(fù)用一套業(yè)務(wù)邏輯代碼羡蛾,所以如果考慮后期可能需要加上RESTful支持的話漓帅,推薦不要直接實現(xiàn)pb.GoStatsServiceServer接口,而是先實現(xiàn)GoStats接口,然后再調(diào)用GoStats接口的實現(xiàn)方法去實現(xiàn)pb.GoStatsServiceServer接口忙干。這樣就實現(xiàn)了業(yè)務(wù)邏輯代碼的復(fù)用屯伞。

我個人傾向于不管現(xiàn)在和以后是否需要支持RESTful,都先執(zhí)行 go-doudou svc http -c 命令生成RESTful相關(guān)代碼豪直。

image.png

現(xiàn)在的項目結(jié)構(gòu)是這樣的:

?  go-stats git:(master) ? tree -L 3   
.
├── Dockerfile
├── client
│   ├── client.go
│   ├── clientproxy.go
│   └── iclient.go
├── cmd
│   └── main.go
├── config
│   └── config.go
├── db
│   └── db.go
├── go.mod
├── go.sum
├── gostats_openapi3.go
├── gostats_openapi3.json
├── svc.go
├── svcimpl.go
├── transport
│   ├── grpc
│   │   ├── annotation.go
│   │   ├── gostats.pb.go
│   │   ├── gostats.proto
│   │   └── gostats_grpc.pb.go
│   └── httpsrv
│       ├── handler.go
│       ├── handlerimpl.go
│       └── middleware.go
└── vo
    └── vo.go

8 directories, 21 files

httpsrv包里是http路由以及http請求解析和返回數(shù)據(jù)序列化相關(guān)的代碼。我們可以刪掉也可以先留著珠移。我們看一下svcimpl.go文件有什么變化弓乙。

/**
* Generated by go-doudou v2.0.3.
* You can edit it as your need.
*/
package service

import (
    "context"
    "go-doudou-tutorials/go-stats/config"
    pb "go-doudou-tutorials/go-stats/transport/grpc"

    "go-doudou-tutorials/go-stats/vo"

    "github.com/brianvoe/gofakeit/v6"
)

var _ GoStats = (*GoStatsImpl)(nil)
var _ pb.GoStatsServiceServer = (*GoStatsImpl)(nil)

type GoStatsImpl struct {
    pb.UnimplementedGoStatsServiceServer
    conf *config.Config
}

func (receiver *GoStatsImpl) LargestRemainderRpc(ctx context.Context, request *pb.PercentageReqVo) (*pb.LargestRemainderRpcResponse, error) {
    //TODO implement me
    panic("implement me")
}

func NewGoStats(conf *config.Config) *GoStatsImpl {
    return &GoStatsImpl{
        conf: conf,
    }
}

func (receiver *GoStatsImpl) LargestRemainder(ctx context.Context, payload vo.PercentageReqVo) (data []vo.PercentageRespVo, err error) {
    var _result struct {
        Data []vo.PercentageRespVo
    }
    _ = gofakeit.Struct(&_result)
    return _result.Data, nil
}

var _ GoStats = (*GoStatsImpl)(nil)LargestRemainder 方法是新生成的代碼。var _ GoStats = (*GoStatsImpl)(nil) 的作用是確保指針類型的GoStatsImpl始終實現(xiàn)GoStats接口钧惧。LargestRemainder 方法是需要我們實現(xiàn)的打樁代碼暇韧。

下面我們編寫業(yè)務(wù)邏輯代碼:

func (receiver *GoStatsImpl) LargestRemainder(ctx context.Context, payload vo.PercentageReqVo) (data []vo.PercentageRespVo, err error) {
    if len(payload.Data) == 0 {
        return
    }
    input := make([]numberutils.Percentage, 0)
    for _, item := range payload.Data {
        input = append(input, numberutils.Percentage{
            Value: item.Value,
            Data:  item.Key,
        })
    }
    numberutils.LargestRemainder(input, int32(payload.Places))
    for _, item := range input {
        data = append(data, vo.PercentageRespVo{
            Value:            item.Value,
            Key:              item.Data.(string),
            Percent:          item.Percent,
            PercentFormatted: item.PercentFormatted,
        })
    }
    return
}

go-doudou在 github.com/unionj-cloud/go-doudou/v2/toolkit/numberutils 包里提供了一個工具函數(shù)LargestRemainder。此處略過具體的算法實現(xiàn)浓瞪。

現(xiàn)在我們可以通過復(fù)用LargestRemainder方法里的代碼實現(xiàn)LargestRemainderRpc方法懈玻。

func (receiver *GoStatsImpl) LargestRemainderRpc(ctx context.Context, request *pb.PercentageReqVo) (*pb.LargestRemainderRpcResponse, error) {
    var payload vo.PercentageReqVo
    copier.DeepCopy(request, &payload)
    data, err := receiver.LargestRemainder(ctx, payload)
    if err != nil {
        return nil, err
    }
    var ret pb.LargestRemainderRpcResponse
    err = copier.DeepCopy(data, &ret.Data)
    if err != nil {
        return nil, err
    }
    return &ret, nil
}

我們無需手工編寫pb.PercentageReqVo轉(zhuǎn)vo.PercentageReqVo和[]vo.PercentageRespVo轉(zhuǎn)[]*pb.PercentageRespVo的代碼,直接用 github.com/unionj-cloud/go-doudou/v2/toolkit/copier 包里的DeepCopy函數(shù)做深拷貝即可乾颁。

測試服務(wù)

因為我們生成了新的代碼涂乌,導(dǎo)入了新的依賴,所以我們需要再執(zhí)行一下 go mod tidy 英岭。然后我們啟動服務(wù)湾盒,測試一下效果。

?  go-stats git:(master) ? go mod tidy && go run cmd/main.go
2022/11/23 13:18:13 maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined
                           _                    _
                          | |                  | |
  __ _   ___   ______   __| |  ___   _   _   __| |  ___   _   _
 / _` | / _ \ |______| / _` | / _ \ | | | | / _` | / _ \ | | | |
| (_| || (_) |        | (_| || (_) || |_| || (_| || (_) || |_| |
 \__, | \___/          \__,_| \___/  \__,_| \__,_| \___/  \__,_|
  __/ |
 |___/
2022-11-23 13:18:13 INF ================ Registered Services ================
2022-11-23 13:18:13 INF +------------------------------------------+----------------------+
2022-11-23 13:18:13 INF |                 SERVICE                  |         RPC          |
2022-11-23 13:18:13 INF +------------------------------------------+----------------------+
2022-11-23 13:18:13 INF | go_stats.GoStatsService                  | LargestRemainderRpc  |
2022-11-23 13:18:13 INF | grpc.reflection.v1alpha.ServerReflection | ServerReflectionInfo |
2022-11-23 13:18:13 INF +------------------------------------------+----------------------+
2022-11-23 13:18:13 INF ===================================================
2022-11-23 13:18:13 INF Grpc server is listening at [::]:50051
2022-11-23 13:18:13 INF Grpc server started in 1.001238ms

我個人傾向于采用 evans 去幫助測試和調(diào)試gRPC服務(wù)诅妹。

?  go-stats git:(master) ? evans -r repl -p 50051

  ______
 |  ____|
 | |__    __   __   __ _   _ __    ___
 |  __|   \ \ / /  / _. | | '_ \  / __|
 | |____   \ V /  | (_| | | | | | \__ \
 |______|   \_/    \__,_| |_| |_| |___/

 more expressive universal gRPC client


go_stats.GoStatsService@127.0.0.1:50051> show service
+----------------+---------------------+-----------------+-----------------------------+
|    SERVICE     |         RPC         |  REQUEST TYPE   |        RESPONSE TYPE        |
+----------------+---------------------+-----------------+-----------------------------+
| GoStatsService | LargestRemainderRpc | PercentageReqVo | LargestRemainderRpcResponse |
+----------------+---------------------+-----------------+-----------------------------+

go_stats.GoStatsService@127.0.0.1:50051> service GoStatsService

go_stats.GoStatsService@127.0.0.1:50051> call LargestRemainderRpc
<repeated> data::value (TYPE_INT32) => 20
<repeated> data::key (TYPE_STRING) => apple
<repeated> data::value (TYPE_INT32) => 30
<repeated> data::key (TYPE_STRING) => banana
<repeated> data::value (TYPE_INT32) => 40
<repeated> data::key (TYPE_STRING) => pear
<repeated> data::value (TYPE_INT32) => 
places (TYPE_INT32) => 2
{
  "data": [
    {
      "key": "apple",
      "percent": 22.22,
      "percentFormatted": "22.22%",
      "value": 20
    },
    {
      "key": "banana",
      "percent": 33.33,
      "percentFormatted": "33.33%",
      "value": 30
    },
    {
      "key": "pear",
      "percent": 44.45,
      "percentFormatted": "44.45%",
      "value": 40
    }
  ]
}

我們輸入apple 20kg罚勾,banana 30kg,pear 40kg和保留2位小數(shù)吭狡,然后得到了我們期望的結(jié)果:22.22 + 33.33 + 44.45 = 100尖殃。

總結(jié)

本文我們學(xué)到了采用go-doudou微服務(wù)框架開發(fā)gRPC服務(wù)的基本技能,同時我們需要知道go-doudou不僅可以幫助開發(fā)者輕松地開發(fā)gRPC服務(wù)划煮,它還包含了一套完善的服務(wù)治理方案幫助開發(fā)者打造完整的微服務(wù)系統(tǒng)送丰。go-doudou雖然開源時間不長,但是非常有發(fā)展?jié)摿Π愦耍M絹碓蕉嗟拈_發(fā)者可以加入進(jìn)來蚪战。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市铐懊,隨后出現(xiàn)的幾起案子邀桑,更是在濱河造成了極大的恐慌,老刑警劉巖科乎,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壁畸,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捏萍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門太抓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人令杈,你說我怎么就攤上這事走敌。” “怎么了逗噩?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵掉丽,是天一觀的道長。 經(jīng)常有香客問我异雁,道長捶障,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任纲刀,我火速辦了婚禮项炼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘示绊。我一直安慰自己锭部,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布面褐。 她就那樣靜靜地躺著空免,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盆耽。 梳的紋絲不亂的頭發(fā)上蹋砚,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機(jī)與錄音摄杂,去河邊找鬼坝咐。 笑死,一個胖子當(dāng)著我的面吹牛析恢,可吹牛的內(nèi)容都是我干的墨坚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼映挂,長吁一口氣:“原來是場噩夢啊……” “哼泽篮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柑船,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤帽撑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鞍时,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亏拉,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡扣蜻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了及塘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莽使。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖笙僚,靈堂內(nèi)的尸體忽然破棺而出芳肌,到底是詐尸還是另有隱情,我是刑警寧澤肋层,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布庇勃,位于F島的核電站,受9級特大地震影響槽驶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸳兽,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一掂铐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧揍异,春花似錦全陨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至戚嗅,卻和暖如春雨涛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背懦胞。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工替久, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人躏尉。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓蚯根,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胀糜。 傳聞我的和親對象是個殘疾皇子颅拦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

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