• 更全面的文档,在github中,推荐的一些官方文档内容不如github中的全,比如ormraw查询,就必须在github中才找到说明。。。
  • 或者看这个,不要看github首页推荐的,不全面

当前环境

[!tip]

之前用的go 1.16很顺畅,这次用的go 1.18,遇到一些bug,都记录一下

GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/d4m1ts/Library/Caches/go-build"
GOENV="/Users/d4m1ts/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/d4m1ts/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/d4m1ts/go"
GOPRIVATE=""
GOPROXY="https://goproxy.cn,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.18.2"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/fw/tddtsjp91wb9q64l5xt7jd540000gn/T/go-build2788719336=/tmp/go-build -gno-record-gcc-switches -fno-common"
  • 注意:Beego V2 之后,我们要求使用go mod特性,请务必确保开启了go mod特性,即设置了GO111MODULE=on

项目创建

bee api githubMonitor

创建后直接bee run启动,可能会出现如下的错误

▶ 0003 Failed to build the application: # golang.org/x/sys/unix
../../../../pkg/mod/golang.org/x/[email protected]/unix/syscall_darwin.1_13.go:29:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.1_13.go:27:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.1_13.go:40:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:28:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:43:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:59:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:75:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:90:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:105:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:121:3: //go:linkname must refer to declared function or variable
../../../../pkg/mod/golang.org/x/[email protected]/unix/zsyscall_darwin_amd64.go:121:3: too many errors

解决方案如下:

go get -u golang.org/x/sys

然后就可以正常启动了,创建的目录树如下:

.
├── conf
│   └── app.conf
├── controllers
│   ├── object.go
│   └── user.go
├── go.mod
├── go.sum
├── lastupdate.tmp
├── main.go
├── models
│   ├── object.go
│   └── user.go
├── routers
│   ├── commentsRouter_controllers.go
│   └── router.go
└── tests
    └── default_test.go

简单分析

提供1个可访问的路由,http://127.0.0.1:8080/v1/user/login?username=astaxie&password=11111,结合roters中的文件分析一下就知道是个什么道理了。

router.go中创建了一个命名空间,所以前置路由就是 /v1/user,有点类似于springboot中的@RequestMapping("/v1/user")

image-20220622115540959

然后routers/下还有另一个文件commentsRouter_controllers.go,可以看到是对路由的划分

image-20220622120004751

最后再看看Login方法,参数来自于param.Make(),到这里再理一下就差不多基础够了

image-20220622120135841

个人总结

所以我们如果要开发的话,一般分为如下几个步骤

  • 数据库ORM(当前数据的结构体一般写到对应的model里面)
  • model层
  • controller层
  • router层

[!note]

注意:结构体里面的元素也尽量要大写开头,这样表示可以被其他地方调用,不然比如在序列化的时候,因为读取不到元素导致序列化出来为空!!!

其他功能

其他一些功能,如上传下载,需要的时候再回过头来补充,可参考官方文档:https://beego.gocn.vip/beego/zh/developing/web/router/ctrl_style/

ORM

为了方便使用,且项目比较小,所以准备先用sqlite来,避免给别人用还一大堆麻烦的配置

所有内容详细参考:https://beego.gocn.vip/beego/zh/developing/orm/

主键

可以用auto显示指定一个字段为自增主键,该字段必须是 int, int32, int64, uint, uint32, 或者 uint64。

MyId int32 `orm:"auto"`

如果一个模型没有定义主键,那么 符合上述类型且名称为 Id 的模型字段将被视为自增主键。

如果不想使用自增主键,那么可以使用pk设置为主键。

Name string `orm:"pk"`

自动更新时间

Created time.Time `orm:"auto_now_add;type(datetime)"`
Updated time.Time `orm:"auto_now;type(datetime)"`
  • auto_now 每次 model 保存时都会对时间自动更新
  • auto_now_add 第一次保存时才设置时间

对于批量的 update 此设置是不生效的

其他

null

数据库表默认为 NOT NULL,设置 null 代表 ALLOW NULL

Name string `orm:"null"`

size

string 类型字段默认为 varchar(255)

设置 size 以后,db type 将使用 varchar(size)

Title string `orm:"size(60)"`

增删改查

官方文档包含内容:增删改查、批量增删改查、以及一些合并操作,如不存在就创建等

高级查询参考(好用,默认只返回1条数据):beego——高级查询

  • Insert
  • Update
  • Delete
  • Read

实例

实例一:(完整)

package models

import (
    "context"
    "fmt"
    "github.com/beego/beego/v2/client/orm"
    _ "github.com/mattn/go-sqlite3"
    "time"
)

