beego学习入门

今天主要来简单学习一下beego,一个比较流行的go框架。前段时间学习了go语言的基础,対go算是有了一个简单了解,但是实际开发中肯定会少不了各种框架得使用,所以我这次选择了一个比较流行得框架——beego,来做为go语言框架入门,beego官网。这里简单先说一下自己得感觉,首先beego是一个一体式的框架,也就是说,它包含了所有项目中常用的所有模块,不像java,不同得部分可能需要不同得框架(当然spring boot现在也能算是一个一体式)。
今天主要学习得就是beego得MVC架构,它主要包括三个部分:model、controller、view。记得当时学习go和数据库交互的时候就说过,go自己和数据库交互其实感觉不太方便,今天看看beego的ORM是不是会带来惊喜。开始学习和写代码之前,先做好准备工作,即安装beego和bee工具,安装过程很简单,这里就不再叙述了,然后创建一个beego项目,项目结构如下图:

项目结构.png

根据项目结构就可以很直观的得看出来这是一个典型的MVC架构,main函数是整个应用得入口,routers主要是进行路由注册,即将用户请求得URL和相应得controller进行映射,models下面主要是beego得ORM模块,另外数据模型,即所谓得struct我也是放在这个目录下的。controller这个都比较熟悉了,就不说了。conf下主要是项目配置文件。static下面是项目得静态文件。views下面是项目得模板,当然我使用的还是html,没有使用模板。
开始之前还是先了解下项目得执行过程,下面这张图就是从beego官网上,关于go的执行过程,这张图非常直观的,main函数执行前会先完成它引入包的init函数,依次类推。最终等main函数自己得init函数执行完之后再执行main函数,当然也不是必须通过init方法,也可以自己定义一个方法,然后显性的调用也是可以的。
go执行过程.png


一、项目配置

beego目前也是支持多种格式的配置文件,比如xml、yml、json等,但是但是默认采用了 INI 格式解析。另外就是可以支持多个配置文件引入,比如这个项目我将数据库相关的配置单独的作为一个配置文件,然后默认配置文件中引入数据库配置文件就行了,比如:

appname=helloGo
httpport=9090
runmode=dev

# 视图的路径
# viewspath="views/user"
viewspath="views"

# 引入的其他配置文件
include "database.conf"
# 接收json参数
copyrequestbody=true

有一点不太方便的就是没有提示,不像spring boot可以提示,这个的话只能自己去查看常用的配置文档,当然也可以通过代码的方式指定相关的配置,选择代码还是配置文件就看自己的喜好或者习惯了。另外还可以指定不同运行环境下的配置,比如测试

二、路由映射

首先说一下就是beego项目的启动我感觉和spring boot项目启动非常的类似,main函数里面实际调用的是app的Run方法,这里先不往下深究了,暂时知道就可以了。首先我在main函数引入router这个包,这个包上面说了主要是路由的映射,即将用户请求的URL和相应的controller进行映射。根据go执行的过程图可以知道它会一级一级的把需要的所有包的init函数都执行一遍,或者用spring的思想就是会完成所有依赖的注入(不知道这么理解有没有问题,但是我觉得思路应该都是相通的)。所以可以在router的init函数里面完成URL和controller的映射,代码如下:

func init() {
    /*固定路由*/
    beego.Router("/", &controllers.MainController{})
    beego.Router("/user",&controllers.UserController{})

    beego.BConfig.EnableGzip = true
    beego.BConfig.RouterCaseSensitive = true
    beego.BConfig.MaxMemory = 1 << 26

    beego.BConfig.WebConfig.AutoRender = true
    beego.BConfig.CopyRequestBody = true

    /*设置模板取值方式*/
    //beego.BConfig.WebConfig.TemplateLeft = "${"
    //beego.BConfig.WebConfig.TemplateRight = "}"
    /*页面文件路径*/
    //beego.BConfig.WebConfig.ViewsPath="views/user"

    /*注解路由*/
    beego.Include(&controllers.UserController{})
    beego.Include(&controllers.CommonController{})
    beego.Include(&controllers.FileController{})
    beego.Include(&controllers.LoginController{})
    
    /*同时注册多个路由*/
    beego.Include(&controllers.UserController{},&controllers.CommonController{},&controllers.FileController{})
    //beego.ErrorController(&controllers.ErrorController{})
}

