版权声明:本文由魏佳原创文章,转载请注明出处:
文章原文连接:https://www.qcloud.com/community/article/173java
来源:腾云阁 https://www.qcloud.com/communitymysql
使用go语言作后台服务已经有3年了,经过项目去检验一个又一个的想法,而后不断总结,优化,最终造成了本身的一整套体系,小到一个打印对象的方法,大到一个web后台项目最佳实践指导,这一点一滴都是在不断的实践中进化开来。如下内容将是一次总体的汇报,各位看官若有兴致,请移步GitHub 关注最新的代码变动。c++
大型互联网社交业务git
路由自动生成,按要求提供controller/action的实现代码,wsp执行后会分析项目代码,自动生成路由表并记录在文件demo/WSP.go里,controller/action定义代码必须符合函数定义:func(http.ResponseWriter, *http.Request)
,而且是带receiver的methoddemo_set.gogithub
package controller import ( "net/http" "github.com/simplejia/wsp/demo/service" ) // @prefilter("Login", {"Method":{"type":"get"}}) // @postfilter("Boss") func (demo *Demo) Set(w http.ResponseWriter, r *http.Request) { key := r.FormValue("key") value := r.FormValue("value") demoService := service.NewDemo() demoService.Set(key, value) json.NewEncoder(w).Encode(map[string]interface{}{ "code": 0, }) }
WSP.gogolang
// generated by wsp, DO NOT EDIT. package main import "net/http" import "time" import "github.com/simplejia/wsp/demo/controller" import "github.com/simplejia/wsp/demo/filter" func init() { http.HandleFunc("/Demo/Get", func(w http.ResponseWriter, r *http.Request) { t := time.Now() _ = t var e interface{} c := new(controller.Demo) defer func() { e = recover() if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok { return } }() c.Get(w, r) }) http.HandleFunc("/Demo/Set", func(w http.ResponseWriter, r *http.Request) { t := time.Now() _ = t var e interface{} c := new(controller.Demo) defer func() { e = recover() if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok { return } }() if ok := filter.Login(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok { return } if ok := filter.Method(w, r, map[string]interface{}{"type": "get", "__T__": t, "__C__": c, "__E__": e}); !ok { return } c.Set(w, r) }) }
func (http.ResponseWriter
, *http.Request
, map[string]interface{}) bool
,过滤器函数以下: method.gopackage filter import ( "net/http" "strings" ) func Method(w http.ResponseWriter, r *http.Request, p map[string]interface{}) bool { method, ok := p["type"].(string) if ok && strings.ToLower(r.Method) != strings.ToLower(method) { http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed) return false } return true }
filter输入参数map[string]interface{},会自动设置"T",time.Time类型,值为执行起始时间,可用于耗时统计,"C",{Controller}类型,值为{Controller}实例,可经过接口方式存取相关数据(这种方式存取数据较context方式更简单实用),"E",值为recover()返回值,用于检测错误并处理(后置过滤器必须recover())web
package main import ( "log" "github.com/simplejia/clog" "github.com/simplejia/lc" "net/http" _ "github.com/simplejia/wsp/demo/clog" _ "github.com/simplejia/wsp/demo/conf" _ "github.com/simplejia/wsp/demo/mysql" _ "github.com/simplejia/wsp/demo/redis" ) func init() { lc.Init(1e5) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) }) } func main() { clog.Info("main()") log.Panic(http.ListenAndServe(":8080", nil)) }
提供一个简单易扩展的项目stubredis
├── WSP.go ├── clog │ └── clog.go ├── conf │ ├── conf.go │ └── conf.json ├── controller │ ├── base.go │ ├── demo.go │ ├── demo_get.go │ └── demo_set.go ├── demo ├── filter │ ├── boss.go │ ├── login.go │ └── method.go ├── main.go ├── model │ ├── demo.go │ ├── demo_get.go │ └── demo_set.go ├── mysql │ ├── demo_db.json │ └── mysql.go ├── redis │ ├── demo.json │ └── redis.go ├── service │ ├── demo.go │ ├── demo_get.go │ └── demo_set.go └── test ├── clog -> ../clog ├── conf -> ../conf ├── demo_get_test.go ├── demo_set_test.go ├── init_test.go ├── mysql -> ../mysql └── redis -> ../redis
接口实现上,建议一个接口对应一个文件,如controller/demo_get.go, service/demo_get.go, model/demo_get.gosql
lc_test.goshell
package lc import ( "testing" "time" ) func init() { Init(65536) // 使用lc以前必需要初始化 } func TestGetValid(t *testing.T) { key := "k" value := "v" Set(key, value, time.Second) time.Sleep(time.Millisecond * 10) // 给异步处理留点时间 v, ok := Get(key) if !ok || v != value { t.Fatal("") } }
写redis+mysql代码时(还可能加上lc),示意代码以下:
func orig(key string) (value string) { value = redis.Get(key) if value != "" { return } value = mysql.Get(key) redis.Set(key, value) return } // 若是再加上lc的话 func orig(key string) (value string) { value = lc.Get(key) if value != "" { return } value = redis.Get(key) if value != "" { lc.Set(key, value) return } value = mysql.Get(key) redis.Set(key, value) lc.Set(key, value) return }
有了lm,再写上面的代码时,一切变的那么简单 lm_test.go
func tGlue(key, value string) (err error) { err = Glue( key, &value, func(p, r interface{}) error { _r := r.(*string) *_r = "test value" return nil }, func(p interface{}) string { return fmt.Sprintf("tGlue:%v", p) }, &LcStru{ Expire: time.Millisecond * 500, Safety: false, }, &McStru{ Expire: time.Minute, Pool: pool, }, ) if err != nil { return } return }
自动添加缓存代码,支持lc, redis,减轻你的心智负担,让你的代码更加简单可靠,少了大段的冗余代码,复杂的事全交给lm自动帮你作了
支持Glue[Lc|Mc]及相应批量操做Glues[Lc|Mc],详见lm_test.go示例代码
lm.LcStru.Safety,当置为true时,对lc在并发状态下返回的nil值不接受,由于lc.Get在并发状态下,同一个key返回的value有多是nil,而且ok状态为true,Safety置为true后,对以上状况不接受,会继续调用下一层逻辑
rows, err := db.Query("SELECT ...") defer rows.Close() for rows.Next() { var id int var name string err = rows.Scan(&id, &name) } err = rows.Err() ...
但实际项目场景里,咱们更想这样:
rows, err := db.Query("SELECT ...") defer rows.Close() var d []*stru err = Rows2Strus(rows, &d)
这就是一种简单的对象映射,经过转为对象的方式,咱们的代码更方便处理了
一共提供四种场景的使用方法:
Rows2Strus, sql.Rows转为struct slice
sql.Rows转为struct,等同db.QueryRow
Rows2Cnts, sql.Rows转为int slice
Rows2Cnt, sql.Rows转为int,用于select count(1)操做
支持tag: orm,以下:
type Demo struct { Id int DemoName string `orm:"demo_name"` // 映射成demo_name字段 }
支持匿名成员,以下:
type C struct { Id int } type P struct { C // 映射成id字段 Name string }
支持snakecase配置,经过设置orm.IsSnakeCase = true,以下:
type Demo struct { Id int DemoName string // 映射成demo_name字段 }
用于进程监控,管理
配置文件:conf.json (json格式,支持注释) conf.json
{ "env": "dev", // 配置运行环境 "envs": { "dev": { "port": 29118, // 配置监听端口 "rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia", "environ": "ulimit -n 65536", // 配置环境变量 "svrs": { // demo "demo": "wsp/demo/demo" // key: 名字 value: 将与rootpath拼接在一块儿运行 }, "log": { "mode": 3, // 0: none, 1: localfile, 2: collector (数字表明bit位) "level": 15 // 0: none, 1: debug, 2: warn 4: error 8: info (数字表明bit位) } }, "test": { "port": 29118, // 配置监听端口 "rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia", "environ": "ulimit -n 65536", // 配置环境变量 "svrs": { // demo "demo": "wsp/demo/demo" }, "log": { "mode": 3, // 0: none, 1: localfile, 2: collector (数字表明bit位) "level": 15 // 0: none, 1: debug, 2: warn 4: error 8: info (数字表明bit位) } }, "prod": { "port": 29118, // 配置监听端口 "rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia", "environ": "ulimit -n 65536", // 配置环境变量 "svrs": { // demo "demo": "wsp/demo/demo" }, "log": { "mode": 2, // 0: none, 1: localfile, 2: collector (数字表明bit位) "level": 14 // 0: none, 1: debug, 2: warn 4: error 8: info (数字表明bit位) } } } }
$ cmonitor -status all *****STATUS OK SERVICE LIST***** demo PID:13539 *****STATUS FAIL SERVICE LIST***** $ cmonitor -restart demo SUCCESS
agent机器
布署本机agent服务:agent/agent,配置文件:agent/conf/conf.json
master机器
布署master服务:master/master,配置文件:master/conf/conf.json
agent和master服务建议用cmonitor启动管理
package procs import ( "encoding/json" "os" ) func init() { // 请替换成你本身的报警处理函数 AlarmFunc = func(sender string, receivers []string, text string) { params := map[string]interface{}{ "Sender": sender, "Receivers": receivers, "Text": text, } json.NewEncoder(os.Stdout).Encode(params) } }
package procs func XxxHandler(cate, subcate string, content []byte, params map[string]interface{}) { } func init() { RegisterHandler("xxxhandler", XxxHandler) }
api_test.go
demo (demo项目里有clog的使用例子)
有时想快速验证go某个函数的使用,临时写个程序过低效,有了gop,立马开一个shell环境,边写边运行,自动为你保存上下文,还可随时导入导出snippet,另外还有代码自动补全等等特性
import "github.com/simplejia/utils" var println = utils.IprintD
$ gop Welcome to the Go Partner! [[version: 1.7, created by simplejia] Enter '?' for a list of commands. [r]$ ? Commands: ?|help help menu -[dpc][#],[#]-[#],... pop last/specific (declaration|package|code) ![!] inspect source [with linenum] <tmpl source tmpl >tmpl write tmpl [#](...) add def or code run run source compile compile source w write source mode on r write source mode off reset reset list tmpl list [r]$ for i:=1; i<3; i++ { ..... print(i) ..... time.Sleep(time.Millisecond) .....} 1 2 [r]$ import _ "github.com/simplejia/wsp/demo/mysql" [r]$ import _ "github.com/simplejia/wsp/demo/redis" [r]$ import _ "github.com/simplejia/wsp/demo/conf" [r]$ import "github.com/simplejia/lc" [r]$ import "github.com/simplejia/wsp/demo/service" [r]$ lc.Init(1024) [r]$ demoService := service.NewDemo() [r]$ demoService.Set("123", "456") [r]$ time.Sleep(time.Millisecond) [r]$ echo demoService.Get("123") 456 [r]$ >gop [r]$ <gop [r]$ ! package main p0: import _ "github.com/simplejia/wsp/demo/mysql" p1: import _ "github.com/simplejia/wsp/demo/redis" p2: import _ "github.com/simplejia/wsp/demo/conf" p3: import "github.com/simplejia/lc" p4: import "github.com/simplejia/wsp/demo/service" p5: import "fmt" // imported and not used p6: import "strconv" // imported and not used p7: import "strings" // imported and not used p8: import "time" // imported and not used p9: import "encoding/json" // imported and not used p10: import "bytes" // imported and not used func main() { c0: lc.Init(1024) c1: demoService := service.NewDemo() c2: _ = demoService c3: demoService.Set("123", "456") c4: time.Sleep(time.Millisecond) } [r]$