// 结构体一般写道对应的model里面
type TestUser struct {
    ID   int    `orm:"column(id)"`
    Name string `orm:"column(name);size(60)"`
    Age int        `orm:"column(age);null"`
    Created time.Time `orm:"auto_now_add;type(datetime)"`
    Updated time.Time `orm:"auto_now;type(datetime)"`
}

func init(){
    // 注册模型(参数为表名,会自动转换为 test_user)
    orm.RegisterModel(new(TestUser))
    // 参数1        数据库的别名,用来在 ORM 中切换数据库使用
    // 参数2        driverName
    // 参数3        对应的链接字符串
    // 参数4(可选)  设置最大空闲连接 orm.MaxIdleConnections(maxIdle)
    // 参数5(可选)  设置最大数据库连接 (go >= 1.2) orm.MaxOpenConnections(maxConn)
    _ = orm.RegisterDataBase("default", "sqlite3", "data.db")
}

func OrmUsage() {
    // 自动创建表
    orm.RunSyncdb("default", false, true)
    // 创建orm对象
    o := orm.NewOrm()
    // 增
    user := new(TestUser)
    user.Name = "mike"
    o.Insert(user)

    // 查
    user1 := new(TestUser)
    user1.ID = 1
    o.Read(user1)
    fmt.Print(user1.Name) // mike

    // 事务
    o.DoTx(
        func(ctx context.Context, txOrm orm.TxOrmer) error {
            // data
            user := new(TestUser)
            user.Name = "test_transaction"

            // insert data
            // Using txOrm to execute SQL
            _, e := txOrm.Insert(user)
            // if e != nil the transaction will be rollback
            // or it will be committed
            return e
        })
}

效果如下:

image-20220622160604443

实例二:(可能自用比较多)

给ormer实例化了在其他地方用。

package models

import (
    "github.com/beego/beego/v2/client/orm"
    _ "github.com/mattn/go-sqlite3"
)

var Ormer orm.Ormer

func init(){
    orm.RegisterModel(new(TaskInfo))
    _ = orm.RegisterDataBase("default", "sqlite3", "data.db")
    _ = orm.RunSyncdb("default", false, true)
    Ormer = orm.NewOrm()
}

实例三:(高级查询)

构建多个or like 语句

func FetchTask(keyword string) {
    var results []TaskInfo
    qs := Ormer.QueryTable(new(TaskInfo))
    condition := orm.NewCondition()
    condition1 := condition.Or("name__icontains", keyword).Or("keyword__icontains", keyword).Or("ignoreUser__icontains", keyword).Or("ignoreRepo__icontains", keyword).Or("noticeEmail__icontains", keyword)
    filter := qs.SetCond(condition1).OrderBy("id")
    filter.All(&results)
    fmt.Print(results)
}

注意事项

  • ormer在更新数据库时,如插入数据,传入的不是结构体变量,而是结构体变量指针,如果传入的格式不对,会提示 cannot use non-ptr model struct

日志

很简单,更多的操作直接看文档,简单的记录一下

https://beego.gocn.vip/beego/zh/developing/logs/#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B

import (
    "github.com/beego/beego/v2/core/logs"
)
    logs.SetLogger(logs.AdapterConsole)
    logs.Debug("my book is bought in the year of ", 2016)
    logs.Info("this %s cat is %v years old", "yellow", 3)
    logs.Warn("json is a type of kv like", map[string]int{"key": 2016})
    logs.Error(1024, "is a very", "good game")
    logs.Critical("oh,crash")

image-20220622161335158

数据类型验证

有时候需要验证用户输入的内容,如不能为空、只能是数字等,但是如果每次都用if去判断,工作量确实有点大,所以beego提供了一个额外的组件validation来辅助验证

go get github.com/beego/beego/v2/core/validation

实例

import (
    "github.com/beego/beego/v2/core/validation"
    "log"
)

type User struct {
    Name string
    Age int
}

func main() {
    u := User{"man", 40}
    valid := validation.Validation{}
    valid.Required(u.Name, "name")
    valid.MaxSize(u.Name, 15, "nameMax")
    valid.Range(u.Age, 0, 18, "age")

    if valid.HasErrors() {
        // 如果有错误信息,证明验证没通过
        // 打印错误信息
        for _, err := range valid.Errors {
            log.Println(err.Key, err.Message)
        }
    }
    // or use like this
    if v := valid.Max(u.Age, 140, "age"); !v.Ok {
        log.Println(v.Error.Key, v.Error.Message)
    }
    // 定制错误信息
    minAge := 18
    valid.Min(u.Age, minAge, "age").Message("少儿不宜!")
    // 错误信息格式化
    valid.Min(u.Age, minAge, "age").Message("%d不禁", minAge)
}

发起web请求

beego也给提供了一个httplib,可以直接用,底层也是net/http没啥区别,封装了一层吧