init函数里面有固定路由即每一个固定的URL都会对应一个controller或者通过正则路由,即满足一定格式的URL对应一个controller,因为我已经习惯了spring注解的方式,所以我比较喜欢的还是注解形式的路由方式。注解路由通过一个调用beego的Include函数将一个或者多个handler注册到应用中。另外beego也支持restful形式的路由,有兴趣的可以看下官方文档。另外就是init函数里面有一些设置,这些设置都是可以写到配置文件内的,这个我觉得不用特别的在意,还是看个人习惯。
注册好路由后还需要的就是将URL和对应的handler进行映射,所以每个controller都有一个URLMapping方法,代码如下:

type UserController struct {
    beego.Controller
}

//配置注解路由
func (u *UserController) URLMapping() {
    u.Mapping("QueryById",u.QueryById)
    u.Mapping("QueryList",u.QueryList)
    u.Mapping("Register",u.Register)
    u.Mapping("SaveUser",u.SaveUser)
    u.Mapping("UserJSON",u.UserJSON)
}

在beego项目中每一个controller结构都包含了一个beego.Controller,可以理解为自定义的controller继承了beego.Controller,而beego.Controller封装了很多我们所需要的信息,看下它的结构就明白了,建议还是看下源码。
其实这个方法主要就是将方法名称和对应的方法或者所谓的handler进行映射,而真正的路由是在相应的方法或者handler上的。比较熟悉springMVC应该都知道,在进行URL映射的时候,通常我们会在类上使用一个@requestMapping注解,然后方法上也会加上相应的@requestMapping,二者拼接在一起就完成整个的URL。beego的稍微有点区别,而且注解的形式也不太一样,下面就贴一个根据id查找用户的handler,这个URL上会附上请求的参数值,看下下面的代码:

// @router /user/get/:id [get]
func (u *UserController) QueryById() {
    strId := u.Ctx.Input.Param(":id")
    logs.Info(">>>> query user by userId start <<<<")
    id,err := strconv.Atoi(strId)
    user := models.QueryUserById(id)
    checkError(err)

    u.Data["User"] = user
    bytes,err := json.Marshal(user)
    u.Ctx.ResponseWriter.Write(bytes)

    //u.TplName = "user/user.html"
}

// @router /user/get/:id [get]就是一个完整的注解路由,请求的URL是/user/get/:id,请求类型是GET请求。和springMVC确实不太一样,相对来讲beego的要更简洁一点(go语言相比java就简洁不少),但是go语言并不支持注解,这种路由形式准确的说应该叫注释,但是实际的作用确实和java的注解效果是相同的。当然我个人还是很喜欢使用这种方式的,虽然和springMVC不太一样,但是相差并不大,还是很好理解的。只要明白了用户请求、路由、映射、处理器等之间的关系,基本就没问题了,无非是具体怎么实现的问题。关于其他形式的路由方式这里就不过多介绍了,不过还是应该了解一下,毕竟具体使用哪种并不是自己能决定的。

三、models设计

这部分我主要说一下beego的ORM部分,前面学习go的时候没有使用任何的ORM框架,所有对数据库的CRUD都需要自己手写SQL,然后对结果进行处理,非常的不方便。
models可以像routers一样,在main函数引入models的包,然后在其init函数获取数据连接等等,但是自己定义一个函数,然后在项目的main函数,显性调用。像这样:

func main() {
    // 数据库初始化
    models.Init()
    // 过滤器
    //beego.InsertFilter("/*",beego.BeforeExec,controllers.UserFilter)
    // 错误handler
    beego.ErrorController(&controllers.ErrorController{})
    beego.Run()
}

这里我自己定义了一个Init函数,并在main函数里面调用,当然也可以自己重写main的init函数,并在init函数内完调用(我觉得应该是可以的,但是按理放到init函数内更好)。接下来看看在models的Init函数内做了什么

func Init() {
    logs.Info(">>>> database init start <<<<")

    //  数据库驱动
    orm.RegisterDriver("postgres",orm.DRPostgres)
    // 注册数据库
    username := beego.AppConfig.String("username")
    password := beego.AppConfig.String("password")
    url := beego.AppConfig.String("url")
    dbname := beego.AppConfig.String("dbname")
    datasource := "postgres://" + username + ":" + password + "@" + url + "/" + dbname + "?sslmode=disable"
    orm.RegisterDataBase("default","postgres",datasource)
    // 最大连接和空闲连接
    orm.SetMaxOpenConns("default",30)
    orm.SetMaxIdleConns("default",10)
    // 注册model
    orm.RegisterModel(new(User))
}

