关于beego:Go-笔记-Beego-之-orm-增删改查

增package mainimport ( "fmt" "github.com/beego/beego/v2/client/orm" _ "github.com/go-sql-driver/mysql" "log" "time")type Account struct { ID int64 Name string Password string Birthday *time.Time Telephone string Email string Addr string Status int8 RoleId int64 DepartmentId int64 CreatedAt *time.Time `orm:"auto_now_add"` UpdatedAt *time.Time `orm:"auto_now"` //DeletedAt *time.Time `orm:"null"` DeletedAt *time.Time Description string Sex bool}func main() { orm.Debug = true // 打印 sql 。可能影响性能。生产不倡议应用 driverName := "mysql" // 驱动名 databaseName := "default" dsn := "golang:123456@tcp(192.168.100.20:33060)/cmdb?charset=utf8mb4&parseTime=true" // 注册数据库驱动到 orm // 参数:自定义的数据库类型名,驱动类型(orm 中提供的) orm.RegisterDriver(driverName, orm.DRMySQL) // 参数:beego 必须指定默认的数据库名称,应用的驱动名称(orm 驱动类型名),数据库的配置信息,数据库(连接池),连贯(池)名称 err := orm.RegisterDataBase(databaseName, driverName, dsn) if err != nil { log.Fatal(err) } // 定义构造 // 注册模型 orm.RegisterModel(new(Account)) now := time.Now() // 增 // 先创立一个 orm 对象 ormer := orm.NewOrm() account := Account{ Name: "dangdang", Password: "123456", Birthday: &now, // 须要应用指针,所以后面须要先定义个 now 变量 Telephone: "88888888", Email: "aaa@126.com", } id, err := ormer.Insert(&account) fmt.Println(id, err, account)}执行过程如下: ...

March 21, 2022 · 6 min · jiezi

关于beego:使用Beego撸了一个社区

话不多说间接放链接霓虹灯下社区Github

August 9, 2021 · 1 min · jiezi

关于beego:Beego-路由组Namespace的定义

案例一func init(){ // 定义路由组 应用 Namespace func 来定义 // 路由组的应用: // 在最外层 个别新建一个路由组 关键字:NewNamespace // 返回值用于调用 AddNamespace func 进行注册 ns := beego.NewNamespace("/v1", // 在外部再次须要定义子路由组的时候。 可应用 // NS 级别的Namespace 去定义 实践上Namespace是能够有限进行嵌套的 beego.NSNamespace("/user", beego.NSRouter( "/login",&controllers.AuthController{}, "post:AcceptLoginParam"), ), ) //注册路由组 beego.AddNamespace(ns)}案例二 //front api front := beego.NewNamespace("/v1", beego.NSNamespace("/front", beego.NSInclude( &api.FrontController{}, ), ), ) //login api login := beego.NewNamespace("/v1", beego.NSNamespace("/login", // 主动匹配 beego.NSInclude( &api.FrontController{}, ), ), ) beego.AddNamespace(front, login)参考beego中武官网

August 7, 2020 · 1 min · jiezi

beego注解路由不生成的解决问题

首先确定app.conf内的runmode的值是否是dev,如果确定了是,那你就碰到了一个Beego到现在都没解决的bug,解决办法如下: 在main.go加入下列代码 //go:generate sh -c "echo 'package routers; import \"github.com/astaxie/beego\"; func init() {beego.BConfig.RunMode = beego.DEV}' > routers/0.go"//go:generate sh -c "echo 'package routers; import \"os\"; func init() {os.Exit(0)}' > routers/z.go"//go:generate go run $GOFILE//go:generate sh -c "rm routers/0.go routers/z.go"然后执行 go generate就会生成路由了。

August 20, 2019 · 1 min · jiezi

借助URLOS快速安装beego-web框架

简介beego是一个快速开发Go应用的http框架,go 语言方面技术大牛。beego可以用来快速开发API、Web、后端服务等各种应用,是一个RESTFul的框架。 今天我们介绍一种更快速的安装方法,那就是通过URLOS一键安装beego。urlos是什么? URLOS是一个云主机管理软件,基于Docker容器技术打包和运行应用,包含负载均衡和故障转移等高级功能,可自动识别机器和云应用的故障并将云应用转移至可用的机器上,单机故障并不影响业务开展。 你可以使用以下命令安装URLOS: curl -LO www.urlos.com/iu && sh iu在此不讨论URLOS的使用方法,感兴趣的朋友请自行搜索,我们直接来看URLOS如何快速安装beego: 安装流程1.登录URLOS系统后台,在应用市场中搜索“beego”,找到之后,直接点击安装按钮 2.填写服务名称、选择运行节点、服务端口、选择智能部署 3.填写域名:www.aaa.com(这里填写自己的域名) 4.设置SFTP选择“上传与下载”选项卡,开启SFTP上传下载并填写SFTP端口、SFTP密码; 然后点击“提交”按钮,等待部署完成; 部署完成后,网站已经成功跑起来了! 上传网站代码用ssh或者sftp客户端登录。 网站根目录是:/mounts/beego001/data/www/web(由于本次教程的服务名称为beego001,实际操作中根据你填写的服务名称自动创建) 如果要更新网站,上传网站文件到网站根目录后,重新部署一下就好了:

July 10, 2019 · 1 min · jiezi

docker在centos上安装beego及部分理解

诚如前面一篇文章,是简单的布置了golang的一个demo,再次布beego 1、写Dockerfile# docker build# Version 1.0FROM centosMAINTAINER yanyue@78dk.comENV GOROOT /usr/local/goENV GOPATH /data/gopathENV PATH $GOROOT/bin:$PATHADD go/ /usr/local/goRUN mkdir -p /data/gopathADD src/ /data/gopath/srcADD pkg/ /data/gopath/pkgWORKDIR /data/gopath/src/lotteryRUN cd /data/gopath/src/lotteryRUN go build -o server.sh main.goRUN cp /data/gopath/src/lottery/server.sh /usr/bin/server.shRUN chmod 777 /usr/bin/server.shENTRYPOINT /usr/bin/server.sh注最后一行不能使用RUN和CMD,不然会将启动日志输出到命令行,加&会导致docker内server.sh未启动(血泪史) 2、创建镜像docker build -t golang:v1 .3、创建容器docker run -itd -p 80:8080 golang:v1 /bin/bash注端口号绑定:前面为本机的端口,后面为容器端口 4、查看容器docker ps注 此命令后面加上-a就能查看所有状态的镜像 坑点:坑点已经填平了,按照上面的步骤,不会错(泪目)

May 6, 2019 · 1 min · jiezi

Beego Logs 源码分析 中篇

文件输出引擎使用到的读写锁 sync.RWMutex读写锁是一种同步机制,允许多个读操作同时读取数据,但是只允许一个写操作写数据。锁的状态有三种:读模式加锁、写模式加锁、无锁。无锁。读/写进程都可以进入。读模式锁。读进程可以进入。写进程不可以进入。写模式锁。读/写进程都不可以进入。就拿文件行数这个变量来看,如果开启了日志文件按小时按行数切割的功能,要先读取当前文件行数变量值。当并发情况下,多个 goroutine 在打日志,读取文件行数和修改文件行数便成为一对“读写”操作,所以需要用读写锁,读写锁对于读操作不会导致锁竞争和 goroutine 阻塞。// WriteMsg write logger message into file.func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error { ··· if w.Rotate { w.RLock() if w.needRotateHourly(len(msg), h) { w.RUnlock() w.Lock() if w.needRotateHourly(len(msg), h) { if err := w.doRotate(when); err != nil { fmt.Fprintf(os.Stderr, “FileLogWriter(%q): %s\n”, w.Filename, err) } } w.Unlock() } else if w.needRotateDaily(len(msg), d) { w.RUnlock() w.Lock() if w.needRotateDaily(len(msg), d) { if err := w.doRotate(when); err != nil { fmt.Fprintf(os.Stderr, “FileLogWriter(%q): %s\n”, w.Filename, err) } } w.Unlock() } else { w.RUnlock() } } w.Lock() , err := w.fileWriter.Write([]byte(msg)) if err == nil { w.maxLinesCurLines++ w.maxSizeCurSize += len(msg) } w.Unlock() ···}总结下 Goroutine 的使用监听 msgChan第一处是开启异步选项时,启动一个 goroutine 监听 msgChan 是否为空,发现不为空便取走日志信息进行输出。// Async set the log to asynchronous and start the goroutinefunc (bl *BeeLogger) Async(msgLen …int64) *BeeLogger { ··· go bl.startLogger() ···}// start logger chan reading.// when chan is not empty, write logs.func (bl *BeeLogger) startLogger() { gameOver := false for { select { case bm := <-bl.msgChan: bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) ··· } ··· }}监听计时器实现日志文件按日期分割文件输出引擎 file.go 文件中,初始化 fileWriter *os.File 时启动一个 goroutine 执行 dailyRotate() :func (w *fileLogWriter) initFd() error { fd := w.fileWriter fInfo, err := fd.Stat() if err != nil { return fmt.Errorf(“get stat err: %s”, err) } w.maxSizeCurSize = int(fInfo.Size()) w.dailyOpenTime = time.Now() w.dailyOpenDate = w.dailyOpenTime.Day() w.maxLinesCurLines = 0 if w.Daily { go w.dailyRotate(w.dailyOpenTime) // <—— } if fInfo.Size() > 0 && w.MaxLines > 0 { count, err := w.lines() if err != nil { return err } w.maxLinesCurLines = count } return nil}dailyRotate() 方法中,tm 定时器时间一到,便会往 tm.C 通道发送当前时间,此时 a 语句便停止阻塞,可以继续往下执行。func (w *fileLogWriter) dailyRotate(openTime time.Time) { y, m, d := openTime.Add(24 * time.Hour).Date() nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location()) tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100)) <-tm.C // <— a 语句 w.Lock() if w.needRotate(0, time.Now().Day()) { if err := w.doRotate(time.Now()); err != nil { fmt.Fprintf(os.Stderr, “FileLogWriter(%q): %s\n”, w.Filename, err) } } w.Unlock()}开启新的 goroutine 删除失效的日志文件因为删除文件涉及文件 IO 处理,为了避免阻塞主线程,便交由另外 goroutine 去做。,go w.deleteOldLog(),超过 MaxDays 的日志文件便是失效的。// DoRotate means it need to write file in new file.// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)func (w fileLogWriter) doRotate(logTime time.Time) error { ··· err = os.Rename(w.Filename, fName) ··· startLoggerErr := w.startLogger() go w.deleteOldLog() ···}func (w fileLogWriter) deleteOldLog() { dir := filepath.Dir(w.Filename) filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { defer func() { if r := recover(); r != nil { fmt.Fprintf(os.Stderr, “Unable to delete old log ‘%s’, error: %v\n”, path, r) } }() if info == nil { return } if !info.IsDir() && info.ModTime().Add(24time.Hourtime.Duration(w.MaxDays)).Before(time.Now()) { if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && strings.HasSuffix(filepath.Base(path), w.suffix) { os.Remove(path) } } return })}使用 goto 语句保证即使发生错误也要重启 LoggerdoRotate() 方法大体逻辑:重命名之前写入的日志文件,err = os.Rename(w.Filename, fName)首先找到 一个可用的 filename ,循环遍历1-999,如果找不到报错;,err:=os.Lstat(fName) :若以 fName 为名的文件不存在则返回 err 不为空。os.Chmod(fName, os.FileMode(rotatePerm)) 修改文件权限。重新启动 Logger :一是启动 Logger ,w.startLogger();二是开启一个 goroutine 删除失效的日志文件。注意到下面代码段中的 a 语句和 b 语句,它们并不是返回错误阻止代码继续执行,而是即使发生错误也会保证重启一个新的 Logger。如果是执行到 a 语句这种情况,有可能是该日志文件已经被别的程序删除或者其他原因导致文件不存在,但大可不必因为一个日志文件的丢失而阻止了新 Logger 的启动,简而言之,这个错误是可以忽略的。// DoRotate means it need to write file in new file.// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)func (w *fileLogWriter) doRotate(logTime time.Time) error { // file exists // Find the next available number num := 1 fName := "" rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64) if err != nil { return err } _, err = os.Lstat(w.Filename) if err != nil { //even if the file is not exist or other ,we should RESTART the logger goto RESTART_LOGGER // <——- a 语句 } if w.MaxLines > 0 || w.MaxSize > 0 { for ; err == nil && num <= 999; num++ { fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(“2006-01-02”), num, w.suffix) _, err = os.Lstat(fName) } } else { fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format(“2006-01-02”), w.suffix) _, err = os.Lstat(fName) for ; err == nil && num <= 999; num++ { fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format(“2006-01-02”), num, w.suffix) _, err = os.Lstat(fName) } } // return error if the last file checked still existed if err == nil { return fmt.Errorf( “Rotate: Cannot find free log number to rename %s”, w.Filename) } // close fileWriter before rename w.fileWriter.Close() // Rename the file to its new found name // even if occurs error,we MUST guarantee to restart new logger err = os.Rename(w.Filename, fName) if err != nil { goto RESTART_LOGGER // <——- b 语句 } err = os.Chmod(fName, os.FileMode(rotatePerm))RESTART_LOGGER: // <——- startLoggerErr := w.startLogger() go w.deleteOldLog() if startLoggerErr != nil { return fmt.Errorf(“Rotate StartLogger: %s”, startLoggerErr) } if err != nil { return fmt.Errorf(“Rotate: %s”, err) } return nil}涉及到 sync.WaitGroup 的使用a 语句处,开启 goroutine 前计数器加一,执行完该 goroutine 后计数器减一,即 b 语句。// Async set the log to asynchronous and start the goroutinefunc (bl *BeeLogger) Async(msgLen …int64) *BeeLogger { ··· bl.wg.Add(1) // <—– a 语句 go bl.startLogger() return bl}// start logger chan reading.// when chan is not empty, write logs.func (bl *BeeLogger) startLogger() { gameOver := false for { select { case bm := <-bl.msgChan: bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) case sg := <-bl.signalChan: // Now should only send “flush” or “close” to bl.signalChan bl.flush() if sg == “close” { for _, l := range bl.outputs { l.Destroy() } bl.outputs = nil gameOver = true } bl.wg.Done() // <—— b 语句 } if gameOver { break } }}分析并发执行下面 Flush() 方法的情况。假设有 A , B , C 三个 goroutine,并且假设 A 先执行到 e 语句,从a 语句知道初始计数器为 1 ,所以 e 语句必须等到上述 startLogger-goroutine 执行 b 语句完毕后才停止阻塞。而后 A 再让计数器加一。因为 bl.signalChan 的缓存大小为1,所以 B,C 阻塞在 d 语句,等到 B,C 其中之一能执行 e 语句的时候计数器必然大于0,才不会导致永久阻塞。所以 f 语句要放在 e 语句之后。// Flush flush all chan data.func (bl *BeeLogger) Flush() { if bl.asynchronous { bl.signalChan <- “flush” // <—— d 语句 bl.wg.Wait() // <—— e 语句 bl.wg.Add(1) // <—— f 语句 return } bl.flush()}因此再看下面的 Close() 方法,它是不能并发执行的,会导致 “panic: close of closed channel"错误。不过笔者暂时没懂为什么 beego logs 不把这里做一下改进,让 Close() 也支持并发调用不好吗?// Close close logger, flush all chan data and destroy all adapters in BeeLogger.func (bl *BeeLogger) Close() { if bl.asynchronous { bl.signalChan <- “close” bl.wg.Wait() // <—— g 语句 close(bl.msgChan) } else { bl.flush() for _, l := range bl.outputs { l.Destroy() } bl.outputs = nil } close(bl.signalChan)} ...

March 14, 2019 · 5 min · jiezi

Beego Logs 源码分析 上篇

最近参加春招,确实挺受打击,平常做项目遇到的问题,学到的知识点没有及时总结,导致在面试的时候无法清晰的描述出来,因此本专栏后续日常更新,总结编程之路的点滴。下面进入正题。Beego Logs 使用先大致了解怎么使用,再进行剖析。 // Test console without color func TestConsoleNoColor(t *testing.T) { log := NewLogger(100) log.SetLogger(“console”, {"color":false}) bl.Error(“error”) bl.Warning(“warning”) } // NewLogger returns a new BeeLogger. // channelLen means the number of messages in // chan(used where asynchronous is true). // if the buffering chan is full, logger adapters write to file or other way. func NewLogger(channelLens …int64) *BeeLogger { bl := new(BeeLogger) bl.level = LevelDebug bl.loggerFuncCallDepth = 2 bl.msgChanLen = append(channelLens, 0)[0] if bl.msgChanLen <= 0 { bl.msgChanLen = defaultAsyncMsgLen } bl.signalChan = make(chan string, 1) bl.setLogger(AdapterConsole) return bl }上面有一句代码:bl.msgChanLen = append(channelLens, 0)[0]往 channelLens 切片添加一个值为零的元素后再取头个元素,这个技巧有以下好处:Go 不支持可选参数,但 Go 支持可变参数,这样做变相达到了可选参数的效果。如果 chanelLens 原来为空的话也能拿出一个值为零的元素出来,不用再去判断参数是否为空数组。loggerFuncCallDepth 的值应设为多少这个变量表示函数调用的栈深度,用于记录日志时同时打印出当时执行语句的位置,包括文件名和行号。虽然 NewLogger 方法里面默认将 loggerFuncCallDepth 置为2,但是如果你单独使用logs包时应根据情况设置不同值。举个栗子:···bl.Error(“error”) // ———-a 语句···// Error Log ERROR level message.func (bl *BeeLogger) Error(format string, v …interface{}) { if LevelError > bl.level { return } bl.writeMsg(LevelError, format, v…) // ———-b 语句}func (bl *BeeLogger) writeMsg(logLevel int, msg string, v …interface{}) error { ··· if bl.enableFuncCallDepth { _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // ———-c 语句 ··· } ···}func Caller(skip int) (pc uintptr, file string, line int, ok bool) { ···}关于 Caller 方法的 skip 参数:The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (For historical reasons the meaning of skip differs between Caller and Callers.)即,skip 为零的时候,表示 Caller 方法本身,而我们需要的是 a 语句的所在的行号和文件名,所以这种情境下需要提升 2 个栈帧数。工厂方法模式自定义日志输出引擎以下是添加 console 输出引擎的用法,直接调用 SetLogger 方法即可。func TestConsole(t *testing.T) { ··· log.SetLogger(“console”, {"color":false}) ···}type newLoggerFunc func() Loggervar adapters = make(map[string]newLoggerFunc)func (bl *BeeLogger) SetLogger(adapterName string, configs …string) error { ··· return bl.setLogger(adapterName, configs…)}func (bl *BeeLogger) setLogger(adapterName string, configs …string) error { ··· log, ok := adapters[adapterName] if !ok { return fmt.Errorf(“logs: unknown adaptername %q (forgotten Register?)”, adapterName) } lg := log() //——— c 语句 err := lg.Init(config) if err != nil { fmt.Fprintln(os.Stderr, “logs.BeeLogger.SetLogger: “+err.Error()) return err } bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg}) return nil}func Register(name string, log newLoggerFunc) { ··· adapters[name] = log}在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。上面 c 语句可以看到,具体需要用到什么输出引擎,BeeLogger 不负责它们的创建,而是由这些输出引擎自己去做。从 adapters 这个 map 结构里找到该输出引擎的构造方法, 并且执行这个构造方法。例如 file.go 里面定义了如何构造一个文件输出引擎,并通过 init 方法注册:func init() { Register(AdapterFile, newFileWriter)}// newFileWriter create a FileLogWriter returning as LoggerInterface.func newFileWriter() Logger { w := &fileLogWriter{ Daily: true, MaxDays: 7, Rotate: true, RotatePerm: “0440”, Level: LevelTrace, Perm: “0660”, } return w}为什么要用到互斥锁?直接找到以下四处代码段:func (bl *BeeLogger) Async(msgLen …int64) *BeeLogger { bl.lock.Lock() defer bl.lock.Unlock() ···}func (bl *BeeLogger) SetLogger(adapterName string, configs …string) error { bl.lock.Lock() defer bl.lock.Unlock() ···}func (bl *BeeLogger) DelLogger(adapterName string) error { bl.lock.Lock() defer bl.lock.Unlock() ···}func (bl *BeeLogger) writeMsg(logLevel int, msg string, v …interface{}) error { if !bl.init { bl.lock.Lock() bl.setLogger(AdapterConsole) bl.lock.Unlock() } ···}可以看出,在进行 SetLogger 、 DelLogger 这些操作时涉及到临界资源 bl *BeeLogger 相关配置字段的更改,必须操作前加锁保证并发安全。临界资源是指每次仅允许一个进程访问的资源。Asynchronous 选项为什么能提升性能func (bl *BeeLogger) writeMsg(logLevel int, msg string, v …interface{}) error { ··· if bl.asynchronous { lm := logMsgPool.Get().(*logMsg) lm.level = logLevel lm.msg = msg lm.when = when bl.msgChan <- lm } else { bl.writeToLoggers(when, msg, logLevel) } return nil}如果开启 asynchronous 选项,将日志信息写进 msgChan 就完事了,可以继续执行其他的逻辑代码,除非 msgChan 缓存满了,否则不会发生阻塞,同时,还开启一个 goroutine 监听 msgChan,一旦 msgChan 不为空,将日志信息输出:func (bl *BeeLogger) Async(msgLen …int64) *BeeLogger { ··· go bl.startLogger() ···}// start logger chan reading.// when chan is not empty, write logs.func (bl *BeeLogger) startLogger() { gameOver := false for { select { case bm := <-bl.msgChan: bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) case sg := <-bl.signalChan: // Now should only send “flush” or “close” to bl.signalChan bl.flush() if sg == “close” { for _, l := range bl.outputs { l.Destroy() } bl.outputs = nil gameOver = true } bl.wg.Done() } if gameOver { break } }}从 logs package 外的 log.go 文件了解 beego 如何解耦在 logs 包(package)外面还有一个 beego package 下的 log.go 文件,截取一段代码: // github.com/astaxie/beego/log.go package beego import “github.com/astaxie/beego/logs” // BeeLogger references the used application logger. var BeeLogger = logs.GetBeeLogger() // SetLevel sets the global log level used by the simple logger. func SetLevel(l int) { logs.SetLevel(l) } // github.com/astaxie/beego/logs/log.go// beeLogger references the used application logger.var beeLogger = NewLogger()// GetBeeLogger returns the default BeeLoggerfunc GetBeeLogger() *BeeLogger { return beeLogger}// SetLevel sets the global log level used by the simple logger.func SetLevel(l int) { beeLogger.SetLevel(l)}beego 为什么还在外面包了一层调用 logs 包里面的方法呢?其实 beego 本身是一个 Web 框架,那么本质就是一个服务端程序,服务端程序需要一个日志记录器来记录服务器的运行状况,那么调用 logs 包的代码以及其他一些配置、初始化的逻辑,就在 log.go 中处理。这里其实也没有什么,就是一开始笔者在读源码的时候老是被这里疑惑,认为多此一举。其实要实现一个功能单一的 logs 包并与其他模块解耦,这么做的确不错。再如, beego 的 session 模块,为了不与 logs 模块耦合,所以 session 模块也造了一个仅供自己模块内使用的日志记录器 SessionLog 。代码如下:// Log implement the log.Loggertype Log struct { *log.Logger}// NewSessionLog set io.Writer to create a Logger for session.func NewSessionLog(out io.Writer) *Log { sl := new(Log) sl.Logger = log.New(out, “[SESSION]”, 1e9) return sl}不妨看看 Beego 官方的架构图:beego 是基于八大独立的模块构建的,是一个高度解耦的框架。用户即使不使用 beego 的 HTTP 逻辑,也依旧可以使用这些独立模块,例如:你可以使用 cache 模块来做你的缓存逻辑;使用日志模块来记录你的操作信息;使用 config 模块来解析你各种格式的文件。所以 beego 不仅可以用于 HTTP 类的应用开发,在你的 socket 游戏开发中也是很有用的模块,这也是 beego 为什么受欢迎的一个原因。大家如果玩过乐高的话,应该知道很多高级的东西都是一块一块的积木搭建出来的,而设计 beego 的时候,这些模块就是积木,高级机器人就是 beego。 ...

March 12, 2019 · 4 min · jiezi

Linux安装Beego: cross-compiled与GOBIN出错

Linux下安装Beego出错问题:go install: cannot install cross-compiled binaries when GOBIN is set遇到这个问题一般是在环境变量中设置了 GOBIN 可以打开 /etc/profile 把这个变量注释掉就,执行 source /etc/profile生效即可同样安装完成之后需要在环境变量中追加bee的路径解决思路注释GOBIN选项,并在 /etc/profile 文件中设置GOPATH/bin(永久)export PATH=$GOPATH/bin:$PATH 重新生成配置source /etc/profile可能遇到的问题:添加之后执行bee不成功。查看bee的所在路径 : echo $GOPATH //获取GOPATH的路径 本人是在/data/www/go: find /home/chun/go -name “bee” //查找目录的含bee的文件夹输出:/data/www/go/bin/linux_386/bee/data/www/go/src/github.com/beego/bee/data/www/go/pkg/linux_386/github.com/beego/bee这里可以看到我的linux系统上bee的安装路径和正常不一样,在linux_386下面,接下来只需把/home/chun/go/bin/linux_386添加到环境变量就ok了。export PATH=$GOPATH/bin/linux_386:$PATHsource /etc/profile在GOPATH下删除GOBIN设置(暂时)cd $GOPATHunset GOBIN作者:子恒|haley出处:http://www.cnblogs.com/mylly/交流沟通:QQ群866437035

February 20, 2019 · 1 min · jiezi

beego框架代码分析

前言也许beego框架在国内应该是众多PHPer转go的首选,因为beego的MVC、ORM、完善的中文文档让PHPer们得心应手,毫无疑问我也是。这种感觉就像当年入门PHP时使用ThinkPHP一样。也许随着你的认知的提升,你会讨厌现在东西,比如某一天你可能慢慢的开始讨厌beego,你会发现go语言里包的真正意义,你开始反思MVC真的适合go吗,或者你开始觉着ORM在静态语言里的鸡肋,等等。我只想说:“也许你成长了~”。但是这些都不重要,每一个受欢迎的事物自然有我们值的学习的地方。今天这篇文章很简单,像一篇笔记,记录了我这几天抽空读beego源码的记录。如何读一个框架?毫无疑问读go的框架和PHP框架也是一样的:配置加载:如何加载配置文件的。路由:分析框架如何通过URI执行对应业务的。ORM:ORM如何实现的。这里(1.)和(3.)无非就是加载个文件和sql解析器的实现,我就忽略了,重点就看看路由的实现。安装简单带过:// Step1: 安装beegogo get github.com/astaxie/beego// Step2: 安装beego get github.com/beego/bee// Step3: 用bee工具创建一个新的项目bee new beego-code-read代码分析go有自己实现的http包,大多go框架也是基于这个http包,所以看beego之前我们先补充或者复习下这个知识点。如下:go如何启动一个http serverpackage mainimport ( // 导入net/http包 “net/http”)func main() { // —————— 使用http包启动一个http服务 方式一 —————— // *http.Request http请求内容实例的指针 // http.ResponseWriter 写http响应内容的实例 http.HandleFunc("/v1/demo", func(w http.ResponseWriter, r *http.Request) { // 写入响应内容 w.Write([]byte(“Hello TIGERB !\n”)) }) // 启动一个http服务并监听8888端口 这里第二个参数可以指定handler http.ListenAndServe(":8888", nil)}// 测试我们的服务// ——————–// 启动:bee run// 访问: curl “http://127.0.0.1:8888/v1/demo”// 响应结果:Hello TIGERB !ListenAndServe是对http.Server的进一步封装,除了上面的方式,还可以使用http.Server直接启服务,这个需要设置Handler,这个Handler要实现Server.Handler这个接口。当请求来了会执行这个Handler的ServeHTTP方法,如下:package main// 导入net/http包import ( “net/http”)// DemoHandle server handle示例type DemoHandle struct {}// ServeHTTP 匹配到路由后执行的方法func (DemoHandle) ServeHTTP(w http.ResponseWriter, r http.Request) { w.Write([]byte(“Hello TIGERB !\n”))}func main() { // —————— 使用http包的Server启动一个http服务 方式二 —————— // 初始化一个http.Server server := &http.Server{} // 初始化handler并赋值给server.Handler server.Handler = DemoHandle{} // 绑定地址 server.Addr = “:8888” // 启动一个http服务 server.ListenAndServe()}// 测试我们的服务// ——————–// 启动:bee run// 访问: curl “http://127.0.0.1:8888/v1/demo”// 响应结果:Hello TIGERB !beego路由分析接下里我们开始看beego的代码。拿访问"http://127.0.0.1:8080/“来说,对于beego代码来说有三个关键点,分别如下:启动:main.go -> beego.Run()注册路由:routersrouter.go -> beego.Router(”/", &controllers.MainController{})控制器:controllersdefault.go -> Get()下面来看3个关键点的详细分析:beego.Run()主要的工作// github.com/astaxie/beego/beego.gofunc Run(params …string) { // 启动http服务之前的一些初始化 忽略 往下看 initBeforeHTTPRun() // http服务的ip&port设置 if len(params) > 0 && params[0] != "" { strs := strings.Split(params[0], “:”) if len(strs) > 0 && strs[0] != "" { BConfig.Listen.HTTPAddr = strs[0] } if len(strs) > 1 && strs[1] != "" { BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) } } // 又一个run 往下看 BeeApp.Run()}// github.com/astaxie/beego/app.gofunc (app App) Run(mws …MiddleWare) { // … 省略 // 看了下这里app.Server的类型就是http.Server 也就是说用的原生http包 且是上面“go如何启动一个http server”中的第二种方式 app.Server.Handler = app.Handlers // … 省略 if BConfig.Listen.EnableHTTP { go func() { app.Server.Addr = addr logs.Info(“http server Running on http://%s”, app.Server.Addr) // 默认配置false不强制tcp4 if BConfig.Listen.ListenTCP4 { //… // 忽略 默认false } else { // 关键点 ListenAndServe: app.Server的类型就是http.Server 所以这里就启动了http服务 if err := app.Server.ListenAndServe(); err != nil { logs.Critical(“ListenAndServe: “, err) time.Sleep(100 * time.Microsecond) endRunning <- true } } }() } // 阻塞到服务启动 <-endRunning}// 看到这里http已经启动了 而且是注册Handler的方式接着去找这个Handler的ServeHTTP方法,通过上面的这段代码app.Server.Handler = app.Handlers,我们找到了下面的定义,Handler即是ControllerRegister的值,所以每次亲求来了就会去执行ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)。// src/github.com/astaxie/beego/app.gofunc init() { // 调用 创建beego框架实例的方法 BeeApp = NewApp()}// App结构体type App struct { // 关键的请求回调Handler Handlers *ControllerRegister // http包的服务 Server *http.Server}func NewApp() *App { // 初始化http handler cr := NewControllerRegister() // 创建beego 实例 app := &App{Handlers: cr, Server: &http.Server{}} return app}通过我们追beego.Run()的代码,目前我们得到的结论就是:使用的http包启动的服务没有使用http.HandleFun()的定义路由策略,而是注册Handler的方式所以beego就是通过beego.Router()自己管理路由,如果http请求来了,回调ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)方法,在ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)方法中去匹配路由并执行对应的controller 也就是beegoControllerInterface类型的控制器的方法,比如RESTFUL或者自定义啊等。beego.Router() 如何注册路由首先路由文件是如何加载的,我们发现在main.go文件里导入了路由包:package mainimport ( // 导入routers包 只执行init方法 _ “beego-code-read/routers” “github.com/astaxie/beego”)func main() { beego.Run()}上面我们启动了http服务,接着关键就是beego.Router()如何注册路由了。追了下代码如下:beego.Router() -> BeeApp.Handlers.Add(rootpath, c, mappingMethods…) -> ControllerRegister.addWithMethodParams(pattern, c, nil, mappingMethods…) -> ControllerRegister.addToRouter(method, pattern string, r *ControllerInfo) -> Tree.AddRouter(pattern string, runObject interface{})最后就是在Tree.AddRouter()完成了路由注册,这里的代码逻辑暂时就先不看了,至此这个beego框架的流程就其本理顺了,最后我们在回头总结下整个流程如下图:备注:go导入包相当于入栈过程,先import后执行init ...

January 20, 2019 · 2 min · jiezi

Beego文件上传至七牛云的玩法

前言Beego是一款GO语言开发的传统MVC的框架,beego对上传这块的代码封装的也非常简单易用。beego的上传贴出官方的一段代码https://beego.me/docs/mvc/con…func (c *FormController) Post() { f, h, err := c.GetFile(“uploadname”) if err != nil { log.Fatal(“getfile err “, err) } defer f.Close() c.SaveToFile(“uploadname”, “static/upload/” + h.Filename) // 保存位置在 static/upload, 没有文件夹要先创建 }这里beego通过 GetFile方法获取文件的name,既设置的 input name=“uploadname” 可以通过其第二个返回值获得文件的详细详细h.Filenameh.Headerh.size上面的例子是一个传统上传,既将文件拷贝到本地项目目录中。七牛云关于注册、登录、获取key什么的本文就不废话了。官方提供了Go的Sdk文档地址:https://developer.qiniu.com/k…安装通过go get 安装包go get -u github.com/qiniu/api.v7使用这里演示两种上传方式七牛官方的SDK提供了几个上传方法,如下所示// 本地文件上传至七牛云func (p *FormUploader) PutFilefunc (p *FormUploader) PutFileWithoutKey// 数据流方式上传至七牛云func (p *FormUploader) Putfunc (p *FormUploader) PutWithoutKey这个SDK也允许你自定义返回结果,通过重写结构体的方式type PutRet struct { Hash string json:"hash" PersistentID string json:"persistentId" Key string json:"key"}本地上传这是官方的一个demo,本地上传这里就不多阐述了,大概都能看懂// 设置上传文件localFile = “/Users/jemy/Documents/github.png”// 设置上传空间名bucket = “if-pbl”// 上传的文件名称key = “github-x.png"putPolicy := storage.PutPolicy{ Scope: bucket,}mac := qbox.NewMac(accessKey, secretKey)upToken := putPolicy.UploadToken(mac)cfg := storage.Config{}// 空间对应的机房cfg.Zone = &storage.ZoneHuadong// 是否使用https域名cfg.UseHTTPS = false// 上传是否使用CDN上传加速cfg.UseCdnDomains = false// 构建表单上传的对象formUploader := storage.NewFormUploader(&cfg)ret := storage.PutRet{}// 可选配置putExtra := storage.PutExtra{ Params: map[string]string{ “x:name”: “github logo”, },}err := formUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)if err != nil { fmt.Println(err) return}fmt.Println(ret.Key,ret.Hash)流信息上传这是我自行封装的流上传代码package qiniuimport ( “github.com/qiniu/api.v7/storage” “github.com/qiniu/api.v7/auth/qbox” “context” “io”)const ( bucket = “avatars” // accessKey = "” secretKey = “")func config() storage.Config { cfg := storage.Config{} cfg.Zone = &storage.ZoneHuadong // 是否使用https域名 cfg.UseHTTPS = false // 上传是否使用CDN上传加速 cfg.UseCdnDomains = false return cfg}func Upload(localFile io.Reader, size int64, filename string) (string, error) { putPolicy := storage.PutPolicy{ Scope: bucket, } mac := qbox.NewMac(accessKey, secretKey) upToken := putPolicy.UploadToken(mac) cig := config() formUploader := storage.NewFormUploader(&cig) ret := storage.PutRet{} putExtra := storage.PutExtra{} err := formUploader.Put(context.Background(), &ret, upToken, filename, localFile, size, &putExtra) if err != nil { return “”, err } return ret.Key, nil}注意localFile io.Reader这个参数,调用upload方法需要传一个byte。下面演示调用方法的代码func (u *UserController) UploadImage() { f, h, err := u.GetFile(“image”) if err != nil { u.Data[“json”] = common.Response{ Data: [0]int{}, Message: “上传失败”, Code: 0, } u.ServeJSON() return } defer f.Close() file, _ := h.Open() // 这里获得的实际就是一个io,通过源码看到这个open方法最终返回的是一个结构体,其内部包含了 io.Reader的接口 // type File interface { // io.Reader // io.ReaderAt // io.Seeker // io.Closer // } unix := time.Now().Unix() timeByte := []byte(strconv.Itoa(int(unix))) filename := function.Md5(timeByte) // 这里是计算了一个md5(time())的字符串作为文件名 if filename, err = qiniu.Upload(file, h.Size, filename); err != nil { // 通过h.size 即可获得文件大小 u.Data[“json”] = common.Response{ Data: [0]int{}, Message: “上传失败”, Code: 0, } } else { u.Data[“json”] = common.Response{ Data: map[string]string{ “filename”: filename, }, Message: “上传成功”, Code: 200, } } u.ServeJSON()}致谢go 自身的特点不应是做web,web使用php,java就够了。一般用go都是写接口。所以上传文件时不应先传到本地再传到七牛,我想很多初学者都在这里被坑。特此写一篇来解释其使用方法。希望本篇文章可以帮到你。谢谢 ...

January 4, 2019 · 2 min · jiezi

beego orm 多对多查询

首先纠正一下beego的文档rel_table设置自动生成的 m2m 关系表的名称rel_through如果要在 m2m 关系中使用自定义的 m2m 关系表通过这个设置其名称,格式为 pkg.path.ModelNameeg: app.models.PostTagRelPostTagRel 表需要有到 Post 和 Tag 的关系实际上rel_through的格式并不是pkg.path.ModelName,正确的姿势是:// 不要照抄哈,这里只是eg应该怎么使用learnBeego/myApp/models.RoleUser在这里卡了一天,后来还是在google的帮助下找到了问题,bd能搜索出关于go的东西实在太少了举个例子需求:用户、角色。我们要实现 一个用户可以有多个角色,一个角色可以有多个用户数据表设计用户表 useridusernamepasswrodcreated_atupdated_at1testa1234562018-01-01 12:36:472018-01-01 12:36:472testb6543212018-01-01 12:36:472018-01-01 12:36:47角色表 roleidnamecreated_atupdated_at1测试角色A2018-01-01 12:36:472018-01-01 12:36:472测试角色B2018-01-01 12:36:472018-01-01 12:36:47角色用户关系表 role_useriduser_idrole_idcreated_atupdated_at1112018-01-01 12:36:472018-01-01 12:36:472122018-01-01 12:36:472018-01-01 12:36:473212018-01-01 12:36:472018-01-01 12:36:47模型用户模型注意! 使用多对多时,想要获取关系字段是需要手动完成的,orm不会为你自动完成这些查询操作,不要以为设置完rel_through就完事了!type User struct { Id int64 Username string orm:"size(128);unique" valid:"Required" password string orm:"size(128);" json:"-" valid:"Required" CreatedAt time.Time orm:"auto_now_add;type(datetime)" UpdatedAt time.Time orm:"auto_now;type(datetime)" Roles []*Role orm:"rel(m2m);rel_through(learnBeego/myApp/models.RoleUser)"}func init() { orm.RegisterModel(new(User))}func (m *User) TableName() string { return “user”}// 通过用户ID获取用户信息及用户所属的角色func GetUserById(id int64) (v *User, err error) { o := orm.NewOrm() v = &User{Id: id} if err = o.QueryTable(new(User)).Filter(“Id”,id).RelatedSel().One(v); err == nil { // 获取关系字段,o.LoadRelated(v, “Roles”) 这是关键 // 查找该用户所属的角色 if _, err = o.LoadRelated(v, “Roles”);err!=nil{ return nil, err } return v, nil } return nil, err}角色模型type Role struct { Id int64 Name string CreatedAt time.Time orm:"auto_now_add;type(datetime)" UpdatedAt time.Time orm:"auto_now;type(datetime)" Users []*User orm:"reverse(many)"}func init() { orm.RegisterModel(new(Role))}func (m *Role) TableName() string { return “role”}用户角色关系模型type RoleUser struct { Id int64 User *User orm:"rel(fk)" Role *Role orm:"rel(fk)" CreatedAt time.Time orm:"type(datetime)" UpdatedAt time.Time orm:"type(datetime)"}func init() { orm.RegisterModel(new(RoleUser))}func (m *RoleUser) TableName() string { return “role_user”}最后在控制器中…func (c *UserController) GetOne() { idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseInt(idStr, 0, 64) v, err := models.GetUserById(id) if err != nil { c.Data[“json”] = “找不到匹配的数据” } else { c.Data[“json”] = v } c.ServeJSON()}…最后,测试假如GetOne()对应的URL是localhost:8080/v1/user/:id请求http://localhost"8080/v1/user/1时,返回的数据{ “Id”: 1, “Username”: “testa”, “CreatedAt”: “2018-01-01T12:36:47+08:00”, “UpdatedAt”: “2018-01-01T12:36:47+08:00”, “Roles”:[ { “Id”: 1, “Name”: “测试角色A”, “CreatedAt”: “2018-01-01T12:36:47+08:00”, “UpdatedAt”: “2018-01-01T12:36:47+08:00”, }, { “Id”: 2, “Name”: “测试角色B”, “CreatedAt”: “2018-01-01T12:36:47+08:00”, “UpdatedAt”: “2018-01-01T12:36:47+08:00”, }, ]} ...

December 28, 2018 · 2 min · jiezi