本文共 14040 字,大约阅读时间需要 46 分钟。
使用go语言做后台服务已经有3年了,通过项目去检验一个又一个的想法,然后不断总结,优化,最终形成了自己的一整套体系,小到一个打印对象的方法,大到一个web后台项目最佳实践指导,这一点一滴都是在不断的实践中进化开来。以下内容将是一次整体的汇报,各位看官如有兴致,请 关注最新的代码变更。
大型互联网社交业务
路由自动生成,按要求提供controller/action的实现代码,wsp执行后会分析项目代码,自动生成路由表并记录在文件demo/WSP.go里,controller/action定义代码必须符合函数定义:func(http.ResponseWriter, *http.Request)
,并且是带receiver的method
package controllerimport ( "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, })}
// generated by wsp, DO NOT EDIT.package mainimport "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
,过滤器函数如下: package filterimport ( "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())
package mainimport ( "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))}
提供一个简单易扩展的项目stub
├── 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.go
package lcimport ( "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,再写上面的代码时,一切变的那么简单
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 []*struerr = 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格式,支持注释)
{ "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 demoSUCCESS
agent机器
布署本机agent服务:agent/agent,配置文件:agent/conf/conf.json
master机器
布署master服务:master/master,配置文件:master/conf/conf.json
agent和master服务建议用启动管理
package procsimport ( "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 procsfunc XxxHandler(cate, subcate string, content []byte, params map[string]interface{}) {}func init() { RegisterHandler("xxxhandler", XxxHandler)}
(demo项目里有clog的使用例子)
有时想快速验证go某个函数的使用,临时写个程序太低效,有了gop,立马开一个shell环境,边写边运行,自动为你保存上下文,还可随时导入导出snippet,另外还有代码自动补全等等特性
import "github.com/simplejia/utils"var println = utils.IprintD
$ gopWelcome 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 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).....}12[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]$
转载地址:http://ohuli.baihongyu.com/