首先是注册数据库驱动,我使用的依然是postgresql,此外beego的ORM还支持mysql、oracle、sqlite和TiDB。应该说对关系型数据库这方面的支持稍微差了一点,比如就没有sql server。其次就是注册数据库,beego的ORM必须注册一个默认名称为default的数据库。数据源所需参数如:数据库名称、密码以及URL等信息都是写在数据库配置文件内的,而获取配置文件的配置参数,是通过使用 beego.AppConfig加参数类型,如string bool int float等来获取。然后就是配置数据库连接池,最大连接、最大空闲连接等。最后注册model,当然是可以注册多个数据模型的,这一步我觉得其实还是很重要的,我的感觉就是将数据库表和对应的结构做一个映射关系,可以根据定义的结构创建数据库表,以及表与表之间的关联关系(什么一对一、一对多等等),另外如果要使用orm.QuerySeter 进行高级查询的话,这一步是必须的。感觉有点像hibernate,我自己是不太喜欢这种方式,当然这种难度感觉也会高一些,因为hibernate当时学的就挺晕的。下面这些代码是官方文档定义结构的一些例子:

type User struct {
    Id          int
    Name        string
    Profile     *Profile   `orm:"rel(one)"` // OneToOne relation
    Post        []*Post `orm:"reverse(many)"` // 设置一对多的反向关系
}

type Profile struct {
    Id          int
    Age         int16
    User        *User   `orm:"reverse(one)"` // 设置一对一反向关系(可选)
}

type Post struct {
    Id    int
    Title string
    User  *User  `orm:"rel(fk)"`    //设置一对多关系
    Tags  []*Tag `orm:"rel(m2m)"`
}

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

当然上面只是一部分,此外还可以设置主键、自增长等等,这里说一下 orm:"reverse(one)"这种表达方式,我看到网上有人把这种形式称为go的注解,我也不知道对或者不对,知道它是一种表达形式即可,除了orm之外我知道的还有form和json,这两个下面会说到,关于orm其实心里挺排斥的,感觉有点复杂了,但是我觉得还是很有必要了解一下的。总的来说beego的ORM功能上确实十分强大,支持的查询方式也很多,比如对象的CRUD,还有高级查询、原生的SQL查询还有构造查询。具体怎么使用就看自己了,当然我还是更喜欢自己来写SQL,但是还是要看具体场景。这次学习项目中会用到对象的CRUD也会使用到高级查询。这部分内容其实比较多,我自己并没有怎么去学习,可能还是受mybatis的影响吧,感觉自己写sql更方便,但是我觉得这部分还是应该多了解一下的。
关于views也即视图模板这部分,自己感觉会使用就好,和其他视图模板应该只是语法上的差别,而且现在基本都是前后端分离的开发模式,后端提供接口就好,但是掌握这部分也不算多,这部分就略过了。


四、项目

1、数据模型定义

接下来就是项目的开发,我还是从最简单的开始,什么登录、注册、查询之类,一是这些相对最简单适合入门,二是这次的内容还是蛮多的,只是自己选择性的忽略了一些。项目还是单库单表(ORM是支持多个数据库的),先从设计结构,即数据模型开始,这里会用到form和json这两种形式的注解(网上有人是这么叫的),看下面的代码:

type User struct {
    Id              int             `form:"-" table:"user" json:"id"`
    UserName        string          `form:"username"  column:"username" json:"userName"`
    Age             int             `form:"age" json:"age"`
    Sex             string          `form:"sex" json:"sex"`
    Mobile          string          `form:"mobile" json:"mobile"`
    Password        string          `json:"password"`
    Email           string          `form:"email" json:"email"`
}

form表示的是使用这个结构体接受前端form表单时对应的名称,"-"表示忽略这一项的值;table表示的这个结构体对应的数据库表的名称;column表示的是这个属性在数据库表中对应列的名称;json表示的是将这个结构体或者对象转成json对象后对应的key的名称,因为结构体里面如果属性对外可见,那么属性名称首字母必须大写,这样转成json后key的首字母也是大写,所以为了美观需要转换一下,当然换个key的名称也是可以。
然后再定义一个作为json响应结果的结构体。我建议是将数据库中有对应表的数据模型都进行注册,数据模型最好单独新建go文件,并在其init函数进行注册,因为我就一个数据模型,所以我放到models的Init函数中了。

2、编写相关handler

这个controller无非就是一些跳转页面、注册登陆之类的逻辑,以及数据库的CRUD,代码如下:

type LoginController struct {
    beego.Controller
}

func (c *LoginController) URLMapping() {
    c.Mapping("/login",c.Login)
    c.Mapping("/register",c.Register)
    c.Mapping("/register/do",c.DoRegister)
    c.Mapping("/login/do",c.DoLogin)

}