go get github.com/beego/beego/v2/client/httplib

过滤器

也可以理解为拦截器,就是在访问路由前执行一个操作,比如安全检查、登陆检查等。

  • loginFilter.go
package filters

import (
    "github.com/beego/beego/v2/server/web/context"
    "githubMonitor/controllers"
)

var FilterUser = func(ctx *context.Context) {
    token := ctx.Input.Header("token")
    if token == "" {
        result := controllers.ReturnRes{
            Code: 401,
            Message: "未登录",
        }
        ctx.Output.JSON(result, true, false)
    }
}
  • router.go
import "githubMonitor/filters"
beego.InsertFilter("/*", beego.BeforeRouter, filters2.FilterUser)
web.InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt)

InsertFilter 函数的三个必填参数,一个可选参数

  • pattern 路由规则,可以根据一定的规则进行路由,如果你全匹配可以用 *
  • position 执行 Filter 的地方,五个固定参数如下,分别表示不同的执行过程
    • BeforeStatic 静态地址之前
    • BeforeRouter 寻找路由之前
    • BeforeExec 找到路由之后,开始执行相应的 Controller 之前
    • AfterExec 执行完 Controller 逻辑之后执行的过滤器
    • FinishRouter 执行完逻辑之后执行的过滤器
  • filter filter 函数 type FilterFunc func(*context.Context)
  • opts
    1. web.WithReturnOnOutput: 设置 returnOnOutput 的值(默认 true), 如果在进行到此过滤之前已经有输出,是否不再继续执行此过滤器,默认设置为如果前面已有输出(参数为true),则不再执行此过滤器
    2. web.WithResetParams: 是否重置 filters 的参数,默认是 false,因为在 filters 的 pattern 和本身的路由的 pattern 冲突的时候,可以把 filters 的参数重置,这样可以保证在后续的逻辑中获取到正确的参数,例如设置了 /api/* 的 filter,同时又设置了 /api/docs/* 的 router,那么在访问 /api/docs/swagger/abc.js 的时候,在执行 filters 的时候设置 :splat 参数为 docs/swagger/abc.js,但是如果不清楚 filter 的这个路由参数,就会在执行路由逻辑的时候保持 docs/swagger/abc.js,如果设置了 true,就会重置 :splat 参数.
    3. web.WithCaseSensitive: 是否大小写敏感。

补充

restful返回struct

  • 定义结构体
// ReturnRes 返回的结果模板,注意开头要大写
type ReturnRes struct {
    Code int `json:"code"`
    Message string `json:"message"`
    Data interface{} `json:"data"`
}
  • 返回结果
fetchResult := models.FetchTask(query)
    if fetchResult != nil {
        result.Code = 200
        result.Message = "查询成功"
        result.Data = fetchResult
    } else {
        result.Code = 0
        result.Message = "查询失败"
    }

    o.Data["json"] = result
    o.ServeJSON()
  • 浏览器结果

image-20220622234749896

复杂的json

复杂的内容,可以拆分成很多个struct,然后再层层嵌套,一定要注意类型不能错!!!

image-20220623141539338

数据库值类型

在数据库里面,只能指定以下有的类型,如 https://beego.gocn.vip/beego/zh/developing/orm/model.html#sqlite3

image-20220623143725609

如果不在这些类型里面,用自己创建的一些类型如结构体,可能会抛出如下异常

field: models.GitInfo.Data, unsupport field type &{%!s(int=0) []}, may be miss setting tag

我的解决办法,就是序列化成string

image-20220623143807330

多个init()

当一个package中同时存在多个init()时,一定要注意顺序问题,不然很有可能因为先后问题出现空指针的问题

因此强烈建议只要1个init()

反序列化坑点

有时候,传入的数据是正确的,但是一直反序列化不出来!!!

可以分析一下这个数据前面的数据是否正常,可能是前面反序列化异常,导致后面都以默认数据返回了,大坑。。。

404

分析后,发现存在正则表达式的路由匹配,所以编写好对应的controller后,像下面这样注册就可以了,记得放到路由最下面

beego.Router("/", &controllers.NotFoundController{}, "*:NotFound")
beego.Router("/*.*", &controllers.NotFoundController{}, "*:NotFound")

设置返回响应头

在控制器中设置,输入是Input,输出就是Output

o.Ctx.Output.Header("Server", "GitHub Monitor")

推荐批量包装输出,抽象一个方法出来

// WrapOutput 包装输出
func WrapOutput(output context.BeegoOutput) *context.BeegoOutput {
    output.Header("Server", "GitHub Monitor")
    return &output
}

附加一:验证码

beego自带的验证码,实在看不懂,所以找了个其他的,也挺好用

  • 安装
go get -u github.com/mojocn/base64Captcha
  • 完整生成验证码和验证实例
package main

import (
    "fmt"
    "github.com/mojocn/base64Captcha"
    "log"
)

//configJsonBody json request body.
type configJsonBody struct {
    Id            string
    CaptchaType   string
    VerifyValue   string
    DriverAudio   *base64Captcha.DriverAudio
    DriverString  *base64Captcha.DriverString
    DriverChinese *base64Captcha.DriverChinese
    DriverMath    *base64Captcha.DriverMath
    DriverDigit   *base64Captcha.DriverDigit
}

var store = base64Captcha.DefaultMemStore

// GetCaptcha base64Captcha create return id, b64s, err
func GetCaptcha() (string, string, error) {

    // Driver配置
    //     {
    //         ShowLineOptions: [],
    //         CaptchaType: "string",
    //         Id: '',
    //         VerifyValue: '',
    //         DriverAudio: {
    //             Length: 6,
    //             Language: 'zh'
    //         },
    //         DriverString: {
    //             Height: 60,
    //             Width: 240,
    //             ShowLineOptions: 0,
    //             NoiseCount: 0,
    //             Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
    //             Length: 6,
    //             Fonts: ["wqy-microhei.ttc"],
    //             BgColor: {R: 0, G: 0, B: 0, A: 0},
    //         },
    //         DriverMath: {
    //             Height: 60,
    //             Width: 240,
    //             ShowLineOptions: 0,
    //             NoiseCount: 0,
    //             Length: 6,
    //             Fonts: ["wqy-microhei.ttc"],
    //             BgColor: {R: 0, G: 0, B: 0, A: 0},
    //         },
    //         DriverChinese: {
    //             Height: 60,
    //             Width: 320,
    //             ShowLineOptions: 0,
    //             NoiseCount: 0,
    //             Source: "设想,你在,处理,消费者,的音,频输,出音,频可,能无,论什,么都,没有,任何,输出,或者,它可,能是,单声道,立体声,或是,环绕立,体声的,,不想要,的值",
    //             Length: 2,
    //             Fonts: ["wqy-microhei.ttc"],
    //             BgColor: {R: 125, G: 125, B: 0, A: 118},
    //         },
    //         DriverDigit: {
    //             Height: 80,
    //             Width: 240,
    //             Length: 5,
    //             MaxSkew: 0.7,
    //             DotCount: 80
    //         }
    //     },
    //     blob: "",
    //     loading: false
    // }

    // 调试配置,生成的种类
    var param = configJsonBody{
        Id:          "",
        CaptchaType: "string",
        VerifyValue: "",
        DriverAudio: &base64Captcha.DriverAudio{},
        DriverString: &base64Captcha.DriverString{
            Length:          4,
            Height:          60,
            Width:           240,
            ShowLineOptions: 2,
            NoiseCount:      0,
            Source:          "1234567890qwertyuioplkjhgfdsazxcvbnm",
        },
        DriverChinese: &base64Captcha.DriverChinese{},
        DriverMath:    &base64Captcha.DriverMath{
                        Height: 60,
                        Width: 240,
                        ShowLineOptions: 0,
                        NoiseCount: 0,
                    },
        DriverDigit:   &base64Captcha.DriverDigit{},
    }
    var driver base64Captcha.Driver

    //create base64 encoding captcha
    switch param.CaptchaType {
    case "audio":
        driver = param.DriverAudio
    case "string":
        driver = param.DriverString.ConvertFonts()
    case "math":
        driver = param.DriverMath.ConvertFonts()
    case "chinese":
        driver = param.DriverChinese.ConvertFonts()
    default:
        driver = param.DriverDigit
    }
    c := base64Captcha.NewCaptcha(driver, store)
    return c.Generate()
    // id, b64s, err := c.Generate()

    // body := map[string]interface{}{"code": 1, "data": b64s, "captchaId": id, "msg": "success"}
    // if err != nil {
    //     body = map[string]interface{}{"code": 0, "msg": err.Error()}
    // }
    // var _ = body

    // // log.Println(body)
    // log.Println(1)
    // log.Println(id)

    // log.Printf("store =%+v\n", store)
}

// base64Captcha verify
func VerifyCaptcha(id, VerifyValue string) bool {
    return store.Verify(id, VerifyValue, true)
}

func main() {
    id, b64s, err := GetCaptcha()
    fmt.Println(b64s) // 图形验证码base64
    if err != nil {
        return
    }
    var _ = b64s
    log.Println("id =", id)
    log.Println("VerifyValue =", store.Get(id, true))
    result := VerifyCaptcha(id, store.Get(id, true))
    log.Println("result =", result)
}
Copyright © d4m1ts 2023 all right reserved,powered by Gitbook该文章修订时间: 2023-03-07 14:17:47

results matching ""

    No results matching ""