更多分享内容可访问我的个人博客
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 main
import (
_ "quickstart/routers"
beego "github.com/beego/beego/v2/server/web"
)
func main() {
if beego.BConfig.RunMode == "dev" {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
beego.Run()
}
main函数中第一条语句是设置在 dev 运行模式下启动 swagger 调试器,这与项目主干无关,暂时不管它。
第二条语句调用了一个Run方法,运行整个程序。那么到底是怎么运行的呢,看到一个关键的引入_ "quickstart/routers"。这个包只引入了其中的 init 函数,我们去到routers/router.go查看到底干了什么。
Routerspackage routers
import (
"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 routers
import (
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,查看这一层干了什么。
Controllerspackage controllers
import (
"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.User
json.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"] = users
u.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.User
json.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。
Modelspackage models
import (
"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 string
Username string
Password string
Profile Profile
}
type Profile struct {
Gender string
Age int
Address string
Email string
}
func AddUser(u User) string {
u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
UserList[u.Id] = &u
return 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 main
import (
"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 model
orm.RegisterModel(new(User))
// register default database
// modify username, password and database for yourself
orm.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/database?charset=utf8")
}
func main() {
orm.Debug = true
// automatically build table
orm.RunSyncdb("default", false, true)
// create orm object
o := orm.NewOrm()
// data
user := new(User)
user.Name = "mike"
// insert data
o.Insert(user)
}
运行go run main.go并查看 mysql 中的数据。发现新建了一个表并且填入了数据。
这是一个很简单的案例,其余操作可以参考相关文档。
测试纯粹的测试很简单,可以参考该系列文章。
现在将上面的代码稍微修改一下,写入models/user.go。
package models
import (
"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 mode
orm.Debug = true
// register model and database
orm.RegisterModel(new(User))
orm.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/database?charset=utf8")
// automatically build table
orm.RunSyncdb("default", false, true)
// create orm object
o := orm.NewOrm()
// data
user := new(User)
user.Name = "mike"
// insert data
o.Insert(user)
}
然后编写tests/models_user_test.go文件。
package tests
import (
"quickstart/models"
"testing"
)
func TestExecute(t *testing.T) {
models.Execute()
}
使用go test ./tests即可完成测试。当然这只是最简单的写法,要进行更精确友好的测试可以自行添加代码。
案例为了方便起见,我们参考原有的程序,从下往上写。
Modelsconfig.go是读取配置的文件,自行在项目根目录下建立一个 config.json 文件,并且把对应字段写好即可(sql_user等)。
package models
import (
"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 Config
json.Unmarshal([]byte(byteValue), &config)
return config
}
user.go定义了 user 这个表的模型。
package models
type 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 models
import (
"fmt"
"strconv"
"github.com/beego/beego/v2/client/orm"
_ "github.com/go-sql-driver/mysql"
)
type DBHandle struct {
orm orm.Ormer
}
var DBH DBHandle
func init() {
// read config
config := ReadConfig("./config.json")
// set to debug mode
orm.Debug = true
// register model and database
orm.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 table
orm.RunSyncdb("default", false, true)
// create orm object with specified database as recommended in the document
DBH.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 controllers
import (
"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 routers
import (
"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 routers
import (
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 目录,且其中包含这两个证书文件。