// @router /login  [get]
func (c *LoginController) Login() {
    logs.Info(">>>> forward to login page start <<<<")

    c.TplName = "login.html"
}

// @router /register  [get]
func (c *LoginController) Register() {
    logs.Info(">>>> forward to Register page start <<<<")

    c.TplName = "register.html"
}
// @router /register/do [post]
func (c *LoginController) DoRegister() {
    var user models.User
    var result models.Result
    err := json.Unmarshal(c.Ctx.Input.RequestBody,&user)
    checkError(err)

    userName := user.UserName
    password := user.Password
    email := user.Email
    logs.Info(">>>> user register information username=" + userName + ",email=" + email + ",password=" + password + " <<<<")

    pass,err := regexp.MatchString(`[a-zA-Z0-9]{8,16}`,password)
    checkError(err)
    if !pass {
        result.Code = 404
        result.Success = false
        result.Message = "密码格式错误"
    }

    em,err := regexp.MatchString(`^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$`,email)
    checkError(err)
    if !em {
        result.Code = 404
        result.Success = false
        result.Message = "邮箱格式错误"
    }
    // 对密码加密
    user.Password = passwordEncode(password)

    id,e := orm.NewOrm().Insert(&user)
    if e != nil {
        result.Code = 404
        result.Success = false
        result.Message = "系统异常"
        logs.Error(e)
    } else {
        logs.Info(">>>> user register success,user id = " + strconv.FormatInt(id,10) + " <<<<")
        result.Code = 200
        result.Success = true
        result.Message = "注册成功"
    }

    bytes,err := json.Marshal(&result)
    c.Ctx.ResponseWriter.Write(bytes)
}
func passwordEncode(password string) string {
    h := md5.New()
    h.Write([]byte(password))
    return hex.EncodeToString(h.Sum(nil))
}


// @router /login/do  [post]
func (c *LoginController) DoLogin() {
    logs.Info(">>>> user do login start <<<<")
    var user models.User
    // 表单映射成struct
    err := json.Unmarshal(c.Ctx.Input.RequestBody,&user)
    logs.Error(err)

    user.Password = passwordEncode(user.Password)

    right := models.QueryByNamePwd(user.UserName,user.Password)

    var r models.Result
    if right {
        // 用户名和密码正确
        r.Code = 200
        r.Success = true
        r.Message = "success"
    } else {
        r.Code = 404
        r.Success = false
        r.Message = "用户或密码错误"
    }

    bytes,err := json.Marshal(&r)
    c.Ctx.ResponseWriter.Write(bytes)
}

另一个用户操作相关的controller

type UserController struct {
    beego.Controller
}

//配置注解路由
func (u *UserController) URLMapping() {
    u.Mapping("QueryById",u.QueryById)
    u.Mapping("QueryList",u.QueryList)
    u.Mapping("Register",u.Register)
    u.Mapping("SaveUser",u.SaveUser)
    u.Mapping("UserJSON",u.UserJSON)
}
// @router /user [get]
func (c *UserController) Register() {
    logs.Info(">>>> forward to register page start <<<<")


    c.TplName = "user/form.html"
}

// @router /user/get/:id [get]
func (u *UserController) QueryById() {
    strId := u.Ctx.Input.Param(":id")
    logs.Info(">>>> query user by userId start <<<<")
    id,err := strconv.Atoi(strId)
    user := models.QueryUserById(id)
    checkError(err)

    u.Data["User"] = user
    bytes,err := json.Marshal(user)
    u.Ctx.ResponseWriter.Write(bytes)

    //u.TplName = "user/user.html"
}
// @router /user/list [get]
func (u *UserController) QueryList() {
    logs.Info(">>>> query user list start <<<<")

    // 数据库查询所有用户
    users := models.QueryUserList()

    //bytes,err := json.Marshal(users)
    //checkError(err)
    //u.Ctx.ResponseWriter.Write(bytes)

    u.Data["List"] = users
    u.TplName = "user/list.html"
}

// @router /user/save [post]
func (u *UserController) SaveUser() {
    logs.Info(">>>> save register information start <<<<")
    // 获取表单数据
    //form := u.Ctx.Request.PostForm
    //username := form.Get("username")
    //age := form.Get("age")
    //sex := form.Get("sex")
    //mobile := form.Get("mobile")

    // 表单转struct
    var user models.User
    err := u.ParseForm(&user)
    checkError(err)
    // 校验...

    // 写入数据库,返回id值
    id := models.InsertUser(&user)
    println(id)
    //var r orm.RawSeter
    //insert := "insert into t_user (username,age,sex,mobile) values(?,?,?,?)"
    //r = o.Raw(insert,username,age,sex,mobile)
    //_,err := r.Exec()
    //checkError(err)
    u.Ctx.Redirect(302,"/success")
}

