Beego API 快速入门
更多分享内容可访问我的个人博客
https://www.niuiic.top/
本文将通过一个简单的案例帮助初学者打通 beego api 开发流程。
环境配置
-
安装 golang,配置
GOPROXY
,GOPATH
,GOROOT
,启用 go module。 -
在 shell 配置文件中加入
export GOPATH="yourPath"
。并且将 GOPATH 中的 bin 目录加入 PATH。 -
安装 bee。
go install github.com/beego/bee/v2@latest
-
创建新项目。
bee api quickstart
go mod tidy
bee generate docs
项目结构与执行逻辑
beego 是一个典型的 MVC 架构。它的执行逻辑如下图所示。
当前创建的是 api 项目,没有前端部分。
相对应的项目目录如下所示。
运行项目
在项目目录下使用bee run -gendoc=true -downdoc=true
运行项目。第一次运行时会自动下载调试工具swagger
。
访问http://127.0.0.1:8080/swagger/
可以看到调试界面。
源码分析
Entry
首先来看入口文件main.go
package mainimport (_ "quickstart/routers"beego "github.com/beego/beego/v2/server/web"
)func main() {if beego.BConfig.RunMode == "dev" {beego.BConfig.WebConfig.DirectoryIndex = truebeego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"}beego.Run()
}
main
函数中第一条语句是设置在 dev 运行模式下启动 swagger 调试器,这与项目主干无关,暂时不管它。
第二条语句调用了一个Run
方法,运行整个程序。那么到底是怎么运行的呢,看到一个关键的引入_ "quickstart/routers"
。这个包只引入了其中的 init 函数,我们去到routers/router.go
查看到底干了什么。
Routers
package routersimport ("quickstart/controllers"beego "github.com/beego/beego/v2/server/web"
)func init() {ns := beego.NewNamespace("/v1",beego.NSNamespace("/object",beego.NSInclude(&controllers.ObjectController{},),),beego.NSNamespace("/user",beego.NSInclude(&controllers.UserController{},),),)beego.AddNamespace(ns)
}
以上init
函数做的工作,简单地说,就是将不同的请求对应到不同的控制器。
本目录中还有另一个文件commentsRouter_controllers.go
。以下列出部分内容。
package routersimport (beego "github.com/beego/beego/v2/server/web""github.com/beego/beego/v2/server/web/context/param"
)func init() {beego.GlobalControllerRouter["quickstart/controllers:UserController"] = append(beego.GlobalControllerRouter["quickstart/controllers:UserController"],beego.ControllerComments{Method: "Get",Router: "/:uid",AllowHTTPMethods: []string{"get"},MethodParams: param.Make(),Filters: nil,Params: nil})}
看到quickstart/controllers:UserController
,这其实对应了 controllers 这一层的一个控制器。再看到Method: "Get"
,这是该控制器实现的一个方法。所以这个文件可以看作是注册控制器与函数。其余的配置暂时不管,重点关注Router: "/:uid"
。这个配置的意思是当有 Get 请求http://127.0.0.1:8080/v1/user/xxx
(可以直接用浏览器访问该地址)时,会执行控制器中的Get
函数。
xxx 可以任意但是必须有,这里写的 :uid 是在实现控制器方法的时候读取输入用的,不是指 xxx 必须是 :uid。
那么,至此便知道如何用不同的请求执行不同的函数了。
然后再去到 controllers/user.go
,查看这一层干了什么。
Controllers
package controllersimport ("quickstart/models""encoding/json"beego "github.com/beego/beego/v2/server/web"
)// Operations about Users
type UserController struct {beego.Controller
}// @Title CreateUser
// @Description create users
// @Param body body models.User true "body for user content"
// @Success 200 {int} models.User.Id
// @Failure 403 body is empty
// @router / [post]
func (u *UserController) Post() {var user models.Userjson.Unmarshal(u.Ctx.Input.RequestBody, &user)uid := models.AddUser(user)u.Data["json"] = map[string]string{"uid": uid}u.ServeJSON()
}// @Title GetAll
// @Description get all Users
// @Success 200 {object} models.User
// @router / [get]
func (u *UserController) GetAll() {users := models.GetAllUsers()u.Data["json"] = usersu.ServeJSON()
}// @Title Get
// @Description get user by uid
// @Param uid path string true "The key for staticblock"
// @Success 200 {object} models.User
// @Failure 403 :uid is empty
// @router /:uid [get]
func (u *UserController) Get() {uid := u.GetString(":uid")if uid != "" {user, err := models.GetUser(uid)if err != nil {u.Data["json"] = err.Error()} else {u.Data["json"] = user}}u.ServeJSON()
}// @Title Update
// @Description update the user
// @Param uid path string true "The uid you want to update"
// @Param body body models.User true "body for user content"
// @Success 200 {object} models.User
// @Failure 403 :uid is not int
// @router /:uid [put]
func (u *UserController) Put() {uid := u.GetString(":uid")if uid != "" {var user models.Userjson.Unmarshal(u.Ctx.Input.RequestBody, &user)uu, err := models.UpdateUser(uid, &user)if err != nil {u.Data["json"] = err.Error()} else {u.Data["json"] = uu}}u.ServeJSON()
}// @Title Delete
// @Description delete the user
// @Param uid path string true "The uid you want to delete"
// @Success 200 {string} delete success!
// @Failure 403 uid is empty
// @router /:uid [delete]
func (u *UserController) Delete() {uid := u.GetString(":uid")models.DeleteUser(uid)u.Data["json"] = "delete success!"u.ServeJSON()
}// @Title Login
// @Description Logs user into the system
// @Param username query string true "The username for login"
// @Param password query string true "The password for login"
// @Success 200 {string} login success
// @Failure 403 user not exist
// @router /login [get]
func (u *UserController) Login() {username := u.GetString("username")password := u.GetString("password")if models.Login(username, password) {u.Data["json"] = "login success"} else {u.Data["json"] = "user not exist"}u.ServeJSON()
}// @Title logout
// @Description Logs out current logged in user session
// @Success 200 {string} logout success
// @router /logout [get]
func (u *UserController) Logout() {u.Data["json"] = "logout success"u.ServeJSON()
}
首先定义了UserController
,该结构体获取了beego.Controller
的所有方法。后面的所有内容则都是在重写这些方法。观察方法的名称可以轻易地发现,这些方法对应不同类型的 http 请求。
方法内使用的结构体及其方法来自models/user.go
。于是,再看models/user.go
。
Models
package modelsimport ("errors""strconv""time"
)var (UserList map[string]*User
)func init() {UserList = make(map[string]*User)u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}}UserList["user_11111"] = &u
}type User struct {Id stringUsername stringPassword stringProfile Profile
}type Profile struct {Gender stringAge intAddress stringEmail string
}func AddUser(u User) string {u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)UserList[u.Id] = &ureturn u.Id
}func GetUser(uid string) (u *User, err error) {if u, ok := UserList[uid]; ok {return u, nil}return nil, errors.New("User not exists")
}func GetAllUsers() map[string]*User {return UserList
}func UpdateUser(uid string, uu *User) (a *User, err error) {if u, ok := UserList[uid]; ok {if uu.Username != "" {u.Username = uu.Username}if uu.Password != "" {u.Password = uu.Password}if uu.Profile.Age != 0 {u.Profile.Age = uu.Profile.Age}if uu.Profile.Address != "" {u.Profile.Address = uu.Profile.Address}if uu.Profile.Gender != "" {u.Profile.Gender = uu.Profile.Gender}if uu.Profile.Email != "" {u.Profile.Email = uu.Profile.Email}return u, nil}return nil, errors.New("User Not Exist")
}func Login(username, password string) bool {for _, u := range UserList {if u.Username == username && u.Password == password {return true}}return false
}func DeleteUser(uid string) {delete(UserList, uid)
}
流程总结
至此,虽然还没有完整分析所有文件,但项目的主体逻辑已经明了。项目运行之后,若接收到 http 请求,则首先通过 router 到对应的 controller,在 controller 中完成所有操作。如果操作较为复杂,则可以编写 model,增加层数,提高复用性。
案例:用户注册与查询
预备技能
与数据库交互
本案例用到数据库,请自行安装并配置 mysql 数据库。案例使用"github.com/beego/beego/v2/client/orm"
及"github.com/go-sql-driver/mysql"
实现数据库交互。mysql driver 需要单独安装,go get github.com/go-sql-driver/mysql
。
先新建一个项目。
mkdir database
cd database
go mod init database
go get github.com/beego/beego/v2/client/orm
go get github.com/go-sql-driver/mysql
编写main.go
。
package mainimport ("github.com/beego/beego/v2/client/orm"_ "github.com/go-sql-driver/mysql"
)type User struct {ID int `orm:"column(id)"`Name string `orm:"column(name)"`Password string `orm:"column(password)"`
}func init() {// register modelorm.RegisterModel(new(User))// register default database// modify username, password and database for yourselform.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/database?charset=utf8")
}func main() {orm.Debug = true// automatically build tableorm.RunSyncdb("default", false, true)// create orm objecto := orm.NewOrm()// datauser := new(User)user.Name = "mike"// insert datao.Insert(user)
}
运行go run main.go
并查看 mysql 中的数据。发现新建了一个表并且填入了数据。
这是一个很简单的案例,其余操作可以参考相关文档。
测试
纯粹的测试很简单,可以参考该系列文章。
现在将上面的代码稍微修改一下,写入models/user.go
。
package modelsimport ("github.com/beego/beego/v2/client/orm"_ "github.com/go-sql-driver/mysql"
)type User struct {ID int `orm:"column(id)"`Name string `orm:"column(name)"`Password string `orm:"column(password)"`
}func Execute() {// set to debug modeorm.Debug = true// register model and databaseorm.RegisterModel(new(User))orm.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/database?charset=utf8")// automatically build tableorm.RunSyncdb("default", false, true)// create orm objecto := orm.NewOrm()// datauser := new(User)user.Name = "mike"// insert datao.Insert(user)
}
然后编写tests/models_user_test.go
文件。
package testsimport ("quickstart/models""testing"
)func TestExecute(t *testing.T) {models.Execute()
}
使用go test ./tests
即可完成测试。当然这只是最简单的写法,要进行更精确友好的测试可以自行添加代码。
案例
为了方便起见,我们参考原有的程序,从下往上写。
Models
config.go
是读取配置的文件,自行在项目根目录下建立一个 config.json
文件,并且把对应字段写好即可(sql_user
等)。
package modelsimport ("encoding/json""fmt""io/ioutil""os"
)type Config struct {SqlUser string `json:"sql_user"`SqlPassword string `json:"sql_password"`SqlDatabase string `json:"sql_database"`SqlPort int `json:"sql_port"`
}func ReadConfig(configPath string) Config {configFile, err := os.Open(configPath)if err != nil {fmt.Println(err)}defer configFile.Close()byteValue, _ := ioutil.ReadAll(configFile)var config Configjson.Unmarshal([]byte(byteValue), &config)return config
}
user.go
定义了 user 这个表的模型。
package modelstype User struct {ID uint `orm:"column(id);auto"`Name string `orm:"column(name);unique;size(20)"`Password string `orm:"column(password);size(16)"`
}
operation.go
定义了针对数据库的一系列操作。其中orm.Ormer
已在 v2 版本中修改,文档建议设置成全局变量,这里在外面包了一层加强安全性。另外因为只是一个简单的案例,方法只是简单地写了几个。
package modelsimport ("fmt""strconv""github.com/beego/beego/v2/client/orm"_ "github.com/go-sql-driver/mysql"
)type DBHandle struct {orm orm.Ormer
}var DBH DBHandlefunc init() {// read configconfig := ReadConfig("./config.json")// set to debug modeorm.Debug = true// register model and databaseorm.RegisterModel(new(User))databaseUrl := config.SqlUser + ":" + config.SqlPassword + "@tcp(127.0.0.1:" +strconv.Itoa(config.SqlPort) + ")/" + config.SqlDatabase + "?charset=utf8"orm.RegisterDataBase("default", "mysql", databaseUrl)// automatically build tableorm.RunSyncdb("default", false, true)// create orm object with specified database as recommended in the documentDBH.orm = orm.NewOrmUsingDB("default")
}// @Title Insert
// @Description insert new data
// @Param mode: must be a pointer
func (this *DBHandle) Insert(mode interface{}) {_, err := this.orm.Insert(mode)if err != nil {fmt.Println(err)}
}// @Title Delete
// @Description delete by primary key
// @Param mode: must be a pointer
func (this *DBHandle) Delete(mode interface{}) {_, err := this.orm.Delete(mode)if err != nil {fmt.Println(err)}
}// @Title Update
// @Description update by primary key
// @Param mode: must be a pointer
func (this *DBHandle) Update(mode interface{}) {_, err := this.orm.Update(mode)if err != nil {fmt.Println(err)}
}// @Title QueryByPK
// @Description query by primary key
// remenber to specify the primary key for mode before pass it
// @Param mode: must be a pointer
func (this *DBHandle) QueryByPK(mode interface{}) {err := this.orm.Read(mode)if err != nil {fmt.Println(err)}
}// @Title QueryByField
// @Description query by field
// @Param mode: must be a pointer
func (this *DBHandle) QueryByField(mode interface{}, table string, field string,value interface{}) {_, err := this.orm.QueryTable(table).Filter(field, value).All(mode)if err != nil {fmt.Println(err)}
}
Controllers
user.go
定义了UserController
这个控制器。
package controllersimport ("quickstart/models""strconv"beego "github.com/beego/beego/v2/server/web"
)type UserController struct {beego.Controller
}func (u *UserController) Get() {uid := u.GetString(":uid")user := models.User{}if uid == "create" {user.Name = "Bob"user.Password = "asdj"models.DBH.Insert(&user)u.Data["json"] = "succeed to add a user"} else if uid != "" {id, _ := strconv.Atoi(uid)user.ID = uint(id)models.DBH.QueryByPK(&user)u.Data["json"] = user.Name}u.ServeJSON()
}
Routers
router.go
只需要 user 的部分。
package routersimport ("quickstart/controllers"beego "github.com/beego/beego/v2/server/web"
)func init() {ns := beego.NewNamespace("/v1",beego.NSNamespace("/user",beego.NSInclude(&controllers.UserController{},),),)beego.AddNamespace(ns)
}
commentsRouter_controllers.go
只留下 user 的 get 函数。
package routersimport (beego "github.com/beego/beego/v2/server/web""github.com/beego/beego/v2/server/web/context/param"
)func init() {beego.GlobalControllerRouter["quickstart/controllers:UserController"] = append(beego.GlobalControllerRouter["quickstart/controllers:UserController"],beego.ControllerComments{Method: "Get",Router: "/:uid",AllowHTTPMethods: []string{"get"},MethodParams: param.Make(),Filters: nil,Params: nil})}
测试
在浏览器中访问127.0.0.1:8080/v1/user/create
可以创建一个用户,然后看到创建成功的信息。
再访问127.0.0.1:8080/v1/user/1
就可以查询到 id 为 1 的用户的名称。
启用 https 协议
为什么需要 https,一句话,只有 http 寸步难行。
这一部分的解决方案在网上随便搜一下就有,这里再写一下,省的继续查。
实现的方法很简单,首先需要一个 SSL 证书。自己做一个或者买一个都可以。不用问怎么选,买的显然更好,自己做的只能用于开发环境。
下面介绍一下怎么自制一个 SSL 证书。因为只是用在开发环境,所以搞得简单点就行了。
# 生成服务器私钥
openssl genrsa -out server.key 1024
# 根据私钥和输入的信息生成证书请求文件,相关信息知道的就填,不清楚的随便填
openssl req -new -key server.key -out server.csr
# 用第一步的私钥和第二步的请求文件生成证书
openssl x509 -req -in server.csr -out server.crt -signkey server.key -days 3650
然后把server.key
和server.crt
放到 beego api 项目的 conf 目录下,并且修改app.conf
如下。
EnableHTTPS = true
EnableHttpTLS = true
EnableHttp = false
HttpsPort = 8000
HTTPSCertFile = "conf/server.crt"
HTTPSKeyFile = "conf/server.key"
最好把 http 关了,防止可能出现的一系列问题。
现在就只能通过https://……
进行请求了,注意由于使用自己制作的 SSL 证书,浏览器一定会报安全问题,不用管它就行。如果客户端不是浏览器,那必须在客户端信任自己生成的证书或者关闭证书校验,否则两边是连不上的。
另外,将项目打包之后可执行文件同一级目录下必须要有一个 conf 目录,且其中包含这两个证书文件。
标签:
相关文章
-
无相关信息