/*接收requestBody参数*/
// @router /json/save [post]
func (u *UserController) UserJSON() {
    logs.Info(">>>> save user json information start <<<<")
    // requestBody数据
    var user models.User
    err := json.Unmarshal(u.Ctx.Input.RequestBody,&user)
    checkError(err)

    // 数据库处理
    id := models.InsertUser(&user)
    println("insert user id=" + strconv.FormatInt(id,10))

    u.Ctx.Redirect(302,"/success")
}

func checkError(e error) {
    if e != nil {
        log.Fatal(e)
    }
}

这个controller有些地方可能实现方式略有不同,还有一点就是这个controller里面对数据库是我在models里面写的,使用的是ORM的高级查询,代码如下:

func QueryUserById(id int) *User {
    var user User
    orm := orm.NewOrm()
    orm.QueryTable("user").Filter("id",id).One(&user,"username","age","sex","mobile")
    logs.Info(">>>> query user by user id from database <<<<")
    return &user
}

func QueryUserList() []*User {
    var list []*User
    orm := orm.NewOrm()
    orm.QueryTable("user").All(&list,"username","age","sex","mobile")

    return list
}

func InsertUser(u *User) int64 {
    o := orm.NewOrm()
    id,err := o.Insert(u)
    if err!= nil {
        log.Fatal(err)
    }

    return id
}

func QueryByNamePwd(username, password string) bool {
    var user User
    orm := orm.NewOrm()
    err := orm.QueryTable("user").Filter("username",username).Filter("password",password).One(&user,"username","age","sex","mobile","email")
    logs.Info(">>>> query user by user id from database <<<<")

    var result bool
    if err!=nil {
        logs.Error(err)
        result = false
    } else {
        result = true
    }
    return result
}

orm.QueryTable("user").Filter("id",id)....
第一个参数表示查询表的名称,filter是过滤条件,可以看成是SQL中的where条件,里面的参数就是对应的列名和值,One表示只有一个结果,All表示所有,里面一个是struct结构体接受查询的值,而后面的"username","age","sex","mobile"表示查询的列。我自己的感觉是简单的查询还好,复杂的话我宁愿写SQL。当然它的功能不止这些,还有一些模糊的查询比如startwith、contains等等,可以参考文档。另外还有构造查询感觉和写SQL没什么却别了,也可以了解一下。
另外就是错误处理器,beego 默认支持 401、403、404、500、503 这几种错误的处理。一般我们都会自定义错误的处理页面,我在main函数注册了一个处理错误的controller,但是只自定义了404和500两种

type ErrorController struct {
    beego.Controller
}

func (c *ErrorController) URLMapping()  {
    c.Mapping("Error404",c.Error404)
    c.Mapping("Error500",c.Error500)
}

func (c *ErrorController) Error404() {
    logs.Info(">>>> forward to 404 error page <<<<")
    c.Data["Content"] = "抱歉,查找的内容不存在"

    c.TplName = "error/404.html"
}

func (c *ErrorController) Error500() {
    logs.Info(">>>> forward to 500 error page <<<<")
    c.Data["Content"] = "抱歉,系统错误,努力解决中"

    c.TplName = "error/500.html"
}

这个里面方法名称好像是不可以变的,自己后面有时间再测试一下吧,另外就是自己其实在main函数还定义了一个过滤器,但是时间关系,自己还没有认真的去看,感觉和springMVC的拦截器有相似之处,都有在路由之前还是之后处理的逻辑,关于这部分内容下次再继续的研究吧!beego模块确实比较多,功能上看非常的全,而且还有缓存、日志等模块,使用beego可以说能解决项目中所需要的大部分问题,所以我说它是一个体式的框架,功能全而且强大。
这次项目的代码已经上传到我的github,最近自己也有使用beego做一个个人小网站的想法。关于文章中有什么疑问或者错误之处希望大家批评指正,谢谢!2018年的最后一天完成2018年的最后一篇文章,感觉还是有点感慨。祝大家2019实现所有的flag.........祝大家新年快乐!!


最后:自己在微信开了一个个人号:超超学堂,都是自己之前写过的一些文章,另外关注还有Java免费自学资料,欢迎大家关注。

二维码.jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260